Merged com.woltlab.wcf.like into WCF
authorMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 22:23:22 +0000 (00:23 +0200)
committerMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 22:23:22 +0000 (00:23 +0200)
32 files changed:
com.woltlab.wcf/dashboardBox.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/option.xml
com.woltlab.wcf/template/dashboardBoxMostLikedMembers.tpl [new file with mode: 0644]
com.woltlab.wcf/template/headInclude.tpl
com.woltlab.wcf/template/membersList.tpl
com.woltlab.wcf/template/userInformationStatistics.tpl
com.woltlab.wcf/template/userSidebar.tpl
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/js/WCF.Like.js [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Like.min.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php
wcfsetup/install/files/lib/data/like/ILikeObjectTypeProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/Like.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/LikeAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/LikeEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/LikeList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/object/AbstractLikeObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/object/LikeObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/object/LikeObjectEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/like/object/LikeObjectList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/page/MembersListPage.class.php
wcfsetup/install/files/lib/system/cache/builder/MostLikedMembersCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/dashboard/box/MostLikedMembersDashboardBox.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/like/LikeHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/activity/point/ReceivedLikesUserActivityPointObjectProcessor.class.php [new file with mode: 0644]
wcfsetup/install/files/style/like.less [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 226ab83a9c4e8dbde122f1f83ad258e7c502aac9..e7ce58956a658a2bc5b3ad2d3b9dc1c07eb63ab8 100644 (file)
                        <classname><![CDATA[wcf\system\dashboard\box\MostActiveMembersDashboardBox]]></classname>
                        <boxtype>sidebar</boxtype>
                </dashboardbox>
+               
+               <dashboardbox name="com.woltlab.wcf.like.mostLikedMembers">
+                       <classname><![CDATA[wcf\system\dashboard\box\MostLikedMembersDashboardBox]]></classname>
+                       <boxtype>sidebar</boxtype>
+               </dashboardbox>
        </import>
 </data>
index 2e628f4653f75ee50ec14eea7b5b37d1021d478e..dca396a5f82e26d25c550f005f28d381a8db1bbe 100644 (file)
                        <classname>wcf\system\moderation\queue\ModerationQueueReportManager</classname>
                </type>
                <!-- /moderation type -->
+               
+               <!-- activity points -->
+               <type>
+                       <name>com.woltlab.wcf.like.activityPointEvent.receivedLikes</name>
+                       <definitionname>com.woltlab.wcf.user.activityPointEvent</definitionname>
+                       <points>1</points>
+                       <classname>wcf\system\user\activity\point\ReceivedLikesUserActivityPointObjectProcessor</classname>
+               </type>
+               <!-- /activity points -->
        </import>
 </data>
\ No newline at end of file
index 57f677e7fdc177444ba87753e4bf64837b11eb25..94cd667e7f67345034987ff560c13eb20bcd69cd 100644 (file)
                        <name>com.woltlab.wcf.moderation.report</name>
                        <interfacename>wcf\system\moderation\queue\report\IModerationQueueReportHandler</interfacename>
                </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.like.likeableObject</name>
+                       <interfacename>wcf\data\like\ILikeObjectTypeProvider</interfacename>
+               </definition>
        </import>
 </data>
index 0a96fccc48fa458c6e0bf76c145d746b0cf73189..cf426faa96112f2b4ff6485ee8bf1f0f496441e8 100644 (file)
                                        <category name="message.general.share">
                                                <parent>message.general</parent>
                                        </category>
+                                       <category name="message.general.likes">
+                                               <parent>message.general</parent>
+                                       </category>
                                
                                <category name="message.attachment">
                                        <parent>message</parent>
                                <defaultvalue>1</defaultvalue>
                        </option>
                        
+                       <option name="module_like">
+                               <categoryname>module.community</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       
                        <!-- general.page -->
                        <option name="page_title">
                                <categoryname>general.page</categoryname>
@@ -849,7 +858,8 @@ no:!cache_source_memcached_host]]></enableoptions>
                                <defaultvalue><![CDATA[username]]></defaultvalue>
                                <selectoptions><![CDATA[username:wcf.user.username
 registrationDate:wcf.user.registrationDate
-activityPoints:wcf.user.activityPoint]]></selectoptions>
+activityPoints:wcf.user.activityPoint
+likedReceived:wcf.like.likesReceived]]></selectoptions>
                        </option>
                        <option name="members_list_default_sort_order">
                                <categoryname>user.list.members</categoryname>
@@ -928,6 +938,27 @@ DESC:wcf.global.sortOrder.descending]]></selectoptions>
                                <defaultvalue>5</defaultvalue>
                        </option>
                        <!-- /dashboard -->
+                       
+                       <option name="like_allow_for_own_content">
+                               <categoryname>message.general.likes</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_like</options>
+                       </option>
+                       
+                       <option name="like_enable_dislike">
+                               <categoryname>message.general.likes</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_like</options>
+                       </option>
+                       
+                       <option name="like_show_summary">
+                               <categoryname>message.general.likes</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                               <options>module_like</options>
+                       </option>
                </options>
        </import>
 </data>
diff --git a/com.woltlab.wcf/template/dashboardBoxMostLikedMembers.tpl b/com.woltlab.wcf/template/dashboardBoxMostLikedMembers.tpl
new file mode 100644 (file)
index 0000000..20b55ba
--- /dev/null
@@ -0,0 +1,12 @@
+<ul class="sidebarBoxList">
+       {foreach from=$mostLikedMembers item=likedMember}
+               <li class="box24">
+                       <a href="{link controller='User' object=$likedMember}{/link}" class="framed">{@$likedMember->getAvatar()->getImageTag(24)}</a>
+                       
+                       <div class="sidebarBoxHeadline">
+                               <h3><a href="{link controller='User' object=$likedMember}{/link}" class="userLink" data-user-id="{@$likedMember->userID}">{$likedMember->username}</a></h3>
+                               <small>{lang}wcf.dashboard.box.mostLikedMembers.likes{/lang}</small>
+                       </div>
+               </li>
+       {/foreach}
+</ul>
index 4f8eaa537ccc54eb9cbf654fb9d6a8cf7b1acdd4..199f08910425653eadac96d0417fed08231c3366 100644 (file)
@@ -45,6 +45,7 @@
 </script>
 <script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Message{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
 <script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript" src="{@$__wcf->getPath('wcf')}js/WCF.Like{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
 {event name='javascriptInclude'}
 
 <!-- Stylesheets -->
                        'wcf.global.thousandsSeparator': '{capture assign=thousandsSeparator}{lang}wcf.global.thousandsSeparator{/lang}{/capture}{@$thousandsSeparator|encodeJS}',
                        'wcf.page.sitemap': '{lang}wcf.page.sitemap{/lang}',
                        'wcf.style.changeStyle': '{lang}wcf.style.changeStyle{/lang}'
+                       {if MODULE_LIKE}
+                               ,'wcf.like.button.like': '{lang}wcf.like.button.like{/lang}',
+                               'wcf.like.button.dislike': '{lang}wcf.like.button.dislike{/lang}',
+                               'wcf.like.tooltip': '{lang}wcf.like.jsTooltip{/lang}',
+                               'wcf.like.summary': '{lang}wcf.like.summary{/lang}',
+                               'wcf.like.details': '{lang}wcf.like.details{/lang}'
+                       {/if}
+                       
                        {event name='javascriptLanguageImport'}
                });
                
index 4f947c4ddb4cc7ec6d0992e7286637d9498e781f..91cd06673e135d00eeb85dae468391c6ed3374d6 100644 (file)
@@ -76,6 +76,7 @@
                                                        <option value="username"{if $sortField == 'username'} selected="selected"{/if}>{lang}wcf.user.username{/lang}</option>
                                                        <option value="registrationDate"{if $sortField == 'registrationDate'} selected="selected"{/if}>{lang}wcf.user.registrationDate{/lang}</option>
                                                        <option value="activityPoints"{if $sortField == 'activityPoints'} selected="selected"{/if}>{lang}wcf.user.activityPoint{/lang}</option>
+                                                       <option value="likesReceived"{if $sortField == 'likesReceived'} selected="selected"{/if}>{lang}wcf.like.likesReceived{/lang}</option>
                                                        {event name='sortField'}
                                                </select>
                                                <select name="sortOrder">
index bc1a9fbefb6c24b5924c14d1dc26a3e7d3f3e62d..b8bb82be5c48405f26f0c347d8ebe9fd3fdb6ebb 100644 (file)
@@ -1,6 +1,11 @@
 <dl class="plain inlineDataList userStats">
        {event name='statistics'}
        
+       {if MODULE_LIKE}
+               <dt>{lang}wcf.like.likesReceived{/lang}</dt>
+               <dd>{#$user->likesReceived}</dd>
+       {/if}
+       
        <dt>{lang}wcf.user.activityPoint{/lang}</dt>
        <dd>{#$user->activityPoints}</dd>
 </dl>
\ No newline at end of file
index 40b7e8309942e3baf52454b878d062f5f0e0d163..11458b6e16161b440de1a0910d593b33feaacf0f 100644 (file)
        <dl class="plain statsDataList">
                {event name='statistics'}
                
+               {if MODULE_LIKE}
+                       <dt>{lang}wcf.like.likesReceived{/lang}</dt>
+                       <dd>{#$user->likesReceived}</dd>
+               {/if}
+               
                <dt>{if $user->activityPoints}<a class="activityPointsDisplay jsTooltip" title="{lang}wcf.user.activityPoint.showDetails{/lang}" data-user-id="{@$user->userID}">{lang}wcf.user.activityPoint{/lang}</a>{else}{lang}wcf.user.activityPoint{/lang}{/if}</dt>
                <dd>{#$user->activityPoints}</dd>
                
index d18ab007d7e417c856881a95bedc41d021a31357..50bcbbf3ffc9dfeabb8065c0179b2fa2f421306a 100644 (file)
@@ -6,6 +6,9 @@
                        <category name="user.message">
                                <parent>user</parent>
                        </category>
+                       <category name="user.like">
+                               <parent>user.message</parent>
+                       </category>
                        <category name="user.message.attachment">
                                <parent>user.message</parent>
                        </category>
@@ -473,6 +476,17 @@ png]]></defaultvalue>
                                <defaultvalue>0</defaultvalue>
                                <admindefaultvalue>1</admindefaultvalue>
                        </option>
+                       
+                       <option name="user.like.canViewLike">
+                               <categoryname>user.like</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       <option name="user.like.canLike">
+                               <categoryname>user.like</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                </options>
        </import>
 </data>
diff --git a/wcfsetup/install/files/js/WCF.Like.js b/wcfsetup/install/files/js/WCF.Like.js
new file mode 100644 (file)
index 0000000..714cf1d
--- /dev/null
@@ -0,0 +1,433 @@
+/**
+ * Like support for WCF
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+WCF.Like = Class.extend({
+       /**
+        * true, if users can like their own content
+        * @var boolean
+        */
+       _allowForOwnContent: false,
+       
+       /**
+        * user can like 
+        * @var boolean
+        */
+       _canLike: false,
+       
+       /**
+        * list of containers
+        * @var object
+        */
+       _containers: { },
+       
+       /**
+        * container meta data
+        * @var object
+        */
+       _containerData: { },
+       
+       /**
+        * enables the dislike option
+        */
+       _enableDislikes: true,
+       
+       /**
+        * prevents like/dislike until the server responded
+        * @var boolean
+        */
+       _isBusy: false,
+       
+       /**
+        * cached template for like details
+        * @var object
+        */
+       _likeDetails: { },
+       
+       /**
+        * dialog overlay for like details
+        * @var jQuery
+        */
+       _likeDetailsDialog: null,
+       
+       /**
+        * proxy object
+        * @var WCF.Action.Proxy
+        */
+       _proxy: null,
+       
+       /**
+        * shows the detailed summary of users who liked the object
+        */
+       _showSummary: true,
+       
+       /**
+        * Initializes like support.
+        * 
+        * @param       boolean         canLike
+        * @param       boolean         enableDislikes
+        * @param       boolean         showSummary
+        * @param       boolean         allowForOwnContent
+        */
+       init: function(canLike, enableDislikes, showSummary, allowForOwnContent) {
+               this._canLike = canLike;
+               this._enableDislikes = enableDislikes;
+               this._isBusy = false;
+               this._likeDetails = { };
+               this._likeDetailsDialog = null;
+               this._showSummary = showSummary;
+               this._allowForOwnContent = allowForOwnContent;
+               
+               var $containers = this._getContainers();
+               this._initContainers($containers);
+               
+               this._proxy = new WCF.Action.Proxy({
+                       success: $.proxy(this._success, this)
+               });
+               
+               // bind dom node inserted listener
+               var $date = new Date();
+               var $identifier = $date.toString().hashCode + $date.getUTCMilliseconds();
+               WCF.DOMNodeInsertedHandler.addCallback('WCF.Like' + $identifier, $.proxy(this._domNodeInserted, this));
+       },
+       
+       /**
+        * Initialize containers once new nodes are inserted.
+        */
+       _domNodeInserted: function() {
+               var $containers = this._getContainers();
+               this._initContainers($containers);
+               
+       },
+       
+       /**
+        * Initializes like containers.
+        * 
+        * @param       object          containers
+        */
+       _initContainers: function(containers) {
+               var $createdWidgets = false;
+               containers.each($.proxy(function(index, container) {
+                       // set container
+                       var $container = $(container);
+                       var $containerID = $container.wcfIdentify();
+                       
+                       if (!this._containers[$containerID]) {
+                               this._containers[$containerID] = $container;
+                               
+                               // set container data
+                               this._containerData[$containerID] = {
+                                       'likeButton': null,
+                                       'badge': null,
+                                       'dislikeButton': null,
+                                       'likes': $container.data('like-likes'),
+                                       'dislikes': $container.data('like-dislikes'),
+                                       'objectType': $container.data('objectType'),
+                                       'objectID': this._getObjectID($containerID),
+                                       'users': eval($container.data('like-users')),
+                                       'liked': $container.data('like-liked')
+                               };
+                               
+                               // create UI
+                               this._createWidget($containerID);
+                               
+                               $createdWidgets = true;
+                       }
+               }, this));
+               
+               if ($createdWidgets) {
+                       new WCF.PeriodicalExecuter(function(pe) {
+                               pe.stop();
+                               
+                               WCF.DOMNodeInsertedHandler.forceExecution();
+                       }, 250);
+               }
+       },
+       
+       /**
+        * Returns a list of available object containers.
+        * 
+        * @return      jQuery
+        */
+       _getContainers: function() { },
+       
+       /**
+        * Returns widget container for target object container.
+        * 
+        * @param       string          containerID
+        * @return      jQuery
+        */
+       _getWidgetContainer: function(containerID) { },
+       
+       /**
+        * Returns object id for targer object container.
+        * 
+        * @param       string          containerID
+        * @return      integer
+        */
+       _getObjectID: function(containerID) { },
+       
+       /**
+        * Adds the like widget.
+        * 
+        * @param       integer         containerID
+        * @param       jQuery          widget
+        */
+       _addWidget: function(containerID, widget) {
+               var $widgetContainer = this._getWidgetContainer(containerID);
+               
+               widget.appendTo($widgetContainer);
+       },
+       
+       /**
+        * Builds the like widget.
+        * 
+        * @param       integer         containerID
+        * @param       jQuery          likeButton
+        * @param       jQuery          dislikeButton
+        * @param       jQuery          badge
+        * @param       jQuery          summary
+        */
+       _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) {
+               var $widget = $('<aside class="likesWidget"><ul></ul></aside>');
+               if (this._canLike) {
+                       likeButton.appendTo($widget.find('ul'));
+                       dislikeButton.appendTo($widget.find('ul'));
+               }
+               badge.appendTo($widget);
+               
+               this._addWidget(containerID, $widget); 
+       },
+       
+       /**
+        * Creates the like widget.
+        * 
+        * @param       integer         containerID
+        */
+       _createWidget: function(containerID) {
+               var $likeButton = $('<li class="likeButton"><a title="'+WCF.Language.get('wcf.like.button.like')+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-up" /> <span class="invisible">'+WCF.Language.get('wcf.like.button.like')+'</span></a></li>');
+               var $dislikeButton = $('<li class="dislikeButton"><a title="'+WCF.Language.get('wcf.like.button.dislike')+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-down" /> <span class="invisible">'+WCF.Language.get('wcf.like.button.dislike')+'</span></a></li>');
+               if (!this._enableDislikes) $dislikeButton.hide();
+               
+               if (!this._allowForOwnContent && (WCF.User.userID == this._containers[containerID].data('userID'))) {
+                       $likeButton.hide();
+                       $dislikeButton.hide();
+               }
+               
+               var $badge = $('<a class="badge jsTooltip likesBadge" />').data('containerID', containerID).click($.proxy(this._showLikeDetails, this));
+               
+               var $summary = null;
+               if (this._showSummary) {
+                       $summary = $('<p class="likesSummary"><span class="pointer" /></p>');
+                       $summary.children('span').data('containerID', containerID).click($.proxy(this._showLikeDetails, this));
+               }
+               this._buildWidget(containerID, $likeButton, $dislikeButton, $badge, $summary);
+               
+               this._containerData[containerID].likeButton = $likeButton;
+               this._containerData[containerID].dislikeButton = $dislikeButton;
+               this._containerData[containerID].badge = $badge;
+               this._containerData[containerID].summary = $summary;
+               
+               $likeButton.data('containerID', containerID).data('type', 'like').click($.proxy(this._click, this));
+               $dislikeButton.data('containerID', containerID).data('type', 'dislike').click($.proxy(this._click, this));
+               this._setActiveState($likeButton, $dislikeButton, this._containerData[containerID].liked);
+               this._updateBadge(containerID);
+               if (this._showSummary) this._updateSummary(containerID);
+       },
+       
+       /**
+        * Displays like details for an object.
+        * 
+        * @param       object          event
+        * @param       string          containerID
+        */
+       _showLikeDetails: function(event, containerID) {
+               var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
+               
+               if (this._likeDetails[$containerID] === undefined) {
+                       this._proxy.setOption('data', {
+                               actionName: 'getLikeDetails',
+                               className: 'wcf\\data\\like\\LikeAction',
+                               parameters: {
+                                       data: {
+                                               containerID: $containerID,
+                                               objectID: this._containerData[$containerID].objectID,
+                                               objectType: this._containerData[$containerID].objectType
+                                       }
+                               }
+                       });
+                       this._proxy.sendRequest();
+               }
+               else {
+                       if (this._likeDetailsDialog === null) {
+                               this._likeDetailsDialog = $('<div>' + this._likeDetails[$containerID] + '</div>').hide().appendTo(document.body);
+                               this._likeDetailsDialog.wcfDialog({
+                                       title: WCF.Language.get('wcf.like.details')
+                               });
+                       }
+                       else {
+                               this._likeDetailsDialog.html(this._likeDetails[$containerID]).wcfDialog('open');
+                       }
+               }
+       },
+       
+       /**
+        * Handles likes and dislikes.
+        * 
+        * @param       object          event
+        */
+       _click: function(event) {
+               var $button = $(event.currentTarget);
+               if ($button === null) {
+                       console.debug("[WCF.Like] Unable to find target button, aborting.");
+                       return;
+               }
+               
+               this._sendRequest($button.data('containerID'), $button.data('type'));
+       },
+       
+       /**
+        * Sends request through proxy.
+        * 
+        * @param       integer         containerID
+        * @param       string          type
+        */
+       _sendRequest: function(containerID, type) {
+               // ignore retards spamming clicks on the buttons
+               if (this._isBusy) {
+                       return;
+               }
+               
+               this._proxy.setOption('data', {
+                       actionName: type,
+                       className: 'wcf\\data\\like\\LikeAction',
+                       parameters: {
+                               data: {
+                                       containerID: containerID,
+                                       objectID: this._containerData[containerID].objectID,
+                                       objectType: this._containerData[containerID].objectType
+                               }
+                       }
+               });
+               
+               this._proxy.sendRequest();
+       },
+       
+       /**
+        * Updates likeable object.
+        * 
+        * @param       object          data
+        * @param       string          textStatus
+        * @param       object          jqXHR
+        */
+       _success: function(data, textStatus, jqXHR) {
+               var $containerID = data.returnValues.containerID;
+               
+               if (!this._containers[$containerID]) {
+                       return;
+               }
+               
+               switch (data.actionName) {
+                       case 'dislike':
+                       case 'like':
+                               // update container data
+                               this._containerData[$containerID].likes = parseInt(data.returnValues.likes);
+                               this._containerData[$containerID].dislikes = parseInt(data.returnValues.dislikes);
+                               this._containerData[$containerID].users = data.returnValues.users;
+                               
+                               // update label
+                               this._updateBadge($containerID);
+                               // update summary
+                               if (this._showSummary) this._updateSummary($containerID);
+                               
+                               // mark button as active
+                               var $likeButton = this._containerData[$containerID].likeButton;
+                               var $dislikeButton = this._containerData[$containerID].dislikeButton;
+                               var $likeStatus = 0;
+                               if (data.returnValues.isLiked) $likeStatus = 1;
+                               else if (data.returnValues.isDisliked) $likeStatus = -1;
+                               this._setActiveState($likeButton, $dislikeButton, $likeStatus);
+                               
+                               // invalidate cache for like details
+                               if (this._likeDetails[$containerID] !== undefined) {
+                                       delete this._likeDetails[$containerID];
+                               }
+                               
+                               this._isBusy = false;
+                       break;
+                       
+                       case 'getLikeDetails':
+                               this._likeDetails[$containerID] = data.returnValues.template;
+                               this._showLikeDetails(null, $containerID);
+                       break;
+               }
+       },
+       
+       _updateBadge: function(containerID) {
+               if (!this._containerData[containerID].likes && !this._containerData[containerID].dislikes) {
+                       this._containerData[containerID].badge.hide();
+               }
+               else {
+                       this._containerData[containerID].badge.show();
+                       
+                       // update like counter
+                       var $cumulativeLikes = this._containerData[containerID].likes - this._containerData[containerID].dislikes;
+                       var $badge = this._containerData[containerID].badge;
+                       $badge.removeClass('green red');
+                       if ($cumulativeLikes > 0) {
+                               $badge.text('+' + WCF.String.formatNumeric($cumulativeLikes));
+                               $badge.addClass('green');
+                       }
+                       else if ($cumulativeLikes < 0) {
+                               $badge.text(WCF.String.formatNumeric($cumulativeLikes));
+                               $badge.addClass('red');
+                       }
+                       else {
+                               $badge.text('\u00B10');
+                       }
+                       
+                       // update tooltip
+                       var $likes = this._containerData[containerID].likes;
+                       var $dislikes = this._containerData[containerID].dislikes;
+                       $badge.data('tooltip', eval(WCF.Language.get('wcf.like.tooltip')));
+               }
+       },
+       
+       _updateSummary: function(containerID) {
+               if (!this._containerData[containerID].likes) {
+                       this._containerData[containerID].summary.hide();
+               }
+               else {
+                       this._containerData[containerID].summary.show();
+                       
+                       var $users = this._containerData[containerID].users;
+                       var $userArray = [];
+                       for (var $userID in $users) $userArray.push($users[$userID].username);
+                       var $others = this._containerData[containerID].likes - $userArray.length;
+                       
+                       this._containerData[containerID].summary.children('span').html(eval(WCF.Language.get('wcf.like.summary')));
+               }
+       },
+       
+       /**
+        * Sets button active state.
+        * 
+        * @param       jquery          likeButton
+        * @param       jquery          dislikeButton
+        * @param       integer         likeStatus
+        */
+       _setActiveState: function(likeButton, dislikeButton, likeStatus) {
+               likeButton.removeClass('active');
+               dislikeButton.removeClass('active');
+               
+               if (likeStatus == 1) {
+                       likeButton.addClass('active');
+               }
+               else if (likeStatus == -1) {
+                       dislikeButton.addClass('active');
+               }
+       }
+});
diff --git a/wcfsetup/install/files/js/WCF.Like.min.js b/wcfsetup/install/files/js/WCF.Like.min.js
new file mode 100644 (file)
index 0000000..ae8b860
--- /dev/null
@@ -0,0 +1 @@
+WCF.Like=Class.extend({_allowForOwnContent:false,_canLike:false,_containers:{},_containerData:{},_enableDislikes:true,_isBusy:false,_likeDetails:{},_likeDetailsDialog:null,_proxy:null,_showSummary:true,init:function(a,c,f,e){this._canLike=a;this._enableDislikes=c;this._isBusy=false;this._likeDetails={};this._likeDetailsDialog=null;this._showSummary=f;this._allowForOwnContent=e;var d=this._getContainers();this._initContainers(d);this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});var g=new Date();var b=g.toString().hashCode+g.getUTCMilliseconds();WCF.DOMNodeInsertedHandler.addCallback("WCF.Like"+b,$.proxy(this._domNodeInserted,this))},_domNodeInserted:function(){var a=this._getContainers();this._initContainers(a)},_initContainers:function(containers){var $createdWidgets=false;containers.each($.proxy(function(index,container){var $container=$(container);var $containerID=$container.wcfIdentify();if(!this._containers[$containerID]){this._containers[$containerID]=$container;this._containerData[$containerID]={likeButton:null,badge:null,dislikeButton:null,likes:$container.data("like-likes"),dislikes:$container.data("like-dislikes"),objectType:$container.data("objectType"),objectID:this._getObjectID($containerID),users:eval($container.data("like-users")),liked:$container.data("like-liked")};this._createWidget($containerID);$createdWidgets=true}},this));if($createdWidgets){new WCF.PeriodicalExecuter(function(pe){pe.stop();WCF.DOMNodeInsertedHandler.forceExecution()},250)}},_getContainers:function(){},_getWidgetContainer:function(a){},_getObjectID:function(a){},_addWidget:function(a,b){var c=this._getWidgetContainer(a);b.appendTo(c)},_buildWidget:function(b,a,d,c,e){var f=$('<aside class="likesWidget"><ul></ul></aside>');if(this._canLike){a.appendTo(f.find("ul"));d.appendTo(f.find("ul"))}c.appendTo(f);this._addWidget(b,f)},_createWidget:function(b){var e=$('<li class="likeButton"><a title="'+WCF.Language.get("wcf.like.button.like")+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-up" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.like")+"</span></a></li>");var c=$('<li class="dislikeButton"><a title="'+WCF.Language.get("wcf.like.button.dislike")+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-down" /> <span class="invisible">'+WCF.Language.get("wcf.like.button.dislike")+"</span></a></li>");if(!this._enableDislikes){c.hide()}if(!this._allowForOwnContent&&(WCF.User.userID==this._containers[b].data("userID"))){e.hide();c.hide()}var d=$('<a class="badge jsTooltip likesBadge" />').data("containerID",b).click($.proxy(this._showLikeDetails,this));var a=null;if(this._showSummary){a=$('<p class="likesSummary"><span class="pointer" /></p>');a.children("span").data("containerID",b).click($.proxy(this._showLikeDetails,this))}this._buildWidget(b,e,c,d,a);this._containerData[b].likeButton=e;this._containerData[b].dislikeButton=c;this._containerData[b].badge=d;this._containerData[b].summary=a;e.data("containerID",b).data("type","like").click($.proxy(this._click,this));c.data("containerID",b).data("type","dislike").click($.proxy(this._click,this));this._setActiveState(e,c,this._containerData[b].liked);this._updateBadge(b);if(this._showSummary){this._updateSummary(b)}},_showLikeDetails:function(c,a){var b=(c===null)?a:$(c.currentTarget).data("containerID");if(this._likeDetails[b]===undefined){this._proxy.setOption("data",{actionName:"getLikeDetails",className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:b,objectID:this._containerData[b].objectID,objectType:this._containerData[b].objectType}}});this._proxy.sendRequest()}else{if(this._likeDetailsDialog===null){this._likeDetailsDialog=$("<div>"+this._likeDetails[b]+"</div>").hide().appendTo(document.body);this._likeDetailsDialog.wcfDialog({title:WCF.Language.get("wcf.like.details")})}else{this._likeDetailsDialog.html(this._likeDetails[b]).wcfDialog("open")}}},_click:function(a){var b=$(a.currentTarget);if(b===null){console.debug("[WCF.Like] Unable to find target button, aborting.");return}this._sendRequest(b.data("containerID"),b.data("type"))},_sendRequest:function(a,b){if(this._isBusy){return}this._proxy.setOption("data",{actionName:b,className:"wcf\\data\\like\\LikeAction",parameters:{data:{containerID:a,objectID:this._containerData[a].objectID,objectType:this._containerData[a].objectType}}});this._proxy.sendRequest()},_success:function(d,g,b){var a=d.returnValues.containerID;if(!this._containers[a]){return}switch(d.actionName){case"dislike":case"like":this._containerData[a].likes=parseInt(d.returnValues.likes);this._containerData[a].dislikes=parseInt(d.returnValues.dislikes);this._containerData[a].users=d.returnValues.users;this._updateBadge(a);if(this._showSummary){this._updateSummary(a)}var f=this._containerData[a].likeButton;var e=this._containerData[a].dislikeButton;var c=0;if(d.returnValues.isLiked){c=1}else{if(d.returnValues.isDisliked){c=-1}}this._setActiveState(f,e,c);if(this._likeDetails[a]!==undefined){delete this._likeDetails[a]}this._isBusy=false;break;case"getLikeDetails":this._likeDetails[a]=d.returnValues.template;this._showLikeDetails(null,a);break}},_updateBadge:function(containerID){if(!this._containerData[containerID].likes&&!this._containerData[containerID].dislikes){this._containerData[containerID].badge.hide()}else{this._containerData[containerID].badge.show();var $cumulativeLikes=this._containerData[containerID].likes-this._containerData[containerID].dislikes;var $badge=this._containerData[containerID].badge;$badge.removeClass("green red");if($cumulativeLikes>0){$badge.text("+"+WCF.String.formatNumeric($cumulativeLikes));$badge.addClass("green")}else{if($cumulativeLikes<0){$badge.text(WCF.String.formatNumeric($cumulativeLikes));$badge.addClass("red")}else{$badge.text("\u00B10")}}var $likes=this._containerData[containerID].likes;var $dislikes=this._containerData[containerID].dislikes;$badge.data("tooltip",eval(WCF.Language.get("wcf.like.tooltip")))}},_updateSummary:function(containerID){if(!this._containerData[containerID].likes){this._containerData[containerID].summary.hide()}else{this._containerData[containerID].summary.show();var $users=this._containerData[containerID].users;var $userArray=[];for(var $userID in $users){$userArray.push($users[$userID].username)}var $others=this._containerData[containerID].likes-$userArray.length;this._containerData[containerID].summary.children("span").html(eval(WCF.Language.get("wcf.like.summary")))}},_setActiveState:function(a,b,c){a.removeClass("active");b.removeClass("active");if(c==1){a.addClass("active")}else{if(c==-1){b.addClass("active")}}}});
\ No newline at end of file
index 54b7b1274a76222a47fd55edb8a0abc809162668..c7fb332530333394b42f53a2307909a12df279e5 100644 (file)
@@ -106,6 +106,32 @@ class UserMergeForm extends AbstractForm {
                
                parent::save();
                
+               // like (userID)
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+               $sql = "UPDATE IGNORE   wcf".WCF_N."_like
+                       SET             userID = ?
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+               // like (objectUserID)
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("objectUserID IN (?)", array($this->mergedUserIDs));
+               $sql = "UPDATE  wcf".WCF_N."_like
+                       SET     objectUserID = ?
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+               
+               // like_object
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("objectUserID IN (?)", array($this->mergedUserIDs));
+               $sql = "UPDATE  wcf".WCF_N."_like_object
+                       SET     objectUserID = ?
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+               
                // user_follow (userID)
                $conditions = new PreparedStatementConditionBuilder();
                $conditions->add("userID IN (?)", array($this->mergedUserIDs));
diff --git a/wcfsetup/install/files/lib/data/like/ILikeObjectTypeProvider.class.php b/wcfsetup/install/files/lib/data/like/ILikeObjectTypeProvider.class.php
new file mode 100644 (file)
index 0000000..617a795
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\data\like;
+use wcf\data\like\object\ILikeObject;
+use wcf\data\object\type\IObjectTypeProvider;
+
+/**
+ * Default interface for like object type providers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like
+ * @category   Community Framework
+ */
+interface ILikeObjectTypeProvider extends IObjectTypeProvider {
+       /**
+        * Returns true if the active user can access the given likeable object.
+        * 
+        * @param       wcf\data\like\object\ILikeObject        $object
+        * @return      boolean
+        */
+       public function checkPermissions(ILikeObject $object);
+}
diff --git a/wcfsetup/install/files/lib/data/like/Like.class.php b/wcfsetup/install/files/lib/data/like/Like.class.php
new file mode 100644 (file)
index 0000000..14ae951
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace wcf\data\like;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a like of an object.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like
+ * @category   Community Framework
+ */
+class Like extends DatabaseObject {
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'like';
+       
+       /**
+        * @see wcf\data\DatabaseObject::$databaseIndexName
+        */
+       protected static $databaseTableIndexName = 'likeID';
+       
+       /**
+        * like value
+        * @var integer
+        */
+       const LIKE = 1;
+       
+       /**
+        * dislike value
+        * @var integer
+        */
+       const DISLIKE = -1;
+       
+       /**
+        * Gets a like by type, object id and user id.
+        * 
+        * @param       integer         $objectTypeID
+        * @param       integer         $objectID
+        * @param       integer         $userID
+        * @return      Like
+        */
+       public static function getLike($objectTypeID, $objectID, $userID) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_like
+                       WHERE   objectTypeID = ?
+                               AND objectID = ?
+                               AND userID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(
+                       $objectTypeID,
+                       $objectID,
+                       $userID
+               ));
+               
+               $row = $statement->fetchArray();
+               
+               if (!$row) {
+                       $row = array();
+               }
+               
+               return new Like(null, $row);
+       }
+       
+       /**
+        * @see wcf\data\IStorableObject::getDatabaseTableAlias()
+        */
+       public static function getDatabaseTableAlias() {
+               return 'like_table';
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/like/LikeAction.class.php b/wcfsetup/install/files/lib/data/like/LikeAction.class.php
new file mode 100644 (file)
index 0000000..7fbc2fe
--- /dev/null
@@ -0,0 +1,200 @@
+<?php
+namespace wcf\data\like;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\like\LikeHandler;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\GroupedUserList;
+use wcf\system\WCF;
+
+/**
+ * Executes like-related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.like
+ * @category   Community Framework
+ */
+class LikeAction extends AbstractDatabaseObjectAction {
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+        */
+       protected $allowGuestAccess = array('getLikeDetails');
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\like\LikeEditor';
+       
+       /**
+        * likeable object
+        * @var wcf\data\like\object\ILikeObject
+        */
+       public $likeableObject = null;
+       
+       /**
+        * object type object
+        * @var wcf\data\object\type\ObjectType
+        */
+       public $objectType = null;
+       
+       /**
+        * like object type provider object
+        * @var wcf\data\like\ILikeObjectTypeProvider
+        */
+       public $objectTypeProvider = null;
+       
+       /**
+        * Validates parameters to fetch like details.
+        */
+       public function validateGetLikeDetails() {
+               $this->validateObjectParameters();
+       }
+       
+       /**
+        * Returns like details.
+        * 
+        * @return      array<string>
+        */
+       public function getLikeDetails() {
+               $sql = "SELECT  userID, likeValue
+                       FROM    wcf".WCF_N."_like
+                       WHERE   objectID = ?
+                               AND objectTypeID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(
+                       $this->parameters['data']['objectID'],
+                       $this->objectType->objectTypeID
+               ));
+               $data = array(
+                       Like::LIKE => array(),
+                       Like::DISLIKE => array()
+               );
+               while ($row = $statement->fetchArray()) {
+                       $data[$row['likeValue']][] = $row['userID'];
+               }
+               
+               $values = array();
+               if (!empty($data[Like::LIKE])) {
+                       $values[Like::LIKE] = new GroupedUserList(WCF::getLanguage()->get('wcf.like.details.like'));
+                       $values[Like::LIKE]->addUserIDs($data[Like::LIKE]);
+               }
+               if (!empty($data[Like::DISLIKE])) {
+                       $values[Like::DISLIKE] = new GroupedUserList(WCF::getLanguage()->get('wcf.like.details.dislike'));
+                       $values[Like::DISLIKE]->addUserIDs($data[Like::DISLIKE]);
+               }
+               
+               // load user profiles
+               GroupedUserList::loadUsers();
+               
+               WCF::getTPL()->assign(array(
+                       'groupedUsers' => $values
+               ));
+               
+               return array(
+                       'containerID' => $this->parameters['data']['containerID'],
+                       'template' => WCF::getTPL()->fetch('groupedUserList')
+               );
+       }
+       
+       /**
+        * Validates parameters for like-related actions.
+        */
+       public function validateLike() {
+               $this->validateObjectParameters();
+               
+               // check permissions
+               if (!WCF::getUser()->userID || !WCF::getSession()->getPermission('user.like.canLike')) {
+                       throw new PermissionDeniedException();  
+               }
+               
+               // check if liking own content but forbidden by configuration
+               $this->likeableObject = $this->objectTypeProvider->getObjectByID($this->parameters['data']['objectID']);
+               $this->likeableObject->setObjectType($this->objectType);
+               if (!LIKE_ALLOW_FOR_OWN_CONTENT && ($this->likeableObject->getUserID() == WCF::getUser()->userID)) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * @see wcf\data\like\LikeAction::updateLike()
+        */
+       public function like() {
+               return $this->updateLike(Like::LIKE);
+       }
+       
+       /**
+        * @see wcf\data\like\LikeAction::validateLike()
+        */
+       public function validateDislike() {
+               $this->validateLike();
+       }
+       
+       /**
+        * @see wcf\data\like\LikeAction::updateLike()
+        */
+       public function dislike() {
+               return $this->updateLike(Like::DISLIKE);
+       }
+       
+       /**
+        * Sets like/dislike for an object, executing this method again with the same parameters
+        * will revert the status (removing like/dislike).
+        * 
+        * @return      array
+        */
+       protected function updateLike($likeValue) {
+               $likeData = LikeHandler::getInstance()->like($this->likeableObject, WCF::getUser(), $likeValue);
+               
+               // handle activity event
+               if (UserActivityEventHandler::getInstance()->getObjectTypeID($this->objectType->objectType.'.recentActivityEvent')) {
+                       if ($likeData['data']['liked'] == 1) {
+                               UserActivityEventHandler::getInstance()->fireEvent($this->objectType->objectType.'.recentActivityEvent', $this->parameters['data']['objectID']);
+                       }
+                       else {
+                               UserActivityEventHandler::getInstance()->removeEvents($this->objectType->objectType.'.recentActivityEvent', array($this->parameters['data']['objectID']));
+                       }
+               }
+               
+               // get stats
+               return array(
+                       'likes' => ($likeData['data']['likes'] === null) ? 0 : $likeData['data']['likes'],
+                       'dislikes' => ($likeData['data']['dislikes'] === null) ? 0 : $likeData['data']['dislikes'],
+                       'cumulativeLikes' => ($likeData['data']['cumulativeLikes'] === null) ? 0 : $likeData['data']['cumulativeLikes'],
+                       'isLiked' => ($likeData['data']['liked'] == 1) ? 1 : 0,
+                       'isDisliked' => ($likeData['data']['liked'] == -1) ? 1 : 0,
+                       'containerID' => $this->parameters['data']['containerID'],
+                       'newValue' => $likeData['newValue'],
+                       'oldValue' => $likeData['oldValue'],
+                       'users' => $likeData['users']
+               );
+       }
+       
+       /**
+        * Validates permissions for given object.
+        */
+       protected function validateObjectParameters() {
+               if (!MODULE_LIKE) {
+                       throw new PermissionDeniedException();
+               }
+               
+               $this->readString('containerID', false, 'data');
+               $this->readInteger('objectID', false, 'data');
+               $this->readString('objectType', false, 'data');
+               
+               $this->objectType = LikeHandler::getInstance()->getObjectType($this->parameters['data']['objectType']);
+               if ($this->objectType === null) {
+                       throw new UserInputException('objectType');
+               }
+               
+               $this->objectTypeProvider = $this->objectType->getProcessor();
+               $this->likeableObject = $this->objectTypeProvider->getObjectByID($this->parameters['data']['objectID']);
+               $this->likeableObject->setObjectType($this->objectType);
+               if (!$this->objectTypeProvider->checkPermissions($this->likeableObject)) {
+                       throw new PermissionDeniedException();
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/like/LikeEditor.class.php b/wcfsetup/install/files/lib/data/like/LikeEditor.class.php
new file mode 100644 (file)
index 0000000..ef09ceb
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\like;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Extends the like object with functions to create, update and delete likes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like
+ * @category   Community Framework
+ */
+class LikeEditor extends DatabaseObjectEditor {
+       /**
+        * @see wcf\data\DatabaseObjectEditor::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\like\Like';
+}
diff --git a/wcfsetup/install/files/lib/data/like/LikeList.class.php b/wcfsetup/install/files/lib/data/like/LikeList.class.php
new file mode 100644 (file)
index 0000000..bb81d87
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\like;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of likes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.like
+ * @category   Community Framework
+ */
+class LikeList extends DatabaseObjectList {
+       /**
+        * @see wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\like\Like';
+}
diff --git a/wcfsetup/install/files/lib/data/like/object/AbstractLikeObject.class.php b/wcfsetup/install/files/lib/data/like/object/AbstractLikeObject.class.php
new file mode 100644 (file)
index 0000000..8c2dd71
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+namespace wcf\data\like\object;
+use wcf\data\object\type\ObjectType;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Provides a default implementation for like objects.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like.object
+ * @category   Community Framework
+ */
+abstract class AbstractLikeObject extends DatabaseObjectDecorator implements ILikeObject {
+       /**
+        * @see wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\like\object\LikeObject';
+       
+       /**
+        * object type
+        * @var wcf\data\object\type\ObjectType
+        */
+       protected $objectType = null;
+       
+       /**
+        * @see wcf\data\like\object\ILikeObject::updateLikeCounter()
+        */
+       public function updateLikeCounter($cumulativeLikes) { }
+       
+       /**
+        * @see wcf\data\like\object\ILikeObject::getObjectType()
+        */
+       public function getObjectType() {
+               return $this->objectType;
+       }
+       
+       /**
+        * @see wcf\data\like\object\ILikeObject::setObjectType()
+        */
+       public function setObjectType(ObjectType $objectType) {
+               $this->objectType = $objectType;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php b/wcfsetup/install/files/lib/data/like/object/ILikeObject.class.php
new file mode 100644 (file)
index 0000000..d314340
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace wcf\data\like\object;
+use wcf\data\object\type\ObjectType;
+use wcf\data\IDatabaseObjectProcessor;
+use wcf\data\ITitledObject;
+
+/**
+ * Any likeable object should implement this interface.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like.object
+ * @category   Community Framework
+ */
+interface ILikeObject extends IDatabaseObjectProcessor, ITitledObject {
+       /**
+        * Returns the url to this likeable.
+        * 
+        * @return      string
+        */
+       public function getURL();
+       
+       /**
+        * Returns the user id of the owner of this object.
+        * 
+        * @return      integer
+        */
+       public function getUserID();
+       
+       /**
+        * Returns the id of this object.
+        * 
+        * @return      integer
+        */
+       public function getObjectID();
+       
+       /**
+        * Gets the object type.
+        * 
+        * @return      wcf\data\like\object\type\LikeObjectType
+        */
+       public function getObjectType();
+       
+       /**
+        * Updates the cumulative likes for this object.
+        * 
+        * @param       integer         $cumulativeLikes
+        */
+       public function updateLikeCounter($cumulativeLikes);
+       
+       /**
+        * Sets the object type.
+        * 
+        * @param       wcf\data\object\type\ObjectType
+        */
+       public function setObjectType(ObjectType $objectType);
+}
diff --git a/wcfsetup/install/files/lib/data/like/object/LikeObject.class.php b/wcfsetup/install/files/lib/data/like/object/LikeObject.class.php
new file mode 100644 (file)
index 0000000..41d50ce
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+namespace wcf\data\like\object;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\User;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a liked object.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like.object
+ * @category   Community Framework
+ */
+class LikeObject extends DatabaseObject {
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'like_object';
+       
+       /**
+        * @see wcf\data\DatabaseObject::$databaseIndexName
+        */
+       protected static $databaseTableIndexName = 'likeObjectID';
+       
+       /**
+        * liked object
+        * @var wcf\data\like\object\ILikeObject
+        */
+       protected $likedObject = null;
+       
+       /**
+        * list of users who liked this object
+        * @var array<wcf\data\user\User>
+        */
+       protected $users = array();
+       
+       /**
+        * @see wcf\data\DatabaseObject::handleData();
+        */
+       protected function handleData($data) {
+               parent::handleData($data);
+               
+               // get user objects from cache
+               if (!empty($data['cachedUsers'])) {
+                       $cachedUsers = @unserialize($data['cachedUsers']);
+                       
+                       if (is_array($cachedUsers)) {
+                               foreach ($cachedUsers as $cachedUserData) {
+                                       $user = new User(null, $cachedUserData);
+                                       $this->users[$user->userID] = $user;
+                               }
+                       }
+               }
+       }
+       
+       /**
+        * Gets the first 3 users who liked this object.
+        * 
+        * @return      array<wcf\data\user\User>
+        */
+       public function getUsers() {
+               return $this->users;
+       }
+       
+       /**
+        * Returns the liked object.
+        * 
+        * @return      wcf\data\like\object\ILikeObject
+        */
+       public function getLikedObject() {
+               if ($this->likedObject === null) {
+                       $this->likedObject = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID)->getProcessor()->getObjectByID($this->objectID);
+               }
+               
+               return $this->likedObject;
+       }
+       
+       /**
+        * Sets the liked object.
+        * 
+        * @param       wcf\data\like\object\ILikeObject        $likeObject
+        */
+       public function setLikedObject(ILikeObject $likedObject) {
+               $this->likedObject = $likedObject;
+       }
+       
+       /**
+        * Gets a like object by type and object id.
+        * 
+        * @param       integer         $objectTypeID
+        * @param       integer         $objectID
+        * @return      wcf\data\like\object\LikeObject
+        */
+       public static function getLikeObject($objectTypeID, $objectID) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_like_object
+                       WHERE   objectTypeID = ?
+                               AND objectID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(
+                       $objectTypeID,
+                       $objectID
+               ));
+               $row = $statement->fetchArray();
+               
+               if (!$row) {
+                       $row = array();
+               }
+               
+               return new LikeObject(null, $row);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/like/object/LikeObjectEditor.class.php b/wcfsetup/install/files/lib/data/like/object/LikeObjectEditor.class.php
new file mode 100644 (file)
index 0000000..fb93deb
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\like\object;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Extends the LikeObject object with functions to create, update and delete liked objects.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage data.like.object
+ * @category   Community Framework
+ */
+class LikeObjectEditor extends DatabaseObjectEditor {
+       /**
+        * @see wcf\data\DatabaseObjectEditor::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\like\object\LikeObject';
+}
diff --git a/wcfsetup/install/files/lib/data/like/object/LikeObjectList.class.php b/wcfsetup/install/files/lib/data/like/object/LikeObjectList.class.php
new file mode 100644 (file)
index 0000000..5aa4842
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\like\object;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of like objects.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.like.object
+ * @category   Community Framework
+ */
+class LikeObjectList extends DatabaseObjectList {
+       /**
+        * @see wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\like\object\LikeObject';
+}
index 4f1bc5615cc2e790de8fc1e38e1f2419d7af8393..81cdd6616654ecc25e29ac7f3e880fa570c2651f 100644 (file)
@@ -58,7 +58,7 @@ class MembersListPage extends SortablePage {
        /**
         * @see wcf\page\SortablePage::$validSortFields
         */
-       public $validSortFields = array('username', 'registrationDate', 'activityPoints');
+       public $validSortFields = array('username', 'registrationDate', 'activityPoints', 'likesReceived');
        
        /**
         * @see wcf\page\MultipleLinkPage::$objectListClassName
diff --git a/wcfsetup/install/files/lib/system/cache/builder/MostLikedMembersCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/MostLikedMembersCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..a30abe1
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\user\UserList;
+use wcf\system\WCF;
+
+/**
+ * Caches a list of the most liked members.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage system.cache.builder
+ * @category   Community Framework
+ */
+class MostLikedMembersCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @see wcf\system\cache\builder\AbstractCacheBuilder::$maxLifetime
+        */
+       protected $maxLifetime = 600;
+       
+       /**
+        * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+        */
+       protected function rebuild(array $parameters) {
+               $userProfileList = new UserList();
+               $userProfileList->getConditionBuilder()->add('user_table.likesReceived > 0');
+               $userProfileList->sqlOrderBy = 'user_table.likesReceived DESC';
+               $userProfileList->sqlLimit = 5;
+               $userProfileList->readObjectIDs();
+               
+               return $userProfileList->getObjectIDs();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/dashboard/box/MostLikedMembersDashboardBox.class.php b/wcfsetup/install/files/lib/system/dashboard/box/MostLikedMembersDashboardBox.class.php
new file mode 100644 (file)
index 0000000..c7d5235
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\user\UserProfileList;
+use wcf\system\cache\builder\MostLikedMembersCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of the most liked members.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage system.dashboard.box
+ * @category   Community Framework
+ */
+class MostLikedMembersDashboardBox extends AbstractSidebarDashboardBox {
+       /**
+        * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+        */
+       protected function render() {
+               // get ids
+               $mostLikedMemberIDs = MostLikedMembersCacheBuilder::getInstance()->getData();
+               if (empty($mostLikedMemberIDs)) return '';
+               
+               // get profile data
+               $userProfileList = new UserProfileList();
+               $userProfileList->sqlOrderBy = 'user_table.likesReceived DESC';
+               $userProfileList->setObjectIDs($mostLikedMemberIDs);
+               $userProfileList->readObjects();
+               
+               WCF::getTPL()->assign(array(
+                       'mostLikedMembers' => $userProfileList
+               ));
+               return WCF::getTPL()->fetch('dashboardBoxMostLikedMembers');
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/like/LikeHandler.class.php b/wcfsetup/install/files/lib/system/like/LikeHandler.class.php
new file mode 100644 (file)
index 0000000..039a9f8
--- /dev/null
@@ -0,0 +1,470 @@
+<?php
+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\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\activity\point\UserActivityPointHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles the likes of liked objects.
+ * 
+ * Usage (retrieve all likes for a list of objects):
+ * // get type object
+ * $objectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.foo.bar');
+ * // load like data
+ * LikeHandler::getInstance()->loadLikeObjects($objectType, $objectIDs);
+ * // get like data
+ * $likeObjects = LikeHandler::getInstance()->getLikeObjects($objectType);
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage system.like
+ * @category   Community Framework
+ */
+class LikeHandler extends SingletonFactory {
+       /**
+        * loaded like objects
+        * @var array<array>
+        */
+       protected $likeObjectCache = array();
+       
+       /**
+        * cached object types
+        * @var array<array>
+        */
+       protected $cache = null;
+       
+       /**
+        * Creates a new LikeHandler instance.
+        */
+       protected function init() {
+               // load cache
+               $this->cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.like.likeableObject');
+       }
+       
+       /**
+        * Returns an object type from cache.
+        * 
+        * @return      wcf\data\object\type\ObjectType
+        */
+       public function getObjectType($objectName) {
+               if (isset($this->cache[$objectName])) {
+                       return $this->cache[$objectName];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * Gets a like object.
+        * 
+        * @param       wcf\data\object\type\ObjectType         $objectType
+        * @param       integer                                 $objectID
+        * @return      wcf\data\like\object\LikeObject
+        */
+       public function getLikeObject(ObjectType $objectType, $objectID) {
+               if (isset($this->likeObjectCache[$objectType->objectTypeID][$objectID])) {
+                       return $this->likeObjectCache[$objectType->objectTypeID][$objectID];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * Gets the like objects of a specific object type.
+        * 
+        * @param       wcf\data\object\type\ObjectType         $objectType
+        * @return      array<wcf\data\like\object\LikeObject>
+        */
+       public function getLikeObjects(ObjectType $objectType) {
+               if (isset($this->likeObjectCache[$objectType->objectTypeID])) {
+                       return $this->likeObjectCache[$objectType->objectTypeID];
+               }
+               
+               return array();
+       }
+       
+       /**
+        * Loads the like data for a set of objects and returns the number of loaded
+        * like objects
+        * 
+        * @param       wcf\data\object\type\ObjectType         $objectType
+        * @param       array                                   $objectIDs
+        * @return      integer
+        */
+       public function loadLikeObjects(ObjectType $objectType, array $objectIDs) {
+               $i = 0;
+               
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("like_object.objectTypeID = ?", array($objectType->objectTypeID));
+               $conditions->add("like_object.objectID IN (?)", array($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;
+       }
+       
+       /**
+        * Saves the like of an object.
+        * 
+        * @param       wcf\data\like\object\ILikeObject        $likeable
+        * @param       wcf\data\user\User                      $user
+        * @param       integer                                 $likeValue
+        * @param       integer                                 $time
+        * @return      array
+        */
+       public function like(ILikeObject $likeable, User $user, $likeValue, $time = TIME_NOW) {
+               if ($user->userID == 35) {
+                       file_put_contents(WCF_DIR.'__like.dt', microtime(true) . "\tt={$likeable->getObjectType()->objectType}\to={$likeable->getObjectID()}\tlike={$likeValue}\n", FILE_APPEND);
+               }
+               
+               // 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 vote is identically just revert the vote
+               if ($like->likeID && ($like->likeValue == $likeValue)) {
+                       return $this->revertLike($like, $likeable, $likeObject, $user);
+               }
+               
+               // like data
+               $cumulativeLikes = 0;
+               $newValue = $oldValue = null;
+               $users = array();
+               
+               // 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 = array(
+                               'likes' => $likes,
+                               'dislikes' => $dislikes,
+                               'cumulativeLikes' => $cumulativeLikes
+                       );
+                       
+                       if ($likeValue == 1) {
+                               $users = unserialize($likeObject->cachedUsers);
+                               if (count($users) < 3) {
+                                       $users[$user->userID] = array('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 = array();
+                       if ($likeValue == 1) $users = array($user->userID => array('userID' => $user->userID, 'username' => $user->username));
+                       
+                       // create cache
+                       $likeObject = LikeObjectEditor::create(array(
+                               '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(array(
+                                       'likesReceived' => ($like->likeValue == Like::LIKE ? -1 : 1)
+                               ));
+                       }
+                       else if ($likeValue == Like::LIKE) {
+                               $userEditor = new UserEditor(new User($likeable->getUserID()));
+                               $userEditor->updateCounters(array(
+                                       'likesReceived' => 1
+                               ));
+                       }
+               }
+               
+               if (!$like->likeID) {
+                       // save like
+                       $like = LikeEditor::create(array(
+                               '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());
+               }
+               else {
+                       $likeEditor = new LikeEditor($like);
+                       $likeEditor->update(array(
+                               'time' => $time,
+                               'likeValue' => $likeValue
+                       ));
+                       
+                       if ($likeValue == Like::DISLIKE) UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', array($like->likeID));
+               }
+               
+               // update object's like counter
+               $likeable->updateLikeCounter($cumulativeLikes);
+               
+               return array(
+                       'data' => $this->loadLikeStatus($likeObject, $user),
+                       'like' => $like,
+                       'newValue' => $newValue,
+                       'oldValue' => $oldValue,
+                       'users' => $users
+               );
+       }
+       
+       /**
+        * Reverts the like of an object.
+        * 
+        * @param       wcf\data\like\Like                      $like
+        * @param       wcf\data\like\object\ILikeObject        $likeable
+        * @param       wcf\data\like\object\LikeObject         $likeObject
+        * @param       wcf\data\user\User                      $user
+        * @return      array
+        */
+       public function revertLike(Like $like, ILikeObject $likeable, LikeObject $likeObject, User $user) {
+               // 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 = array(
+                       'likes' => $likes,
+                       'dislikes' => $dislikes,
+                       'cumulativeLikes' => $cumulativeLikes
+               );
+               
+               $users = $likeObject->getUsers();
+               $usersArray = array();
+               foreach ($users as $user2) {
+                       $usersArray[$user2->userID] = array('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(array(
+                                       'likesReceived' => -1
+                               ));
+                       }
+                       
+                       UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', array($like->likeID));
+               }
+               
+               // update object's like counter
+               $likeable->updateLikeCounter($cumulativeLikes);
+               
+               return array(
+                       'data' => $this->loadLikeStatus($likeObject, $user),
+                       'newValue' => null,
+                       'oldValue' => $like->likeValue,
+                       'users' => $usersArray
+               );
+       }
+       
+       /**
+        * Removes all likes for given objects.
+        * 
+        * @param       string          $objectType
+        * @param       array<integer>  $objectIDs
+        */
+       public function removeLikes($objectType, array $objectIDs) {
+               $objectTypeObj = $this->getObjectType($objectType);
+               
+               // get like objects
+               $likeObjectList = new LikeObjectList();
+               $likeObjectList->getConditionBuilder()->add('like_object.objectTypeID = ?', array($objectTypeObj->objectTypeID));
+               $likeObjectList->getConditionBuilder()->add('like_object.objectID IN (?)', array($objectIDs));
+               $likeObjectList->readObjects();
+               $likeObjects = $likeObjectList->getObjects();
+               $likeObjectIDs = $likeObjectList->getObjectIDs();
+               
+               // reduce count of received users
+               $users = array();
+               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, array('userID' => $userID)));
+                       $userEditor->updateCounters(array(
+                               'likesReceived' => $receivedLikes * -1
+                       ));
+               }
+               
+               // get like ids
+               $likeList = new LikeList();
+               $likeList->getConditionBuilder()->add('like_table.objectTypeID = ?', array($objectTypeObj->objectTypeID));
+               $likeList->getConditionBuilder()->add('like_table.objectID IN (?)', array($objectIDs));
+               $likeList->readObjectIDs();
+               $likeIDs = $likeList->getObjectIDs();
+               
+               if (!empty($likeIDs)) {
+                       // revoke activity points
+                       UserActivityPointHandler::getInstance()->removeEvents('com.woltlab.wcf.like.activityPointEvent.receivedLikes', $likeIDs);
+                       
+                       // delete likes
+                       LikeEditor::deleteAll($likeIDs);
+               }
+               
+               // 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);
+               }
+       }
+       
+       /**
+        * Returns current like object status.
+        * 
+        * @param       wcf\data\like\object\LikeObject         $likeObject
+        * @param       wcf\data\user\User                      $user
+        * @return      array
+        */
+       protected function loadLikeStatus(LikeObject $likeObject, User $user) {
+               $sql = "SELECT          like_object.likes, like_object.dislikes, like_object.cumulativeLikes,
+                                       CASE WHEN like_table.likeValue 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 = ".$likeObject->objectTypeID."
+                                       AND like_table.objectID = like_object.objectID
+                                       AND like_table.userID = ?)
+                       WHERE           like_object.likeObjectID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(
+                       $user->userID,
+                       $likeObject->likeObjectID
+               ));
+               $row = $statement->fetchArray();
+               
+               return $row;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/activity/point/ReceivedLikesUserActivityPointObjectProcessor.class.php b/wcfsetup/install/files/lib/system/user/activity/point/ReceivedLikesUserActivityPointObjectProcessor.class.php
new file mode 100644 (file)
index 0000000..9a72573
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+namespace wcf\system\user\activity\point;
+use wcf\data\like\Like;
+use wcf\data\object\type\ObjectType;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\user\activity\point\IUserActivityPointObjectProcessor;
+use wcf\system\WCF;
+
+/**
+ * Updates events for received likes.
+ * 
+ * @author     Tim Düsterhus
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.like
+ * @subpackage system.user.activity.point
+ * @category   Community Framework
+ */
+class ReceivedLikesUserActivityPointObjectProcessor implements IUserActivityPointObjectProcessor {
+       public $limit = 500;
+       public $objectType = null;
+       
+       /**
+        * Creates a new instance of ReceivedLikesUserActivityPointObjectProcessor.
+        * 
+        * @param       wcf\data\object\type\ObjectType         $objectType
+        */
+       public function __construct(ObjectType $objectType) {
+               $this->objectType = $objectType;
+       }
+       
+       /**
+        * @see wcf\system\user\activity\point\IUserActivityPointObject::countRequests();
+        */
+       public function countRequests() {
+               $sql = "SELECT  COUNT(*) AS count
+                       FROM    wcf".WCF_N."_like
+                       WHERE   likeValue = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(Like::LIKE));
+               $row = $statement->fetchArray();
+               
+               return ceil($row['count'] / $this->limit) + 1;
+       }
+       
+       /**
+        * @see wcf\system\user\activity\point\IUserActivityPointObject::updateActivityPointEvents();
+        */
+       public function updateActivityPointEvents($request) {
+               if ($request == 0) {
+                       // first request
+                       $sql = "DELETE FROM     wcf".WCF_N."_user_activity_point_event 
+                               WHERE           objectTypeID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute(array($this->objectType->objectTypeID));
+               }
+               else {
+                       $sql = "SELECT          likeID
+                               FROM            wcf".WCF_N."_like
+                               WHERE           likeValue = ?
+                               ORDER BY        likeID ASC";
+                       $statement = WCF::getDB()->prepareStatement($sql, $this->limit, ($this->limit * ($request - 1)));
+                       $statement->execute(array(Like::LIKE));
+                       $likeIDs = array();
+                       while ($row = $statement->fetchArray()) {
+                               $likeIDs[] = $row['likeID'];
+                       }
+                       
+                       if (empty($likeIDs)) return;
+                       
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add("objectTypeID = ?", array($this->objectType->objectTypeID));
+                       $conditionBuilder->add("objectID IN (?)", array($likeIDs));
+                       
+                       // avoid problems with duplicate keys, as likes may be created in the meantime
+                       $sql = "DELETE FROM     wcf".WCF_N."_user_activity_point_event 
+                               ".$conditionBuilder;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add("likeID IN (?)", array($likeIDs));
+                       // use INSERT … SELECT as this makes bulk updating easier
+                       $sql = "INSERT INTO     wcf".WCF_N."_user_activity_point_event
+                                               (userID, objectTypeID, objectID, additionalData)
+                               SELECT          objectUserID AS userID,
+                                               ?,
+                                               likeID AS objectID,
+                                               ?
+                               FROM    wcf".WCF_N."_like
+                               ".$conditionBuilder;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute(array_merge(array($this->objectType->objectTypeID, serialize(array())), $conditionBuilder->getParameters()));
+               }
+       }
+}
diff --git a/wcfsetup/install/files/style/like.less b/wcfsetup/install/files/style/like.less
new file mode 100644 (file)
index 0000000..bfda15d
--- /dev/null
@@ -0,0 +1,11 @@
+.likeButton.active {
+       .icon {
+               color: rgba(0, 153, 0, 1);
+       }
+}
+
+.dislikeButton.active {
+       .icon {
+               color: rgba(204, 0, 0, 1); 
+       }
+}
\ No newline at end of file
index 04a51a543aa50737bd61e2251944098e2f96993a..ea86530fe5bd277f70581a103a798ebed74c1afa 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>6
 <language xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/language.xsd" languagecode="de" languagename="Deutsch" countrycode="de">
        <category name="wcf.acl">
                <item name="wcf.acl.option.deny"><![CDATA[Verweigern]]></item>
                <item name="wcf.acp.group.option.user.profile.renamePeriod.description"><![CDATA[Zeitraum nach dem Mitglieder dieser Benutzergruppe ihren Benutzernamen ändern können. [Zeit in Tagen]]]></item>
                <item name="wcf.acp.group.option.user.profile.cannotBeIgnored"><![CDATA[Kann nicht ignoriert werden]]></item>
                <item name="wcf.acp.group.option.mod.general.canUseModeration"><![CDATA[Kann Moderation benutzen]]></item>
+               <item name="wcf.acp.group.option.category.user.like"><![CDATA[Like-System]]></item>
+               <item name="wcf.acp.group.option.user.like.canViewLike"><![CDATA[Kann vergebene Likes sehen]]></item>
+               <item name="wcf.acp.group.option.user.like.canLike"><![CDATA[Kann Inhalte liken]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.option.user_cleanup_profile_visitor_lifetime.description"><![CDATA[Zeitraum nach dem Profil-Besucher automatisch verworfen werden. [Zeitraum in Tagen]]]></item>
                <item name="wcf.acp.option.recent_activity_items"><![CDATA[Anzahl Einträge]]></item>
                <item name="wcf.acp.option.recent_activity_sidebar_items"><![CDATA[Anzahl Einträge]]></item>
+               <item name="wcf.acp.option.category.message.general.likes"><![CDATA[Like-System]]></item>
+               <item name="wcf.acp.option.module_like"><![CDATA[Like-System]]></item>
+               <item name="wcf.acp.option.like_allow_for_own_content"><![CDATA[Benutzer können eigene Inhalte liken]]></item>
+               <item name="wcf.acp.option.like_enable_dislike"><![CDATA[Benutzer können Inhalte disliken]]></item>
+               <item name="wcf.acp.option.like_show_summary"><![CDATA[Zusammenfassung anzeigen]]></item>
        </category>
        
        <category name="wcf.acp.package">
@@ -1227,6 +1235,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.dashboard.objectType.com.woltlab.wcf.user.MembersListPage"><![CDATA[Mitgliederliste]]></item>
                <item name="wcf.dashboard.box.mostActiveMembers.points"><![CDATA[{#$activeMember->activityPoints} Punkt{if $activeMember->activityPoints != 1}e{/if}]]></item>
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.recentActivity"><![CDATA[Letzte Aktivität]]></item>
+               <item name="wcf.dashboard.box.com.woltlab.wcf.like.mostLikedMembers"><![CDATA[Mitglieder mit den meisten Likes]]></item>
+               <item name="wcf.dashboard.box.mostLikedMembers.likes"><![CDATA[{#$likedMember->likesReceived} Like{if $likedMember->likesReceived != 1}s{/if}]]></item>
        </category>
        
        <category name="wcf.date">
@@ -1483,6 +1493,19 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.imageViewer.previous"><![CDATA[Vorheriges Bild]]></item>
        </category>
        
+       <category name="wcf.like">
+               <item name="wcf.like.cumulativeLikes"><![CDATA[Likes]]></item>
+               <item name="wcf.like.tooltip"><![CDATA[{if $likes}{#$likes} Like{if $likes != 1}s{/if}{if $dislikes}, {/if}{/if}{if $dislikes}{#$dislikes} Dislike{if $dislikes != 1}s{/if}{/if}]]></item>
+               <item name="wcf.like.jsTooltip"><![CDATA[($likes > 0 ? ($likes + " Like" + ($likes != 1 ? "s" : "") + ($dislikes > 0 ? ", " : "")) : "") + ($dislikes > 0 ? ($dislikes + " Dislike" + ($dislikes != 1 ? "s" : "")) : "")]]></item>
+               <item name="wcf.like.button.like"><![CDATA[Gefällt mir]]></item>
+               <item name="wcf.like.button.dislike"><![CDATA[Gefällt mir nicht]]></item>
+               <item name="wcf.like.likesReceived"><![CDATA[Erhaltene Likes]]></item>
+               <item name="wcf.like.summary"><![CDATA[$userArray.join(", ") + ($others > 0 ? (" und " + $others + (" weitere" + ($others > 1 ? "n" : "m"))) : "") + " gefällt das."]]></item>
+               <item name="wcf.like.details"><![CDATA[Details]]></item>
+               <item name="wcf.like.details.like"><![CDATA[Likes]]></item>
+               <item name="wcf.like.details.dislike"><![CDATA[Dislikes]]></item>
+       </category>
+       
        <category name="wcf.message">
                <item name="wcf.message.bbcode.code.copy"><![CDATA[Inhalt kopieren]]></item>
                <item name="wcf.message.quote.insertAllQuotes"><![CDATA[Alle Zitate einfügen]]></item>
@@ -1792,6 +1815,7 @@ Wenn Sie Probleme mit der Aktivierung haben, wenden Sie sich bitte an den Admini
                <item name="wcf.user.login.3rdParty"><![CDATA[Anmeldung über Drittanbieter]]></item>
                <item name="wcf.user.search.results"><![CDATA[Suchergebnisse]]></item>
                <item name="wcf.user.lastActivityTime"><![CDATA[Letzte Aktivität]]></item>
+               <item name="wcf.user.activityPoint.objectType.com.woltlab.wcf.like.activityPointEvent.receivedLikes"><![CDATA[Erhaltene Likes]]></item>
        </category>
        
        <category name="wcf.user.menu">
index 1433ff768ba12814b5ae3cf9aac8b325002b9f06..a7fe5f246c6825b33a7f84f36e1eb6349d3455eb 100644 (file)
@@ -292,6 +292,9 @@ Examples for medium ID detection:
                <item name="wcf.acp.group.option.user.profile.renamePeriod.description"><![CDATA[Minimum period until members may rename themselves. [time in days]]]></item>
                <item name="wcf.acp.group.option.user.profile.cannotBeIgnored"><![CDATA[Can not be ignored]]></item>
                <item name="wcf.acp.group.option.mod.general.canUseModeration"><![CDATA[Can access moderation]]></item>
+               <item name="wcf.acp.group.option.category.user.like"><![CDATA[Like System]]></item>
+               <item name="wcf.acp.group.option.user.like.canViewLike"><![CDATA[Can view likes]]></item>
+               <item name="wcf.acp.group.option.user.like.canLike"><![CDATA[Can like content]]></item>
        </category>
        
        <category name="wcf.acp.index">
@@ -699,6 +702,11 @@ Examples for medium ID detection:
                <item name="wcf.acp.option.user_cleanup_profile_visitor_lifetime.description"><![CDATA[Profile visitors will be discarded if they are older than the given period. [time in days]]]></item>
                <item name="wcf.acp.option.recent_activity_items"><![CDATA[Number of Entries]]></item>
                <item name="wcf.acp.option.recent_activity_sidebar_items"><![CDATA[Number of Entries]]></item>
+               <item name="wcf.acp.option.category.message.general.likes"><![CDATA[Like System]]></item>
+               <item name="wcf.acp.option.module_like"><![CDATA[Like System]]></item>
+               <item name="wcf.acp.option.like_allow_for_own_content"><![CDATA[Users can like their own content]]></item>
+               <item name="wcf.acp.option.like_enable_dislike"><![CDATA[Users can dislike content]]></item>
+               <item name="wcf.acp.option.like_show_summary"><![CDATA[Show like summary]]></item>
        </category>
        
        <category name="wcf.acp.package">
@@ -1224,6 +1232,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <item name="wcf.dashboard.objectType.com.woltlab.wcf.user.MembersListPage"><![CDATA[Members List]]></item>
                <item name="wcf.dashboard.box.mostActiveMembers.points"><![CDATA[{#$activeMember->activityPoints} Point{if $activeMember->activityPoints != 1}s{/if}]]></item>
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.recentActivity"><![CDATA[Recent Activity]]></item>
+               <item name="wcf.dashboard.box.com.woltlab.wcf.like.mostLikedMembers"><![CDATA[Most Liked Members]]></item>
+               <item name="wcf.dashboard.box.mostLikedMembers.likes"><![CDATA[{#$likedMember->likesReceived} Like{if $likedMember->likesReceived != 1}s{/if}]]></item>
        </category>
        
        <category name="wcf.date">
@@ -1479,7 +1489,20 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <item name="wcf.imageViewer.next"><![CDATA[Next Image]]></item>
                <item name="wcf.imageViewer.previous"><![CDATA[Previous Image]]></item>
        </category>
-               
+       
+       <category name="wcf.like">
+               <item name="wcf.like.cumulativeLikes"><![CDATA[Likes]]></item>
+               <item name="wcf.like.tooltip"><![CDATA[{if $likes}{#$likes} Like{if $likes != 1}s{/if}{if $dislikes}, {/if}{/if}{if $dislikes}{#$dislikes} Dislike{if $dislikes != 1}s{/if}{/if}]]></item>
+               <item name="wcf.like.jsTooltip"><![CDATA[($likes > 0 ? ($likes + " Like" + ($likes != 1 ? "s" : "") + ($dislikes > 0 ? ", " : "")) : "") + ($dislikes > 0 ? ($dislikes + " Dislike" + ($dislikes != 1 ? "s" : "")) : "")]]></item>
+               <item name="wcf.like.button.like"><![CDATA[Like]]></item>
+               <item name="wcf.like.button.dislike"><![CDATA[Dislike]]></item>
+               <item name="wcf.like.likesReceived"><![CDATA[Likes Received]]></item>
+               <item name="wcf.like.summary"><![CDATA[$userArray.join(", ") + ($others > 0 ? (" and " + $others + (" other" + ($others > 1 ? "s" : ""))) : "") + " like" + ($userArray.length == 1 ? "s" : "") +  " this."]]></item>
+               <item name="wcf.like.details"><![CDATA[Details]]></item>
+               <item name="wcf.like.details.like"><![CDATA[Likes]]></item>
+               <item name="wcf.like.details.dislike"><![CDATA[Dislikes]]></item>
+       </category>
+       
        <category name="wcf.message">
                <item name="wcf.message.bbcode.code.copy"><![CDATA[Copy Contents]]></item>
                <item name="wcf.message.quote.insertAllQuotes"><![CDATA[Insert All Quotes]]></item>
@@ -1795,6 +1818,7 @@ Your {@PAGE_TITLE|language} team]]></item>
                <item name="wcf.user.login.3rdParty"><![CDATA[Third-Party Login]]></item>
                <item name="wcf.user.search.results"><![CDATA[Search Results]]></item>
                <item name="wcf.user.lastActivityTime"><![CDATA[Last Activity]]></item>
+               <item name="wcf.user.activityPoint.objectType.com.woltlab.wcf.like.activityPointEvent.receivedLikes"><![CDATA[Likes Received]]></item>
        </category>
        
        <category name="wcf.user.menu">
index 9283a40d733244a06af51be5c6aebd53943cb30f..f9a4b92d964bd8009d80f389389612bd76d68483 100644 (file)
@@ -339,6 +339,31 @@ CREATE TABLE wcf1_language_server (
        isDisabled TINYINT(1) NOT NULL DEFAULT 0
 );
 
+DROP TABLE IF EXISTS wcf1_like;
+CREATE TABLE wcf1_like (
+       likeID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, 
+       objectID INT(10) NOT NULL DEFAULT 0,
+       objectTypeID INT(10) NOT NULL,
+       objectUserID INT(10),
+       userID INT(10) NOT NULL,
+       time INT(10) NOT NULL DEFAULT 0,
+       likeValue TINYINT(1) NOT NULL DEFAULT 1,
+       UNIQUE KEY (objectTypeID, objectID, userID)
+);
+
+DROP TABLE IF EXISTS wcf1_like_object;
+CREATE TABLE wcf1_like_object (
+       likeObjectID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       objectTypeID INT(10) NOT NULL,
+       objectID INT(10) NOT NULL DEFAULT 0, 
+       objectUserID INT(10),
+       likes MEDIUMINT(7) NOT NULL DEFAULT 0,
+       dislikes MEDIUMINT(7) NOT NULL DEFAULT 0,
+       cumulativeLikes MEDIUMINT(7) NOT NULL DEFAULT 0,
+       cachedUsers TEXT,
+       UNIQUE KEY (objectTypeID, objectID)
+);
+
 DROP TABLE IF EXISTS wcf1_moderation_queue;
 CREATE TABLE wcf1_moderation_queue (
        queueID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -804,13 +829,15 @@ CREATE TABLE wcf1_user (
        activityPoints INT(10) NOT NULL DEFAULT 0,
        notificationMailToken VARCHAR(20) NOT NULL DEFAULT '',
        authData VARCHAR(255) NOT NULL DEFAULT '',
+       likesReceived MEDIUMINT(7) NOT NULL DEFAULT 0,
        
        KEY username (username),
        KEY registrationDate (registrationDate),
        KEY styleID (styleID),
        KEY activationCode (activationCode),
        KEY registrationData (registrationIpAddress, registrationDate),
-       KEY activityPoints (activityPoints)
+       KEY activityPoints (activityPoints),
+       KEY likesReceived (likesReceived)
 );
 
 DROP TABLE IF EXISTS wcf1_user_activity_event;
@@ -1317,6 +1344,13 @@ ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (assignedUserID) REFERENCES wc
 ALTER TABLE wcf1_moderation_queue_to_user ADD FOREIGN KEY (queueID) REFERENCES wcf1_moderation_queue (queueID) ON DELETE CASCADE;
 ALTER TABLE wcf1_moderation_queue_to_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_like ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_like ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_like ADD FOREIGN KEY (objectUserID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
+
+ALTER TABLE wcf1_like_object ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_like_object ADD FOREIGN KEY (objectUserID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
+
 
 /* default inserts */
 -- default user groups