From 285b1d92ac314ef5c93d54694f46b27a28c1373f Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Tue, 21 May 2013 00:34:36 +0200 Subject: [PATCH] Merged com.woltlab.wcf.comment into WCF --- com.woltlab.wcf/objectType.xml | 57 ++ com.woltlab.wcf/objectTypeDefinition.xml | 5 + com.woltlab.wcf/option.xml | 6 + .../template/__commentJavaScript.tpl | 30 + com.woltlab.wcf/template/commentList.tpl | 33 + .../template/commentResponseList.tpl | 27 + .../template/moderationComment.tpl | 29 + .../template/userProfileCommentList.tpl | 19 + com.woltlab.wcf/userGroupOption.xml | 48 ++ com.woltlab.wcf/userNotificationEvent.xml | 19 + com.woltlab.wcf/userOption.xml | 10 + com.woltlab.wcf/userProfileMenu.xml | 6 + wcfsetup/install/files/js/WCF.Comment.js | 757 ++++++++++++++++++ wcfsetup/install/files/js/WCF.Comment.min.js | 1 + .../lib/acp/form/UserMergeForm.class.php | 30 + .../files/lib/data/comment/Comment.class.php | 117 +++ .../lib/data/comment/CommentAction.class.php | 576 +++++++++++++ .../lib/data/comment/CommentEditor.class.php | 41 + .../lib/data/comment/CommentList.class.php | 20 + .../data/comment/LikeableComment.class.php | 53 ++ .../comment/LikeableCommentProvider.class.php | 41 + .../data/comment/StructuredComment.class.php | 184 +++++ .../comment/StructuredCommentList.class.php | 176 ++++ .../data/comment/ViewableComment.class.php | 41 + .../response/CommentResponse.class.php | 130 +++ .../response/CommentResponseAction.class.php | 170 ++++ .../response/CommentResponseEditor.class.php | 20 + .../response/CommentResponseList.class.php | 20 + .../LikeableCommentResponse.class.php | 53 ++ .../LikeableCommentResponseProvider.class.php | 47 ++ .../StructuredCommentResponse.class.php | 115 +++ .../StructuredCommentResponseList.class.php | 117 +++ .../ViewableCommentResponse.class.php | 41 + .../system/comment/CommentHandler.class.php | 163 ++++ .../manager/AbstractCommentManager.class.php | 163 ++++ .../comment/manager/ICommentManager.class.php | 109 +++ .../UserProfileCommentManager.class.php | 97 +++ .../CommentUserProfileMenuContent.class.php | 61 ++ ...mentModerationQueueReportHandler.class.php | 208 +++++ ...onseModerationQueueReportHandler.class.php | 203 +++++ ...CommentResponseUserActivityEvent.class.php | 85 ++ .../ProfileCommentUserActivityEvent.class.php | 68 ++ ...sponseOwnerUserNotificationEvent.class.php | 62 ++ ...entResponseUserNotificationEvent.class.php | 63 ++ ...fileCommentUserNotificationEvent.class.php | 53 ++ ...ntResponseUserNotificationObject.class.php | 41 + .../CommentUserNotificationObject.class.php | 41 + ...ommentUserNotificationObjectType.class.php | 22 + ...sponseUserNotificationObjectType.class.php | 29 + ...ommentUserNotificationObjectType.class.php | 44 + wcfsetup/install/files/style/comment.less | 114 +++ wcfsetup/install/lang/de.xml | 46 +- wcfsetup/install/lang/en.xml | 45 ++ wcfsetup/setup/db/install.sql | 33 + 54 files changed, 4788 insertions(+), 1 deletion(-) create mode 100644 com.woltlab.wcf/template/__commentJavaScript.tpl create mode 100644 com.woltlab.wcf/template/commentList.tpl create mode 100644 com.woltlab.wcf/template/commentResponseList.tpl create mode 100644 com.woltlab.wcf/template/moderationComment.tpl create mode 100644 com.woltlab.wcf/template/userProfileCommentList.tpl create mode 100644 wcfsetup/install/files/js/WCF.Comment.js create mode 100644 wcfsetup/install/files/js/WCF.Comment.min.js create mode 100644 wcfsetup/install/files/lib/data/comment/Comment.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/CommentAction.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/CommentEditor.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/CommentList.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/LikeableComment.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/LikeableCommentProvider.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/StructuredComment.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/ViewableComment.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/CommentResponseEditor.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/CommentResponseList.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponse.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponseProvider.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponse.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponseList.class.php create mode 100644 wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php create mode 100644 wcfsetup/install/files/lib/system/comment/CommentHandler.class.php create mode 100644 wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php create mode 100644 wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php create mode 100644 wcfsetup/install/files/lib/system/comment/manager/UserProfileCommentManager.class.php create mode 100644 wcfsetup/install/files/lib/system/menu/user/profile/content/CommentUserProfileMenuContent.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/report/CommentCommentModerationQueueReportHandler.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php create mode 100644 wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php create mode 100644 wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentUserActivityEvent.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseOwnerUserNotificationEvent.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseUserNotificationEvent.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/CommentResponseUserNotificationObject.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/CommentUserNotificationObject.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/type/ICommentUserNotificationObjectType.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentResponseUserNotificationObjectType.class.php create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentUserNotificationObjectType.class.php create mode 100644 wcfsetup/install/files/style/comment.less diff --git a/com.woltlab.wcf/objectType.xml b/com.woltlab.wcf/objectType.xml index dca396a5f8..785418ca82 100644 --- a/com.woltlab.wcf/objectType.xml +++ b/com.woltlab.wcf/objectType.xml @@ -148,5 +148,62 @@ wcf\system\user\activity\point\ReceivedLikesUserActivityPointObjectProcessor + + + com.woltlab.wcf.user.profileComment + com.woltlab.wcf.comment.commentableContent + wcf\system\comment\manager\UserProfileCommentManager + + + + com.woltlab.wcf.comment + com.woltlab.wcf.like.likeableObject + wcf\data\comment\LikeableCommentProvider + + + + com.woltlab.wcf.comment.response + com.woltlab.wcf.like.likeableObject + wcf\data\comment\response\LikeableCommentResponseProvider + + + + com.woltlab.wcf.user.profileComment.recentActivityEvent + com.woltlab.wcf.user.recentActivityEvent + wcf\system\user\activity\event\ProfileCommentUserActivityEvent + + + + com.woltlab.wcf.user.profileComment.response.recentActivityEvent + com.woltlab.wcf.user.recentActivityEvent + wcf\system\user\activity\event\ProfileCommentResponseUserActivityEvent + + + + com.woltlab.wcf.user.profileComment.notification + com.woltlab.wcf.notification.objectType + wcf\system\user\notification\object\type\UserProfileCommentUserNotificationObjectType + com.woltlab.wcf.user + + + com.woltlab.wcf.user.profileComment.response.notification + com.woltlab.wcf.notification.objectType + wcf\system\user\notification\object\type\UserProfileCommentResponseUserNotificationObjectType + com.woltlab.wcf.user + + + + + com.woltlab.wcf.comment.comment + com.woltlab.wcf.moderation.report + wcf\system\moderation\queue\report\CommentCommentModerationQueueReportHandler + + + + com.woltlab.wcf.comment.response + com.woltlab.wcf.moderation.report + wcf\system\moderation\queue\report\CommentResponseModerationQueueReportHandler + + \ No newline at end of file diff --git a/com.woltlab.wcf/objectTypeDefinition.xml b/com.woltlab.wcf/objectTypeDefinition.xml index 94cd667e7f..f836db6673 100644 --- a/com.woltlab.wcf/objectTypeDefinition.xml +++ b/com.woltlab.wcf/objectTypeDefinition.xml @@ -94,5 +94,10 @@ com.woltlab.wcf.like.likeableObject wcf\data\like\ILikeObjectTypeProvider + + + com.woltlab.wcf.comment.commentableContent + wcf\system\comment\manager\ICommentManager + diff --git a/com.woltlab.wcf/option.xml b/com.woltlab.wcf/option.xml index cf426faa96..58570a2b86 100644 --- a/com.woltlab.wcf/option.xml +++ b/com.woltlab.wcf/option.xml @@ -287,6 +287,12 @@ 1 + + + + + + + + + + + + + + + + diff --git a/com.woltlab.wcf/userNotificationEvent.xml b/com.woltlab.wcf/userNotificationEvent.xml index 31513410eb..9821ee7816 100644 --- a/com.woltlab.wcf/userNotificationEvent.xml +++ b/com.woltlab.wcf/userNotificationEvent.xml @@ -6,5 +6,24 @@ com.woltlab.wcf.user.follow wcf\system\user\notification\event\UserFollowFollowingUserNotificationEvent + + + comment + com.woltlab.wcf.user.profileComment.notification + wcf\system\user\notification\event\UserProfileCommentUserNotificationEvent + 1 + + + commentResponse + com.woltlab.wcf.user.profileComment.response.notification + wcf\system\user\notification\event\UserProfileCommentResponseUserNotificationEvent + 1 + + + commentResponseOwner + com.woltlab.wcf.user.profileComment.response.notification + wcf\system\user\notification\event\UserProfileCommentResponseOwnerUserNotificationEvent + 1 + diff --git a/com.woltlab.wcf/userOption.xml b/com.woltlab.wcf/userOption.xml index 3edcbe8817..1bda37ea4f 100644 --- a/com.woltlab.wcf/userOption.xml +++ b/com.woltlab.wcf/userOption.xml @@ -243,6 +243,16 @@ 1 + + diff --git a/com.woltlab.wcf/userProfileMenu.xml b/com.woltlab.wcf/userProfileMenu.xml index 59b608ba58..7c3eabc69d 100644 --- a/com.woltlab.wcf/userProfileMenu.xml +++ b/com.woltlab.wcf/userProfileMenu.xml @@ -10,5 +10,11 @@ wcf\system\menu\user\profile\content\RecentActivityUserProfileMenuContent 1 + + + wcf\system\menu\user\profile\content\CommentUserProfileMenuContent + 1 + module_user_profile_wall + diff --git a/wcfsetup/install/files/js/WCF.Comment.js b/wcfsetup/install/files/js/WCF.Comment.js new file mode 100644 index 0000000000..61a3073775 --- /dev/null +++ b/wcfsetup/install/files/js/WCF.Comment.js @@ -0,0 +1,757 @@ +/** + * Namespace for comments + */ +WCF.Comment = {}; + +/** + * Comment support for WCF + * + * @author Alexander Ebert + * @copyright 2001-2013 WoltLab GmbH + * @license GNU Lesser General Public License + */ +WCF.Comment.Handler = Class.extend({ + /** + * input element to add a comment + * @var jQuery + */ + _commentAdd: null, + + /** + * list of comment objects + * @var object + */ + _comments: { }, + + /** + * comment container object + * @var jQuery + */ + _container: null, + + /** + * container id + * @var string + */ + _containerID: '', + + /** + * number of currently displayed comments + * @var integer + */ + _displayedComments: 0, + + /** + * button to load next comments + * @var jQuery + */ + _loadNextComments: null, + + /** + * buttons to load next responses per comment + * @var object + */ + _loadNextResponses: { }, + + /** + * action proxy + * @var WCF.Action.Proxy + */ + _proxy: null, + + /** + * list of response objects + * @var object + */ + _responses: { }, + + /** + * user's avatar + * @var string + */ + _userAvatar: '', + + /** + * Initializes the WCF.Comment.Handler class. + * + * @param string containerID + * @param string userAvatar + */ + init: function(containerID, userAvatar) { + this._commentAdd = null; + this._comments = { }; + this._containerID = containerID; + this._displayedComments = 0; + this._loadNextComments = null; + this._loadNextResponses = { }; + this._responses = { }; + this._userAvatar = userAvatar; + + this._container = $('#' + $.wcfEscapeID(this._containerID)); + if (!this._container.length) { + console.debug("[WCF.Comment.Handler] Unable to find container identified by '" + this._containerID + "'"); + } + + this._proxy = new WCF.Action.Proxy({ + success: $.proxy(this._success, this) + }); + + WCF.DOMNodeInsertedHandler.enable(); + + this._initComments(); + this._initResponses(); + + // add new comment + if (this._container.data('canAdd')) { + this._initAddComment(); + } + + WCF.DOMNodeInsertedHandler.disable(); + WCF.DOMNodeInsertedHandler.addCallback('WCF.Comment.Handler', $.proxy(this._domNodeInserted, this)); + }, + + /** + * Shows a button to load next comments. + */ + _handleLoadNextComments: function() { + if (this._displayedComments < this._container.data('comments')) { + if (this._loadNextComments === null) { + this._loadNextComments = $('
  • ').appendTo(this._container); + this._loadNextComments.children('button').click($.proxy(this._loadComments, this)); + } + + this._loadNextComments.children('button').enable(); + } + else if (this._loadNextComments !== null) { + this._loadNextComments.hide(); + } + }, + + /** + * Shows a button to load next responses per comment. + * + * @param integer commentID + */ + _handleLoadNextResponses: function(commentID) { + var $comment = this._comments[commentID]; + $comment.data('displayedResponses', $comment.find('ul.commentResponseList > li').length); + + if ($comment.data('displayedResponses') < $comment.data('responses')) { + if (this._loadNextResponses[commentID] === undefined) { + this._loadNextResponses[commentID] = $('
    ').insertAfter($comment.find('ul.commentResponseList')); + this._loadNextResponses[commentID].children('button').data('commentID', commentID).click($.proxy(this._loadResponses, this)); + } + + this._loadNextResponses[commentID].children('button').enable(); + } + else if (this._loadNextResponses[commentID] !== undefined) { + this._loadNextResponses[commentID].hide(); + } + }, + + /** + * Loads next comments. + */ + _loadComments: function() { + this._loadNextComments.children('button').disable(); + + this._proxy.setOption('data', { + actionName: 'loadComments', + className: 'wcf\\data\\comment\\CommentAction', + parameters: { + data: { + objectID: this._container.data('objectID'), + objectTypeID: this._container.data('objectTypeID'), + lastCommentTime: this._container.data('lastCommentTime') + } + } + }); + this._proxy.sendRequest(); + }, + + /** + * Loads next responses for given comment. + * + * @param object event + */ + _loadResponses: function(event) { + var $button = $(event.currentTarget).disable(); + var $commentID = $button.data('commentID'); + + this._proxy.setOption('data', { + actionName: 'loadResponses', + className: 'wcf\\data\\comment\\response\\CommentResponseAction', + parameters: { + data: { + commentID: $commentID, + lastResponseTime: this._comments[$commentID].data('lastResponseTime') + } + } + }); + this._proxy.sendRequest(); + }, + + /** + * Handles DOMNodeInserted events. + */ + _domNodeInserted: function() { + this._initComments(); + this._initResponses(); + }, + + /** + * Initializes available comments. + */ + _initComments: function() { + var self = this; + var $loadedComments = false; + this._container.find('.jsComment').each(function(index, comment) { + var $comment = $(comment).removeClass('jsComment'); + var $commentID = $comment.data('commentID'); + self._comments[$commentID] = $comment; + + self._initComment($commentID, $comment); + self._displayedComments++; + + $loadedComments = true; + self._handleLoadNextResponses($commentID); + }); + + if ($loadedComments) { + this._handleLoadNextComments(); + } + }, + + /** + * Initializes a specific comment. + * + * @param integer commentID + * @param jQuery comment + */ + _initComment: function(commentID, comment) { + if (this._container.data('canAdd')) { + this._initAddResponse(commentID, comment); + } + + if (comment.data('canEdit')) { + var $editButton = $('
  • '); + $editButton.data('commentID', commentID).appendTo(comment.find('ul.commentOptions:eq(0)')).click($.proxy(this._prepareEdit, this)); + } + + if (comment.data('canDelete')) { + var $deleteButton = $('
  • '); + $deleteButton.data('commentID', commentID).appendTo(comment.find('ul.commentOptions:eq(0)')).click($.proxy(this._delete, this)); + } + }, + + /** + * Initializes available responses. + */ + _initResponses: function() { + var self = this; + this._container.find('.jsCommentResponse').each(function(index, response) { + var $response = $(response).removeClass('jsCommentResponse'); + var $responseID = $response.data('responseID'); + self._responses[$responseID] = $response; + + self._initResponse($responseID, $response); + }); + }, + + /** + * Initializes a specific response. + * + * @param integer responseID + * @param jQuery response + */ + _initResponse: function(responseID, response) { + if (response.data('canEdit')) { + var $editButton = $('
  • '); + + var self = this; + $editButton.data('responseID', responseID).appendTo(response.find('ul.commentOptions:eq(0)')).click(function(event) { self._prepareEdit(event, true); }); + } + + if (response.data('canDelete')) { + var $deleteButton = $('
  • '); + + var self = this; + $deleteButton.data('responseID', responseID).appendTo(response.find('ul.commentOptions:eq(0)')).click(function(event) { self._delete(event, true); }); + } + }, + + /** + * Initializes the UI components to add a comment. + */ + _initAddComment: function() { + // create UI + this._commentAdd = $('
  • ' + this._userAvatar + '
  • ').prependTo(this._container); + var $inputContainer = this._commentAdd.children('div'); + var $input = $('').appendTo($inputContainer); + $('' + WCF.Language.get('wcf.comment.description') + '').appendTo($inputContainer); + + $input.keyup($.proxy(this._keyUp, this)); + }, + + /** + * Initializes the UI elements to add a response. + * + * @param integer commentID + * @param jQuery comment + */ + _initAddResponse: function(commentID, comment) { + var $placeholder = $('').insertBefore(comment.find('ul.commentResponseList')); + $placeholder.data('commentID', commentID).click($.proxy(this._showAddResponse, this)); + + var $listItem = $('
    ' + this._userAvatar + '
    ').hide().insertAfter($placeholder); + var $inputContainer = $listItem.children('div'); + var $input = $('').data('commentID', commentID).appendTo($inputContainer); + $('' + WCF.Language.get('wcf.comment.description') + '').appendTo($inputContainer); + + var self = this; + $input.keyup(function(event) { self._keyUp(event, true); }).blur($.proxy(this._hideAddResponse, this)); + + comment.data('responsePlaceholder', $placeholder).data('responseInput', $listItem); + }, + + /** + * Prepares editing of a comment or response. + * + * @param object event + * @param boolean isResponse + */ + _prepareEdit: function(event, isResponse) { + var $button = $(event.currentTarget); + var $data = { + objectID: this._container.data('objectID'), + objectTypeID: this._container.data('objectTypeID') + }; + + if (isResponse === true) { + $data.responseID = $button.data('responseID'); + } + else { + $data.commentID = $button.data('commentID'); + } + + this._proxy.setOption('data', { + actionName: 'prepareEdit', + className: 'wcf\\data\\comment\\CommentAction', + parameters: { + data: $data + } + }); + this._proxy.sendRequest(); + }, + + /** + * Displays the UI elements to create a response. + * + * @param object event + */ + _showAddResponse: function(event) { + var $commentID = $(event.currentTarget).data('commentID'); + this._comments[$commentID].data('responsePlaceholder').hide(); + + var $responseInput = this._comments[$commentID].data('responseInput').show(); + $responseInput.find('input').focus(); + }, + + /** + * Hides the UI elements to create a response. + * + * @param object event + */ + _hideAddResponse: function(event) { + var $input = $(event.currentTarget); + if ($.trim($input.val()) !== '') { + return; + } + + // delay execution by 50ms + var self = this; + new WCF.PeriodicalExecuter(function(pe) { + pe.stop(); + + self._comments[$input.data('commentID')].data('responsePlaceholder').show(); + + var $responseInput = self._comments[$input.data('commentID')].data('responseInput'); + $responseInput.hide().find('input').val(''); + }, 50); + }, + + /** + * Handles the keyup event for comments and responses. + * + * @param object event + * @param boolean isResponse + */ + _keyUp: function(event, isResponse) { + // ignore every key except for [Enter] and [Esc] + if (event.which !== 13 && event.which !== 27) { + return; + } + + var $input = $(event.currentTarget); + + // cancel input + if (event.which === 27) { + $input.val('').trigger('blur', event); + return; + } + + var $value = $.trim($input.val()); + + // ignore empty comments + if ($value == '') { + return; + } + + var $actionName = 'addComment'; + var $data = { + message: $value, + objectID: this._container.data('objectID'), + objectTypeID: this._container.data('objectTypeID') + }; + if (isResponse === true) { + $actionName = 'addResponse'; + $data.commentID = $input.data('commentID'); + } + + this._proxy.setOption('data', { + actionName: $actionName, + className: 'wcf\\data\\comment\\CommentAction', + parameters: { + data: $data + } + }); + this._proxy.sendRequest(); + + // reset input + $input.val('').blur(); + }, + + /** + * Shows a confirmation message prior to comment or response deletion. + * + * @param object event + * @param boolean isResponse + */ + _delete: function(event, isResponse) { + WCF.System.Confirmation.show(WCF.Language.get('wcf.comment.delete.confirmMessage'), $.proxy(function(action) { + if (action === 'confirm') { + var $data = { + objectID: this._container.data('objectID'), + objectTypeID: this._container.data('objectTypeID') + }; + if (isResponse !== true) { + $data.commentID = $(event.currentTarget).data('commentID'); + } + else { + $data.responseID = $(event.currentTarget).data('responseID'); + } + + this._proxy.setOption('data', { + actionName: 'remove', + className: 'wcf\\data\\comment\\CommentAction', + parameters: { + data: $data + } + }); + this._proxy.sendRequest(); + } + }, this)); + }, + + /** + * Handles successful AJAX requests. + * + * @param object data + * @param string textStatus + * @param jQuery jqXHR + */ + _success: function(data, textStatus, jqXHR) { + switch (data.actionName) { + case 'addComment': + $(data.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn(); + break; + + case 'addResponse': + $(data.returnValues.template).prependTo(this._comments[data.returnValues.commentID].find('ul.commentResponseList')).wcfFadeIn(); + break; + + case 'edit': + this._update(data); + break; + + case 'loadComments': + this._insertComments(data); + break; + + case 'loadResponses': + this._insertResponses(data); + break; + + case 'prepareEdit': + this._edit(data); + break; + + case 'remove': + this._remove(data); + break; + } + + WCF.DOMNodeInsertedHandler.forceExecution(); + }, + + /** + * Inserts previously loaded comments. + * + * @param object data + */ + _insertComments: function(data) { + // insert comments + $(data.returnValues.template).insertBefore(this._loadNextComments); + + // update time of last comment + this._container.data('lastCommentTime', data.returnValues.lastCommentTime); + }, + + /** + * Inserts previously loaded responses. + * + * @param object data + */ + _insertResponses: function(data) { + var $comment = this._comments[data.returnValues.commentID]; + + // insert responses + $(data.returnValues.template).appendTo($comment.find('ul.commentResponseList')); + + // update time of last response + $comment.data('lastResponseTime', data.returnValues.lastResponseTime); + + // update button state to load next responses + this._handleLoadNextResponses(data.returnValues.commentID); + }, + + /** + * Removes a comment or response from list. + * + * @param object data + */ + _remove: function(data) { + if (data.returnValues.commentID) { + this._comments[data.returnValues.commentID].remove(); + delete this._comments[data.returnValues.commentID]; + } + else { + this._responses[data.returnValues.responseID].remove(); + delete this._responses[data.returnValues.responseID]; + } + }, + + /** + * Prepares editing of a comment or response. + * + * @param object data + */ + _edit: function(data) { + if (data.returnValues.commentID) { + var $content = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0)'); + } + else { + var $content = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0)'); + } + + // replace content with input field + $content.html($.proxy(function(index, oldHTML) { + var $input = $('' + WCF.Language.get('wcf.comment.description') + '').val(data.returnValues.message); + $input.data('__html', oldHTML).keyup($.proxy(this._saveEdit, this)); + + if (data.returnValues.commentID) { + $input.data('commentID', data.returnValues.commentID); + } + else { + $input.data('responseID', data.returnValues.responseID); + } + + return $input; + }, this)); + $content.children('input').focus(); + + // hide elements + $content.parent().find('.containerHeadline:eq(0)').hide(); + $content.parent().find('.buttonGroupNavigation:eq(0)').hide(); + }, + + /** + * Updates a comment or response. + * + * @param object data + */ + _update: function(data) { + if (data.returnValues.commentID) { + var $input = this._comments[data.returnValues.commentID].find('.commentContent:eq(0) .userMessage:eq(0) > input'); + } + else { + var $input = this._responses[data.returnValues.responseID].find('.commentContent:eq(0) .userMessage:eq(0) > input'); + } + + $input.data('__html', data.returnValues.message); + + this._cancelEdit($input); + }, + + /** + * Saves editing of a comment or response. + * + * @param object event + */ + _saveEdit: function(event) { + var $input = $(event.currentTarget); + + // abort with [Esc] + if (event.which === 27) { + this._cancelEdit($input); + return; + } + else if (event.which !== 13) { + // ignore everything except for [Enter] + return; + } + + var $message = $.trim($input.val()); + + // ignore empty message + if ($message === '') { + return; + } + + var $data = { + message: $message, + objectID: this._container.data('objectID'), + objectTypeID: this._container.data('objectTypeID') + }; + if ($input.data('commentID')) { + $data.commentID = $input.data('commentID'); + } + else { + $data.responseID = $input.data('responseID'); + } + + this._proxy.setOption('data', { + actionName: 'edit', + className: 'wcf\\data\\comment\\CommentAction', + parameters: { + data: $data + } + }); + this._proxy.sendRequest() + }, + + /** + * Cancels editing of a comment or response. + * + * @param jQuery input + */ + _cancelEdit: function(input) { + // restore elements + input.parent().prev('.containerHeadline:eq(0)').show(); + input.parent().next('.buttonGroupNavigation:eq(0)').show(); + + // restore HTML + input.parent().html(input.data('__html')); + } +}); + +/** + * Like support for comments + * + * @see WCF.Like + */ +WCF.Comment.Like = WCF.Like.extend({ + /** + * @see WCF.Like._getContainers() + */ + _getContainers: function() { + return $('.commentList > li.comment'); + }, + + /** + * @see WCF.Like._getObjectID() + */ + _getObjectID: function(containerID) { + return this._containers[containerID].data('commentID'); + }, + + /** + * @see WCF.Like._buildWidget() + */ + _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) { + this._containers[containerID].find('.containerHeadline:eq(0) > h3').append(badge); + + if (this._canLike) { + likeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)')); + dislikeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)')); + } + }, + + /** + * @see WCF.Like._getWidgetContainer() + */ + _getWidgetContainer: function(containerID) {}, + + /** + * @see WCF.Like._addWidget() + */ + _addWidget: function(containerID, widget) {} +}); + +/** + * Namespace for comment responses + */ +WCF.Comment.Response = { }; + +/** + * Like support for comments responses. + * + * @see WCF.Like + */ +WCF.Comment.Response.Like = WCF.Like.extend({ + /** + * @see WCF.Like._addWidget() + */ + _addWidget: function(containerID, widget) { }, + + /** + * @see WCF.Like._buildWidget() + */ + _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) { + this._containers[containerID].find('.containerHeadline:eq(0) > h3').append(badge); + + if (this._canLike) { + likeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)')); + dislikeButton.appendTo(this._containers[containerID].find('.commentOptions:eq(0)')); + } + }, + + /** + * @see WCF.Like._getContainers() + */ + _getContainers: function() { + return $('.commentResponseList > li.commentResponse'); + }, + + /** + * @see WCF.Like._getObjectID() + */ + _getObjectID: function(containerID) { + return this._containers[containerID].data('responseID'); + }, + + /** + * @see WCF.Like._getWidgetContainer() + */ + _getWidgetContainer: function(containerID) { } +}); \ No newline at end of file diff --git a/wcfsetup/install/files/js/WCF.Comment.min.js b/wcfsetup/install/files/js/WCF.Comment.min.js new file mode 100644 index 0000000000..af7456dc73 --- /dev/null +++ b/wcfsetup/install/files/js/WCF.Comment.min.js @@ -0,0 +1 @@ +WCF.Comment={};WCF.Comment.Handler=Class.extend({_commentAdd:null,_comments:{},_container:null,_containerID:"",_displayedComments:0,_loadNextComments:null,_loadNextResponses:{},_proxy:null,_responses:{},_userAvatar:"",init:function(a,b){this._commentAdd=null;this._comments={};this._containerID=a;this._displayedComments=0;this._loadNextComments=null;this._loadNextResponses={};this._responses={};this._userAvatar=b;this._container=$("#"+$.wcfEscapeID(this._containerID));if(!this._container.length){console.debug("[WCF.Comment.Handler] Unable to find container identified by '"+this._containerID+"'")}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});WCF.DOMNodeInsertedHandler.enable();this._initComments();this._initResponses();if(this._container.data("canAdd")){this._initAddComment()}WCF.DOMNodeInsertedHandler.disable();WCF.DOMNodeInsertedHandler.addCallback("WCF.Comment.Handler",$.proxy(this._domNodeInserted,this))},_handleLoadNextComments:function(){if(this._displayedComments").appendTo(this._container);this._loadNextComments.children("button").click($.proxy(this._loadComments,this))}this._loadNextComments.children("button").enable()}else{if(this._loadNextComments!==null){this._loadNextComments.hide()}}},_handleLoadNextResponses:function(a){var b=this._comments[a];b.data("displayedResponses",b.find("ul.commentResponseList > li").length);if(b.data("displayedResponses")
    ").insertAfter(b.find("ul.commentResponseList"));this._loadNextResponses[a].children("button").data("commentID",a).click($.proxy(this._loadResponses,this))}this._loadNextResponses[a].children("button").enable()}else{if(this._loadNextResponses[a]!==undefined){this._loadNextResponses[a].hide()}}},_loadComments:function(){this._loadNextComments.children("button").disable();this._proxy.setOption("data",{actionName:"loadComments",className:"wcf\\data\\comment\\CommentAction",parameters:{data:{objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID"),lastCommentTime:this._container.data("lastCommentTime")}}});this._proxy.sendRequest()},_loadResponses:function(b){var c=$(b.currentTarget).disable();var a=c.data("commentID");this._proxy.setOption("data",{actionName:"loadResponses",className:"wcf\\data\\comment\\response\\CommentResponseAction",parameters:{data:{commentID:a,lastResponseTime:this._comments[a].data("lastResponseTime")}}});this._proxy.sendRequest()},_domNodeInserted:function(){this._initComments();this._initResponses()},_initComments:function(){var a=this;var b=false;this._container.find(".jsComment").each(function(d,f){var e=$(f).removeClass("jsComment");var c=e.data("commentID");a._comments[c]=e;a._initComment(c,e);a._displayedComments++;b=true;a._handleLoadNextResponses(c)});if(b){this._handleLoadNextComments()}},_initComment:function(a,d){if(this._container.data("canAdd")){this._initAddResponse(a,d)}if(d.data("canEdit")){var b=$('
  • ");b.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._prepareEdit,this))}if(d.data("canDelete")){var c=$('
  • ");c.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._delete,this))}},_initResponses:function(){var a=this;this._container.find(".jsCommentResponse").each(function(d,c){var b=$(c).removeClass("jsCommentResponse");var e=b.data("responseID");a._responses[e]=b;a._initResponse(e,b)})},_initResponse:function(a,c){if(c.data("canEdit")){var d=$('
  • ");var b=this;d.data("responseID",a).appendTo(c.find("ul.commentOptions:eq(0)")).click(function(f){b._prepareEdit(f,true)})}if(c.data("canDelete")){var e=$('
  • ");var b=this;e.data("responseID",a).appendTo(c.find("ul.commentOptions:eq(0)")).click(function(f){b._delete(f,true)})}},_initAddComment:function(){this._commentAdd=$('
  • '+this._userAvatar+"
  • ").prependTo(this._container);var a=this._commentAdd.children("div");var b=$('').appendTo(a);$(""+WCF.Language.get("wcf.comment.description")+"").appendTo(a);b.keyup($.proxy(this._keyUp,this))},_initAddResponse:function(d,g){var c=$('").insertBefore(g.find("ul.commentResponseList"));c.data("commentID",d).click($.proxy(this._showAddResponse,this));var e=$('
    '+this._userAvatar+"
    ").hide().insertAfter(c);var a=e.children("div");var f=$('').data("commentID",d).appendTo(a);$(""+WCF.Language.get("wcf.comment.description")+"").appendTo(a);var b=this;f.keyup(function(h){b._keyUp(h,true)}).blur($.proxy(this._hideAddResponse,this));g.data("responsePlaceholder",c).data("responseInput",e)},_prepareEdit:function(c,a){var d=$(c.currentTarget);var b={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(a===true){b.responseID=d.data("responseID")}else{b.commentID=d.data("commentID")}this._proxy.setOption("data",{actionName:"prepareEdit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:b}});this._proxy.sendRequest()},_showAddResponse:function(b){var a=$(b.currentTarget).data("commentID");this._comments[a].data("responsePlaceholder").hide();var c=this._comments[a].data("responseInput").show();c.find("input").focus()},_hideAddResponse:function(b){var c=$(b.currentTarget);if($.trim(c.val())!==""){return}var a=this;new WCF.PeriodicalExecuter(function(d){d.stop();a._comments[c.data("commentID")].data("responsePlaceholder").show();var e=a._comments[c.data("commentID")].data("responseInput");e.hide().find("input").val("")},50)},_keyUp:function(e,b){if(e.which!==13&&e.which!==27){return}var f=$(e.currentTarget);if(e.which===27){f.val("").trigger("blur",e);return}var d=$.trim(f.val());if(d==""){return}var a="addComment";var c={message:d,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(b===true){a="addResponse";c.commentID=f.data("commentID")}this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\comment\\CommentAction",parameters:{data:c}});this._proxy.sendRequest();f.val("").blur()},_delete:function(b,a){WCF.System.Confirmation.show(WCF.Language.get("wcf.comment.delete.confirmMessage"),$.proxy(function(d){if(d==="confirm"){var c={objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(a!==true){c.commentID=$(b.currentTarget).data("commentID")}else{c.responseID=$(b.currentTarget).data("responseID")}this._proxy.setOption("data",{actionName:"remove",className:"wcf\\data\\comment\\CommentAction",parameters:{data:c}});this._proxy.sendRequest()}},this))},_success:function(b,c,a){switch(b.actionName){case"addComment":$(b.returnValues.template).insertAfter(this._commentAdd).wcfFadeIn();break;case"addResponse":$(b.returnValues.template).prependTo(this._comments[b.returnValues.commentID].find("ul.commentResponseList")).wcfFadeIn();break;case"edit":this._update(b);break;case"loadComments":this._insertComments(b);break;case"loadResponses":this._insertResponses(b);break;case"prepareEdit":this._edit(b);break;case"remove":this._remove(b);break}WCF.DOMNodeInsertedHandler.forceExecution()},_insertComments:function(a){$(a.returnValues.template).insertBefore(this._loadNextComments);this._container.data("lastCommentTime",a.returnValues.lastCommentTime)},_insertResponses:function(b){var a=this._comments[b.returnValues.commentID];$(b.returnValues.template).appendTo(a.find("ul.commentResponseList"));a.data("lastResponseTime",b.returnValues.lastResponseTime);this._handleLoadNextResponses(b.returnValues.commentID)},_remove:function(a){if(a.returnValues.commentID){this._comments[a.returnValues.commentID].remove();delete this._comments[a.returnValues.commentID]}else{this._responses[a.returnValues.responseID].remove();delete this._responses[a.returnValues.responseID]}},_edit:function(b){if(b.returnValues.commentID){var a=this._comments[b.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0)")}else{var a=this._responses[b.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0)")}a.html($.proxy(function(d,c){var e=$(''+WCF.Language.get("wcf.comment.description")+"").val(b.returnValues.message);e.data("__html",c).keyup($.proxy(this._saveEdit,this));if(b.returnValues.commentID){e.data("commentID",b.returnValues.commentID)}else{e.data("responseID",b.returnValues.responseID)}return e},this));a.children("input").focus();a.parent().find(".containerHeadline:eq(0)").hide();a.parent().find(".buttonGroupNavigation:eq(0)").hide()},_update:function(a){if(a.returnValues.commentID){var b=this._comments[a.returnValues.commentID].find(".commentContent:eq(0) .userMessage:eq(0) > input")}else{var b=this._responses[a.returnValues.responseID].find(".commentContent:eq(0) .userMessage:eq(0) > input")}b.data("__html",a.returnValues.message);this._cancelEdit(b)},_saveEdit:function(c){var d=$(c.currentTarget);if(c.which===27){this._cancelEdit(d);return}else{if(c.which!==13){return}}var b=$.trim(d.val());if(b===""){return}var a={message:b,objectID:this._container.data("objectID"),objectTypeID:this._container.data("objectTypeID")};if(d.data("commentID")){a.commentID=d.data("commentID")}else{a.responseID=d.data("responseID")}this._proxy.setOption("data",{actionName:"edit",className:"wcf\\data\\comment\\CommentAction",parameters:{data:a}});this._proxy.sendRequest()},_cancelEdit:function(a){a.parent().prev(".containerHeadline:eq(0)").show();a.parent().next(".buttonGroupNavigation:eq(0)").show();a.parent().html(a.data("__html"))}});WCF.Comment.Like=WCF.Like.extend({_getContainers:function(){return $(".commentList > li.comment")},_getObjectID:function(a){return this._containers[a].data("commentID")},_buildWidget:function(b,a,d,c,e){this._containers[b].find(".containerHeadline:eq(0) > h3").append(c);if(this._canLike){a.appendTo(this._containers[b].find(".commentOptions:eq(0)"));d.appendTo(this._containers[b].find(".commentOptions:eq(0)"))}},_getWidgetContainer:function(a){},_addWidget:function(a,b){}});WCF.Comment.Response={};WCF.Comment.Response.Like=WCF.Like.extend({_addWidget:function(a,b){},_buildWidget:function(b,a,d,c,e){this._containers[b].find(".containerHeadline:eq(0) > h3").append(c);if(this._canLike){a.appendTo(this._containers[b].find(".commentOptions:eq(0)"));d.appendTo(this._containers[b].find(".commentOptions:eq(0)"))}},_getContainers:function(){return $(".commentResponseList > li.commentResponse")},_getObjectID:function(a){return this._containers[a].data("responseID")},_getWidgetContainer:function(a){}}); \ No newline at end of file diff --git a/wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php b/wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php index c7fb332530..311717d65b 100644 --- a/wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php @@ -1,5 +1,6 @@ add("userID IN (?)", array($this->mergedUserIDs)); + $sql = "UPDATE wcf".WCF_N."_comment + SET userID = ? + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters())); + + // comment_response + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("userID IN (?)", array($this->mergedUserIDs)); + $sql = "UPDATE wcf".WCF_N."_comment_response + SET userID = ? + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters())); + + // profile comments + $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.comment.commentableContent', 'com.woltlab.wcf.user.profileComment'); + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("objectTypeID = ?", array($objectType->objectTypeID)); + $conditions->add("objectID IN (?)", array($this->mergedUserIDs)); + $sql = "UPDATE wcf".WCF_N."_comment + SET objectID = ? + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters())); + // like (userID) $conditions = new PreparedStatementConditionBuilder(); $conditions->add("userID IN (?)", array($this->mergedUserIDs)); diff --git a/wcfsetup/install/files/lib/data/comment/Comment.class.php b/wcfsetup/install/files/lib/data/comment/Comment.class.php new file mode 100644 index 0000000000..73f122b486 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/Comment.class.php @@ -0,0 +1,117 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class Comment extends DatabaseObject implements IMessage { + /** + * @see wcf\data\DatabaseObject::$databaseTableName + */ + protected static $databaseTableName = 'comment'; + + /** + * @see wcf\data\DatabaseObject::$databaseTableIndexName + */ + protected static $databaseTableIndexName = 'commentID'; + + /** + * Returns a list of last response ids. + * + * @return array + */ + public function getLastResponseIDs() { + if ($this->lastResponseIDs === null || $this->lastResponseIDs == '') { + return array(); + } + + $lastResponseIDs = @unserialize($this->lastResponseIDs); + if ($lastResponseIDs === false) { + return array(); + } + + return $lastResponseIDs; + } + + /** + * @see wcf\data\IMessage::getFormattedMessage() + */ + public function getFormattedMessage() { + return SimpleMessageParser::getInstance()->parse($this->message); + } + + /** + * @see wcf\data\IMessage::getExcerpt() + */ + public function getExcerpt($maxLength = 255) { + return StringUtil::truncateHTML($this->getFormattedMessage(), $maxLength); + } + + /** + * @see wcf\data\IMessage::getMessage() + */ + public function getMessage() { + return $this->message; + } + + /** + * @see wcf\data\IUserContent::getTime() + */ + public function getTime() { + return $this->time; + } + + /** + * @see wcf\data\IUserContent::getUserID() + */ + public function getUserID() { + return $this->userID; + } + + /** + * @see wcf\data\IUserContent::getUsername() + */ + public function getUsername() { + return $this->username; + } + + /** + * @see wcf\data\ILinkableObject::getLink() + */ + public function getLink() { + return CommentHandler::getInstance()->getObjectType($this->objectTypeID)->getProcessor()->getLink($this->objectTypeID, $this->objectID); + } + + /** + * @see wcf\data\ITitledObject::getTitle() + */ + public function getTitle() { + return CommentHandler::getInstance()->getObjectType($this->objectTypeID)->getProcessor()->getTitle($this->objectTypeID, $this->objectID); + } + + /** + * @see wcf\data\IMessage::isVisible() + */ + public function isVisible() { + return true; + } + + /** + * @see wcf\data\IMessage::__toString() + */ + public function __toString() { + return $this->getFormattedMessage(); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/CommentAction.class.php b/wcfsetup/install/files/lib/data/comment/CommentAction.class.php new file mode 100644 index 0000000000..c758a1e153 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/CommentAction.class.php @@ -0,0 +1,576 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class CommentAction extends AbstractDatabaseObjectAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess + */ + protected $allowGuestAccess = array('loadComments'); + + /** + * @see wcf\data\AbstractDatabaseObjectAction::$className + */ + protected $className = 'wcf\data\comment\CommentEditor'; + + /** + * comment object + * @var wcf\data\comment\Comment + */ + protected $comment = null; + + /** + * comment processor + * @var wcf\system\comment\manager\ICommentManager + */ + protected $commentProcessor = null; + + /** + * response object + * @var wcf\data\comment\response\CommentResponse + */ + protected $response = null; + + /** + * @see wcf\data\AbstractDatabaseObjectAction::delete() + */ + public function delete() { + if (empty($this->objects)) { + $this->readObjects(); + } + + // update counters + $processors = array(); + $groupCommentIDs = $commentIDs = array(); + foreach ($this->objects as $comment) { + if (!isset($processors[$comment->objectTypeID])) { + $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID); + $processors[$comment->objectTypeID] = $objectType->getProcessor(); + + $groupCommentIDs[$comment->objectTypeID] = array(); + } + + $processors[$comment->objectTypeID]->updateCounter($comment->objectID, -1 * ($comment->responses + 1)); + $groupCommentIDs[$comment->objectTypeID][] = $comment->commentID; + $commentIDs[] = $comment->commentID; + } + + if (!empty($groupCommentIDs)) { + $likeObjectIDs = array(); + foreach ($groupCommentIDs as $objectTypeID => $objectIDs) { + // remove activity events + $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID); + if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->removeEvents($objectType->objectType.'.recentActivityEvent', $objectIDs); + } + + $likeObjectIDs = array_merge($likeObjectIDs, $objectIDs); + + // delete notifications + $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID); + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) { + UserNotificationHandler::getInstance()->deleteNotifications('comment', $objectType->objectType.'.notification', array(), $objectIDs); + } + } + + // remove likes + LikeHandler::getInstance()->removeLikes('com.woltlab.wcf.comment', $likeObjectIDs); + } + + // delete responses + if (!empty($commentIDs)) { + $commentResponseList = new CommentResponseList(); + $commentResponseList->getConditionBuilder()->add('comment_response.commentID IN (?)', array($commentIDs)); + $commentResponseList->readObjectIDs(); + if (count($commentResponseList->getObjectIDs())) { + $action = new CommentResponseAction($commentResponseList->getObjectIDs(), 'delete'); + $action->executeAction(); + } + } + + return parent::delete(); + } + + /** + * Validates parameters to load comments. + */ + public function validateLoadComments() { + $this->readInteger('lastCommentTime', false, 'data'); + $this->readInteger('objectID', false, 'data'); + + $objectType = $this->validateObjectType(); + $this->commentProcessor = $objectType->getProcessor(); + if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) { + throw new PermissionDeniedException(); + } + } + + /** + * Returns parsed comments. + * + * @return array + */ + public function loadComments() { + $commentList = CommentHandler::getInstance()->getCommentList($this->commentProcessor, $this->parameters['data']['objectTypeID'], $this->parameters['data']['objectID'], false); + $commentList->getConditionBuilder()->add("comment.time < ?", array($this->parameters['data']['lastCommentTime'])); + $commentList->readObjects(); + + WCF::getTPL()->assign(array( + 'commentList' => $commentList, + 'likeData' => (MODULE_LIKE ? $commentList->getLikeData() : array()) + )); + + return array( + 'lastCommentTime' => $commentList->getMinCommentTime(), + 'template' => WCF::getTPL()->fetch('commentList') + ); + } + + /** + * Validates parameters to add a comment. + */ + public function validateAddComment() { + $this->readInteger('objectID', false, 'data'); + $this->validateMessage(); + $objectType = $this->validateObjectType(); + + // validate object id and permissions + $this->commentProcessor = $objectType->getProcessor(); + if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) { + throw new PermissionDeniedException(); + } + } + + /** + * Adds a comment. + * + * @return array + */ + public function addComment() { + // create comment + $comment = CommentEditor::create(array( + 'objectTypeID' => $this->parameters['data']['objectTypeID'], + 'objectID' => $this->parameters['data']['objectID'], + 'time' => TIME_NOW, + 'userID' => WCF::getUser()->userID, + 'username' => WCF::getUser()->username, + 'message' => $this->parameters['data']['message'], + 'responses' => 0, + 'lastResponseIDs' => serialize(array()) + )); + + // update counter + $this->commentProcessor->updateCounter($this->parameters['data']['objectID'], 1); + + // fire activity event + $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['data']['objectTypeID']); + if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.recentActivityEvent', $comment->commentID); + } + + // fire notification event + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) { + $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification'); + $userID = $notificationObjectType->getOwnerID($comment->commentID); + if ($userID != WCF::getUser()->userID) { + $notificationObject = new CommentUserNotificationObject($comment); + + UserNotificationHandler::getInstance()->fireEvent('comment', $objectType->objectType.'.notification', $notificationObject, array($userID)); + } + } + + return array( + 'template' => $this->renderComment($comment) + ); + } + + /** + * Validates parameters to add a response. + */ + public function validateAddResponse() { + $this->readInteger('objectID', false, 'data'); + + // validate comment id + $this->validateCommentID(); + + // validate object type id + $objectType = $this->validateObjectType(); + + // validate object id and permissions + $this->commentProcessor = $objectType->getProcessor(); + if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) { + throw new PermissionDeniedException(); + } + } + + /** + * Adds a response. + * + * @return array + */ + public function addResponse() { + // create response + $response = CommentResponseEditor::create(array( + 'commentID' => $this->comment->commentID, + 'time' => TIME_NOW, + 'userID' => WCF::getUser()->userID, + 'username' => WCF::getUser()->username, + 'message' => $this->parameters['data']['message'] + )); + + // update response data + $lastResponseIDs = $this->comment->getLastResponseIDs(); + if (count($lastResponseIDs) == 3) array_shift($lastResponseIDs); + $lastResponseIDs[] = $response->responseID; + $responses = $this->comment->responses + 1; + + // update comment + $commentEditor = new CommentEditor($this->comment); + $commentEditor->update(array( + 'lastResponseIDs' => serialize($lastResponseIDs), + 'responses' => $responses + )); + + // update counter + $this->commentProcessor->updateCounter($this->parameters['data']['objectID'], 1); + + // fire activity event + $objectType = ObjectTypeCache::getInstance()->getObjectType($this->comment->objectTypeID); + if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.response.recentActivityEvent', $response->responseID); + } + + // fire notification event + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification')) { + $notificationObject = new CommentResponseUserNotificationObject($response); + if ($this->comment->userID != WCF::getUser()->userID) { + UserNotificationHandler::getInstance()->fireEvent('commentResponse', $objectType->objectType.'.response.notification', $notificationObject, array($this->comment->userID)); + } + + // notify the container owner + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) { + $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification'); + $userID = $notificationObjectType->getOwnerID($this->comment->commentID); + + if ($userID != $this->comment->userID && $userID != WCF::getUser()->userID) { + UserNotificationHandler::getInstance()->fireEvent('commentResponseOwner', $objectType->objectType.'.response.notification', $notificationObject, array($userID)); + } + } + } + + return array( + 'commentID' => $this->comment->commentID, + 'template' => $this->renderResponse($response), + 'responses' => $responses + ); + } + + /** + * Validates parameters to edit a comment or a response. + */ + public function validatePrepareEdit() { + // validate comment id or response id + try { + $this->validateCommentID(); + } + catch (UserInputException $e) { + try { + $this->validateResponseID(); + } + catch (UserInputException $e) { + throw new UserInputException('objectIDs'); + } + } + + // validate object type id + $objectType = $this->validateObjectType(); + + // validate object id and permissions + $this->commentProcessor = $objectType->getProcessor(); + if ($this->comment !== null) { + if (!$this->commentProcessor->canEditComment($this->comment)) { + throw new PermissionDeniedException(); + } + } + else { + if (!$this->commentProcessor->canEditResponse($this->response)) { + throw new PermissionDeniedException(); + } + } + } + + /** + * Prepares editing of a comment or a response. + * + * @return array + */ + public function prepareEdit() { + $message = ''; + if ($this->comment !== null) { + $message = $this->comment->message; + } + else { + $message = $this->response->message; + } + + $returnValues = array( + 'action' => 'prepare', + 'message' => $message + ); + if ($this->comment !== null) { + $returnValues['commentID'] = $this->comment->commentID; + } + else { + $returnValues['responseID'] = $this->response->responseID; + } + + return $returnValues; + } + + /** + * @see wcf\data\comment\CommentAction::validatePrepareEdit() + */ + public function validateEdit() { + $this->validatePrepareEdit(); + + $this->validateMessage(); + } + + /** + * Edits a comment or response. + * + * @return array + */ + public function edit() { + $returnValues = array( + 'action' => 'saved', + ); + + if ($this->response === null) { + $editor = new CommentEditor($this->comment); + $editor->update(array( + 'message' => $this->parameters['data']['message'] + )); + $comment = new Comment($this->comment->commentID); + $returnValues['commentID'] = $this->comment->commentID; + $returnValues['message'] = $comment->getFormattedMessage(); + } + else { + $editor = new CommentResponseEditor($this->response); + $editor->update(array( + 'message' => $this->parameters['data']['message'] + )); + $response = new CommentResponse($this->response->responseID); + $returnValues['responseID'] = $this->response->responseID; + $returnValues['message'] = $response->getFormattedMessage(); + } + + return $returnValues; + } + + /** + * Validates parameters to remove a comment or response. + */ + public function validateRemove() { + // validate comment id or response id + try { + $this->validateCommentID(); + } + catch (UserInputException $e) { + try { + $this->validateResponseID(); + } + catch (UserInputException $e) { + throw new UserInputException('objectIDs'); + } + } + + // validate object type id + $objectType = $this->validateObjectType(); + + // validate object id and permissions + $this->commentProcessor = $objectType->getProcessor(); + if ($this->comment !== null) { + if (!$this->commentProcessor->canDeleteComment($this->comment)) { + throw new PermissionDeniedException(); + } + } + else { + if (!$this->commentProcessor->canDeleteResponse($this->response)) { + throw new PermissionDeniedException(); + } + } + } + + /** + * Removes a comment or response. + * + * @return array + */ + public function remove() { + if ($this->comment !== null) { + $objectAction = new CommentAction(array($this->comment), 'delete'); + $objectAction->executeAction(); + + return array( + 'commentID' => $this->comment->commentID + ); + } + else { + $objectAction = new CommentResponseAction(array($this->response), 'delete'); + $objectAction->executeAction(); + + return array( + 'responseID' => $this->response->responseID + ); + } + } + + /** + * Renders a comment. + * + * @param wcf\data\comment\Comment $comment + * @return string + */ + protected function renderComment(Comment $comment) { + $comment = new StructuredComment($comment); + $comment->setIsDeletable($this->commentProcessor->canDeleteComment($comment->getDecoratedObject())); + $comment->setIsEditable($this->commentProcessor->canEditComment($comment->getDecoratedObject())); + + // set user profile + $userProfile = UserProfile::getUserProfile($comment->userID); + $comment->setUserProfile($userProfile); + + WCF::getTPL()->assign(array( + 'commentList' => array($comment) + )); + return WCF::getTPL()->fetch('commentList'); + } + + /** + * Renders a response. + * + * @param wcf\data\comment\response\CommentResponse $response + * @return string + */ + protected function renderResponse(CommentResponse $response) { + $response = new StructuredCommentResponse($response); + $response->setIsDeletable($this->commentProcessor->canDeleteResponse($response->getDecoratedObject())); + $response->setIsEditable($this->commentProcessor->canEditResponse($response->getDecoratedObject())); + + // set user profile + $userProfile = UserProfile::getUserProfile($response->userID); + $response->setUserProfile($userProfile); + + // render response + WCF::getTPL()->assign(array( + 'responseList' => array($response) + )); + return WCF::getTPL()->fetch('commentResponseList'); + } + + /** + * Validates message parameter. + */ + protected function validateMessage() { + $this->readString('message', false, 'data'); + + if (empty($this->parameters['data']['message'])) { + throw new UserInputException('message'); + } + } + + /** + * Validates object type id parameter. + * + * @return wcf\data\object\type\ObjectType + */ + protected function validateObjectType() { + $this->readInteger('objectTypeID', false, 'data'); + + $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['data']['objectTypeID']); + if ($objectType === null) { + throw new UserInputException('objectTypeID'); + } + + return $objectType; + } + + /** + * Validates comment id parameter. + */ + protected function validateCommentID() { + $this->readInteger('commentID', false, 'data'); + + $this->comment = new Comment($this->parameters['data']['commentID']); + if ($this->comment === null || !$this->comment->commentID) { + throw new UserInputException('commentID'); + } + } + + /** + * Validates response id parameter. + */ + protected function validateResponseID() { + if (isset($this->parameters['data']['responseID'])) { + $this->response = new CommentResponse($this->parameters['data']['responseID']); + } + if ($this->response === null || !$this->response->responseID) { + throw new UserInputException('responseID'); + } + } + + /** + * Returns the comment object. + * + * @return wcf\data\comment\Comment + */ + public function getComment() { + return $this->comment; + } + + /** + * Returns the comment response object. + * + * @return wcf\data\comment\response\CommentResponse + */ + public function getResponse() { + return $this->response; + } + + /** + * Returns the comment manager. + * + * @return wcf\system\comment\manager\ICommentManager + */ + public function getCommentManager() { + return $this->commentProcessor; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/CommentEditor.class.php b/wcfsetup/install/files/lib/data/comment/CommentEditor.class.php new file mode 100644 index 0000000000..4000a937e7 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/CommentEditor.class.php @@ -0,0 +1,41 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class CommentEditor extends DatabaseObjectEditor { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\Comment'; + + /** + * Updates last response ids. + */ + public function updateLastResponseIDs() { + $sql = "SELECT responseID + FROM wcf".WCF_N."_comment_response + WHERE commentID = ? + ORDER BY time DESC"; + $statement = WCF::getDB()->prepareStatement($sql, 3); + $statement->execute(array($this->commentID)); + $responseIDs = array(); + while ($row = $statement->fetchArray()) { + $responseIDs[] = $row['responseID']; + } + + $this->update(array( + 'lastResponseIDs' => serialize($responseIDs) + )); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/CommentList.class.php b/wcfsetup/install/files/lib/data/comment/CommentList.class.php new file mode 100644 index 0000000000..b454f2619c --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/CommentList.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class CommentList extends DatabaseObjectList { + /** + * @see wcf\data\DatabaseObjectList::$className + */ + public $className = 'wcf\data\comment\Comment'; +} diff --git a/wcfsetup/install/files/lib/data/comment/LikeableComment.class.php b/wcfsetup/install/files/lib/data/comment/LikeableComment.class.php new file mode 100644 index 0000000000..386849b270 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/LikeableComment.class.php @@ -0,0 +1,53 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class LikeableComment extends AbstractLikeObject { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\Comment'; + + /** + * @see wcf\data\like\object\ILikeObject::getTitle() + */ + public function getTitle() { + return $this->message; + } + + /** + * @see wcf\data\like\object\ILikeObject::getURL() + */ + public function getURL() { + return $this->getLink(); + } + + /** + * @see wcf\data\like\object\ILikeObject::getUserID() + */ + public function getUserID() { + return $this->userID; + } + + /** + * @see wcf\data\like\object\ILikeObject::getObjectType() + */ + public function getObjectType() { + if ($this->objectType === null) { + $this->objectType = ObjectTypeCache::getInstance()->getObjectType($this->getDecoratedObject()->objectTypeID); + } + + return $this->objectType; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/LikeableCommentProvider.class.php b/wcfsetup/install/files/lib/data/comment/LikeableCommentProvider.class.php new file mode 100644 index 0000000000..b5ec9d3435 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/LikeableCommentProvider.class.php @@ -0,0 +1,41 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class LikeableCommentProvider extends AbstractObjectTypeProvider implements ILikeObjectTypeProvider { + /** + * @see wcf\data\object\type\AbstractObjectTypeProvider::$className + */ + public $className = 'wcf\data\comment\Comment'; + + /** + * @see wcf\data\object\type\AbstractObjectTypeProvider::$decoratorClassName + */ + public $decoratorClassName = 'wcf\data\comment\LikeableComment'; + + /** + * @see wcf\data\object\type\AbstractObjectTypeProvider::$listClassName + */ + public $listClassName = 'wcf\data\comment\CommentList'; + + /** + * @see wcf\data\like\ILikeObjectTypeProvider::checkPermissions() + */ + public function checkPermissions(ILikeObject $comment) { + $objectType = CommentHandler::getInstance()->getObjectType($comment->objectTypeID); + return CommentHandler::getInstance()->getCommentManager($objectType->objectType)->isAccessible($comment->objectID); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/StructuredComment.class.php b/wcfsetup/install/files/lib/data/comment/StructuredComment.class.php new file mode 100644 index 0000000000..5a13c6d35b --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/StructuredComment.class.php @@ -0,0 +1,184 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class StructuredComment extends DatabaseObjectDecorator implements \Countable, \Iterator { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + public static $baseClass = 'wcf\data\comment\Comment'; + + /** + * list of ordered responses + * @var array + */ + protected $responses = array(); + + /** + * deletable by current user + * @var boolean + */ + public $deletable = false; + + /** + * editable by current user + * @var boolean + */ + public $editable = false; + + /** + * iterator index + * @var integer + */ + private $position = 0; + + /** + * user profile object + * @var wcf\data\user\UserProfile + */ + public $userProfile = null; + + /** + * Adds an response + * + * @param wcf\data\comment\response\StructuredCommentResponse $response + */ + public function addResponse(StructuredCommentResponse $response) { + $this->responses[] = $response; + } + + /** + * Returns the last responses for this comment. + * + * @return array + */ + public function getResponses() { + return $this->responses; + } + + /** + * Returns timestamp of oldest response loaded. + * + * @return integer + */ + public function getLastResponseTime() { + $lastResponseTime = 0; + foreach ($this->responses as $response) { + if (!$lastResponseTime) { + $lastResponseTime = $response->time; + } + + $lastResponseTime = min($lastResponseTime, $response->time); + } + + return $lastResponseTime; + } + + /** + * Sets the user's profile. + * + * @param wcf\data\user\UserProfile $userProfile + */ + public function setUserProfile(UserProfile $userProfile) { + $this->userProfile = $userProfile; + } + + /** + * Returns the user's profile. + * + * @return wcf\data\user\UserProfile + */ + public function getUserProfile() { + return $this->userProfile; + } + + /** + * Sets deletable state. + * + * @param boolean $deletable + */ + public function setIsDeletable($deletable) { + $this->deletable = $deletable; + } + + /** + * Sets editable state. + * + * @param boolean $editable + */ + public function setIsEditable($editable) { + $this->editable = $editable; + } + + /** + * Returns true if the comment is deletable by current user. + * + * @return boolean + */ + public function isDeletable() { + return $this->deletable; + } + + /** + * Returns true if the comment is editable by current user. + * + * @return boolean + */ + public function isEditable() { + return $this->editable; + } + + /** + * @see \Countable::count() + */ + public function count() { + return count($this->responses); + } + + /** + * @see \Iterator::current() + */ + public function current() { + return $this->responses[$this->position]; + } + + /** + * @see \Iterator::key() + */ + public function key() { + return $this->postition; + } + + /** + * @see \Iterator::next() + */ + public function next() { + $this->position++; + } + + /** + * @see \Iterator::rewind() + */ + public function rewind() { + $this->position = 0; + } + + /** + * @see \Iterator::valid() + */ + public function valid() { + return isset($this->responses[$this->position]); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php b/wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php new file mode 100644 index 0000000000..de6b99d6e7 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php @@ -0,0 +1,176 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class StructuredCommentList extends CommentList { + /** + * comment manager object + * @var wcf\system\comment\manager\ICommentManager + */ + public $commentManager = null; + + /** + * minimum comment time + * @var integer + */ + public $minCommentTime = 0; + + /** + * object type id + * @var integer + */ + public $objectTypeID = 0; + + /** + * object id + * @var integer + */ + public $objectID = 0; + + /** + * ids of the responses of the comments in the list + * @var array + */ + public $responseIDs = array(); + + /** + * @see wcf\data\DatabaseObjectList::$sqlLimit + */ + public $sqlLimit = 10; + + /** + * @see wcf\data\DatabaseObjectList::$sqlOrderBy + */ + public $sqlOrderBy = 'comment.time DESC'; + + /** + * Creates a new structured comment list. + * + * @param wcf\system\comment\manager\ICommentManager $commentManager + * @param integer $objectTypeID + * @param integer $objectID + */ + public function __construct(ICommentManager $commentManager, $objectTypeID, $objectID) { + parent::__construct(); + + $this->commentManager = $commentManager; + $this->objectTypeID = $objectTypeID; + $this->objectID = $objectID; + + $this->getConditionBuilder()->add("comment.objectTypeID = ?", array($objectTypeID)); + $this->getConditionBuilder()->add("comment.objectID = ?", array($objectID)); + $this->sqlLimit = $this->commentManager->getCommentsPerPage(); + } + + /** + * @see wcf\data\DatabaseObjectList::readObjects() + */ + public function readObjects() { + parent::readObjects(); + + // fetch last response ids + $responseIDs = array(); + $userIDs = array(); + foreach ($this->objects as &$comment) { + if (!$this->minCommentTime || $comment->time < $this->minCommentTime) $this->minCommentTime = $comment->time; + $lastResponseIDs = $comment->getLastResponseIDs(); + if (!empty($lastResponseIDs)) { + foreach ($lastResponseIDs as $responseID) { + $this->responseIDs[] = $responseID; + $responseIDs[$responseID] = $comment->commentID; + } + } + $userIDs[] = $comment->userID; + + $comment = new StructuredComment($comment); + $comment->setIsDeletable($this->commentManager->canDeleteComment($comment->getDecoratedObject())); + $comment->setIsEditable($this->commentManager->canEditComment($comment->getDecoratedObject())); + } + unset($comment); + + // fetch last responses + if (!empty($responseIDs)) { + // invert sort order (maintains order within StructuredComment's response array) + $sqlOrder = (strpos($this->sqlOrderBy, 'ASC') === false) ? 'DESC' : 'ASC'; + + $responseList = new CommentResponseList(); + $responseList->getConditionBuilder()->add("comment_response.responseID IN (?)", array(array_keys($responseIDs))); + $responseList->sqlOrderBy = "comment_response.time ".$sqlOrder; + $responseList->readObjects(); + + foreach ($responseList as $response) { + $response = new StructuredCommentResponse($response); + $response->setIsDeletable($this->commentManager->canDeleteResponse($response->getDecoratedObject())); + $response->setIsEditable($this->commentManager->canEditResponse($response->getDecoratedObject())); + + $commentID = $responseIDs[$response->responseID]; + $this->objects[$commentID]->addResponse($response); + + $userIDs[] = $response->userID; + } + } + + // fetch user data and avatars + if (!empty($userIDs)) { + $userIDs = array_unique($userIDs); + + $users = UserProfile::getUserProfiles($userIDs); + foreach ($this->objects as $comment) { + if (isset($users[$comment->userID])) { + $comment->setUserProfile($users[$comment->userID]); + } + + foreach ($comment as $response) { + if (isset($users[$response->userID])) { + $response->setUserProfile($users[$response->userID]); + } + } + } + } + } + + /** + * Fetches the like data. + * + * @return array + */ + public function getLikeData() { + if (empty($this->objectIDs)) return array(); + + $likeData = array(); + $commentObjectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.comment'); + LikeHandler::getInstance()->loadLikeObjects($commentObjectType, $this->getObjectIDs()); + $likeData['comment'] = LikeHandler::getInstance()->getLikeObjects($commentObjectType); + + if (!empty($this->responseIDs)) { + $responseObjectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.comment.response'); + LikeHandler::getInstance()->loadLikeObjects($responseObjectType, $this->responseIDs); + $likeData['response'] = LikeHandler::getInstance()->getLikeObjects($responseObjectType); + } + + return $likeData; + } + + /** + * Returns minimum comment time. + * + * @return integer + */ + public function getMinCommentTime() { + return $this->minCommentTime; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/ViewableComment.class.php b/wcfsetup/install/files/lib/data/comment/ViewableComment.class.php new file mode 100644 index 0000000000..ca0f34106d --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/ViewableComment.class.php @@ -0,0 +1,41 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment + * @category Community Framework + */ +class ViewableComment extends DatabaseObjectDecorator { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\Comment'; + + /** + * user profile object + * @var wcf\data\user\UserProfile + */ + protected $userProfile = null; + + /** + * Returns the user profile object. + * + * @return wcf\data\user\UserProfile + */ + public function getUserProfile() { + if ($this->userProfile === null) { + $this->userProfile = new UserProfile(new User(null, $this->getDecoratedObject()->data)); + } + + return $this->userProfile; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php b/wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php new file mode 100644 index 0000000000..53ddc9645c --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php @@ -0,0 +1,130 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class CommentResponse extends DatabaseObject implements IMessage { + /** + * @see wcf\data\DatabaseObject::$databaseTableName + */ + protected static $databaseTableName = 'comment_response'; + + /** + * @see wcf\data\DatabaseObject::$databaseTableIndexName + */ + protected static $databaseTableIndexName = 'responseID'; + + /** + * comment object + * @var wcf\data\comment\Comment + */ + protected $comment = null; + + /** + * @see wcf\data\IMessage::getFormattedMessage() + */ + public function getFormattedMessage() { + return SimpleMessageParser::getInstance()->parse($this->message); + } + + /** + * Returns comment object related to this response. + * + * @return wcf\data\comment\Comment + */ + public function getComment() { + if ($this->comment === null) { + $this->comment = new Comment($this->commentID); + } + + return $this->comment; + } + + /** + * Sets related comment object. + * + * @param wcf\data\comment\Comment + */ + public function setComment(Comment $comment) { + if ($this->commentID == $comment->commentID) { + $this->comment = $comment; + } + } + + /** + * @see wcf\data\IMessage::getExcerpt() + */ + public function getExcerpt($maxLength = 255) { + return StringUtil::truncateHTML($this->getFormattedMessage(), $maxLength); + } + + /** + * @see wcf\data\IMessage::getMessage() + */ + public function getMessage() { + return $this->message; + } + + /** + * @see wcf\data\IUserContent::getTime() + */ + public function getTime() { + return $this->time; + } + + /** + * @see wcf\data\IUserContent::getUserID() + */ + public function getUserID() { + return $this->userID; + } + + /** + * @see wcf\data\IUserContent::getUsername() + */ + public function getUsername() { + return $this->username; + } + + /** + * @see wcf\data\ILinkableObject::getLink() + */ + public function getLink() { + return CommentHandler::getInstance()->getObjectType($this->getComment()->objectTypeID)->getProcessor()->getLink($this->getComment()->objectTypeID, $this->getComment()->objectID); + } + + /** + * @see wcf\data\ITitledObject::getTitle() + */ + public function getTitle() { + return CommentHandler::getInstance()->getObjectType($this->getComment()->objectTypeID)->getProcessor()->getTitle($this->getComment()->objectTypeID, $this->getComment()->objectID, true); + } + + /** + * @see wcf\data\IMessage::isVisible() + */ + public function isVisible() { + return true; + } + + /** + * @see wcf\data\IMessage::__toString() + */ + public function __toString() { + return $this->getFormattedMessage(); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php b/wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php new file mode 100644 index 0000000000..8795aee5c1 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php @@ -0,0 +1,170 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class CommentResponseAction extends AbstractDatabaseObjectAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess + */ + protected $allowGuestAccess = array('loadResponses'); + + /** + * @see wcf\data\AbstractDatabaseObjectAction::$className + */ + protected $className = 'wcf\data\comment\response\CommentResponseEditor'; + + /** + * comment object + * @var wcf\data\comment\Comment + */ + public $comment = null; + + /** + * comment manager object + * @var wcf\system\comment\manager\ICommentManager + */ + public $commentManager = null; + + /** + * @see wcf\data\AbstractDatabaseObjectAction::delete() + */ + public function delete() { + if (empty($this->objects)) { + $this->readObjects(); + } + + if (empty($this->objects)) { + return 0; + } + + // read object type ids for comments + $commentIDs = array(); + foreach ($this->objects as $response) { + $commentIDs[] = $response->commentID; + } + + $commentList = new CommentList(); + $commentList->getConditionBuilder()->add("comment.commentID IN (?)", array($commentIDs)); + $commentList->readObjects(); + $comments = $commentList->getObjects(); + + // update counters + $processors = $responseIDs = $updateComments = array(); + foreach ($this->objects as $response) { + $objectTypeID = $comments[$response->commentID]->objectTypeID; + + if (!isset($processors[$objectTypeID])) { + $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID); + $processors[$objectTypeID] = $objectType->getProcessor(); + $responseIDs[$objectTypeID] = array(); + } + + $processors[$objectTypeID]->updateCounter($comments[$response->commentID]->objectID, -1); + $responseIDs[$objectTypeID][] = $response->responseID; + + if (!isset($updateComments[$response->commentID])) { + $updateComments[$response->commentID] = 0; + } + + $updateComments[$response->commentID]++; + } + + // remove responses + $count = parent::delete(); + + // update comment responses and cached response ids + foreach ($comments as $comment) { + $commentEditor = new CommentEditor($comment); + $commentEditor->updateLastResponseIDs(); + $commentEditor->updateCounters(array( + 'responses' => -1 * $updateComments[$comment->commentID] + )); + } + + foreach ($responseIDs as $objectTypeID => $objectIDs) { + // remove activity events + $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID); + if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->removeEvents($objectType->objectType.'.response.recentActivityEvent', $objectIDs); + } + + // delete notifications + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification')) { + UserNotificationHandler::getInstance()->deleteNotifications('commentResponse', $objectType->objectType.'.response.notification', array(), $objectIDs); + UserNotificationHandler::getInstance()->deleteNotifications('commentResponseOwner', $objectType->objectType.'.response.notification', array(), $objectIDs); + } + } + + return $count; + } + + /** + * Validates parameters to load responses for a given comment id. + */ + public function validateLoadResponses() { + $this->readInteger('commentID', false, 'data'); + $this->readInteger('lastResponseTime', false, 'data'); + + $this->comment = new Comment($this->parameters['data']['commentID']); + if (!$this->comment->commentID) { + throw new UserInputException('commentID'); + } + + $this->commentManager = ObjectTypeCache::getInstance()->getObjectType($this->comment->objectTypeID)->getProcessor(); + if (!$this->commentManager->isAccessible($this->comment->objectID)) { + throw new PermissionDeniedException(); + } + } + + /** + * Returns parsed responses for given comment id. + * + * @return array + */ + public function loadResponses() { + // get response list + $responseList = new StructuredCommentResponseList($this->commentManager, $this->comment); + $responseList->getConditionBuilder()->add("comment_response.time < ?", array($this->parameters['data']['lastResponseTime'])); + $responseList->sqlLimit = 50; + $responseList->readObjects(); + + $lastResponseTime = 0; + foreach ($responseList as $response) { + if (!$lastResponseTime) { + $lastResponseTime = $response->time; + } + + $lastResponseTime = min($lastResponseTime, $response->time); + } + + WCF::getTPL()->assign(array( + 'likeData' => (MODULE_LIKE ? $responseList->getLikeData() : array()), + 'responseList' => $responseList + )); + + return array( + 'commentID' => $this->comment->commentID, + 'lastResponseTime' => $lastResponseTime, + 'template' => WCF::getTPL()->fetch('commentResponseList') + ); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/CommentResponseEditor.class.php b/wcfsetup/install/files/lib/data/comment/response/CommentResponseEditor.class.php new file mode 100644 index 0000000000..c391bdf761 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/CommentResponseEditor.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class CommentResponseEditor extends DatabaseObjectEditor { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\response\CommentResponse'; +} diff --git a/wcfsetup/install/files/lib/data/comment/response/CommentResponseList.class.php b/wcfsetup/install/files/lib/data/comment/response/CommentResponseList.class.php new file mode 100644 index 0000000000..86b165b5ff --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/CommentResponseList.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class CommentResponseList extends DatabaseObjectList { + /** + * @see wcf\data\DatabaseObjectList::$className + */ + public $className = 'wcf\data\comment\response\CommentResponse'; +} diff --git a/wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponse.class.php b/wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponse.class.php new file mode 100644 index 0000000000..016501cf78 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponse.class.php @@ -0,0 +1,53 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class LikeableCommentResponse extends AbstractLikeObject { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\response\CommentResponse'; + + /** + * @see wcf\data\like\object\ILikeObject::getObjectType() + */ + public function getObjectType() { + if ($this->objectType === null) { + $this->objectType = ObjectTypeCache::getInstance()->getObjectType($this->getDecoratedObject()->objectTypeID); + } + + return $this->objectType; + } + + /** + * @see wcf\data\ITitledObject::getTitle() + */ + public function getTitle() { + return $this->message; + } + + /** + * @see wcf\data\like\object\ILikeObject::getURL() + */ + public function getURL() { + return $this->getLink(); + } + + /** + * @see wcf\data\like\object\ILikeObject::getUserID() + */ + public function getUserID() { + return $this->userID; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponseProvider.class.php b/wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponseProvider.class.php new file mode 100644 index 0000000000..b95a93eb10 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponseProvider.class.php @@ -0,0 +1,47 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class LikeableCommentResponseProvider extends AbstractObjectTypeProvider implements ILikeObjectTypeProvider { + /** + * @see wcf\data\object\type\AbstractObjectTypeProvider::$className + */ + public $className = 'wcf\data\comment\response\CommentResponse'; + + /** + * @see wcf\data\object\type\AbstractObjectTypeProvider::$decoratorClassName + */ + public $decoratorClassName = 'wcf\data\comment\response\LikeableCommentResponse'; + + /** + * @see wcf\data\object\type\AbstractObjectTypeProvider::$listClassName + */ + public $listClassName = 'wcf\data\comment\response\CommentResponseList'; + + /** + * @see wcf\data\like\ILikeObjectTypeProvider::checkPermissions() + */ + public function checkPermissions(ILikeObject $response) { + $comment = new Comment($response->commentID); + if (!$comment->commentID) { + return false; + } + + $objectType = CommentHandler::getInstance()->getObjectType($comment->objectTypeID); + return CommentHandler::getInstance()->getCommentManager($objectType->objectType)->isAccessible($comment->objectID); + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponse.class.php b/wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponse.class.php new file mode 100644 index 0000000000..7e56d80c5c --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponse.class.php @@ -0,0 +1,115 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class StructuredCommentResponse extends DatabaseObjectDecorator { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + public static $baseClass = 'wcf\data\comment\response\CommentResponse'; + + /** + * deletable by current user + * @var boolean + */ + public $deletable = false; + + /** + * editable for current user + * @var boolean + */ + public $editable = false; + + /** + * user profile object + * @var wcf\data\user\UserProfile + */ + public $userProfile = null; + + /** + * Sets the user's profile. + * + * @param wcf\data\user\UserProfile $userProfile + */ + public function setUserProfile(UserProfile $userProfile) { + $this->userProfile = $userProfile; + } + + /** + * Returns the user's profile. + * + * @return wcf\data\user\UserProfile + */ + public function getUserProfile() { + return $this->userProfile; + } + + /** + * Returns a structured response. + * + * @param integer $responseID + * @return wcf\data\comment\response\StructuredCommentResponse + */ + public static function getResponse($responseID) { + $response = new CommentResponse($responseID); + if (!$response->responseID) { + return null; + } + + // prepare structured response + $response = new StructuredCommentResponse($response); + + // add user profile + $userProfile = UserProfile::getUserProfile($response->userID); + $response->setUserProfile($userProfile); + + return $response; + } + + /** + * Sets deletable state. + * + * @param boolean $deletable + */ + public function setIsDeletable($deletable) { + $this->deletable = $deletable; + } + + /** + * Sets editable state. + * + * @param boolean $editable + */ + public function setIsEditable($editable) { + $this->editable = $editable; + } + + /** + * Returns true if the response is deletable by current user. + * + * @return boolean + */ + public function isDeletable() { + return $this->deletable; + } + + /** + * Returns true if the response is editable by current user. + * + * @return boolean + */ + public function isEditable() { + return $this->editable; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponseList.class.php b/wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponseList.class.php new file mode 100644 index 0000000000..2ebfa06204 --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponseList.class.php @@ -0,0 +1,117 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class StructuredCommentResponseList extends CommentResponseList { + /** + * comment object + * @var wcf\data\comment\Comment; + */ + public $comment = null; + + /** + * comment manager + * @var wcf\system\comment\manager\ICommentManager + */ + public $commentManager = null; + + /** + * minimum response time + * @var integer + */ + public $minResponseTime = 0; + + /** + * @see wcf\data\DatabaseObjectList::$sqlLimit + */ + public $sqlLimit = 50; + + /** + * @see wcf\data\DatabaseObjectList::$sqlOrderBy + */ + public $sqlOrderBy = 'comment_response.time DESC'; + + /** + * Creates a new structured comment response list. + * + * @param wcf\system\comment\manager\ICommentManager $commentManager + * @param wcf\data\comment\Comment $comment + */ + public function __construct(ICommentManager $commentManager, Comment $comment) { + parent::__construct(); + + $this->comment = $comment; + $this->commentManager = $commentManager; + + $this->getConditionBuilder()->add("comment_response.commentID = ?", array($this->comment->commentID)); + $this->sqlLimit = $this->commentManager->getCommentsPerPage(); + } + + /** + * @see wcf\data\DatabaseObjectList::readObjects() + */ + public function readObjects() { + parent::readObjects(); + + // get user ids + $userIDs = array(); + foreach ($this->objects as &$response) { + if (!$this->minResponseTime || $response->time < $this->minResponseTime) $this->minResponseTime = $response->time; + $userIDs[] = $response->userID; + + $response = new StructuredCommentResponse($response); + $response->setIsDeletable($this->commentManager->canDeleteResponse($response->getDecoratedObject())); + $response->setIsEditable($this->commentManager->canEditResponse($response->getDecoratedObject())); + } + unset($response); + + // fetch user data and avatars + if (!empty($userIDs)) { + $userIDs = array_unique($userIDs); + + $users = UserProfile::getUserProfiles($userIDs); + foreach ($this->objects as $response) { + if (isset($users[$response->userID])) { + $response->setUserProfile($users[$response->userID]); + } + } + } + } + + /** + * Fetches the like data. + * + * @return array + */ + public function getLikeData() { + if (empty($this->objectIDs)) return array(); + + $objectType = LikeHandler::getInstance()->getObjectType('com.woltlab.wcf.comment.response'); + LikeHandler::getInstance()->loadLikeObjects($objectType, $this->objectIDs); + $likeData = array('response' => LikeHandler::getInstance()->getLikeObjects($objectType)); + + return $likeData; + } + + /** + * Returns mimimum response time. + * + * @return integer + */ + public function getMinResponseTime() { + return $this->minResponseTime; + } +} diff --git a/wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php b/wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php new file mode 100644 index 0000000000..04a49abbae --- /dev/null +++ b/wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php @@ -0,0 +1,41 @@ + + * @package com.woltlab.wcf.comment + * @subpackage data.comment.response + * @category Community Framework + */ +class ViewableCommentResponse extends DatabaseObjectDecorator { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\response\CommentResponse'; + + /** + * user profile object + * @var wcf\data\user\UserProfile + */ + protected $userProfile = null; + + /** + * Returns the user profile object. + * + * @return wcf\data\user\UserProfile + */ + public function getUserProfile() { + if ($this->userProfile === null) { + $this->userProfile = new UserProfile(new User(null, $this->getDecoratedObject()->data)); + } + + return $this->userProfile; + } +} diff --git a/wcfsetup/install/files/lib/system/comment/CommentHandler.class.php b/wcfsetup/install/files/lib/system/comment/CommentHandler.class.php new file mode 100644 index 0000000000..62c677ecf7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/comment/CommentHandler.class.php @@ -0,0 +1,163 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.comment + * @category Community Framework + */ +class CommentHandler extends SingletonFactory { + /** + * cached object types + * @var array + */ + protected $cache = null; + + /** + * @see wcf\system\SingletonFactory::init() + */ + protected function init() { + $this->cache = array( + 'objectTypes' => array(), + 'objectTypeIDs' => array() + ); + + $cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.comment.commentableContent'); + foreach ($cache as $objectType) { + $this->cache['objectTypes'][$objectType->objectTypeID] = $objectType; + $this->cache['objectTypeIDs'][$objectType->objectType] = $objectType->objectTypeID; + } + } + + /** + * Returns the object type id for a given object type. + * + * @param string $objectType + * @return integer + */ + public function getObjectTypeID($objectType) { + if (isset($this->cache['objectTypeIDs'][$objectType])) { + return $this->cache['objectTypeIDs'][$objectType]; + } + + return null; + } + + /** + * Returns the object type for a given object type id. + * + * @param integer $objectTypeID + * @return wcf\data\object\type\ObjectType + */ + public function getObjectType($objectTypeID) { + if (isset($this->cache['objectTypes'][$objectTypeID])) { + return $this->cache['objectTypes'][$objectTypeID]; + } + + return null; + } + + /** + * Returns comment manager object for given object type. + * + * @param string $objectType + * @return wcf\system\comment\manager\ICommentManager + */ + public function getCommentManager($objectType) { + $objectTypeID = $this->getObjectTypeID($objectType); + if ($objectTypeID === null) { + throw new SystemException("Unable to find object type for '".$objectType."'"); + } + + return $this->getObjectType($objectTypeID)->getProcessor(); + + } + + /** + * Returns a comment list for a given object type and object id. + * + * @param wcf\data\comment\manager\ICommentManager $commentManager + * @param integer $objectTypeID + * @param integer $objectID + * @param boolean $readObjects + * @return wcf\data\comment\StructuredCommentList + */ + public function getCommentList(ICommentManager $commentManager, $objectTypeID, $objectID, $readObjects = true) { + $commentList = new StructuredCommentList($commentManager, $objectTypeID, $objectID); + if ($readObjects) { + $commentList->readObjects(); + } + + return $commentList; + } + + /** + * Removes all comments for given objects. + * + * @param string $objectType + * @param array $objectIDs + */ + public function deleteObjects($objectType, array $objectIDs) { + $objectTypeID = $this->getObjectTypeID($objectType); + $objectTypeObj = $this->getObjectType($objectTypeID); + + // get comment ids + $commentList = new CommentList(); + $commentList->getConditionBuilder()->add('comment.objectTypeID = ?', array($objectTypeID)); + $commentList->getConditionBuilder()->add('comment.objectID IN (?)', array($objectIDs)); + $commentList->readObjectIDs(); + $commentIDs = $commentList->getObjectIDs(); + + // no comments -> skip + if (empty($commentIDs)) return; + + // get response ids + $responseList = new CommentResponseList(); + $responseList->getConditionBuilder()->add('comment_response.commentID IN (?)', array($commentIDs)); + $responseList->readObjectIDs(); + $responseIDs = $responseList->getObjectIDs(); + + // delete likes + LikeHandler::getInstance()->removeLikes('com.woltlab.wcf.comment', $commentIDs); + + // delete activity events + if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectTypeObj->objectType.'.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->removeEvents($objectTypeObj->objectType.'.recentActivityEvent', $commentIDs); + } + // delete notifications + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectTypeObj->objectType.'.notification')) { + UserNotificationHandler::getInstance()->deleteNotifications('comment', $objectTypeObj->objectType.'.notification', array(), $commentIDs); + } + + if (!empty($responseIDs)) { + // delete activity events (for responses) + if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectTypeObj->objectType.'.response.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->removeEvents($objectTypeObj->objectType.'.response.recentActivityEvent', $responseIDs); + } + // delete notifications (for responses) + if (UserNotificationHandler::getInstance()->getObjectTypeID($objectTypeObj->objectType.'.response.notification')) { + UserNotificationHandler::getInstance()->deleteNotifications('commentResponse', $objectTypeObj->objectType.'.response.notification', array(), $responseIDs); + UserNotificationHandler::getInstance()->deleteNotifications('commentResponseOwner', $objectTypeObj->objectType.'.response.notification', array(), $responseIDs); + } + } + + // delete comments / responses + CommentEditor::deleteAll($commentIDs); + } +} diff --git a/wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php b/wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php new file mode 100644 index 0000000000..a881dbeabf --- /dev/null +++ b/wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php @@ -0,0 +1,163 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.comment.manager + * @category Community Framework + */ +abstract class AbstractCommentManager extends SingletonFactory implements ICommentManager { + /** + * display comments per page + * @var integer + */ + public $commentsPerPage = 10; + + /** + * permission name for comment/response creation + * @var string + */ + protected $permissionAdd = ''; + + /** + * permission name for comment/response moderation + * @var string + */ + protected $permissionCanModerate = ''; + + /** + * permission name for deletion of own comments/responses + * @var string + */ + protected $permissionDelete = ''; + + /** + * permission name for editing of own comments/responses + * @var string + */ + protected $permissionEdit = ''; + + /** + * permission name for deletion of comments/responses (moderator) + * @var string + */ + protected $permissionModDelete = ''; + + /** + * permission name for editing of comments/responses (moderator) + * @var string + */ + protected $permissionModEdit = ''; + + /** + * @see wcf\system\comment\manager\ICommentManager::canAdd() + */ + public function canAdd($objectID) { + if (!$this->isAccessible($objectID, true)) { + return false; + } + + return (WCF::getSession()->getPermission($this->permissionAdd) ? true : false); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::canEditComment() + */ + public function canEditComment(Comment $comment) { + return $this->canEdit(($comment->userID == WCF::getUser()->userID)); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::canEditResponse() + */ + public function canEditResponse(CommentResponse $response) { + return $this->canEdit(($response->userID == WCF::getUser()->userID)); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::canDeleteComment() + */ + public function canDeleteComment(Comment $comment) { + return $this->canDelete(($comment->userID == WCF::getUser()->userID)); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::canDeleteResponse() + */ + public function canDeleteResponse(CommentResponse $response) { + return $this->canDelete(($response->userID == WCF::getUser()->userID)); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::canModerate() + */ + public function canModerate($objectTypeID, $objectID) { + return (WCF::getSession()->getPermission($this->permissionCanModerate) ? true : false); + } + + /** + * Returns true if the current user may edit a comment/response. + * + * @param boolean $isOwner + * @return boolean + */ + protected function canEdit($isOwner) { + // disallow guests + if (!WCF::getUser()->userID) { + return false; + } + + // check moderator permission + if (WCF::getSession()->getPermission($this->permissionModEdit)) { + return true; + } + + // check user permission and ownership + if ($isOwner && WCF::getSession()->getPermission($this->permissionEdit)) { + return true; + } + + return false; + } + + /** + * Returns true if the current user may delete a comment/response. + * + * @param boolean $isOwner + * @return boolean + */ + protected function canDelete($isOwner) { + // disallow guests + if (!WCF::getUser()->userID) { + return false; + } + + // check moderator permission + if (WCF::getSession()->getPermission($this->permissionModDelete)) { + return true; + } + + // check user permission and ownership + if ($isOwner && WCF::getSession()->getPermission($this->permissionDelete)) { + return true; + } + + return false; + } + + /** + * @see wcf\system\comment\manager\ICommentManager::getCommentsPerPage() + */ + public function getCommentsPerPage() { + return $this->commentsPerPage; + } +} diff --git a/wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php b/wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php new file mode 100644 index 0000000000..6e47956b27 --- /dev/null +++ b/wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php @@ -0,0 +1,109 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.comment.manager + * @category Community Framework + */ +interface ICommentManager { + /** + * Returns true if the current user may add comments or responses. + * + * @param integer $objectID + * @return boolean + */ + public function canAdd($objectID); + + /** + * Returns true if the current user may edit given comment. + * + * @param wcf\data\comment\Comment $comment + * @return boolean + */ + public function canEditComment(Comment $comment); + + /** + * Returns true if the current user may edit given response. + * + * @param wcf\data\comment\response\CommentResponse $response + * @return boolean + */ + public function canEditResponse(CommentResponse $response); + + /** + * Returns true if the current user may delete given comment. + * + * @param wcf\data\comment\Comment $comment + * @return boolean + */ + public function canDeleteComment(Comment $comment); + + /** + * Returns true if the current user may delete given response. + * + * @param wcf\data\comment\response\CommentResponse $response + */ + public function canDeleteResponse(CommentResponse $response); + + /** + * Returns true if the current user may moderated content identified by + * object type id and object id. + * + * @param integer $objectTypeID + * @param integer $objectID + * @return boolean + */ + public function canModerate($objectTypeID, $objectID); + + /** + * Returns the amount of comments per page. + * + * @return integer + */ + public function getCommentsPerPage(); + + /** + * Returns a link to given object type id and object id. + * + * @param integer $objectTypeID + * @param integer $objectID + * @return string + */ + public function getLink($objectTypeID, $objectID); + + /** + * Returns the title for a comment or response. + * + * @param integer $objectTypeID + * @param integer $objectID + * @param boolean $isResponse + * @return string + */ + public function getTitle($objectTypeID, $objectID, $isResponse = false); + + /** + * Returns true if comments and responses for given object id are accessible + * by current user. + * + * @param integer $objectID + * @param boolean $validateWritePermission + * @return boolean + */ + public function isAccessible($objectID, $validateWritePermission = false); + + /** + * Updates total count of comments (includes responses). + * + * @param integer $objectID + * @param integer $value + */ + public function updateCounter($objectID, $value); +} diff --git a/wcfsetup/install/files/lib/system/comment/manager/UserProfileCommentManager.class.php b/wcfsetup/install/files/lib/system/comment/manager/UserProfileCommentManager.class.php new file mode 100644 index 0000000000..545fa4ad17 --- /dev/null +++ b/wcfsetup/install/files/lib/system/comment/manager/UserProfileCommentManager.class.php @@ -0,0 +1,97 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.comment.manager + * @category Community Framework + */ +class UserProfileCommentManager extends AbstractCommentManager { + /** + * @see wcf\system\comment\manager\AbstractCommentManager::$permissionAdd + */ + protected $permissionAdd = 'user.profileComment.canAddComment'; + + /** + * @see wcf\system\comment\manager\AbstractCommentManager::$permissionCanModerate + */ + protected $permissionCanModerate = 'mod.profileComment.canModerateComment'; + + /** + * @see wcf\system\comment\manager\AbstractCommentManager::$permissionDelete + */ + protected $permissionDelete = 'user.profileComment.canDeleteComment'; + + /** + * @see wcf\system\comment\manager\AbstractCommentManager::$permissionEdit + */ + protected $permissionEdit = 'user.profileComment.canEditComment'; + + /** + * @see wcf\system\comment\manager\AbstractCommentManager::$permissionModDelete + */ + protected $permissionModDelete = 'mod.profileComment.canDeleteComment'; + + /** + * @see wcf\system\comment\manager\AbstractCommentManager::$permissionModEdit + */ + protected $permissionModEdit = 'mod.profileComment.canEditComment'; + + /** + * @see wcf\system\comment\manager\ICommentManager::isAccessible() + */ + public function isAccessible($objectID, $validateWritePermission = false) { + // check object id + $userProfile = UserProfile::getUserProfile($objectID); + if ($userProfile === null) { + return false; + } + + // check visibility + if ($userProfile->isProtected()) { + return false; + } + + // check target user settings + if ($validateWritePermission) { + if (!$userProfile->isAccessible('canWriteProfileComments') && $userProfile->userID != WCF::getUser()->userID) { + return false; + } + + if ($userProfile->isIgnoredUser(WCF::getUser()->userID)) { + return false; + } + } + + return true; + } + + /** + * @see wcf\system\comment\manager\ICommentManager::getLink() + */ + public function getLink($objectTypeID, $objectID) { + return LinkHandler::getInstance()->getLink('User', array('id' => $objectID)); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::getTitle() + */ + public function getTitle($objectTypeID, $objectID, $isResponse = false) { + if ($isResponse) return WCF::getLanguage()->get('wcf.user.profile.content.wall.commentResponse'); + + return WCF::getLanguage()->getDynamicVariable('wcf.user.profile.content.wall.comment'); + } + + /** + * @see wcf\system\comment\manager\ICommentManager::updateCounter() + */ + public function updateCounter($objectID, $value) { } +} diff --git a/wcfsetup/install/files/lib/system/menu/user/profile/content/CommentUserProfileMenuContent.class.php b/wcfsetup/install/files/lib/system/menu/user/profile/content/CommentUserProfileMenuContent.class.php new file mode 100644 index 0000000000..e629c382da --- /dev/null +++ b/wcfsetup/install/files/lib/system/menu/user/profile/content/CommentUserProfileMenuContent.class.php @@ -0,0 +1,61 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.menu.user.profile.content + * @category Community Framework + */ +class CommentUserProfileMenuContent extends SingletonFactory implements IUserProfileMenuContent { + /** + * comment manager object + * @var wcf\system\comment\manager\ICommentManager + */ + public $commentManager = null; + + /** + * object type id + * @var integer + */ + public $objectTypeID = 0; + + /** + * @see wcf\system\menu\user\profile\content\IUserProfileMenuContent::getContent() + */ + public function getContent($userID) { + if ($this->commentManager === null) { + $this->objectTypeID = CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user.profileComment'); + $objectType = CommentHandler::getInstance()->getObjectType($this->objectTypeID); + $this->commentManager = $objectType->getProcessor(); + } + + $commentList = CommentHandler::getInstance()->getCommentList($this->commentManager, $this->objectTypeID, $userID); + + // assign variables + WCF::getTPL()->assign(array( + 'commentCanAdd' => $this->commentManager->canAdd($userID), + 'commentList' => $commentList, + 'commentObjectTypeID' => $this->objectTypeID, + 'userID' => $userID, + 'lastCommentTime' => $commentList->getMinCommentTime(), + 'likeData' => (MODULE_LIKE ? $commentList->getLikeData() : array()) + )); + + return WCF::getTPL()->fetch('userProfileCommentList'); + } + + /** + * @see wcf\system\menu\user\profile\content\IUserProfileMenuContent::isVisible() + */ + public function isVisible($userID) { + return true; + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/report/CommentCommentModerationQueueReportHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/report/CommentCommentModerationQueueReportHandler.class.php new file mode 100644 index 0000000000..c2562b8482 --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/report/CommentCommentModerationQueueReportHandler.class.php @@ -0,0 +1,208 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.moderation.queue + * @category Community Framework + */ +class CommentCommentModerationQueueReportHandler extends AbstractModerationQueueHandler implements IModerationQueueReportHandler { + /** + * @see wcf\system\moderation\queue\AbstractModerationQueueHandler::$definitionName + */ + protected $definitionName = 'com.woltlab.wcf.moderation.report'; + + /** + * @see wcf\system\moderation\queue\AbstractModerationQueueHandler::$objectType + */ + protected $objectType = 'com.woltlab.wcf.comment.comment'; + + /** + * list of comments + * @var array + */ + protected static $comments = array(); + + /** + * list of comment managers + * @var array + */ + protected static $commentManagers = array(); + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::assignQueues() + */ + public function assignQueues(array $queues) { + $assignments = array(); + + // read comments + $commentIDs = array(); + foreach ($queues as $queue) { + $commentIDs[] = $queue->objectID; + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("commentID IN (?)", array($commentIDs)); + + $sql = "SELECT commentID, objectTypeID, objectID + FROM wcf".WCF_N."_comment + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + $comments = array(); + while ($row = $statement->fetchArray()) { + $comments[$row['commentID']] = new Comment(null, $row); + } + + foreach ($queues as $queue) { + $assignUser = false; + + $comment = $comments[$queue->objectID]; + if ($this->getCommentManager($comment)->canModerate($comment->objectTypeID, $comment->objectID)) { + $assignUser = true; + } + + $assignments[$queue->queueID] = $assignUser; + } + + ModerationQueueManager::getInstance()->setAssignment($assignments); + } + + /** + * @see wcf\system\moderation\queue\report\IModerationQueueReportHandler::canReport() + */ + public function canReport($objectID) { + if (!$this->isValid($objectID)) { + return false; + } + + $comment = $this->getComment($objectID); + if (!$this->getCommentManager($comment)->isAccessible($comment->objectID)) { + return false; + } + + return true; + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::getContainerID() + */ + public function getContainerID($objectID) { + return 0; + } + + /** + * @see wcf\system\moderation\queue\report\IModerationQueueReportHandler::getReportedContent() + */ + public function getReportedContent(ViewableModerationQueue $queue) { + WCF::getTPL()->assign(array( + 'message' => new ViewableComment($queue->getAffectedObject()) + )); + + return WCF::getTPL()->fetch('moderationComment'); + } + + /** + * @see wcf\system\moderation\queue\report\IModerationQueueReportHandler::getReportedObject() + */ + public function getReportedObject($objectID) { + if ($this->isValid($objectID)) { + return $this->getComment($objectID); + } + + return null; + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::isValid() + */ + public function isValid($objectID) { + if ($this->getComment($objectID) === null) { + return false; + } + + return true; + } + + /** + * Returns a comment object by comment id or null if comment id is invalid. + * + * @param integer $objectID + * @return wcf\data\comment\Comment + */ + protected function getComment($objectID) { + if (!array_key_exists($objectID, self::$comments)) { + self::$comments[$objectID] = new Comment($objectID); + if (!self::$comments[$objectID]->commentID) { + self::$comments[$objectID] = null; + } + } + + return self::$comments[$objectID]; + } + + /** + * Returns a comment manager for given comment. + * + * @param wcf\data\comment\Comment $comment + * @return wcf\system\comment\manager\ICommentManager + */ + protected function getCommentManager(Comment $comment) { + if (!isset(self::$commentManagers[$comment->objectTypeID])) { + self::$commentManagers[$comment->objectTypeID] = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID)->getProcessor(); + } + + return self::$commentManagers[$comment->objectTypeID]; + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::populate() + */ + public function populate(array $queues) { + $objectIDs = array(); + foreach ($queues as $object) { + $objectIDs[] = $object->objectID; + } + + // fetch comments + $commentList = new CommentList(); + $commentList->getConditionBuilder()->add("comment.commentID IN (?)", array($objectIDs)); + $commentList->readObjects(); + $comments = $commentList->getObjects(); + + foreach ($queues as $object) { + if (isset($comments[$object->objectID])) { + $object->setAffectedObject($comments[$object->objectID]); + } + else { + $object->setIsOrphaned(); + } + } + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::removeContent() + */ + public function removeContent(ModerationQueue $queue, $message) { + if ($this->isValid($queue->objectID)) { + $commentAction = new CommentAction(array($this->getComment($queue->objectID)), 'delete'); + $commentAction->executeAction(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php new file mode 100644 index 0000000000..d06ffc04ff --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php @@ -0,0 +1,203 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.moderation.queue + * @category Community Framework + */ +class CommentResponseModerationQueueReportHandler extends CommentCommentModerationQueueReportHandler { + /** + * @see wcf\system\moderation\queue\AbstractModerationQueueHandler::$objectType + */ + protected $objectType = 'com.woltlab.wcf.comment.response'; + + /** + * list of comment responses + * @var array + */ + protected static $responses = array(); + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::assignQueues() + */ + public function assignQueues(array $queues) { + $assignments = array(); + + // read comments and responses + $responseIDs = array(); + foreach ($queues as $queue) { + $responseIDs[] = $queue->objectID; + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("comment_response.responseID IN (?)", array($responseIDs)); + + $sql = "SELECT comment_response.responseID, comment.commentID, comment.objectTypeID, comment.objectID + FROM wcf".WCF_N."_comment_response comment_response + LEFT JOIN wcf".WCF_N."_comment comment + ON (comment.commentID = comment_response.commentID) + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + $comments = $responses = array(); + while ($row = $statement->fetchArray()) { + $comments[$row['commentID']] = new Comment(null, $row); + $responses[$row['responseID']] = new CommentResponse(null, $row); + } + + foreach ($queues as $queue) { + $assignUser = false; + + $comment = $comments[$responses[$queue->objectID]->commentID]; + if ($this->getCommentManager($comment)->canModerate($comment->objectTypeID, $comment->objectID)) { + $assignUser = true; + } + + $assignments[$queue->queueID] = $assignUser; + } + + ModerationQueueManager::getInstance()->setAssignment($assignments); + } + + /** + * @see wcf\system\moderation\queue\report\IModerationQueueReportHandler::canReport() + */ + public function canReport($objectID) { + if (!$this->isValid($objectID)) { + return false; + } + + $response = $this->getResponse($objectID); + $comment = $this->getComment($response->commentID); + if (!$this->getCommentManager($comment)->isAccessible($comment->objectID)) { + return false; + } + + return true; + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::getContainerID() + */ + public function getContainerID($objectID) { + return 0; + } + + /** + * @see wcf\system\moderation\queue\report\IModerationQueueReportHandler::getReportedContent() + */ + public function getReportedContent(ViewableModerationQueue $queue) { + WCF::getTPL()->assign(array( + 'message' => new ViewableCommentResponse($queue->getAffectedObject()) + )); + + return WCF::getTPL()->fetch('moderationComment'); + } + + /** + * @see wcf\system\moderation\queue\report\IModerationQueueReportHandler::getReportedObject() + */ + public function getReportedObject($objectID) { + if ($this->isValid($objectID)) { + return $this->getResponse($objectID); + } + + return null; + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::isValid() + */ + public function isValid($objectID) { + if ($this->getResponse($objectID) === null) { + return false; + } + + return true; + } + + /** + * Returns a comment response object by response id or null if response id is invalid. + * + * @param integer $objectID + * @return wcf\data\comment\response\CommentResponse + */ + protected function getResponse($objectID) { + if (!array_key_exists($objectID, self::$responses)) { + self::$responses[$objectID] = new CommentResponse($objectID); + if (!self::$responses[$objectID]->responseID) { + self::$responses[$objectID] = null; + } + } + + return self::$responses[$objectID]; + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::populate() + */ + public function populate(array $queues) { + $objectIDs = array(); + foreach ($queues as $object) { + $objectIDs[] = $object->objectID; + } + + // fetch responses + $responseList = new CommentResponseList(); + $responseList->getConditionBuilder()->add("comment_response.responseID IN (?)", array($objectIDs)); + $responseList->readObjects(); + $responses = $responseList->getObjects(); + + // fetch comments + $commentIDs = array(); + foreach ($responses as $response) { + $commentIDs[] = $response->commentID; + } + + if (!empty($commentIDs)) { + $commentList = new CommentList(); + $commentList->getConditionBuilder()->add("comment.commentID IN (?)", array($commentIDs)); + $commentList->readObjects(); + $comments = $commentList->getObjects(); + } + + foreach ($queues as $object) { + if (isset($responses[$object->objectID])) { + $response = $responses[$object->objectID]; + $response->setComment($comments[$response->commentID]); + + $object->setAffectedObject($response); + } + else { + $object->setIsOrphaned(); + } + } + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::removeContent() + */ + public function removeContent(ModerationQueue $queue, $message) { + if ($this->isValid($queue->objectID)) { + $responseAction = new CommentResponseAction(array($this->getResponse($queue->objectID)), 'delete'); + $responseAction->executeAction(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php b/wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php new file mode 100644 index 0000000000..e6d9f2f8f3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php @@ -0,0 +1,85 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.activity.event + * @category Community Framework + */ +class ProfileCommentResponseUserActivityEvent extends SingletonFactory implements IUserActivityEvent { + /** + * @see wcf\system\user\activity\event\IUserActivityEvent::prepare() + */ + public function prepare(array $events) { + $responseIDs = array(); + foreach ($events as $event) { + $responseIDs[] = $event->objectID; + } + + // fetch responses + $responseList = new CommentResponseList(); + $responseList->getConditionBuilder()->add("comment_response.responseID IN (?)", array($responseIDs)); + $responseList->readObjects(); + $responses = $responseList->getObjects(); + + // fetch comments + $commentIDs = $comments = array(); + foreach ($responses as $response) { + $commentIDs[] = $response->commentID; + } + if (!empty($commentIDs)) { + $commentList = new CommentList(); + $commentList->getConditionBuilder()->add("comment.commentID IN (?)", array($commentIDs)); + $commentList->readObjects(); + $comments = $commentList->getObjects(); + } + + // fetch users + $userIDs = $users = array(); + foreach ($comments as $comment) { + $userIDs[] = $comment->objectID; + $userIDs[] = $comment->userID; + } + if (!empty($userIDs)) { + $userList = new UserList(); + $userList->getConditionBuilder()->add("user_table.userID IN (?)", array($userIDs)); + $userList->readObjects(); + $users = $userList->getObjects(); + } + + // set message + foreach ($events as $event) { + if (isset($responses[$event->objectID])) { + $response = $responses[$event->objectID]; + $comment = $comments[$response->commentID]; + if (isset($users[$comment->objectID]) && isset($users[$comment->userID])) { + $event->setIsAccessible(); + + // title + $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.profileCommentResponse', array( + 'commentAuthor' => $users[$comment->userID], + 'user' => $users[$comment->objectID] + )); + $event->setTitle($text); + + // description + $event->setDescription($response->getExcerpt()); + continue; + } + } + + $event->setIsOrphaned(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentUserActivityEvent.class.php b/wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentUserActivityEvent.class.php new file mode 100644 index 0000000000..971f42452b --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentUserActivityEvent.class.php @@ -0,0 +1,68 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.activity.event + * @category Community Framework + */ +class ProfileCommentUserActivityEvent extends SingletonFactory implements IUserActivityEvent { + /** + * @see wcf\system\user\activity\event\IUserActivityEvent::prepare() + */ + public function prepare(array $events) { + $comentIDs = array(); + foreach ($events as $event) { + $comentIDs[] = $event->objectID; + } + + // fetch comments + $commentList = new CommentList(); + $commentList->getConditionBuilder()->add("comment.commentID IN (?)", array($comentIDs)); + $commentList->readObjects(); + $comments = $commentList->getObjects(); + + // fetch users + $userIDs = $users = array(); + foreach ($comments as $comment) { + $userIDs[] = $comment->objectID; + } + if (!empty($userIDs)) { + $userList = new UserList(); + $userList->getConditionBuilder()->add("user_table.userID IN (?)", array($userIDs)); + $userList->readObjects(); + $users = $userList->getObjects(); + } + + // set message + foreach ($events as $event) { + if (isset($comments[$event->objectID])) { + // short output + $comment = $comments[$event->objectID]; + if (isset($users[$comment->objectID])) { + $event->setIsAccessible(); + + $user = $users[$comment->objectID]; + $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.profileComment', array('user' => $user)); + $event->setTitle($text); + + // output + $event->setDescription($comment->getExcerpt()); + continue; + } + } + + $event->setIsOrphaned(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseOwnerUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseOwnerUserNotificationEvent.class.php new file mode 100644 index 0000000000..5436520419 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseOwnerUserNotificationEvent.class.php @@ -0,0 +1,62 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.notification.event + * @category Community Framework + */ +class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractUserNotificationEvent { + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getTitle() + */ + public function getTitle() { + return $this->getLanguage()->get('wcf.user.notification.commentResponseOwner.title'); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getMessage() + */ + public function getMessage() { + // @todo: use cache or a single query to retrieve required data + $comment = new Comment($this->userNotificationObject->commentID); + $commentAuthor = new User($comment->userID); + + return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponseOwner.message', array( + 'author' => $this->author, + 'commentAuthor' => $commentAuthor + )); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getEmailMessage() + */ + public function getEmailMessage() { + $comment = new Comment($this->userNotificationObject->commentID); + $commentAuthor = new User($comment->userID); + $owner = new User($comment->objectID); + + return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponseOwner.mail', array( + 'author' => $this->author, + 'commentAuthor' => $commentAuthor, + 'owner' => $owner + )); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getLink() + */ + public function getLink() { + return LinkHandler::getInstance()->getLink('User', array('object' => WCF::getUser()), '#wall'); + } +} diff --git a/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseUserNotificationEvent.class.php new file mode 100644 index 0000000000..e5d4b8c385 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseUserNotificationEvent.class.php @@ -0,0 +1,63 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.notification.event + * @category Community Framework + */ +class UserProfileCommentResponseUserNotificationEvent extends AbstractUserNotificationEvent { + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getTitle() + */ + public function getTitle() { + return $this->getLanguage()->get('wcf.user.notification.commentResponse.title'); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getMessage() + */ + public function getMessage() { + // @todo: use cache or a single query to retrieve required data + $comment = new Comment($this->userNotificationObject->commentID); + $user = new User($comment->objectID); + + return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponse.message', array( + 'author' => $this->author, + 'owner' => $user + )); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getEmailMessage() + */ + public function getEmailMessage() { + $comment = new Comment($this->userNotificationObject->commentID); + $user = new User($comment->objectID); + + return $this->getLanguage()->getDynamicVariable('wcf.user.notification.commentResponse.mail', array( + 'author' => $this->author, + 'owner' => $user + )); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getLink() + */ + public function getLink() { + // @todo: use cache or a single query to retrieve required data + $comment = new Comment($this->userNotificationObject->commentID); + $user = new User($comment->objectID); + + return LinkHandler::getInstance()->getLink('User', array('object' => $user), '#wall'); + } +} diff --git a/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php new file mode 100644 index 0000000000..0c1f7cf976 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php @@ -0,0 +1,53 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.notification.event + * @category Community Framework + */ +class UserProfileCommentUserNotificationEvent extends AbstractUserNotificationEvent { + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getTitle() + */ + public function getTitle() { + return $this->getLanguage()->get('wcf.user.notification.comment.title'); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getMessage() + */ + public function getMessage() { + return $this->getLanguage()->getDynamicVariable('wcf.user.notification.comment.message', array( + 'author' => $this->author + )); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getEmailMessage() + */ + public function getEmailMessage() { + $user = new User($this->userNotificationObject->objectID); + + return $this->getLanguage()->getDynamicVariable('wcf.user.notification.comment.mail', array( + 'author' => $this->author, + 'owner' => $user + )); + } + + /** + * @see wcf\system\user\notification\event\IUserNotificationEvent::getLink() + */ + public function getLink() { + return LinkHandler::getInstance()->getLink('User', array('object' => WCF::getUser()), '#wall'); + } +} diff --git a/wcfsetup/install/files/lib/system/user/notification/object/CommentResponseUserNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/CommentResponseUserNotificationObject.class.php new file mode 100644 index 0000000000..f37d29237b --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/object/CommentResponseUserNotificationObject.class.php @@ -0,0 +1,41 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.notification.object + * @category Community Framework + */ +class CommentResponseUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\response\CommentResponse'; + + /** + * @see wcf\system\user\notification\object\IUserNotificationObject::getTitle() + */ + public function getTitle() { + return ''; + } + + /** + * @see wcf\system\user\notification\object\IUserNotificationObject::getURL() + */ + public function getURL() { + return ''; + } + + /** + * @see wcf\system\user\notification\object\IUserNotificationObject::getAuthorID() + */ + public function getAuthorID() { + return $this->userID; + } +} diff --git a/wcfsetup/install/files/lib/system/user/notification/object/CommentUserNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/CommentUserNotificationObject.class.php new file mode 100644 index 0000000000..5761d1fb84 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/object/CommentUserNotificationObject.class.php @@ -0,0 +1,41 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.notification.object + * @category Community Framework + */ +class CommentUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\comment\Comment'; + + /** + * @see wcf\system\user\notification\object\IUserNotificationObject::getTitle() + */ + public function getTitle() { + return ''; + } + + /** + * @see wcf\system\user\notification\object\IUserNotificationObject::getURL() + */ + public function getURL() { + return ''; + } + + /** + * @see wcf\system\user\notification\object\IUserNotificationObject::getAuthorID() + */ + public function getAuthorID() { + return $this->userID; + } +} diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/ICommentUserNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/ICommentUserNotificationObjectType.class.php new file mode 100644 index 0000000000..edc0cebca8 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/object/type/ICommentUserNotificationObjectType.class.php @@ -0,0 +1,22 @@ + + * @package com.woltlab.wcf.comment + * @subpackage system.user.notification.object.type + * @category Community Framework + */ +interface ICommentUserNotificationObjectType { + /** + * Returns owner id of comment context. + * + * @param integer $objectID + * @return integer + */ + public function getOwnerID($objectID); +} diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentResponseUserNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentResponseUserNotificationObjectType.class.php new file mode 100644 index 0000000000..3c5279ee67 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentResponseUserNotificationObjectType.class.php @@ -0,0 +1,29 @@ + + * @package com.woltlab.wcf.user + * @subpackage system.user.notification.object.type + * @category Community Framework + */ +class UserProfileCommentResponseUserNotificationObjectType extends AbstractUserNotificationObjectType { + /** + * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$decoratorClassName + */ + protected static $decoratorClassName = 'wcf\system\user\notification\object\CommentResponseUserNotificationObject'; + + /** + * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$objectClassName + */ + protected static $objectClassName = 'wcf\data\comment\response\CommentResponse'; + + /** + * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$objectListClassName + */ + protected static $objectListClassName = 'wcf\data\comment\response\CommentResponseList'; +} diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentUserNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentUserNotificationObjectType.class.php new file mode 100644 index 0000000000..be08d04573 --- /dev/null +++ b/wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentUserNotificationObjectType.class.php @@ -0,0 +1,44 @@ + + * @package com.woltlab.wcf.user + * @subpackage system.user.notification.object.type + * @category Community Framework + */ +class UserProfileCommentUserNotificationObjectType extends AbstractUserNotificationObjectType implements ICommentUserNotificationObjectType { + /** + * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$decoratorClassName + */ + protected static $decoratorClassName = 'wcf\system\user\notification\object\CommentUserNotificationObject'; + + /** + * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$objectClassName + */ + protected static $objectClassName = 'wcf\data\comment\Comment'; + + /** + * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$objectListClassName + */ + protected static $objectListClassName = 'wcf\data\comment\CommentList'; + + /** + * @see wcf\system\user\notification\object\type\ICommentUserNotificationObjectType::getOwnerID() + */ + public function getOwnerID($objectID) { + $sql = "SELECT objectID + FROM wcf".WCF_N."_comment + WHERE commentID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array($objectID)); + $row = $statement->fetchArray(); + + return ($row ? $row['objectID'] : 0); + } +} diff --git a/wcfsetup/install/files/style/comment.less b/wcfsetup/install/files/style/comment.less new file mode 100644 index 0000000000..a450df1a7d --- /dev/null +++ b/wcfsetup/install/files/style/comment.less @@ -0,0 +1,114 @@ +/* ############## Profile Comments ############## */ +.comment, +.commentResponse { + position: relative; +} + +.commentList .buttonGroupNavigation { + position: absolute; + top: @wcfGapTiny; + right: @wcfGapMedium; + + > ul { + > li { + float: left; + opacity: 0; + + .transition(opacity, .1s); + + > a { + padding: @wcfGapTiny; + } + } + } +} + +.commentResponseList .buttonGroupNavigation { + top: @wcfGapSmall; + right: @wcfGapSmall; +} + +.commentContent:hover > .buttonGroupNavigation > ul > li { + opacity: 1; +} + +.commentList input[type='text'] { + + small { + color: @wcfDimmedColor; + opacity: 0; + + .transition(opacity, .1s); + } + + &:focus + small { + opacity: 1; + } +} + +.commentResponse { + border-top: 1px solid @wcfContainerBorderColor; + padding: @wcfGapSmall; +} + +.commentResponseAdd { + border-top: 1px solid @wcfContainerBorderColor; + margin-top: @wcfGapMedium; + padding: 7px 7px 0; +} + +.commentResponseList .commentResponse:first-child { + margin-top: @wcfGapMedium; +} + +.commentResponseAdd + .commentResponseList .commentResponse:first-child { + margin-top: 7px; +} + +.commentList > li:nth-child(2n) .commentResponseList .commentResponse:nth-child(2n+1) { + background-color: @wcfContainerBackgroundColor; + + .transition(background-color, .1s); +} + +.commentList > li:nth-child(2n+1) .commentResponseList .commentResponse:nth-child(2n+1) { + background-color: @wcfContainerAccentBackgroundColor; + + .transition(background-color, .1s); +} + +.commentResponseList > li:hover { + background-color: @wcfContainerHoverBackgroundColor !important; +} + +.commentList > li:not(.commentAdd):hover { + background-color: @wcfContainerBackgroundColor; + + &:nth-child(2n) { + background-color: @wcfContainerAccentBackgroundColor; + } +} + +/* buttons to load comments/responses */ +.commentList > .commentLoadNext, +.comment .responseLoadNext { + text-align: center; + + > button { + padding-left: 30px; + padding-right: 30px; + } +} + +.comment .responseLoadNext { + padding-top: @wcfGapMedium; +} + +.commentList .userMessage { + margin-top: 0; +} + +/* like display */ +.commentList .likesBadge { + display: inline-block; + margin: -2px 0 -2px @wcfGapTiny; +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index ea86530fe5..d6d4bf2931 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -296,6 +296,14 @@ + + + + + + + + @@ -708,6 +716,7 @@ + @@ -1190,7 +1199,6 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions() - @@ -1219,6 +1227,16 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions() + + + + + + + + + + @@ -1504,6 +1522,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions() + + @@ -1965,6 +1985,8 @@ Sollten Sie sich nicht auf der Website: {@PAGE_TITLE|language} angemeldet haben, + + @@ -2065,6 +2087,21 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn + + + username} hat einen Kommentar an Ihrer Pinnwand verfasst: +{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]> + + username} verfasst.]]> + username} hat eine Antwort zu Ihrem Kommentar an der Pinnwand von "{@$owner->username}" verfasst: +{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]> + + username} an Ihrer Pinnwand verfasst.]]> + username} hat eine Antwort zum Kommentar von "{@$commentAuthor->username}" an Ihrer Pinnwand verfasst: +{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]> + + + @@ -2079,6 +2116,12 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn getOldUsername()}“]]> {$user->username}.]]> + + + + + Pinnwand von {$user->username} geschrieben.]]> + {$commentAuthor->username} an der Pinnwand von {$user->username} geantwortet.]]> @@ -2123,6 +2166,7 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index a7fe5f246c..d703d0e12d 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -295,6 +295,14 @@ Examples for medium ID detection: + + + + + + + + @@ -707,6 +715,7 @@ Examples for medium ID detection: + @@ -1216,6 +1225,16 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]> + + + + + + + + + + @@ -1501,6 +1520,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]> + + @@ -1928,6 +1949,8 @@ You can safely ignore this email if you did not register with the website: {@PAG + + @@ -2028,6 +2051,21 @@ If you do not want to receive further email notifications for this event, you ca + + + username} wrote a comment on your wall: +{link controller='User' object=$owner encode=false forceFrontend=true}#wall{/link}]]> + + username}’s wall.]]> + username} wrote a reply to your comment on {@$owner->username}’s wall: +{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]> + + username}’s comment on your wall.]]> + username} wrote a reply to {@$commentAuthor->username}’s comment on your wall: +{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]> + + + @@ -2042,6 +2080,12 @@ If you do not want to receive further email notifications for this event, you ca getOldUsername()}”]]> {$user->username}.]]> + + + + + {$user->username}’s wall.]]> + {$commentAuthor->username} on {$user->username}’s wall.]]> @@ -2086,6 +2130,7 @@ If you do not want to receive further email notifications for this event, you ca + diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index f9a4b92d96..edf04c52c7 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -231,6 +231,33 @@ CREATE TABLE wcf1_clipboard_page ( actionID INT(10) NOT NULL DEFAULT 0 ); +DROP TABLE IF EXISTS wcf1_comment; +CREATE TABLE wcf1_comment ( + commentID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectTypeID INT(10) NOT NULL, + objectID INT(10) NOT NULL, + time INT(10) NOT NULL DEFAULT '0', + userID INT(10), + username VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + responses MEDIUMINT(7) NOT NULL DEFAULT '0', + lastResponseIDs VARCHAR(255) NOT NULL DEFAULT '', + + KEY (objectTypeID, objectID, time) +); + +DROP TABLE IF EXISTS wcf1_comment_response; +CREATE TABLE wcf1_comment_response ( + responseID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + commentID INT(10) NOT NULL, + time INT(10) NOT NULL DEFAULT '0', + userID INT(10), + username VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + + KEY (commentID, time) +); + DROP TABLE IF EXISTS wcf1_core_object; CREATE TABLE wcf1_core_object ( objectID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, @@ -1351,6 +1378,12 @@ ALTER TABLE wcf1_like ADD FOREIGN KEY (objectUserID) REFERENCES wcf1_user (userI 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; +ALTER TABLE wcf1_comment ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE; +ALTER TABLE wcf1_comment ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL; + +ALTER TABLE wcf1_comment_response ADD FOREIGN KEY (commentID) REFERENCES wcf1_comment (commentID) ON DELETE CASCADE; +ALTER TABLE wcf1_comment_response ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL; + /* default inserts */ -- default user groups -- 2.20.1