2 namespace wcf\system\like
;
3 use wcf\data\like\
object\ILikeObject
;
4 use wcf\data\like\
object\LikeObject
;
5 use wcf\data\like\
object\LikeObjectEditor
;
6 use wcf\data\like\
object\LikeObjectList
;
7 use wcf\data\like\Like
;
8 use wcf\data\like\LikeEditor
;
9 use wcf\data\like\LikeList
;
10 use wcf\data\
object\type\ObjectType
;
11 use wcf\data\
object\type\ObjectTypeCache
;
12 use wcf\data\user\User
;
13 use wcf\data\user\UserEditor
;
14 use wcf\system\database\util\PreparedStatementConditionBuilder
;
15 use wcf\system\database\DatabaseException
;
16 use wcf\system\user\activity\event\UserActivityEventHandler
;
17 use wcf\system\user\activity\point\UserActivityPointHandler
;
18 use wcf\system\user\notification\UserNotificationHandler
;
19 use wcf\system\SingletonFactory
;
23 * Handles the likes of liked objects.
25 * Usage (retrieve all likes for a list of objects):
27 * $objectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.foo.bar');
29 * LikeHandler::getInstance()->loadLikeObjects($objectType, $objectIDs);
31 * $likeObjects = LikeHandler::getInstance()->getLikeObjects($objectType);
34 * @copyright 2001-2018 WoltLab GmbH
35 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
36 * @package WoltLabSuite\Core\System\Like
38 class LikeHandler
extends SingletonFactory
{
43 protected $likeObjectCache = [];
49 protected $cache = null;
52 * Creates a new LikeHandler instance.
54 protected function init() {
56 $this->cache
= ObjectTypeCache
::getInstance()->getObjectTypes('com.woltlab.wcf.like.likeableObject');
60 * Returns an object type from cache.
62 * @param string $objectName
65 public function getObjectType($objectName) {
66 if (isset($this->cache
[$objectName])) {
67 return $this->cache
[$objectName];
74 * Returns a like object.
76 * @param ObjectType $objectType
77 * @param integer $objectID
78 * @return LikeObject|null
80 public function getLikeObject(ObjectType
$objectType, $objectID) {
81 if (isset($this->likeObjectCache
[$objectType->objectTypeID
][$objectID])) {
82 return $this->likeObjectCache
[$objectType->objectTypeID
][$objectID];
89 * Returns the like objects of a specific object type.
91 * @param ObjectType $objectType
92 * @return LikeObject[]
94 public function getLikeObjects(ObjectType
$objectType) {
95 if (isset($this->likeObjectCache
[$objectType->objectTypeID
])) {
96 return $this->likeObjectCache
[$objectType->objectTypeID
];
103 * Loads the like data for a set of objects and returns the number of loaded
106 * @param ObjectType $objectType
107 * @param array $objectIDs
110 public function loadLikeObjects(ObjectType
$objectType, array $objectIDs) {
111 if (empty($objectIDs)) {
117 $conditions = new PreparedStatementConditionBuilder();
118 $conditions->add("like_object.objectTypeID = ?", [$objectType->objectTypeID
]);
119 $conditions->add("like_object.objectID IN (?)", [$objectIDs]);
120 $parameters = $conditions->getParameters();
122 if (WCF
::getUser()->userID
) {
123 $sql = "SELECT like_object.*,
124 CASE WHEN like_table.userID IS NOT NULL THEN like_table.likeValue ELSE 0 END AS liked
125 FROM wcf".WCF_N
."_like_object like_object
126 LEFT JOIN wcf".WCF_N
."_like like_table
127 ON (like_table.objectTypeID = like_object.objectTypeID
128 AND like_table.objectID = like_object.objectID
129 AND like_table.userID = ?)
132 array_unshift($parameters, WCF
::getUser()->userID
);
135 $sql = "SELECT like_object.*, 0 AS liked
136 FROM wcf".WCF_N
."_like_object like_object
140 $statement = WCF
::getDB()->prepareStatement($sql);
141 $statement->execute($parameters);
142 while ($row = $statement->fetchArray()) {
143 $this->likeObjectCache
[$objectType->objectTypeID
][$row['objectID']] = new LikeObject(null, $row);
151 * Saves the like of an object.
153 * @param ILikeObject $likeable
155 * @param integer $likeValue
156 * @param integer $time
159 public function like(ILikeObject
$likeable, User
$user, $likeValue, $time = TIME_NOW
) {
160 // verify if object is already liked by user
161 $like = Like
::getLike($likeable->getObjectType()->objectTypeID
, $likeable->getObjectID(), $user->userID
);
164 $likeObject = LikeObject
::getLikeObject($likeable->getObjectType()->objectTypeID
, $likeable->getObjectID());
166 // if vote is identically just revert the vote
167 if ($like->likeID
&& ($like->likeValue
== $likeValue)) {
168 return $this->revertLike($like, $likeable, $likeObject, $user);
172 /** @noinspection PhpUnusedLocalVariableInspection */
173 $cumulativeLikes = 0;
174 /** @noinspection PhpUnusedLocalVariableInspection */
175 $newValue = $oldValue = null;
179 WCF
::getDB()->beginTransaction();
181 // update existing object
182 if ($likeObject->likeObjectID
) {
183 $likes = $likeObject->likes
;
184 $dislikes = $likeObject->dislikes
;
185 $cumulativeLikes = $likeObject->cumulativeLikes
;
187 // previous (dis-)like already exists
189 $oldValue = $like->likeValue
;
191 // revert like and replace it with a dislike
192 if ($like->likeValue
== Like
::LIKE
) {
195 $cumulativeLikes -= 2;
196 $newValue = Like
::DISLIKE
;
199 // revert dislike and replace it with a like
202 $cumulativeLikes +
= 2;
203 $newValue = Like
::LIKE
;
207 if ($likeValue == Like
::LIKE
) {
210 $newValue = Like
::LIKE
;
215 $newValue = Like
::DISLIKE
;
222 'dislikes' => $dislikes,
223 'cumulativeLikes' => $cumulativeLikes
226 if ($likeValue == 1) {
227 $users = unserialize($likeObject->cachedUsers
);
228 if (count($users) < 3) {
229 $users[$user->userID
] = ['userID' => $user->userID
, 'username' => $user->username
];
230 $updateData['cachedUsers'] = serialize($users);
233 else if ($likeValue == -1) {
234 $users = unserialize($likeObject->cachedUsers
);
235 if (isset($users[$user->userID
])) {
236 unset($users[$user->userID
]);
237 $updateData['cachedUsers'] = serialize($users);
242 $likeObjectEditor = new LikeObjectEditor($likeObject);
243 $likeObjectEditor->update($updateData);
246 $cumulativeLikes = $likeValue;
247 $newValue = $likeValue;
249 if ($likeValue == 1) $users = [$user->userID
=> ['userID' => $user->userID
, 'username' => $user->username
]];
252 $likeObject = LikeObjectEditor
::create([
253 'objectTypeID' => $likeable->getObjectType()->objectTypeID
,
254 'objectID' => $likeable->getObjectID(),
255 'objectUserID' => $likeable->getUserID() ?
: null,
256 'likes' => ($likeValue == Like
::LIKE
) ?
1 : 0,
257 'dislikes' => ($likeValue == Like
::DISLIKE
) ?
1 : 0,
258 'cumulativeLikes' => $cumulativeLikes,
259 'cachedUsers' => serialize($users)
263 // update owner's like counter
264 if ($likeable->getUserID()) {
266 $userEditor = new UserEditor(new User($likeable->getUserID()));
267 $userEditor->updateCounters([
268 'likesReceived' => $like->likeValue
== Like
::LIKE ?
-1 : 1
271 else if ($likeValue == Like
::LIKE
) {
272 $userEditor = new UserEditor(new User($likeable->getUserID()));
273 $userEditor->updateCounters([
279 if (!$like->likeID
) {
281 $like = LikeEditor
::create([
282 'objectID' => $likeable->getObjectID(),
283 'objectTypeID' => $likeable->getObjectType()->objectTypeID
,
284 'objectUserID' => $likeable->getUserID() ?
: null,
285 'userID' => $user->userID
,
287 'likeValue' => $likeValue
290 if ($likeValue == Like
::LIKE
&& $likeable->getUserID()) {
291 UserActivityPointHandler
::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID
, $likeable->getUserID());
292 $likeable->sendNotification($like);
296 $likeEditor = new LikeEditor($like);
297 $likeEditor->update([
299 'likeValue' => $likeValue
302 if ($likeable->getUserID()) {
303 if ($likeValue == Like
::DISLIKE
) {
304 UserActivityPointHandler
::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]);
307 UserActivityPointHandler
::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID
, $likeable->getUserID());
308 $likeable->sendNotification($like);
313 // update object's like counter
314 $likeable->updateLikeCounter($cumulativeLikes);
316 WCF
::getDB()->commitTransaction();
318 catch (DatabaseException
$e) {
319 WCF
::getDB()->rollBackTransaction();
323 'data' => $this->loadLikeStatus($likeObject, $user),
325 'newValue' => $newValue,
326 'oldValue' => $oldValue,
332 * Reverts the like of an object.
335 * @param ILikeObject $likeable
336 * @param LikeObject $likeObject
340 public function revertLike(Like
$like, ILikeObject
$likeable, LikeObject
$likeObject, User
$user) {
344 WCF
::getDB()->beginTransaction();
347 $editor = new LikeEditor($like);
350 // update like object cache
351 $likes = $likeObject->likes
;
352 $dislikes = $likeObject->dislikes
;
353 $cumulativeLikes = $likeObject->cumulativeLikes
;
355 if ($like->likeValue
== Like
::LIKE
) {
367 'dislikes' => $dislikes,
368 'cumulativeLikes' => $cumulativeLikes
371 $users = $likeObject->getUsers();
372 foreach ($users as $user2) {
373 $usersArray[$user2->userID
] = ['userID' => $user2->userID
, 'username' => $user2->username
];
376 if (isset($usersArray[$user->userID
])) {
377 unset($usersArray[$user->userID
]);
378 $updateData['cachedUsers'] = serialize($usersArray);
381 $likeObjectEditor = new LikeObjectEditor($likeObject);
382 if (!$updateData['likes'] && !$updateData['dislikes']) {
383 // remove object instead
384 $likeObjectEditor->delete();
388 $likeObjectEditor->update($updateData);
391 // update owner's like counter and activity points
392 if ($likeable->getUserID()) {
393 if ($like->likeValue
== Like
::LIKE
) {
394 $userEditor = new UserEditor(new User($likeable->getUserID()));
395 $userEditor->updateCounters([
396 'likesReceived' => -1
399 UserActivityPointHandler
::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]);
403 // update object's like counter
404 $likeable->updateLikeCounter($cumulativeLikes);
406 WCF
::getDB()->commitTransaction();
408 catch (DatabaseException
$e) {
409 WCF
::getDB()->rollBackTransaction();
413 'data' => $this->loadLikeStatus($likeObject, $user),
415 'oldValue' => $like->likeValue
,
416 'users' => $usersArray
421 * Removes all likes for given objects.
423 * @param string $objectType
424 * @param integer[] $objectIDs
425 * @param string[] $notificationObjectTypes
427 public function removeLikes($objectType, array $objectIDs, array $notificationObjectTypes = []) {
428 $objectTypeObj = $this->getObjectType($objectType);
431 $likeObjectList = new LikeObjectList();
432 $likeObjectList->getConditionBuilder()->add('like_object.objectTypeID = ?', [$objectTypeObj->objectTypeID
]);
433 $likeObjectList->getConditionBuilder()->add('like_object.objectID IN (?)', [$objectIDs]);
434 $likeObjectList->readObjects();
435 $likeObjects = $likeObjectList->getObjects();
436 $likeObjectIDs = $likeObjectList->getObjectIDs();
438 // reduce count of received users
440 foreach ($likeObjects as $likeObject) {
441 if ($likeObject->likes
) {
442 if (!isset($users[$likeObject->objectUserID
])) $users[$likeObject->objectUserID
] = 0;
443 $users[$likeObject->objectUserID
] +
= $likeObject->likes
;
446 foreach ($users as $userID => $receivedLikes) {
447 $userEditor = new UserEditor(new User(null, ['userID' => $userID]));
448 $userEditor->updateCounters([
449 'likesReceived' => $receivedLikes * -1
454 $likeList = new LikeList();
455 $likeList->getConditionBuilder()->add('like_table.objectTypeID = ?', [$objectTypeObj->objectTypeID
]);
456 $likeList->getConditionBuilder()->add('like_table.objectID IN (?)', [$objectIDs]);
457 $likeList->readObjects();
459 if (count($likeList)) {
461 foreach ($likeList as $like) {
462 $likeData[$like->likeID
] = $like->userID
;
465 // delete like notifications
466 if (!empty($notificationObjectTypes)) {
467 foreach ($notificationObjectTypes as $notificationObjectType) {
468 UserNotificationHandler
::getInstance()->removeNotifications($notificationObjectType, $likeList->getObjectIDs());
471 else if (UserNotificationHandler
::getInstance()->getObjectTypeID($objectType.'.notification')) {
472 UserNotificationHandler
::getInstance()->removeNotifications($objectType.'.notification', $likeList->getObjectIDs());
475 // revoke activity points
476 UserActivityPointHandler
::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $likeData);
479 LikeEditor
::deleteAll(array_keys($likeData));
482 // delete like objects
483 if (!empty($likeObjectIDs)) {
484 LikeObjectEditor
::deleteAll($likeObjectIDs);
487 // delete activity events
488 if (UserActivityEventHandler
::getInstance()->getObjectTypeID($objectTypeObj->objectType
.'.recentActivityEvent')) {
489 UserActivityEventHandler
::getInstance()->removeEvents($objectTypeObj->objectType
.'.recentActivityEvent', $objectIDs);
494 * Returns current like object status.
496 * @param LikeObject $likeObject
500 protected function loadLikeStatus(LikeObject
$likeObject, User
$user) {
501 $sql = "SELECT like_object.likes, like_object.dislikes, like_object.cumulativeLikes,
502 CASE WHEN like_table.likeValue IS NOT NULL THEN like_table.likeValue ELSE 0 END AS liked
503 FROM wcf".WCF_N
."_like_object like_object
504 LEFT JOIN wcf".WCF_N
."_like like_table
505 ON (like_table.objectTypeID = ?
506 AND like_table.objectID = like_object.objectID
507 AND like_table.userID = ?)
508 WHERE like_object.likeObjectID = ?";
509 $statement = WCF
::getDB()->prepareStatement($sql);
510 $statement->execute([
511 $likeObject->objectTypeID
,
513 $likeObject->likeObjectID
516 return $statement->fetchArray();