Apply PSR-12 code style (#3886)
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / label / LabelHandler.class.php
CommitLineData
3b75466f 1<?php
a9229942 2
3b75466f 3namespace wcf\system\label;
a9229942 4
157054c9 5use wcf\data\label\group\LabelGroup;
2b770bdd 6use wcf\data\label\group\ViewableLabelGroup;
004fe3ae 7use wcf\data\label\Label;
3b75466f 8use wcf\data\object\type\ObjectTypeCache;
eb66d59c 9use wcf\data\user\User;
3b75466f
MW
10use wcf\system\cache\builder\LabelCacheBuilder;
11use wcf\system\database\util\PreparedStatementConditionBuilder;
12use wcf\system\exception\SystemException;
13use wcf\system\SingletonFactory;
14use wcf\system\WCF;
15
16/**
17 * Manages labels and label-to-object associations.
a9229942
TD
18 *
19 * @author Alexander Ebert, Joshua Ruesweg
20 * @copyright 2001-2019 WoltLab GmbH
21 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
22 * @package WoltLabSuite\Core\System\Label
3b75466f 23 */
a9229942
TD
24class LabelHandler extends SingletonFactory
25{
26 /**
27 * cached list of object types
28 * @var mixed[][]
29 */
30 protected $cache;
31
32 /**
33 * list of label groups
34 * @var mixed[][]
35 */
36 protected $labelGroups;
37
38 /**
39 * @inheritDoc
40 */
41 protected function init()
42 {
43 $this->cache = [
44 'objectTypes' => [],
45 'objectTypeNames' => [],
46 ];
47
48 $cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.label.object');
49 foreach ($cache as $objectType) {
50 $this->cache['objectTypes'][$objectType->objectTypeID] = $objectType;
51 $this->cache['objectTypeNames'][$objectType->objectType] = $objectType->objectTypeID;
52 }
53
54 $this->labelGroups = LabelCacheBuilder::getInstance()->getData();
55 }
56
57 /**
58 * Returns the id of the label ACL option with the given name or null if
59 * no such option exists.
60 *
61 * @param string $optionName
62 * @return int
63 */
64 public function getOptionID($optionName)
65 {
66 foreach ($this->labelGroups['options'] as $option) {
67 if ($option->optionName == $optionName) {
68 return $option->optionID;
69 }
70 }
71 }
72
73 /**
74 * Returns the label object type with the given name or null of no such
75 * object.
76 *
77 * @param string $objectType
78 * @return \wcf\data\object\type\ObjectType
79 */
80 public function getObjectType($objectType)
81 {
82 if (isset($this->cache['objectTypeNames'][$objectType])) {
83 $objectTypeID = $this->cache['objectTypeNames'][$objectType];
84
85 return $this->cache['objectTypes'][$objectTypeID];
86 }
87 }
88
89 /**
90 * Returns an array with view permissions for the labels with the given id.
91 *
92 * @param int[] $labelIDs
93 * @param User $user
94 * @return array
95 * @see \wcf\system\label\LabelHandler::getPermissions()
96 */
97 public function validateCanView(array $labelIDs, ?User $user = null)
98 {
99 return $this->getPermissions('canViewLabel', $labelIDs, $user);
100 }
101
102 /**
103 * Returns an array with use permissions for the labels with the given id.
104 *
105 * @param int[] $labelIDs
106 * @param User $user
107 * @return array
108 * @see \wcf\system\label\LabelHandler::getPermissions()
109 */
110 public function validateCanUse(array $labelIDs, ?User $user = null)
111 {
112 return $this->getPermissions('canUseLabel', $labelIDs, $user);
113 }
114
115 /**
116 * Returns an array with boolean values for each given label id.
117 *
118 * @param string $optionName
119 * @param int[] $labelIDs
120 * @param User $user
121 * @return array
122 * @throws SystemException
123 */
124 public function getPermissions($optionName, array $labelIDs, ?User $user = null)
125 {
126 if (empty($labelIDs)) {
127 // nothing to validate anyway
128 return [];
129 }
130
131 if (empty($this->labelGroups['groups'])) {
132 // pretend given label ids aren't valid
133 $data = [];
134 foreach ($labelIDs as $labelID) {
135 $data[$labelID] = false;
136 }
137
138 return $data;
139 }
140
141 $optionID = $this->getOptionID($optionName);
142 if ($optionID === null) {
143 throw new SystemException("cannot validate label ids, ACL options missing");
144 }
145
146 // validate each label
147 $data = [];
148 foreach ($labelIDs as $labelID) {
149 $isValid = false;
150
151 foreach ($this->labelGroups['groups'] as $group) {
152 if (!$group->isValid($labelID)) {
153 continue;
154 }
155
156 if (!$group->hasPermissions() || $group->getPermission($optionID, $user)) {
157 $isValid = true;
158 }
159 }
160
161 $data[$labelID] = $isValid;
162 }
163
164 return $data;
165 }
166
167 /**
168 * Sets labels for given object id, pass an empty array to remove all previously
169 * assigned labels.
170 *
171 * @param int[] $labelIDs
172 * @param int $objectTypeID
173 * @param int $objectID
174 * @param bool $validatePermissions
175 */
176 public function setLabels(array $labelIDs, $objectTypeID, $objectID, $validatePermissions = true)
177 {
178 // get accessible label ids to prevent inaccessible ones to be removed
179 $accessibleLabelIDs = $this->getAccessibleLabelIDs();
180
181 // delete previous labels
182 if (!$validatePermissions || ($validatePermissions && !empty($accessibleLabelIDs))) {
183 $conditions = new PreparedStatementConditionBuilder();
184 if ($validatePermissions) {
185 $conditions->add("labelID IN (?)", [$accessibleLabelIDs]);
186 }
187 $conditions->add("objectTypeID = ?", [$objectTypeID]);
188 $conditions->add("objectID = ?", [$objectID]);
189
190 $sql = "DELETE FROM wcf" . WCF_N . "_label_object
191 " . $conditions;
192 $statement = WCF::getDB()->prepareStatement($sql);
193 $statement->execute($conditions->getParameters());
194 }
195
196 // insert new labels
197 if (!empty($labelIDs)) {
198 $sql = "INSERT INTO wcf" . WCF_N . "_label_object
199 (labelID, objectTypeID, objectID)
200 VALUES (?, ?, ?)";
201 $statement = WCF::getDB()->prepareStatement($sql);
202 foreach ($labelIDs as $labelID) {
203 $statement->execute([
204 $labelID,
205 $objectTypeID,
206 $objectID,
207 ]);
208 }
209 }
210 }
211
212 /**
213 * Replaces the labels of the label groups with the given ids with the labels with the given
214 * ids. Existing labels of the object from other label groups will not be changed. If no
215 * label for any of the given label group is given, an existing label from this group will
216 * be removed.
217 *
218 * @param int[] $groupIDs ids of the relevant label groups
219 * @param int[] $labelIDs ids of the new labels
220 * @param string $objectType label object type of the updated object
221 * @param int $objectID id of the updated object
222 * @since 5.2
223 */
224 public function replaceLabels(array $groupIDs, array $labelIDs, $objectType, $objectID)
225 {
226 $objectTypeID = $this->getObjectType($objectType)->objectTypeID;
227
228 // get the ids of the labels in the relevant label groups
229 $replacedLabelIDs = [];
230 foreach ($groupIDs as $groupID) {
231 $replacedLabelIDs = \array_merge(
232 $replacedLabelIDs,
233 $this->getLabelGroup($groupID)->getLabelIDs()
234 );
235 }
236
237 // delete old labels first
238 $conditionBuilder = new PreparedStatementConditionBuilder();
239 $conditionBuilder->add('labelID IN (?)', [$replacedLabelIDs]);
240 $conditionBuilder->add("objectTypeID = ?", [$objectTypeID]);
241 $conditionBuilder->add("objectID = ?", [$objectID]);
242
243 $sql = "DELETE FROM wcf" . WCF_N . "_label_object
244 " . $conditionBuilder;
245 $statement = WCF::getDB()->prepareStatement($sql);
246 $statement->execute($conditionBuilder->getParameters());
247
248 // assign new labels
249 if (!empty($labelIDs)) {
250 $sql = "INSERT INTO wcf" . WCF_N . "_label_object
251 (labelID, objectTypeID, objectID)
252 VALUES (?, ?, ?)";
253 $statement = WCF::getDB()->prepareStatement($sql);
254 foreach ($labelIDs as $labelID) {
255 $statement->execute([
256 $labelID,
257 $objectTypeID,
258 $objectID,
259 ]);
260 }
261 }
262 }
263
264 /**
265 * Returns all assigned labels, optionally filtered to validate permissions.
266 *
267 * @param int $objectTypeID
268 * @param int[] $objectIDs
269 * @param bool $validatePermissions
270 * @return Label[][]
271 */
272 public function getAssignedLabels($objectTypeID, array $objectIDs, $validatePermissions = true)
273 {
274 $conditions = new PreparedStatementConditionBuilder();
275 $conditions->add("objectTypeID = ?", [$objectTypeID]);
276 $conditions->add("objectID IN (?)", [$objectIDs]);
277 $sql = "SELECT objectID, labelID
278 FROM wcf" . WCF_N . "_label_object
279 " . $conditions;
280 $statement = WCF::getDB()->prepareStatement($sql);
281 $statement->execute($conditions->getParameters());
282 $labels = $statement->fetchMap('labelID', 'objectID', false);
283
284 // optionally filter out labels without permissions
285 if ($validatePermissions) {
286 $labelIDs = \array_keys($labels);
287 $result = $this->validateCanView($labelIDs);
288
289 foreach ($labelIDs as $labelID) {
290 if (!$result[$labelID]) {
291 unset($labels[$labelID]);
292 }
293 }
294 }
295
296 // reorder the array by object id
297 $data = [];
298 foreach ($labels as $labelID => $objectIDs) {
299 foreach ($objectIDs as $objectID) {
300 if (!isset($data[$objectID])) {
301 $data[$objectID] = [];
302 }
303
304 /** @var ViewableLabelGroup $group */
305 foreach ($this->labelGroups['groups'] as $group) {
306 $label = $group->getLabel($labelID);
307 if ($label !== null) {
308 $data[$objectID][$labelID] = $label;
309 }
310 }
311 }
312 }
313
314 // order label ids by label group
315 $labelGroups = &$this->labelGroups;
316 foreach ($data as &$labels) {
317 \uasort($labels, static function ($a, $b) use ($labelGroups) {
318 $groupA = $labelGroups['groups'][$a->groupID];
319 $groupB = $labelGroups['groups'][$b->groupID];
320
321 if ($groupA->showOrder == $groupB->showOrder) {
322 return ($groupA->groupID > $groupB->groupID) ? 1 : -1;
323 }
324
325 return ($groupA->showOrder > $groupB->showOrder) ? 1 : -1;
326 });
327 }
328 unset($labels);
329
330 return $data;
331 }
332
333 /**
334 * Returns given label groups by id.
335 *
336 * @param int[] $groupIDs
337 * @param bool $validatePermissions
338 * @param string $permission
339 * @return ViewableLabelGroup[]
340 * @throws SystemException
341 */
342 public function getLabelGroups(array $groupIDs = [], $validatePermissions = true, $permission = 'canSetLabel')
343 {
344 $data = [];
345
346 $optionID = null;
347 if ($validatePermissions) {
348 $optionID = $this->getOptionID($permission);
349 if ($optionID === null) {
350 throw new SystemException("cannot validate group ids, ACL options missing");
351 }
352 }
353
354 if (empty($groupIDs)) {
355 $groupIDs = \array_keys($this->labelGroups['groups']);
356 }
357 foreach ($groupIDs as $groupID) {
358 // validate given group ids
359 if (!isset($this->labelGroups['groups'][$groupID])) {
360 throw new SystemException("unknown label group identified by group id '" . $groupID . "'");
361 }
362
363 // validate permissions
364 if ($validatePermissions) {
365 if (
366 $this->labelGroups['groups'][$groupID]->hasPermissions()
367 && !$this->labelGroups['groups'][$groupID]->getPermission($optionID)
368 ) {
369 continue;
370 }
371 }
372
373 $data[$groupID] = $this->labelGroups['groups'][$groupID];
374 }
375
376 \uasort($data, [LabelGroup::class, 'sortLabelGroups']);
377
378 return $data;
379 }
380
381 /**
382 * Returns a list of accessible label ids.
383 *
384 * @return int[]
385 */
386 public function getAccessibleLabelIDs()
387 {
388 $labelIDs = [];
389 $groups = $this->getLabelGroups();
390
391 foreach ($groups as $group) {
392 $labelIDs = \array_merge($labelIDs, $group->getLabelIDs());
393 }
394
395 return $labelIDs;
396 }
397
398 /**
399 * Returns label group by id.
400 *
401 * @param int $groupID
402 * @return ViewableLabelGroup
403 */
404 public function getLabelGroup($groupID)
405 {
406 if (isset($this->labelGroups['groups'][$groupID])) {
407 return $this->labelGroups['groups'][$groupID];
408 }
409 }
410
411 /**
412 * Removes all assigned labels for given object ids.
413 *
414 * @param int $objectTypeID
415 * @param int[] $objectIDs
416 */
417 public function removeLabels($objectTypeID, array $objectIDs)
418 {
419 $conditions = new PreparedStatementConditionBuilder();
420 $conditions->add("objectTypeID = ?", [$objectTypeID]);
421 $conditions->add("objectID IN (?)", [$objectIDs]);
422 $sql = "DELETE FROM wcf" . WCF_N . "_label_object
423 " . $conditions;
424 $statement = WCF::getDB()->prepareStatement($sql);
425 $statement->execute($conditions->getParameters());
426 }
3b75466f 427}