2 declare(strict_types
=1);
3 namespace wcf\system\like
;
4 use wcf\data\like\
object\ILikeObject
;
5 use wcf\data\like\
object\LikeObject
;
6 use wcf\data\like\
object\LikeObjectEditor
;
7 use wcf\data\like\
object\LikeObjectList
;
8 use wcf\data\like\Like
;
9 use wcf\data\like\LikeEditor
;
10 use wcf\data\like\LikeList
;
11 use wcf\data\
object\type\ObjectType
;
12 use wcf\data\
object\type\ObjectTypeCache
;
13 use wcf\data\user\User
;
14 use wcf\data\user\UserEditor
;
15 use wcf\system\database\util\PreparedStatementConditionBuilder
;
16 use wcf\system\database\DatabaseException
;
17 use wcf\system\user\activity\event\UserActivityEventHandler
;
18 use wcf\system\user\activity\point\UserActivityPointHandler
;
19 use wcf\system\user\notification\UserNotificationHandler
;
20 use wcf\system\SingletonFactory
;
24 * Handles the likes of liked objects.
26 * Usage (retrieve all likes for a list of objects):
28 * $objectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.foo.bar');
30 * LikeHandler::getInstance()->loadLikeObjects($objectType, $objectIDs);
32 * $likeObjects = LikeHandler::getInstance()->getLikeObjects($objectType);
35 * @copyright 2001-2018 WoltLab GmbH
36 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
37 * @package WoltLabSuite\Core\System\Like
38 * @deprecated The LikeHandler is deprecated since 3.2 in favor of the \wcf\system\reaction\ReactionHandler
40 class LikeHandler
extends SingletonFactory
{
45 protected $likeObjectCache = [];
51 protected $cache = null;
54 * Creates a new LikeHandler instance.
56 protected function init() {
58 $this->cache
= ObjectTypeCache
::getInstance()->getObjectTypes('com.woltlab.wcf.like.likeableObject');
62 * Returns an object type from cache.
64 * @param string $objectName
67 public function getObjectType($objectName) {
68 if (isset($this->cache
[$objectName])) {
69 return $this->cache
[$objectName];
76 * Returns a like object.
78 * @param ObjectType $objectType
79 * @param integer $objectID
80 * @return LikeObject|null
82 public function getLikeObject(ObjectType
$objectType, $objectID) {
83 if (isset($this->likeObjectCache
[$objectType->objectTypeID
][$objectID])) {
84 return $this->likeObjectCache
[$objectType->objectTypeID
][$objectID];
91 * Returns the like objects of a specific object type.
93 * @param ObjectType $objectType
94 * @return LikeObject[]
96 public function getLikeObjects(ObjectType
$objectType) {
97 if (isset($this->likeObjectCache
[$objectType->objectTypeID
])) {
98 return $this->likeObjectCache
[$objectType->objectTypeID
];
105 * Loads the like data for a set of objects and returns the number of loaded
108 * @param ObjectType $objectType
109 * @param array $objectIDs
112 public function loadLikeObjects(ObjectType
$objectType, array $objectIDs) {
113 if (empty($objectIDs)) {
119 $conditions = new PreparedStatementConditionBuilder();
120 $conditions->add("like_object.objectTypeID = ?", [$objectType->objectTypeID
]);
121 $conditions->add("like_object.objectID IN (?)", [$objectIDs]);
122 $parameters = $conditions->getParameters();
124 if (WCF
::getUser()->userID
) {
125 $sql = "SELECT like_object.*,
126 CASE WHEN like_table.userID IS NOT NULL THEN like_table.likeValue ELSE 0 END AS liked
127 FROM wcf".WCF_N
."_like_object like_object
128 LEFT JOIN wcf".WCF_N
."_like like_table
129 ON (like_table.objectTypeID = like_object.objectTypeID
130 AND like_table.objectID = like_object.objectID
131 AND like_table.userID = ?)
134 array_unshift($parameters, WCF
::getUser()->userID
);
137 $sql = "SELECT like_object.*, 0 AS liked
138 FROM wcf".WCF_N
."_like_object like_object
142 $statement = WCF
::getDB()->prepareStatement($sql);
143 $statement->execute($parameters);
144 while ($row = $statement->fetchArray()) {
145 $this->likeObjectCache
[$objectType->objectTypeID
][$row['objectID']] = new LikeObject(null, $row);
153 * Saves the like of an object.
155 * @param ILikeObject $likeable
157 * @param integer $likeValue
158 * @param integer $time
161 public function like(ILikeObject
$likeable, User
$user, $likeValue, $time = TIME_NOW
) {
162 // verify if object is already liked by user
163 $like = Like
::getLike($likeable->getObjectType()->objectTypeID
, $likeable->getObjectID(), $user->userID
);
166 $likeObject = LikeObject
::getLikeObject($likeable->getObjectType()->objectTypeID
, $likeable->getObjectID());
168 // if vote is identically just revert the vote
169 if ($like->likeID
&& ($like->likeValue
== $likeValue)) {
170 return $this->revertLike($like, $likeable, $likeObject, $user);
174 /** @noinspection PhpUnusedLocalVariableInspection */
175 $cumulativeLikes = 0;
176 /** @noinspection PhpUnusedLocalVariableInspection */
177 $newValue = $oldValue = null;
181 WCF
::getDB()->beginTransaction();
183 // update existing object
184 if ($likeObject->likeObjectID
) {
185 $likes = $likeObject->likes
;
186 $dislikes = $likeObject->dislikes
;
187 $cumulativeLikes = $likeObject->cumulativeLikes
;
189 // previous (dis-)like already exists
191 $oldValue = $like->likeValue
;
193 // revert like and replace it with a dislike
194 if ($like->likeValue
== Like
::LIKE
) {
197 $cumulativeLikes -= 2;
198 $newValue = Like
::DISLIKE
;
201 // revert dislike and replace it with a like
204 $cumulativeLikes +
= 2;
205 $newValue = Like
::LIKE
;
209 if ($likeValue == Like
::LIKE
) {
212 $newValue = Like
::LIKE
;
217 $newValue = Like
::DISLIKE
;
224 'dislikes' => $dislikes,
225 'cumulativeLikes' => $cumulativeLikes
228 if ($likeValue == 1) {
229 $users = unserialize($likeObject->cachedUsers
);
230 if (count($users) < 3) {
231 $users[$user->userID
] = ['userID' => $user->userID
, 'username' => $user->username
];
232 $updateData['cachedUsers'] = serialize($users);
235 else if ($likeValue == -1) {
236 $users = unserialize($likeObject->cachedUsers
);
237 if (isset($users[$user->userID
])) {
238 unset($users[$user->userID
]);
239 $updateData['cachedUsers'] = serialize($users);
244 $likeObjectEditor = new LikeObjectEditor($likeObject);
245 $likeObjectEditor->update($updateData);
248 $cumulativeLikes = $likeValue;
249 $newValue = $likeValue;
251 if ($likeValue == 1) $users = [$user->userID
=> ['userID' => $user->userID
, 'username' => $user->username
]];
254 $likeObject = LikeObjectEditor
::create([
255 'objectTypeID' => $likeable->getObjectType()->objectTypeID
,
256 'objectID' => $likeable->getObjectID(),
257 'objectUserID' => $likeable->getUserID() ?
: null,
258 'likes' => ($likeValue == Like
::LIKE
) ?
1 : 0,
259 'dislikes' => ($likeValue == Like
::DISLIKE
) ?
1 : 0,
260 'cumulativeLikes' => $cumulativeLikes,
261 'cachedUsers' => serialize($users)
265 // update owner's like counter
266 if ($likeable->getUserID()) {
268 $userEditor = new UserEditor(new User($likeable->getUserID()));
269 $userEditor->updateCounters([
270 'likesReceived' => $like->likeValue
== Like
::LIKE ?
-1 : 1
273 else if ($likeValue == Like
::LIKE
) {
274 $userEditor = new UserEditor(new User($likeable->getUserID()));
275 $userEditor->updateCounters([
281 if (!$like->likeID
) {
283 $like = LikeEditor
::create([
284 'objectID' => $likeable->getObjectID(),
285 'objectTypeID' => $likeable->getObjectType()->objectTypeID
,
286 'objectUserID' => $likeable->getUserID() ?
: null,
287 'userID' => $user->userID
,
289 'likeValue' => $likeValue
292 if ($likeValue == Like
::LIKE
&& $likeable->getUserID()) {
293 UserActivityPointHandler
::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID
, $likeable->getUserID());
294 $likeable->sendNotification($like);
298 $likeEditor = new LikeEditor($like);
299 $likeEditor->update([
301 'likeValue' => $likeValue
304 if ($likeable->getUserID()) {
305 if ($likeValue == Like
::DISLIKE
) {
306 UserActivityPointHandler
::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]);
309 UserActivityPointHandler
::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID
, $likeable->getUserID());
310 $likeable->sendNotification($like);
315 // update object's like counter
316 $likeable->updateLikeCounter($cumulativeLikes);
318 WCF
::getDB()->commitTransaction();
320 catch (DatabaseException
$e) {
321 WCF
::getDB()->rollBackTransaction();
325 'data' => $this->loadLikeStatus($likeObject, $user),
327 'newValue' => $newValue,
328 'oldValue' => $oldValue,
334 * Reverts the like of an object.
337 * @param ILikeObject $likeable
338 * @param LikeObject $likeObject
342 public function revertLike(Like
$like, ILikeObject
$likeable, LikeObject
$likeObject, User
$user) {
346 WCF
::getDB()->beginTransaction();
349 $editor = new LikeEditor($like);
352 // update like object cache
353 $likes = $likeObject->likes
;
354 $dislikes = $likeObject->dislikes
;
355 $cumulativeLikes = $likeObject->cumulativeLikes
;
357 if ($like->likeValue
== Like
::LIKE
) {
369 'dislikes' => $dislikes,
370 'cumulativeLikes' => $cumulativeLikes
373 $users = $likeObject->getUsers();
374 foreach ($users as $user2) {
375 $usersArray[$user2->userID
] = ['userID' => $user2->userID
, 'username' => $user2->username
];
378 if (isset($usersArray[$user->userID
])) {
379 unset($usersArray[$user->userID
]);
380 $updateData['cachedUsers'] = serialize($usersArray);
383 $likeObjectEditor = new LikeObjectEditor($likeObject);
384 if (!$updateData['likes'] && !$updateData['dislikes']) {
385 // remove object instead
386 $likeObjectEditor->delete();
390 $likeObjectEditor->update($updateData);
393 // update owner's like counter and activity points
394 if ($likeable->getUserID()) {
395 if ($like->likeValue
== Like
::LIKE
) {
396 $userEditor = new UserEditor(new User($likeable->getUserID()));
397 $userEditor->updateCounters([
398 'likesReceived' => -1
401 UserActivityPointHandler
::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]);
405 // update object's like counter
406 $likeable->updateLikeCounter($cumulativeLikes);
408 WCF
::getDB()->commitTransaction();
410 catch (DatabaseException
$e) {
411 WCF
::getDB()->rollBackTransaction();
415 'data' => $this->loadLikeStatus($likeObject, $user),
417 'oldValue' => $like->likeValue
,
418 'users' => $usersArray
423 * Removes all likes for given objects.
425 * @param string $objectType
426 * @param integer[] $objectIDs
427 * @param string[] $notificationObjectTypes
429 public function removeLikes($objectType, array $objectIDs, array $notificationObjectTypes = []) {
430 $objectTypeObj = $this->getObjectType($objectType);
433 $likeObjectList = new LikeObjectList();
434 $likeObjectList->getConditionBuilder()->add('like_object.objectTypeID = ?', [$objectTypeObj->objectTypeID
]);
435 $likeObjectList->getConditionBuilder()->add('like_object.objectID IN (?)', [$objectIDs]);
436 $likeObjectList->readObjects();
437 $likeObjects = $likeObjectList->getObjects();
438 $likeObjectIDs = $likeObjectList->getObjectIDs();
440 // reduce count of received users
442 foreach ($likeObjects as $likeObject) {
443 if ($likeObject->likes
) {
444 if (!isset($users[$likeObject->objectUserID
])) $users[$likeObject->objectUserID
] = 0;
445 $users[$likeObject->objectUserID
] +
= $likeObject->likes
;
448 foreach ($users as $userID => $receivedLikes) {
449 $userEditor = new UserEditor(new User(null, ['userID' => $userID]));
450 $userEditor->updateCounters([
451 'likesReceived' => $receivedLikes * -1
456 $likeList = new LikeList();
457 $likeList->getConditionBuilder()->add('like_table.objectTypeID = ?', [$objectTypeObj->objectTypeID
]);
458 $likeList->getConditionBuilder()->add('like_table.objectID IN (?)', [$objectIDs]);
459 $likeList->readObjects();
461 if (count($likeList)) {
463 foreach ($likeList as $like) {
464 $likeData[$like->likeID
] = $like->userID
;
467 // delete like notifications
468 if (!empty($notificationObjectTypes)) {
469 foreach ($notificationObjectTypes as $notificationObjectType) {
470 UserNotificationHandler
::getInstance()->removeNotifications($notificationObjectType, $likeList->getObjectIDs());
473 else if (UserNotificationHandler
::getInstance()->getObjectTypeID($objectType.'.notification')) {
474 UserNotificationHandler
::getInstance()->removeNotifications($objectType.'.notification', $likeList->getObjectIDs());
477 // revoke activity points
478 UserActivityPointHandler
::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $likeData);
481 LikeEditor
::deleteAll(array_keys($likeData));
484 // delete like objects
485 if (!empty($likeObjectIDs)) {
486 LikeObjectEditor
::deleteAll($likeObjectIDs);
489 // delete activity events
490 if (UserActivityEventHandler
::getInstance()->getObjectTypeID($objectTypeObj->objectType
.'.recentActivityEvent')) {
491 UserActivityEventHandler
::getInstance()->removeEvents($objectTypeObj->objectType
.'.recentActivityEvent', $objectIDs);
496 * Returns current like object status.
498 * @param LikeObject $likeObject
502 protected function loadLikeStatus(LikeObject
$likeObject, User
$user) {
503 $sql = "SELECT like_object.likes, like_object.dislikes, like_object.cumulativeLikes,
504 CASE WHEN like_table.likeValue IS NOT NULL THEN like_table.likeValue ELSE 0 END AS liked
505 FROM wcf".WCF_N
."_like_object like_object
506 LEFT JOIN wcf".WCF_N
."_like like_table
507 ON (like_table.objectTypeID = ?
508 AND like_table.objectID = like_object.objectID
509 AND like_table.userID = ?)
510 WHERE like_object.likeObjectID = ?";
511 $statement = WCF
::getDB()->prepareStatement($sql);
512 $statement->execute([
513 $likeObject->objectTypeID
,
515 $likeObject->likeObjectID
518 return $statement->fetchArray();