Commit | Line | Data |
---|---|---|
3b75466f | 1 | <?php |
a9229942 | 2 | |
3b75466f | 3 | namespace wcf\system\label; |
a9229942 | 4 | |
157054c9 | 5 | use wcf\data\label\group\LabelGroup; |
2b770bdd | 6 | use wcf\data\label\group\ViewableLabelGroup; |
004fe3ae | 7 | use wcf\data\label\Label; |
3b75466f | 8 | use wcf\data\object\type\ObjectTypeCache; |
eb66d59c | 9 | use wcf\data\user\User; |
3b75466f MW |
10 | use wcf\system\cache\builder\LabelCacheBuilder; |
11 | use wcf\system\database\util\PreparedStatementConditionBuilder; | |
12 | use wcf\system\exception\SystemException; | |
13 | use wcf\system\SingletonFactory; | |
14 | use 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 |
24 | class 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 | } |