Fixed reaction import, removed disabled reactions
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / reaction / ReactionHandler.class.php
CommitLineData
2e926c5e 1<?php
2e926c5e
JR
2namespace wcf\system\reaction;
3use wcf\data\like\ILikeObjectTypeProvider;
be505648 4use wcf\data\like\LikeList;
2e926c5e
JR
5use wcf\data\like\object\ILikeObject;
6use wcf\data\like\object\LikeObject;
7use wcf\data\like\object\LikeObjectEditor;
8use wcf\data\like\Like;
9use wcf\data\like\LikeEditor;
be505648 10use wcf\data\like\object\LikeObjectList;
2e926c5e
JR
11use wcf\data\object\type\ObjectType;
12use wcf\data\object\type\ObjectTypeCache;
42ce3082 13use wcf\data\reaction\object\IReactionObject;
2e926c5e
JR
14use wcf\data\reaction\type\ReactionType;
15use wcf\data\reaction\type\ReactionTypeCache;
16use wcf\data\user\User;
17use wcf\data\user\UserEditor;
18use wcf\system\cache\runtime\UserRuntimeCache;
19use wcf\system\database\exception\DatabaseQueryException;
20use wcf\system\database\util\PreparedStatementConditionBuilder;
21use wcf\system\event\EventHandler;
22use wcf\system\exception\ImplementationException;
98e163c9 23use wcf\system\user\activity\event\UserActivityEventHandler;
2e926c5e
JR
24use wcf\system\user\activity\point\UserActivityPointHandler;
25use wcf\system\SingletonFactory;
be505648 26use wcf\system\user\notification\UserNotificationHandler;
2e926c5e
JR
27use wcf\system\WCF;
28use wcf\util\JSON;
a87abbb2 29use wcf\util\StringUtil;
2e926c5e
JR
30
31/**
32 * Handles the reactions of objects.
33 *
34 * @author Joshua Ruesweg
7b7b9764 35 * @copyright 2001-2019 WoltLab GmbH
2e926c5e
JR
36 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
37 * @package WoltLabSuite\Core\System\Reaction
dd2d8c0c 38 * @since 5.2
2e926c5e
JR
39 */
40class ReactionHandler extends SingletonFactory {
41 /**
42 * loaded like objects
43 * @var LikeObject[][]
44 */
45 protected $likeObjectCache = [];
46
47 /**
48 * cached object types
49 * @var ObjectType[]
50 */
51 protected $cache = null;
52
94451637
JR
53 /**
54 * Cache for likeable objects sorted by objectType.
55 * @var ILikeObject[][]
56 */
57 private $likeableObjectsCache = [];
58
2e926c5e
JR
59 /**
60 * Creates a new ReactionHandler instance.
61 */
62 protected function init() {
63 $this->cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.like.likeableObject');
64 }
65
66 /**
67 * Returns the JSON encoded JavaScript variable for the template.
68 *
69 * @return string
70 */
ba997ccc 71 public function getReactionsJSVariable() {
1626fe3e 72 $reactions = ReactionTypeCache::getInstance()->getReactionTypes();
2e926c5e
JR
73
74 $returnValues = [];
75
76 foreach ($reactions as $reaction) {
77 $returnValues[$reaction->reactionTypeID] = [
78 'title' => $reaction->getTitle(),
79 'renderedIcon' => $reaction->renderIcon(),
3fe98bbd
JR
80 'iconPath' => $reaction->getIconPath(),
81 'showOrder' => $reaction->showOrder,
82 'reactionTypeID' => $reaction->reactionTypeID
2e926c5e
JR
83 ];
84 }
85
86 return JSON::encode($returnValues);
87 }
88
e3bfe7d4
JR
89 /**
90 * Returns all enabled reaction types.
91 *
92 * @return ReactionType[]
93 */
94 public function getReactionTypes() {
1626fe3e 95 return ReactionTypeCache::getInstance()->getReactionTypes();
e3bfe7d4
JR
96 }
97
afc11566
JR
98 /**
99 * Returns a reaction type by id.
100 *
101 * @param integer $reactionID
102 * @return ReactionType|null
103 */
104 public function getReactionTypeByID($reactionID) {
105 return ReactionTypeCache::getInstance()->getReactionTypeByID($reactionID);
106 }
107
2e926c5e
JR
108 /**
109 * Builds the data attributes for the object container.
110 *
94451637 111 * @param string $objectTypeName
2e926c5e
JR
112 * @param integer $objectID
113 * @return string
114 */
94451637
JR
115 public function getDataAttributes($objectTypeName, $objectID) {
116 $object = $this->getLikeableObject($objectTypeName, $objectID);
2e926c5e
JR
117
118 $dataAttributes = [
119 'object-id' => $object->getObjectID(),
94451637 120 'object-type' => $objectTypeName,
2e926c5e
JR
121 'user-id' => $object->getUserID()
122 ];
123
124 EventHandler::getInstance()->fireAction($this, 'getDataAttributes', $dataAttributes);
125
126 $returnDataAttributes = '';
127
128 foreach ($dataAttributes as $key => $value) {
a87abbb2
JR
129 if (!preg_match('/^[a-z0-9-]+$/', $key)) {
130 throw new \RuntimeException("Invalid key '". $key ."' for data attribute.");
131 }
132
2e926c5e
JR
133 if (!empty($returnDataAttributes)) {
134 $returnDataAttributes .= ' ';
135 }
136
a87abbb2 137 $returnDataAttributes .= 'data-'. $key .'="'. StringUtil::encodeHTML($value) .'"';
2e926c5e
JR
138 }
139
140 return $returnDataAttributes;
141 }
142
94451637
JR
143 /**
144 * Cache likeable objects.
145 *
146 * @param string $objectTypeName
147 * @param integer[] $objectIDs
148 */
149 public function cacheLikeableObjects($objectTypeName, array $objectIDs) {
150 $objectType = $this->getObjectType($objectTypeName);
151 if ($objectType === null) {
152 throw new \InvalidArgumentException("ObjectName '{$objectTypeName}' is unknown for definition 'com.woltlab.wcf.like.likeableObject'.");
153 }
154
155 /** @var ILikeObjectTypeProvider $objectTypeProcessor */
156 $objectTypeProcessor = $objectType->getProcessor();
157
158 $objects = $objectTypeProcessor->getObjectsByIDs($objectIDs);
159
160 if (!isset($this->likeableObjectsCache[$objectTypeName])) {
161 $this->likeableObjectsCache[$objectTypeName] = [];
162 }
163
164 foreach ($objects as $object) {
165 $this->likeableObjectsCache[$objectTypeName][$object->getObjectID()] = $object;
166 }
167 }
168
169 /**
170 * Get an likeable object from the internal cache.
171 *
172 * @param string $objectTypeName
173 * @param integer $objectID
174 * @return ILikeObject
175 */
176 public function getLikeableObject($objectTypeName, $objectID) {
177 if (!isset($this->likeableObjectsCache[$objectTypeName][$objectID])) {
178 $this->cacheLikeableObjects($objectTypeName, [$objectID]);
179 }
180
181 if (!isset($this->likeableObjectsCache[$objectTypeName][$objectID])) {
182 throw new \InvalidArgumentException("Object with the object id '{$objectID}' for object type '{$objectTypeName}' is unknown.");
183 }
184
185 if (!($this->likeableObjectsCache[$objectTypeName][$objectID] instanceof ILikeObject)) {
186 throw new ImplementationException(get_class($this->likeableObjectsCache[$objectTypeName][$objectID]), ILikeObject::class);
187 }
188
189 return $this->likeableObjectsCache[$objectTypeName][$objectID];
190 }
191
2e926c5e
JR
192 /**
193 * Returns an object type from cache.
194 *
afc11566
JR
195 * @param string $objectName
196 * @return ObjectType|null
2e926c5e
JR
197 */
198 public function getObjectType($objectName) {
199 if (isset($this->cache[$objectName])) {
200 return $this->cache[$objectName];
201 }
202
203 return null;
204 }
205
206 /**
207 * Returns a like object.
208 *
209 * @param ObjectType $objectType
210 * @param integer $objectID
211 * @return LikeObject|null
212 */
213 public function getLikeObject(ObjectType $objectType, $objectID) {
214 if (!isset($this->likeObjectCache[$objectType->objectTypeID][$objectID])) {
215 $this->loadLikeObjects($objectType, [$objectID]);
216 }
217
218 return isset($this->likeObjectCache[$objectType->objectTypeID][$objectID]) ? $this->likeObjectCache[$objectType->objectTypeID][$objectID] : null;
219 }
220
221 /**
222 * Returns the like objects of a specific object type.
223 *
224 * @param ObjectType $objectType
225 * @return LikeObject[]
226 */
ba997ccc 227 public function getLikeObjects(ObjectType $objectType) {
2e926c5e
JR
228 if (isset($this->likeObjectCache[$objectType->objectTypeID])) {
229 return $this->likeObjectCache[$objectType->objectTypeID];
230 }
231
232 return [];
233 }
234
235 /**
236 * Loads the like data for a set of objects and returns the number of loaded
237 * like objects
238 *
239 * @param ObjectType $objectType
240 * @param array $objectIDs
241 * @return integer
242 */
ba997ccc 243 public function loadLikeObjects(ObjectType $objectType, array $objectIDs) {
2e926c5e
JR
244 if (empty($objectIDs)) {
245 return 0;
246 }
247
94451637
JR
248 $this->cacheLikeableObjects($objectType->objectType, $objectIDs);
249
2e926c5e
JR
250 $i = 0;
251
252 $conditions = new PreparedStatementConditionBuilder();
253 $conditions->add("like_object.objectTypeID = ?", [$objectType->objectTypeID]);
254 $conditions->add("like_object.objectID IN (?)", [$objectIDs]);
255 $parameters = $conditions->getParameters();
256
257 if (WCF::getUser()->userID) {
258 $sql = "SELECT like_object.*,
60770242
JR
259 COALESCE(like_table.reactionTypeID, 0) AS reactionTypeID,
260 COALESCE(like_table.likeValue, 0) AS liked
2e926c5e
JR
261 FROM wcf".WCF_N."_like_object like_object
262 LEFT JOIN wcf".WCF_N."_like like_table
263 ON (like_table.objectTypeID = like_object.objectTypeID
264 AND like_table.objectID = like_object.objectID
265 AND like_table.userID = ?)
266 ".$conditions;
267
268 array_unshift($parameters, WCF::getUser()->userID);
269 }
270 else {
271 $sql = "SELECT like_object.*, 0 AS liked
272 FROM wcf".WCF_N."_like_object like_object
273 ".$conditions;
274 }
275
276 $statement = WCF::getDB()->prepareStatement($sql);
277 $statement->execute($parameters);
278 while ($row = $statement->fetchArray()) {
279 $this->likeObjectCache[$objectType->objectTypeID][$row['objectID']] = new LikeObject(null, $row);
280 $i++;
281 }
282
283 return $i;
284 }
285
286 /**
287 * Add a reaction to an object.
288 *
289 * @param ILikeObject $likeable
290 * @param User $user
291 * @param integer $reactionTypeID
292 * @param integer $time
293 * @return array
294 */
ba997ccc 295 public function react(ILikeObject $likeable, User $user, $reactionTypeID, $time = TIME_NOW) {
2e926c5e
JR
296 // verify if object is already liked by user
297 $like = Like::getLike($likeable->getObjectType()->objectTypeID, $likeable->getObjectID(), $user->userID);
298
299 // get like object
300 $likeObject = LikeObject::getLikeObject($likeable->getObjectType()->objectTypeID, $likeable->getObjectID());
301
302 // if vote is identically just revert the vote
303 if ($like->likeID && ($like->reactionTypeID == $reactionTypeID)) {
304 return $this->revertReact($like, $likeable, $likeObject, $user);
305 }
306
307 $reaction = ReactionTypeCache::getInstance()->getReactionTypeByID($reactionTypeID);
308
309 try {
310 WCF::getDB()->beginTransaction();
311
312 $likeObjectData = $this->updateLikeObject($likeable, $likeObject, $like, $reaction);
313
314 // update owner's like counter
315 $this->updateUsersLikeCounter($likeable, $likeObject, $like, $reaction);
316
317 if (!$like->likeID) {
318 // save like
319 $like = LikeEditor::create([
320 'objectID' => $likeable->getObjectID(),
321 'objectTypeID' => $likeable->getObjectType()->objectTypeID,
322 'objectUserID' => $likeable->getUserID() ?: null,
323 'userID' => $user->userID,
324 'time' => $time,
ec21ca41 325 'likeValue' => 1,
2e926c5e
JR
326 'reactionTypeID' => $reactionTypeID
327 ]);
328
25f91a2f 329 if ($likeable->getUserID()) {
2e926c5e 330 UserActivityPointHandler::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID, $likeable->getUserID());
2e926c5e
JR
331 }
332 }
333 else {
334 $likeEditor = new LikeEditor($like);
335 $likeEditor->update([
336 'time' => $time,
ec21ca41 337 'likeValue' => 1,
2e926c5e
JR
338 'reactionTypeID' => $reactionTypeID
339 ]);
340
341 if ($likeable->getUserID()) {
25f91a2f 342 UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]);
2e926c5e
JR
343 }
344 }
345
dd2d8c0c 346 // This interface should help to determine whether the plugin has been adapted to the API 5.2.
42ce3082 347 // If a LikeableObject does not implement this interface, no notification will be sent, because
9532293e 348 // we assume, that the plugin has not been adapted to the new API.
42ce3082
JR
349 if ($likeable instanceof IReactionObject) {
350 $likeable->sendNotification($like);
351 }
352
2e926c5e
JR
353 // update object's like counter
354 $likeable->updateLikeCounter($likeObjectData['cumulativeLikes']);
355
98e163c9
JR
356 // update recent activity
357 if (UserActivityEventHandler::getInstance()->getObjectTypeID($likeable->getObjectType()->objectType.'.recentActivityEvent')) {
c77ae4be 358 $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.recentActivityEvent', $likeable->getObjectType()->objectType.'.recentActivityEvent');
98e163c9 359
a87abbb2 360 if ($objectType->supportsReactions) {
c77ae4be
JR
361 if ($like->likeID) {
362 UserActivityEventHandler::getInstance()->removeEvent($likeable->getObjectType()->objectType . '.recentActivityEvent', $likeable->getObjectID(), $user->userID);
363 }
364
365 UserActivityEventHandler::getInstance()->fireEvent($likeable->getObjectType()->objectType . '.recentActivityEvent', $likeable->getObjectID(), $likeable->getLanguageID(), $user->userID, TIME_NOW, ['reactionType' => $reaction]);
366 }
98e163c9
JR
367 }
368
2e926c5e
JR
369 WCF::getDB()->commitTransaction();
370
371 return [
372 'cachedReactions' => $likeObjectData['cachedReactions'],
60770242 373 'reactionTypeID' => $reactionTypeID,
fbb65635 374 'like' => $like,
f3baec65
JR
375 'likeObject' => $likeObjectData['likeObject'],
376 'cumulativeLikes' => $likeObjectData['cumulativeLikes']
2e926c5e
JR
377 ];
378 }
379 catch (DatabaseQueryException $e) {
380 WCF::getDB()->rollBackTransaction();
381 }
382
2e926c5e
JR
383 return [
384 'cachedReactions' => [],
2e926c5e
JR
385 ];
386 }
387
388 /**
389 * Creates or updates a LikeObject for an likable object.
390 *
391 * @param ILikeObject $likeable
392 * @param LikeObject $likeObject
393 * @param Like $like
394 * @param ReactionType $reactionType
395 * @return array
396 */
ba997ccc 397 private function updateLikeObject(ILikeObject $likeable, LikeObject $likeObject, Like $like, ReactionType $reactionType) {
2e926c5e
JR
398 // update existing object
399 if ($likeObject->likeObjectID) {
2e926c5e 400 $cumulativeLikes = $likeObject->cumulativeLikes;
41abef8c
JR
401
402 if ($likeObject->cachedReactions !== null) {
403 $cachedReactions = @unserialize($likeObject->cachedReactions);
404 }
405 else {
406 $cachedReactions = [];
407 }
408
2e926c5e
JR
409 if (!is_array($cachedReactions)) {
410 $cachedReactions = [];
411 }
412
413 if ($like->likeID) {
25f91a2f 414 $cumulativeLikes--;
2e926c5e
JR
415
416 if (isset($cachedReactions[$like->getReactionType()->reactionTypeID])) {
417 if (--$cachedReactions[$like->getReactionType()->reactionTypeID] == 0) {
418 unset($cachedReactions[$like->getReactionType()->reactionTypeID]);
419 }
420 }
421 }
422
25f91a2f 423 $cumulativeLikes++;
2e926c5e
JR
424
425 if (isset($cachedReactions[$reactionType->reactionTypeID])) {
426 $cachedReactions[$reactionType->reactionTypeID]++;
427 }
428 else {
429 $cachedReactions[$reactionType->reactionTypeID] = 1;
430 }
431
432 // build update date
433 $updateData = [
25f91a2f
AE
434 'likes' => $cumulativeLikes,
435 'dislikes' => 0,
2e926c5e 436 'cumulativeLikes' => $cumulativeLikes,
25f91a2f 437 'cachedReactions' => serialize($cachedReactions),
2e926c5e
JR
438 ];
439
440 // update data
441 $likeObjectEditor = new LikeObjectEditor($likeObject);
442 $likeObjectEditor->update($updateData);
443 }
444 else {
25f91a2f 445 $cumulativeLikes = 1;
2e926c5e 446 $cachedReactions = [
25f91a2f 447 $reactionType->reactionTypeID => 1,
2e926c5e
JR
448 ];
449
450 // create cache
60770242 451 $likeObject = LikeObjectEditor::create([
2e926c5e
JR
452 'objectTypeID' => $likeable->getObjectType()->objectTypeID,
453 'objectID' => $likeable->getObjectID(),
454 'objectUserID' => $likeable->getUserID() ?: null,
25f91a2f
AE
455 'likes' => $cumulativeLikes,
456 'dislikes' => 0,
2e926c5e 457 'cumulativeLikes' => $cumulativeLikes,
25f91a2f 458 'cachedReactions' => serialize($cachedReactions),
2e926c5e
JR
459 ]);
460 }
461
462 return [
463 'cumulativeLikes' => $cumulativeLikes,
60770242 464 'cachedReactions' => $cachedReactions,
25f91a2f 465 'likeObject' => $likeObject,
2e926c5e
JR
466 ];
467 }
468
469 /**
470 * Updates the like counter for a user.
471 *
472 * @param ILikeObject $likeable
473 * @param LikeObject $likeObject
474 * @param Like $like
475 * @param ReactionType $reactionType
476 */
477 private function updateUsersLikeCounter(ILikeObject $likeable, LikeObject $likeObject, Like $like, ReactionType $reactionType = null) {
478 if ($likeable->getUserID()) {
25f91a2f 479 $likesReceived = 0;
be532907 480 if ($like->likeID) {
25f91a2f 481 $likesReceived--;
be532907
JR
482 }
483
484 if ($reactionType !== null) {
25f91a2f 485 $likesReceived++;
2e926c5e 486 }
be532907 487
25f91a2f
AE
488 if ($likesReceived !== 0) {
489 $userEditor = new UserEditor(UserRuntimeCache::getInstance()->getObject($likeable->getUserID()));
490 $userEditor->updateCounters(['likesReceived' => $likesReceived]);
491 }
2e926c5e
JR
492 }
493 }
494
495 /**
496 * Reverts a reaction for an object.
497 *
498 * @param Like $like
499 * @param ILikeObject $likeable
500 * @param LikeObject $likeObject
501 * @param User $user
502 * @return array
503 */
ba997ccc 504 public function revertReact(Like $like, ILikeObject $likeable, LikeObject $likeObject, User $user) {
2e926c5e
JR
505 if (!$like->likeID) {
506 throw new \InvalidArgumentException('The given parameter $like is invalid.');
507 }
508
509 try {
510 WCF::getDB()->beginTransaction();
511
512 $likeObjectData = $this->revertLikeObject($likeObject, $like);
513
514 // update owner's like counter
515 $this->updateUsersLikeCounter($likeable, $likeObject, $like, null);
516
517 $likeEditor = new LikeEditor($like);
518 $likeEditor->delete();
519
25f91a2f 520 if ($likeable->getUserID()) {
2e926c5e
JR
521 UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]);
522 }
523
524 // update object's like counter
525 $likeable->updateLikeCounter($likeObjectData['cumulativeLikes']);
526
98e163c9
JR
527 // delete recent activity
528 if (UserActivityEventHandler::getInstance()->getObjectTypeID($likeable->getObjectType()->objectType.'.recentActivityEvent')) {
529 UserActivityEventHandler::getInstance()->removeEvent($likeable->getObjectType()->objectType.'.recentActivityEvent', $likeable->getObjectID(), $user->userID);
530 }
531
2e926c5e
JR
532 WCF::getDB()->commitTransaction();
533
534 return [
535 'cachedReactions' => $likeObjectData['cachedReactions'],
60770242 536 'reactionTypeID' => null,
f3baec65
JR
537 'likeObject' => $likeObjectData['likeObject'],
538 'cumulativeLikes' => $likeObjectData['cumulativeLikes']
2e926c5e
JR
539 ];
540 }
541 catch (DatabaseQueryException $e) {
542 WCF::getDB()->rollBackTransaction();
543 }
544
2e926c5e
JR
545 return [
546 'cachedReactions' => [],
60770242 547 'reactionTypeID' => null,
f3baec65
JR
548 'likeObject' => [],
549 'cumulativeLikes' => null
2e926c5e
JR
550 ];
551 }
552
553 /**
554 * Creates or updates a LikeObject for an likable object.
555 *
556 * @param LikeObject $likeObject
557 * @param Like $like
558 * @return array
559 */
ba997ccc 560 private function revertLikeObject(LikeObject $likeObject, Like $like) {
2e926c5e
JR
561 if (!$likeObject->likeObjectID) {
562 throw new \InvalidArgumentException('The given parameter $likeObject is invalid.');
563 }
564
565 // update existing object
2e926c5e
JR
566 $cumulativeLikes = $likeObject->cumulativeLikes;
567 $cachedReactions = @unserialize($likeObject->cachedReactions);
568 if (!is_array($cachedReactions)) {
569 $cachedReactions = [];
570 }
571
572 if ($like->likeID) {
25f91a2f 573 $cumulativeLikes--;
2e926c5e
JR
574
575 if (isset($cachedReactions[$like->getReactionType()->reactionTypeID])) {
576 if (--$cachedReactions[$like->getReactionType()->reactionTypeID] == 0) {
577 unset($cachedReactions[$like->getReactionType()->reactionTypeID]);
578 }
579 }
580
581 // build update date
582 $updateData = [
25f91a2f
AE
583 'likes' => $cumulativeLikes,
584 'dislikes' => 0,
2e926c5e
JR
585 'cumulativeLikes' => $cumulativeLikes,
586 'cachedReactions' => serialize($cachedReactions)
587 ];
588
589 // update data
590 $likeObjectEditor = new LikeObjectEditor($likeObject);
591 $likeObjectEditor->update($updateData);
592 }
593
594 return [
595 'cumulativeLikes' => $cumulativeLikes,
60770242
JR
596 'cachedReactions' => $cachedReactions,
597 'likeObject' => $likeObject
2e926c5e
JR
598 ];
599 }
600
be505648
JR
601 /**
602 * Removes all reactions for given objects.
603 *
604 * @param string $objectType
605 * @param integer[] $objectIDs
606 * @param string[] $notificationObjectTypes
607 */
cbc9baa9 608 public function removeReactions($objectType, array $objectIDs, array $notificationObjectTypes = []) {
be505648
JR
609 $objectTypeObj = $this->getObjectType($objectType);
610
611 if ($objectTypeObj === null) {
612 throw new \InvalidArgumentException('Given objectType is invalid.');
613 }
614
615 // get like objects
616 $likeObjectList = new LikeObjectList();
617 $likeObjectList->getConditionBuilder()->add('like_object.objectTypeID = ?', [$objectTypeObj->objectTypeID]);
618 $likeObjectList->getConditionBuilder()->add('like_object.objectID IN (?)', [$objectIDs]);
619 $likeObjectList->readObjects();
620 $likeObjects = $likeObjectList->getObjects();
621 $likeObjectIDs = $likeObjectList->getObjectIDs();
622
623 // reduce count of received users
624 $users = [];
625 foreach ($likeObjects as $likeObject) {
626 if ($likeObject->likes) {
627 if (!isset($users[$likeObject->objectUserID])) {
25f91a2f 628 $users[$likeObject->objectUserID] = 0;
be505648
JR
629 }
630
25f91a2f 631 $users[$likeObject->objectUserID] -= count($likeObject->getReactions());
be505648
JR
632 }
633 }
634
635 foreach ($users as $userID => $reactionData) {
636 $userEditor = new UserEditor(new User(null, ['userID' => $userID]));
637 $userEditor->updateCounters([
25f91a2f 638 'likesReceived' => $users[$userID],
be505648
JR
639 ]);
640 }
641
642 // get like ids
643 $likeList = new LikeList();
644 $likeList->getConditionBuilder()->add('like_table.objectTypeID = ?', [$objectTypeObj->objectTypeID]);
645 $likeList->getConditionBuilder()->add('like_table.objectID IN (?)', [$objectIDs]);
646 $likeList->readObjects();
647
648 if (count($likeList)) {
25f91a2f 649 $likeData = [];
be505648
JR
650 foreach ($likeList as $like) {
651 $likeData[$like->likeID] = $like->userID;
be505648
JR
652 }
653
654 // delete like notifications
655 if (!empty($notificationObjectTypes)) {
656 foreach ($notificationObjectTypes as $notificationObjectType) {
657 UserNotificationHandler::getInstance()->removeNotifications($notificationObjectType, $likeList->getObjectIDs());
658 }
659 }
660 else if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType.'.notification')) {
661 UserNotificationHandler::getInstance()->removeNotifications($objectType.'.notification', $likeList->getObjectIDs());
662 }
663
664 // revoke activity points
25f91a2f 665 UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $likeData);
be505648
JR
666
667 // delete likes
668 LikeEditor::deleteAll(array_keys($likeData));
669 }
670
671 // delete like objects
672 if (!empty($likeObjectIDs)) {
673 LikeObjectEditor::deleteAll($likeObjectIDs);
674 }
675
676 // delete activity events
677 if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectTypeObj->objectType.'.recentActivityEvent')) {
678 UserActivityEventHandler::getInstance()->removeEvents($objectTypeObj->objectType.'.recentActivityEvent', $objectIDs);
679 }
2e926c5e
JR
680 }
681
682 /**
683 * Returns current like object status.
684 *
685 * @param LikeObject $likeObject
686 * @param User $user
687 * @return array
688 */
689 protected function loadLikeStatus(LikeObject $likeObject, User $user) {
690 $sql = "SELECT like_object.likes, like_object.dislikes, like_object.cumulativeLikes,
60770242
JR
691 COALESCE(like_table.reactionTypeID, 0) AS reactionTypeID,
692 COALESCE(like_table.likeValue, 0) AS liked
2e926c5e
JR
693 FROM wcf".WCF_N."_like_object like_object
694 LEFT JOIN wcf".WCF_N."_like like_table
695 ON (like_table.objectTypeID = ?
696 AND like_table.objectID = like_object.objectID
697 AND like_table.userID = ?)
698 WHERE like_object.likeObjectID = ?";
699 $statement = WCF::getDB()->prepareStatement($sql);
700 $statement->execute([
701 $likeObject->objectTypeID,
702 $user->userID,
703 $likeObject->likeObjectID
704 ]);
705
706 return $statement->fetchArray();
707 }
60770242
JR
708
709 /**
25f91a2f 710 * Returns the first available reaction type.
60770242 711 *
25f91a2f 712 * @return ReactionType|null
60770242 713 */
25f91a2f
AE
714 public function getFirstReactionType() {
715 static $firstReactionType;
716
717 if ($firstReactionType === null) {
1626fe3e 718 $reactionTypes = ReactionTypeCache::getInstance()->getReactionTypes();
25f91a2f 719 ReactionType::sort($reactionTypes, 'showOrder');
d3fb7128 720
25f91a2f 721 $firstReactionType = reset($reactionTypes);
60770242 722 }
25f91a2f
AE
723
724 return $firstReactionType;
725 }
726
727 /**
728 * Returns the first available reaction type's id.
729 *
730 * @return int|null
731 */
732 public function getFirstReactionTypeID() {
733 $firstReactionType = $this->getFirstReactionType();
734
735 return $firstReactionType ? $firstReactionType->reactionTypeID : null;
60770242 736 }
63e477f7
AE
737
738 /**
739 * @param string|null $cachedReactions
740 * @return array|null
741 * @since 5.2
742 */
743 public function getTopReaction($cachedReactions) {
744 if ($cachedReactions) {
745 $cachedReactions = @unserialize($cachedReactions);
746
747 if (is_array($cachedReactions) && !empty($cachedReactions)) {
d5d52ef5 748 $allReactions = array_sum($cachedReactions);
63e477f7 749
d5d52ef5
AE
750 arsort($cachedReactions, SORT_NUMERIC);
751
752 $count = current($cachedReactions);
63e477f7 753 return [
d5d52ef5
AE
754 'count' => $count,
755 'other' => $allReactions - $count,
63e477f7
AE
756 'reaction' => ReactionTypeCache::getInstance()->getReactionTypeByID(key($cachedReactions)),
757 ];
758 }
759 }
760
761 return null;
762 }
2e926c5e 763}