From 6077024256f9a325daa8b58173c764e62fad24d9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Joshua=20R=C3=BCsweg?= Date: Sun, 22 Jul 2018 20:44:16 +0200 Subject: [PATCH] Adapt the old like system to the new reaction system See #2508 --- .../js/WoltLabSuite/Core/Ui/Like/Handler.js | 337 ++++----------- .../lib/data/like/object/LikeObject.class.php | 27 +- .../lib/system/like/LikeHandler.class.php | 402 ++---------------- .../system/reaction/ReactionHandler.class.php | 48 ++- .../install/files/style/ui/reactions.scss | 23 +- 5 files changed, 194 insertions(+), 643 deletions(-) diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js index 67ff3d41a8..18f278506d 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js @@ -11,18 +11,16 @@ define( [ 'Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/ChangeListener', 'Dom/Util', - 'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List', 'User' + 'Ui/Dialog', 'WoltLabSuite/Core/Ui/User/List', 'User', 'WoltLabSuite/Core/Ui/Reaction/Handler' ], function( Ajax, Core, Dictionary, Language, ObjectMap, StringUtil, DomChangeListener, DomUtil, - UiDialog, UiUserList, User + UiDialog, UiUserList, User, UiReactionHandler ) { "use strict"; - var _isBusy = false; - /** * @constructor */ @@ -68,6 +66,17 @@ define( this.initContainers(options, objectType); DomChangeListener.add('WoltLabSuite/Core/Ui/Like/Handler-' + objectType, this.initContainers.bind(this)); + + new UiReactionHandler(this._objectType, { + // permissions + canReact: this._options.canLike, + canReactToOwnContent: this._options.canLikeOwnContent, + canViewReactions: this._options.canViewSummary, + + // selectors + containerSelector: this._options.containerSelector, + summaryListSelector: this._options.summarySelector + }); }, /** @@ -112,61 +121,60 @@ define( * @param {object} elementData like data */ _buildWidget: function(element, elementData) { - // build summary - if (this._options.canViewSummary) { - var summary, summaryContent, summaryIcon; - var summaryContainer = (this._options.isSingleItem) ? elBySel(this._options.summarySelector) : elBySel(this._options.summarySelector, element); - if (summaryContainer !== null) { - summary = elCreate('div'); - summary.className = 'likesSummary'; + // build reaction summary list + var summaryList, listItem, badgeContainer, isSummaryPosition = true; + badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.summarySelector) : elBySel(this._options.summarySelector, element); + if (badgeContainer === null) { + badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.badgeContainerSelector) : elBySel(this._options.badgeContainerSelector, element); + isSummaryPosition = false; + } + + if (badgeContainer !== null) { + summaryList = elCreate('ul'); + summaryList.className = 'reactionSummaryList' + (isSummaryPosition ? ' likesSummary' : ' reactionSummaryListTiny') + ((isSummaryPosition && this._options.badgeClassNames) ? ' ' + this._options.badgeClassNames : ''); + + for (var key in elementData.users) { + if (key === "reactionTypeID") continue; + if (!REACTION_TYPES.hasOwnProperty(key)) continue; - if (this._options.summaryUseIcon) { - summaryIcon = elCreate('span'); - summaryIcon.className = 'icon icon16 fa-thumbs-o-up'; - summary.appendChild(summaryIcon); - } + // create element + var createdElement = elCreate('li'); + createdElement.className = 'reactCountButton'; + createdElement.innerHTML = REACTION_TYPES[key].renderedIcon +' '; + elData(createdElement, 'reaction-type-id', key); + + var countSpan = elCreate('span'); + countSpan.className = 'reactionCount'; + countSpan.innerHTML = StringUtil.shortUnit(elementData.users[key]); + createdElement.appendChild(countSpan); - summaryContent = elCreate('span'); - summaryContent.className = 'likesSummaryContent'; - summaryContent.addEventListener(WCF_CLICK_EVENT, this._showSummary.bind(this, element)); - summary.appendChild(summaryContent); + summaryList.appendChild(createdElement); + } + + if (isSummaryPosition) { if (this._options.summaryPrepend) { - DomUtil.prepend(summary, summaryContainer); + DomUtil.prepend(summaryList, badgeContainer); } else { - summaryContainer.appendChild(summary); + badgeContainer.appendChild(summaryList); } - - elementData.summary = summaryContent; - - this._updateSummary(element); - } - } - - // cumulative likes - var badge, listItem; - var badgeContainer = (this._options.isSingleItem) ? elBySel(this._options.badgeContainerSelector) : elBySel(this._options.badgeContainerSelector, element); - if (badgeContainer !== null) { - badge = elCreate('a'); - badge.href = '#'; - badge.className = 'wcfLikeCounter jsTooltip' + (this._options.badgeClassNames ? ' ' + this._options.badgeClassNames : ''); - badge.addEventListener(WCF_CLICK_EVENT, this._showSummary.bind(this, element)); - - if (badgeContainer.nodeName === 'OL' || badgeContainer.nodeName === 'UL') { - listItem = elCreate('li'); - listItem.appendChild(badge); - badgeContainer.appendChild(listItem); } else { - badgeContainer.appendChild(badge); + if (badgeContainer.nodeName === 'OL' || badgeContainer.nodeName === 'UL') { + listItem = elCreate('li'); + listItem.appendChild(summaryList); + badgeContainer.appendChild(listItem); + } + else { + badgeContainer.appendChild(summaryList); + } } - elementData.badge = badge; - - this._updateBadge(element); + elementData.badge = summaryList; } + // build reaction button if (this._options.canLike && (User.userId != elData(element, 'user-id') || this._options.canLikeOwnContent)) { var appendTo = (this._options.buttonAppendToSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonAppendToSelector) : elBySel(this._options.buttonAppendToSelector, element)) : null; var insertPosition = (this._options.buttonBeforeSelector) ? ((this._options.isSingleItem) ? elBySel(this._options.buttonBeforeSelector) : elBySel(this._options.buttonBeforeSelector, element)) : null; @@ -174,241 +182,64 @@ define( throw new Error("Unable to find insert location for like/dislike buttons."); } else { - // like button - elementData.likeButton = this._createButton(element, true, insertPosition, appendTo); - - // dislike button - if (this._options.canDislike) { - elementData.dislikeButton = this._createButton(element, false, insertPosition, appendTo); - } - - this._updateActiveState(element); + elementData.likeButton = this._createButton(element, elementData.users.reactionTypeID, insertPosition, appendTo); } } }, /** - * Creates a like or dislike button. + * Creates a reaction button. * - * @param {Element} element container element - * @param {boolean} isLike false if this is a dislike button - * @param {Element?} insertBefore insert button before given element - * @param {Element?} appendTo append button to given element + * @param {Element} element container element + * @param {int} reactionTypeID the reactionTypeID of the current state + * @param {Element?} insertBefore insert button before given element + * @param {Element?} appendTo append button to given element * @return {Element} button element */ - _createButton: function(element, isLike, insertBefore, appendTo) { - var title = Language.get('wcf.like.button.' + (isLike ? 'like' : 'dislike')); + _createButton: function(element, reactionTypeID, insertBefore, appendTo) { + var title = Language.get('wcf.reactions.react'); var listItem = elCreate('li'); - listItem.className = 'wcf' + (isLike ? 'Like' : 'Dislike') + 'Button'; + listItem.className = 'wcfReactButton'; var button = elCreate('a'); - button.className = 'jsTooltip' + (this._options.renderAsButton ? ' button' : ''); + button.className = 'jsTooltip reactButton' + (this._options.renderAsButton ? ' button' : ''); button.href = '#'; button.title = title; - button.innerHTML = ' '; - button.addEventListener(WCF_CLICK_EVENT, this._like.bind(this, element)); - elData(button, 'type', (isLike ? 'like' : 'dislike')); - listItem.appendChild(button); - - if (insertBefore) { - insertBefore.parentNode.insertBefore(listItem, insertBefore); - } - else { - appendTo.appendChild(listItem); - } - - return button; - }, - - /** - * Shows the summary of likes/dislikes. - * - * @param {Element} element container element - * @param {object} event event object - */ - _showSummary: function(element, event) { - event.preventDefault(); - - if (!this._details.has(element)) { - this._details.set(element, new UiUserList({ - className: 'wcf\\data\\like\\LikeAction', - dialogTitle: Language.get('wcf.like.details'), - parameters: { - data: { - containerID: DomUtil.identify(element), - objectID: this._containers.get(element).objectId, - objectType: this._objectType - } - } - })); - } - - this._details.get(element).open(); - }, - - /** - * Updates the display of cumulative likes. - * - * @param {Element} element container element - */ - _updateBadge: function(element) { - var data = this._containers.get(element); + var icon = elCreate('img'); + icon.className = 'reactionType'; - if (data.likes === 0 && data.dislikes === 0) { - elHide(data.badge); + if (reactionTypeID === undefined || reactionTypeID == 0) { + icon.src = WCF_PATH+'images/reaction/reactionIcon.svg'; + elData(icon, 'reaction-type-id', 0); } else { - elShow(data.badge); - - // remove old classes - data.badge.classList.remove('likeCounterLiked', 'likeCounterDisliked'); + icon.src = REACTION_TYPES[reactionTypeID].iconPath; + elData(icon, 'reaction-type-id', reactionTypeID); - // update like counter - var cumulativeLikes = data.likes - data.dislikes; - var content = ''; - if (cumulativeLikes > 0) { - content += '+' + StringUtil.addThousandsSeparator(cumulativeLikes); - data.badge.classList.add('likeCounterLiked'); - } - else if (cumulativeLikes < 0) { - // U+2212 = minus sign - content += '\u2212' + StringUtil.addThousandsSeparator(Math.abs(cumulativeLikes)); - data.badge.classList.add('likeCounterDisliked'); - } - else { - // U+00B1 = plus-minus sign - content += '\u00B1' + '0'; - } - - data.badge.innerHTML = content + ''; - data.badge.setAttribute('data-tooltip', Language.get('wcf.like.tooltip', { - dislikes: data.dislikes, - likes: data.likes - })); - } - }, - - /** - * Updates the like summary. - * - * @param {Element} element container element - */ - _updateSummary: function(element) { - var data = this._containers.get(element); - - if (data.likes) { - elShow(data.summary.parentNode); - - var usernames = []; - var keys = Object.keys(data.users); - for (var i = 0, length = keys.length; i < length; i++) { - usernames.push(data.users[keys[i]]); - } - - var others = data.likes - usernames.length; - data.summary.innerHTML = Language.get('wcf.like.summary', { users: usernames, others: others }); - } - else { - elHide(data.summary.parentNode); + button.classList.add("active"); } - }, - - /** - * Updates the active like/dislike button state. - * - * @param {Element} element container element - */ - _updateActiveState: function(element) { - var data = this._containers.get(element); - var likeTarget = (this._options.markListItemAsActive) ? data.likeButton.parentNode : data.likeButton; - likeTarget.classList.remove('active'); - - if (data.liked === 1) { - likeTarget.classList.add('active'); - } + button.appendChild(icon); - if (this._options.canDislike) { - var dislikeTarget = (this._options.markListItemAsActive) ? data.dislikeButton.parentNode : data.dislikeButton; - dislikeTarget.classList.remove('active'); - - if (data.liked === -1) { - dislikeTarget.classList.add('active'); - } - } - }, - - /** - * Likes or dislikes an element. - * - * @param {Element} element container element - * @param {object} event event object - */ - _like: function(element, event) { - event.preventDefault(); + var invisibleText = elCreate("span"); + invisibleText.className = "invisible"; + invisibleText.innerHTML = title; - if (_isBusy) { - return; - } + button.appendChild(document.createTextNode (" ")); + button.appendChild(invisibleText); - _isBusy = true; + listItem.appendChild(button); - Ajax.api(this, { - actionName: elData(event.currentTarget, 'type'), - parameters: { - data: { - containerID: DomUtil.identify(element), - objectID: this._containers.get(element).objectId, - objectType: this._objectType - } - } - }); - }, - - _ajaxSuccess: function(data) { - var element = elById(data.returnValues.containerID); - var elementData = this._containers.get(element); - if (elementData === undefined) { - return; + if (insertBefore) { + insertBefore.parentNode.insertBefore(listItem, insertBefore); } - - elementData.dislikes = ~~data.returnValues.dislikes; - elementData.likes = ~~data.returnValues.likes; - - var users = data.returnValues.users; - elementData.users = []; - var keys = Object.keys(users); - for (var i = 0, length = keys.length; i < length; i++) { - elementData.users.push(StringUtil.escapeHTML(users[keys[i]].username)); + else { + appendTo.appendChild(listItem); } - if (data.returnValues.isLiked == 1) elementData.liked = 1; - else if (data.returnValues.isDisliked == 1) elementData.liked = -1; - else elementData.liked = 0; - - // update label - this._updateBadge(element); - - // update summary - if (this._options.canViewSummary) this._updateSummary(element); - - // mark button as active - this._updateActiveState(element); - - // invalidate cache for like details - this._details['delete'](element); - - _isBusy = false; - }, - - _ajaxSetup: function() { - return { - data: { - className: 'wcf\\data\\like\\LikeAction' - } - }; + return button; } }; diff --git a/wcfsetup/install/files/lib/data/like/object/LikeObject.class.php b/wcfsetup/install/files/lib/data/like/object/LikeObject.class.php index 8b5a8ec678..90277b58f6 100644 --- a/wcfsetup/install/files/lib/data/like/object/LikeObject.class.php +++ b/wcfsetup/install/files/lib/data/like/object/LikeObject.class.php @@ -92,13 +92,32 @@ class LikeObject extends DatabaseObject { } /** - * Returns the first 3 users who liked this object. + * Since version 3.2, this method returns all reactionCounts for the different reactionTypes, + * instead of the user (as the method name suggests). This behavior is intentional and helps + * to establish backward compatibility. * - * @return User[] - * @deprecated since 3.2, this value is no longer maintained + * @return mixed[] + * @deprecated since 3.2 */ public function getUsers() { - return $this->users; + $returnValues = []; + + foreach ($this->getReactions() as $reactionID => $reaction) { + $returnValues[] = (object) [ + 'userID' => $reactionID, + 'username' => $reaction['reactionCount'], + ]; + } + + // this value is only set, if the object was loaded over the ReactionHandler::loadLikeObjects() + if ($this->reactionTypeID) { + $returnValues[] = (object) [ + 'userID' => 'reactionTypeID', + 'username' => $this->reactionTypeID, + ]; + } + + return $returnValues; } /** diff --git a/wcfsetup/install/files/lib/system/like/LikeHandler.class.php b/wcfsetup/install/files/lib/system/like/LikeHandler.class.php index 3ad88536c4..b250aa5030 100644 --- a/wcfsetup/install/files/lib/system/like/LikeHandler.class.php +++ b/wcfsetup/install/files/lib/system/like/LikeHandler.class.php @@ -3,20 +3,12 @@ declare(strict_types=1); namespace wcf\system\like; use wcf\data\like\object\ILikeObject; use wcf\data\like\object\LikeObject; -use wcf\data\like\object\LikeObjectEditor; -use wcf\data\like\object\LikeObjectList; use wcf\data\like\Like; -use wcf\data\like\LikeEditor; -use wcf\data\like\LikeList; use wcf\data\object\type\ObjectType; -use wcf\data\object\type\ObjectTypeCache; +use wcf\data\reaction\type\ReactionType; +use wcf\data\reaction\type\ReactionTypeCache; use wcf\data\user\User; -use wcf\data\user\UserEditor; -use wcf\system\database\util\PreparedStatementConditionBuilder; -use wcf\system\database\DatabaseException; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\activity\point\UserActivityPointHandler; -use wcf\system\user\notification\UserNotificationHandler; +use wcf\system\reaction\ReactionHandler; use wcf\system\SingletonFactory; use wcf\system\WCF; @@ -54,8 +46,7 @@ class LikeHandler extends SingletonFactory { * Creates a new LikeHandler instance. */ protected function init() { - // load cache - $this->cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.like.likeableObject'); + // does nothing } /** @@ -65,11 +56,7 @@ class LikeHandler extends SingletonFactory { * @return ObjectType */ public function getObjectType($objectName) { - if (isset($this->cache[$objectName])) { - return $this->cache[$objectName]; - } - - return null; + return ReactionHandler::getInstance()->getObjectType($objectName); } /** @@ -80,11 +67,7 @@ class LikeHandler extends SingletonFactory { * @return LikeObject|null */ public function getLikeObject(ObjectType $objectType, $objectID) { - if (isset($this->likeObjectCache[$objectType->objectTypeID][$objectID])) { - return $this->likeObjectCache[$objectType->objectTypeID][$objectID]; - } - - return null; + return ReactionHandler::getInstance()->getLikeObject($objectType, $objectID); } /** @@ -94,11 +77,7 @@ class LikeHandler extends SingletonFactory { * @return LikeObject[] */ public function getLikeObjects(ObjectType $objectType) { - if (isset($this->likeObjectCache[$objectType->objectTypeID])) { - return $this->likeObjectCache[$objectType->objectTypeID]; - } - - return []; + return ReactionHandler::getInstance()->getLikeObjects($objectType); } /** @@ -110,43 +89,7 @@ class LikeHandler extends SingletonFactory { * @return integer */ public function loadLikeObjects(ObjectType $objectType, array $objectIDs) { - if (empty($objectIDs)) { - return 0; - } - - $i = 0; - - $conditions = new PreparedStatementConditionBuilder(); - $conditions->add("like_object.objectTypeID = ?", [$objectType->objectTypeID]); - $conditions->add("like_object.objectID IN (?)", [$objectIDs]); - $parameters = $conditions->getParameters(); - - if (WCF::getUser()->userID) { - $sql = "SELECT like_object.*, - CASE WHEN like_table.userID IS NOT NULL THEN like_table.likeValue ELSE 0 END AS liked - FROM wcf".WCF_N."_like_object like_object - LEFT JOIN wcf".WCF_N."_like like_table - ON (like_table.objectTypeID = like_object.objectTypeID - AND like_table.objectID = like_object.objectID - AND like_table.userID = ?) - ".$conditions; - - array_unshift($parameters, WCF::getUser()->userID); - } - else { - $sql = "SELECT like_object.*, 0 AS liked - FROM wcf".WCF_N."_like_object like_object - ".$conditions; - } - - $statement = WCF::getDB()->prepareStatement($sql); - $statement->execute($parameters); - while ($row = $statement->fetchArray()) { - $this->likeObjectCache[$objectType->objectTypeID][$row['objectID']] = new LikeObject(null, $row); - $i++; - } - - return $i; + return ReactionHandler::getInstance()->loadLikeObjects($objectType, $objectIDs); } /** @@ -159,174 +102,41 @@ class LikeHandler extends SingletonFactory { * @return array */ public function like(ILikeObject $likeable, User $user, $likeValue, $time = TIME_NOW) { - // verify if object is already liked by user - $like = Like::getLike($likeable->getObjectType()->objectTypeID, $likeable->getObjectID(), $user->userID); - - // get like object - $likeObject = LikeObject::getLikeObject($likeable->getObjectType()->objectTypeID, $likeable->getObjectID()); + if ($likeValue == 1) { + $reactionTypeID = ReactionHandler::getInstance()->getLegacyReactionTypeID(ReactionType::REACTION_TYPE_POSITIVE); + } + else { + $reactionTypeID = ReactionHandler::getInstance()->getLegacyReactionTypeID(ReactionType::REACTION_TYPE_NEGATIVE); + } - // if vote is identically just revert the vote - if ($like->likeID && ($like->likeValue == $likeValue)) { - return $this->revertLike($like, $likeable, $likeObject, $user); + if ($reactionTypeID === null) { + return [ + 'data' => [], + 'like' => 0, + 'newValue' => 0, + 'oldValue' => 0, + 'users' => [] + ]; } - // like data - /** @noinspection PhpUnusedLocalVariableInspection */ - $cumulativeLikes = 0; - /** @noinspection PhpUnusedLocalVariableInspection */ - $newValue = $oldValue = null; - $users = []; - try { - WCF::getDB()->beginTransaction(); - - // update existing object - if ($likeObject->likeObjectID) { - $likes = $likeObject->likes; - $dislikes = $likeObject->dislikes; - $cumulativeLikes = $likeObject->cumulativeLikes; - - // previous (dis-)like already exists - if ($like->likeID) { - $oldValue = $like->likeValue; - - // revert like and replace it with a dislike - if ($like->likeValue == Like::LIKE) { - $likes--; - $dislikes++; - $cumulativeLikes -= 2; - $newValue = Like::DISLIKE; - } - else { - // revert dislike and replace it with a like - $likes++; - $dislikes--; - $cumulativeLikes += 2; - $newValue = Like::LIKE; - } - } - else { - if ($likeValue == Like::LIKE) { - $likes++; - $cumulativeLikes++; - $newValue = Like::LIKE; - } - else { - $dislikes++; - $cumulativeLikes--; - $newValue = Like::DISLIKE; - } - } - - // build update date - $updateData = [ - 'likes' => $likes, - 'dislikes' => $dislikes, - 'cumulativeLikes' => $cumulativeLikes - ]; - - if ($likeValue == 1) { - $users = unserialize($likeObject->cachedUsers); - if (count($users) < 3) { - $users[$user->userID] = ['userID' => $user->userID, 'username' => $user->username]; - $updateData['cachedUsers'] = serialize($users); - } - } - else if ($likeValue == -1) { - $users = unserialize($likeObject->cachedUsers); - if (isset($users[$user->userID])) { - unset($users[$user->userID]); - $updateData['cachedUsers'] = serialize($users); - } - } - - // update data - $likeObjectEditor = new LikeObjectEditor($likeObject); - $likeObjectEditor->update($updateData); - } - else { - $cumulativeLikes = $likeValue; - $newValue = $likeValue; - $users = []; - if ($likeValue == 1) $users = [$user->userID => ['userID' => $user->userID, 'username' => $user->username]]; - - // create cache - $likeObject = LikeObjectEditor::create([ - 'objectTypeID' => $likeable->getObjectType()->objectTypeID, - 'objectID' => $likeable->getObjectID(), - 'objectUserID' => $likeable->getUserID() ?: null, - 'likes' => ($likeValue == Like::LIKE) ? 1 : 0, - 'dislikes' => ($likeValue == Like::DISLIKE) ? 1 : 0, - 'cumulativeLikes' => $cumulativeLikes, - 'cachedUsers' => serialize($users) - ]); - } - - // update owner's like counter - if ($likeable->getUserID()) { - if ($like->likeID) { - $userEditor = new UserEditor(new User($likeable->getUserID())); - $userEditor->updateCounters([ - 'likesReceived' => $like->likeValue == Like::LIKE ? -1 : 1 - ]); - } - else if ($likeValue == Like::LIKE) { - $userEditor = new UserEditor(new User($likeable->getUserID())); - $userEditor->updateCounters([ - 'likesReceived' => 1 - ]); - } - } - - if (!$like->likeID) { - // save like - $like = LikeEditor::create([ - 'objectID' => $likeable->getObjectID(), - 'objectTypeID' => $likeable->getObjectType()->objectTypeID, - 'objectUserID' => $likeable->getUserID() ?: null, - 'userID' => $user->userID, - 'time' => $time, - 'likeValue' => $likeValue - ]); - - if ($likeValue == Like::LIKE && $likeable->getUserID()) { - UserActivityPointHandler::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID, $likeable->getUserID()); - $likeable->sendNotification($like); - } - } - else { - $likeEditor = new LikeEditor($like); - $likeEditor->update([ - 'time' => $time, - 'likeValue' => $likeValue - ]); - - if ($likeable->getUserID()) { - if ($likeValue == Like::DISLIKE) { - UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]); - } - else { - UserActivityPointHandler::getInstance()->fireEvent('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $like->likeID, $likeable->getUserID()); - $likeable->sendNotification($like); - } - } - } - - // update object's like counter - $likeable->updateLikeCounter($cumulativeLikes); - - WCF::getDB()->commitTransaction(); + $reactData = ReactionHandler::getInstance()->react($likeable, $user, $reactionTypeID, $time); + if ($reactData['reactionTypeID'] === null) { + $newValue = 0; } - catch (DatabaseException $e) { - WCF::getDB()->rollBackTransaction(); + else if (ReactionTypeCache::getInstance()->getReactionTypeByID($reactData['reactionTypeID'])->type == ReactionType::REACTION_TYPE_NEGATIVE) { + $newValue = -1; + } + else { + $newValue = 1; } return [ - 'data' => $this->loadLikeStatus($likeObject, $user), - 'like' => $like, + 'data' => $this->loadLikeStatus($reactData['likeObject'], $user), + 'like' => $reactData['likeObject'], 'newValue' => $newValue, - 'oldValue' => $oldValue, - 'users' => $users + 'oldValue' => 0, // this value is currently a dummy value, maybe determine a real value + 'users' => [] ]; } @@ -340,82 +150,14 @@ class LikeHandler extends SingletonFactory { * @return array */ public function revertLike(Like $like, ILikeObject $likeable, LikeObject $likeObject, User $user) { - $usersArray = []; - - try { - WCF::getDB()->beginTransaction(); - - // delete like - $editor = new LikeEditor($like); - $editor->delete(); - - // update like object cache - $likes = $likeObject->likes; - $dislikes = $likeObject->dislikes; - $cumulativeLikes = $likeObject->cumulativeLikes; - - if ($like->likeValue == Like::LIKE) { - $likes--; - $cumulativeLikes--; - } - else { - $dislikes--; - $cumulativeLikes++; - } - - // build update data - $updateData = [ - 'likes' => $likes, - 'dislikes' => $dislikes, - 'cumulativeLikes' => $cumulativeLikes - ]; - - $users = $likeObject->getUsers(); - foreach ($users as $user2) { - $usersArray[$user2->userID] = ['userID' => $user2->userID, 'username' => $user2->username]; - } - - if (isset($usersArray[$user->userID])) { - unset($usersArray[$user->userID]); - $updateData['cachedUsers'] = serialize($usersArray); - } - - $likeObjectEditor = new LikeObjectEditor($likeObject); - if (!$updateData['likes'] && !$updateData['dislikes']) { - // remove object instead - $likeObjectEditor->delete(); - } - else { - // update data - $likeObjectEditor->update($updateData); - } - - // update owner's like counter and activity points - if ($likeable->getUserID()) { - if ($like->likeValue == Like::LIKE) { - $userEditor = new UserEditor(new User($likeable->getUserID())); - $userEditor->updateCounters([ - 'likesReceived' => -1 - ]); - - UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', [$likeable->getUserID() => 1]); - } - } - - // update object's like counter - $likeable->updateLikeCounter($cumulativeLikes); - - WCF::getDB()->commitTransaction(); - } - catch (DatabaseException $e) { - WCF::getDB()->rollBackTransaction(); - } + $reactData = ReactionHandler::getInstance()->revertReact($like, $likeable, $likeObject, $user); return [ - 'data' => $this->loadLikeStatus($likeObject, $user), - 'newValue' => null, - 'oldValue' => $like->likeValue, - 'users' => $usersArray + 'data' => $this->loadLikeStatus($reactData['likeObject'], $user), + 'like' => null, + 'newValue' => 0, + 'oldValue' => 0, // this value is currently a dummy value, maybe determine a real value + 'users' => [] ]; } @@ -427,69 +169,7 @@ class LikeHandler extends SingletonFactory { * @param string[] $notificationObjectTypes */ public function removeLikes($objectType, array $objectIDs, array $notificationObjectTypes = []) { - $objectTypeObj = $this->getObjectType($objectType); - - // get like objects - $likeObjectList = new LikeObjectList(); - $likeObjectList->getConditionBuilder()->add('like_object.objectTypeID = ?', [$objectTypeObj->objectTypeID]); - $likeObjectList->getConditionBuilder()->add('like_object.objectID IN (?)', [$objectIDs]); - $likeObjectList->readObjects(); - $likeObjects = $likeObjectList->getObjects(); - $likeObjectIDs = $likeObjectList->getObjectIDs(); - - // reduce count of received users - $users = []; - foreach ($likeObjects as $likeObject) { - if ($likeObject->likes) { - if (!isset($users[$likeObject->objectUserID])) $users[$likeObject->objectUserID] = 0; - $users[$likeObject->objectUserID] += $likeObject->likes; - } - } - foreach ($users as $userID => $receivedLikes) { - $userEditor = new UserEditor(new User(null, ['userID' => $userID])); - $userEditor->updateCounters([ - 'likesReceived' => $receivedLikes * -1 - ]); - } - - // get like ids - $likeList = new LikeList(); - $likeList->getConditionBuilder()->add('like_table.objectTypeID = ?', [$objectTypeObj->objectTypeID]); - $likeList->getConditionBuilder()->add('like_table.objectID IN (?)', [$objectIDs]); - $likeList->readObjects(); - - if (count($likeList)) { - $likeData = []; - foreach ($likeList as $like) { - $likeData[$like->likeID] = $like->userID; - } - - // delete like notifications - if (!empty($notificationObjectTypes)) { - foreach ($notificationObjectTypes as $notificationObjectType) { - UserNotificationHandler::getInstance()->removeNotifications($notificationObjectType, $likeList->getObjectIDs()); - } - } - else if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType.'.notification')) { - UserNotificationHandler::getInstance()->removeNotifications($objectType.'.notification', $likeList->getObjectIDs()); - } - - // revoke activity points - UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $likeData); - - // delete likes - LikeEditor::deleteAll(array_keys($likeData)); - } - - // delete like objects - if (!empty($likeObjectIDs)) { - LikeObjectEditor::deleteAll($likeObjectIDs); - } - - // delete activity events - if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectTypeObj->objectType.'.recentActivityEvent')) { - UserActivityEventHandler::getInstance()->removeEvents($objectTypeObj->objectType.'.recentActivityEvent', $objectIDs); - } + ReactionHandler::getInstance()->removeReacts($objectType, $objectIDs, $notificationObjectTypes); } /** diff --git a/wcfsetup/install/files/lib/system/reaction/ReactionHandler.class.php b/wcfsetup/install/files/lib/system/reaction/ReactionHandler.class.php index 7092bdb522..4fd3df72a1 100644 --- a/wcfsetup/install/files/lib/system/reaction/ReactionHandler.class.php +++ b/wcfsetup/install/files/lib/system/reaction/ReactionHandler.class.php @@ -200,7 +200,8 @@ class ReactionHandler extends SingletonFactory { if (WCF::getUser()->userID) { $sql = "SELECT like_object.*, - COALESCE(like_table.reactionTypeID, 0) AS reactionTypeID + COALESCE(like_table.reactionTypeID, 0) AS reactionTypeID, + COALESCE(like_table.likeValue, 0) AS liked FROM wcf".WCF_N."_like_object like_object LEFT JOIN wcf".WCF_N."_like like_table ON (like_table.objectTypeID = like_object.objectTypeID @@ -316,7 +317,8 @@ class ReactionHandler extends SingletonFactory { return [ 'cachedReactions' => $likeObjectData['cachedReactions'], - 'reactionTypeID' => $reactionTypeID + 'reactionTypeID' => $reactionTypeID, + 'likeObject' => $likeObjectData['likeObject'] ]; } catch (DatabaseQueryException $e) { @@ -326,7 +328,6 @@ class ReactionHandler extends SingletonFactory { // @TODO return some dummy values return [ 'cachedReactions' => [], - 'reactionTypeID' => null ]; } @@ -409,7 +410,7 @@ class ReactionHandler extends SingletonFactory { ]; // create cache - LikeObjectEditor::create([ + $likeObject = LikeObjectEditor::create([ 'objectTypeID' => $likeable->getObjectType()->objectTypeID, 'objectID' => $likeable->getObjectID(), 'objectUserID' => $likeable->getUserID() ?: null, @@ -422,7 +423,8 @@ class ReactionHandler extends SingletonFactory { return [ 'cumulativeLikes' => $cumulativeLikes, - 'cachedReactions' => $cachedReactions + 'cachedReactions' => $cachedReactions, + 'likeObject' => $likeObject ]; } @@ -515,7 +517,8 @@ class ReactionHandler extends SingletonFactory { return [ 'cachedReactions' => $likeObjectData['cachedReactions'], - 'reactionTypeID' => null + 'reactionTypeID' => null, + 'likeObject' => $likeObjectData['likeObject'] ]; } catch (DatabaseQueryException $e) { @@ -525,7 +528,8 @@ class ReactionHandler extends SingletonFactory { // @TODO return some dummy values return [ 'cachedReactions' => [], - 'reactionTypeID' => null + 'reactionTypeID' => null, + 'likeObject' => [] ]; } @@ -581,7 +585,8 @@ class ReactionHandler extends SingletonFactory { return [ 'cumulativeLikes' => $cumulativeLikes, - 'cachedReactions' => $cachedReactions + 'cachedReactions' => $cachedReactions, + 'likeObject' => $likeObject ]; } @@ -705,7 +710,8 @@ class ReactionHandler extends SingletonFactory { */ protected function loadLikeStatus(LikeObject $likeObject, User $user) { $sql = "SELECT like_object.likes, like_object.dislikes, like_object.cumulativeLikes, - COALESCE(like_table.reactionTypeID, 0) AS reactionTypeID + COALESCE(like_table.reactionTypeID, 0) AS reactionTypeID, + COALESCE(like_table.likeValue, 0) AS liked FROM wcf".WCF_N."_like_object like_object LEFT JOIN wcf".WCF_N."_like like_table ON (like_table.objectTypeID = ? @@ -721,4 +727,28 @@ class ReactionHandler extends SingletonFactory { return $statement->fetchArray(); } + + /** + * Returns the legacy reactionTypeID for a specific type. The given type must be + * ReactionType::REACTION_TYPE_POSITIVE for a like + * or ReactionType::REACTION_TYPE_NEGATIVE for a dislike + * other values are not allowed and will resulted in a LogicException. + * If there are no legacy reaction type, the method returns null. + * + * @param integer $type + * @return integer|null + */ + public function getLegacyReactionTypeID($type) { + // @TODO determine real values + switch ($type) { + case ReactionType::REACTION_TYPE_POSITIVE: + return 2; + + case ReactionType::REACTION_TYPE_NEGATIVE: + return 1; + + default: + throw new \LogicException('Invalid type given.'); + } + } } diff --git a/wcfsetup/install/files/style/ui/reactions.scss b/wcfsetup/install/files/style/ui/reactions.scss index e676bb27b9..4d4c3ce422 100644 --- a/wcfsetup/install/files/style/ui/reactions.scss +++ b/wcfsetup/install/files/style/ui/reactions.scss @@ -26,8 +26,8 @@ } img.reactionType { - width: 24px; - height: 24px; + width: 16px; + height: 16px; } .reactionPopoverContent { @@ -44,6 +44,11 @@ img.reactionType { } } + > ul > li.reactionTypeButton > img { + width: 24px; + height: 24px; + } + > ul > li.reactionTypeButton > img:hover { transform: scale(1.2); } @@ -108,17 +113,3 @@ li.reactCountButton:hover { padding: 0px; } } - -ul.buttonList.iconList { - img.reactionType { - width: 16px; - height: 16px; - } -} - -li.notificationItem { - img.reactionType { - width: 16px; - height: 16px; - } -} \ No newline at end of file -- 2.20.1