Adapt the old like system to the new reaction system
authorJoshua Rüsweg <josh@bastelstu.be>
Sun, 22 Jul 2018 18:44:16 +0000 (20:44 +0200)
committerJoshua Rüsweg <josh@bastelstu.be>
Sun, 22 Jul 2018 18:44:16 +0000 (20:44 +0200)
See #2508

wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Like/Handler.js
wcfsetup/install/files/lib/data/like/object/LikeObject.class.php
wcfsetup/install/files/lib/system/like/LikeHandler.class.php
wcfsetup/install/files/lib/system/reaction/ReactionHandler.class.php
wcfsetup/install/files/style/ui/reactions.scss

index 67ff3d41a8104102909fa816b5a43e9677e3b23c..18f278506ded3a63f5ccab5af5a7fefd76c51425 100644 (file)
@@ -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 = '<span class="icon icon16 fa-thumbs-o-' + (isLike ? 'up' : 'down') + '"></span> <span class="invisible">' + title + '</span>';
-                       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 = '<span class="icon icon16 fa-thumbs-o-' + (cumulativeLikes < 0 ? 'down' : 'up' ) + '"></span><span class="wcfLikeValue">';
-                               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 + '</span>';
-                               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;
                }
        };
        
index 8b5a8ec6783471dbe6e499f6c2abc4fdc92fed77..90277b58f6fdabe875bf38005d739f0ff0db7b3c 100644 (file)
@@ -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;
        }
        
        /**
index 3ad88536c48e0c157dda627c7954dbca20a1b91e..b250aa50302a1ed07acdeed7b41097315a8359dd 100644 (file)
@@ -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);
        }
        
        /**
index 7092bdb522baba290056fc975c29108f512f64e1..4fd3df72a14159f4136f4e37fa78a25ffeeee17c 100644 (file)
@@ -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.');
+               }
+       }
 }
index e676bb27b94930d79e74ad39726bb54c7c750426..4d4c3ce422b70fcbc6ee5a77b9d8f29bf8301476 100644 (file)
@@ -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