Merged com.woltlab.wcf.comment into WCF
authorMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 22:34:36 +0000 (00:34 +0200)
committerMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 22:34:36 +0000 (00:34 +0200)
54 files changed:
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/option.xml
com.woltlab.wcf/template/__commentJavaScript.tpl [new file with mode: 0644]
com.woltlab.wcf/template/commentList.tpl [new file with mode: 0644]
com.woltlab.wcf/template/commentResponseList.tpl [new file with mode: 0644]
com.woltlab.wcf/template/moderationComment.tpl [new file with mode: 0644]
com.woltlab.wcf/template/userProfileCommentList.tpl [new file with mode: 0644]
com.woltlab.wcf/userGroupOption.xml
com.woltlab.wcf/userNotificationEvent.xml
com.woltlab.wcf/userOption.xml
com.woltlab.wcf/userProfileMenu.xml
wcfsetup/install/files/js/WCF.Comment.js [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Comment.min.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/UserMergeForm.class.php
wcfsetup/install/files/lib/data/comment/Comment.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/CommentAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/CommentEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/CommentList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/LikeableComment.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/LikeableCommentProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/StructuredComment.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/StructuredCommentList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/ViewableComment.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/CommentResponse.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/CommentResponseEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/CommentResponseList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponse.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/LikeableCommentResponseProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponse.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/StructuredCommentResponseList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/comment/CommentHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/comment/manager/UserProfileCommentManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/menu/user/profile/content/CommentUserProfileMenuContent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/moderation/queue/report/CommentCommentModerationQueueReportHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentUserActivityEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseOwnerUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/CommentResponseUserNotificationObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/CommentUserNotificationObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/ICommentUserNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentResponseUserNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/UserProfileCommentUserNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/style/comment.less [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index dca396a5f82e26d25c550f005f28d381a8db1bbe..785418ca827a94c0677ba99095565a4b3366fc3a 100644 (file)
                        <classname>wcf\system\user\activity\point\ReceivedLikesUserActivityPointObjectProcessor</classname>
                </type>
                <!-- /activity points -->
+               
+               <type>
+                       <name>com.woltlab.wcf.user.profileComment</name>
+                       <definitionname>com.woltlab.wcf.comment.commentableContent</definitionname>
+                       <classname>wcf\system\comment\manager\UserProfileCommentManager</classname>
+               </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.comment</name>
+                       <definitionname>com.woltlab.wcf.like.likeableObject</definitionname>
+                       <classname>wcf\data\comment\LikeableCommentProvider</classname>
+               </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.like.likeableObject</definitionname>
+                       <classname>wcf\data\comment\response\LikeableCommentResponseProvider</classname>
+               </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.user.profileComment.recentActivityEvent</name>
+                       <definitionname>com.woltlab.wcf.user.recentActivityEvent</definitionname>
+                       <classname>wcf\system\user\activity\event\ProfileCommentUserActivityEvent</classname>
+               </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.user.profileComment.response.recentActivityEvent</name>
+                       <definitionname>com.woltlab.wcf.user.recentActivityEvent</definitionname>
+                       <classname>wcf\system\user\activity\event\ProfileCommentResponseUserActivityEvent</classname>
+               </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.user.profileComment.notification</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\UserProfileCommentUserNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.user</category>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.user.profileComment.response.notification</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\UserProfileCommentResponseUserNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.user</category>
+               </type>
+               
+               <!-- moderation -->
+               <type>
+                       <name>com.woltlab.wcf.comment.comment</name>
+                       <definitionname>com.woltlab.wcf.moderation.report</definitionname>
+                       <classname>wcf\system\moderation\queue\report\CommentCommentModerationQueueReportHandler</classname>
+               </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.comment.response</name>
+                       <definitionname>com.woltlab.wcf.moderation.report</definitionname>
+                       <classname>wcf\system\moderation\queue\report\CommentResponseModerationQueueReportHandler</classname>
+               </type>
+               <!-- /moderation -->
        </import>
 </data>
\ No newline at end of file
index 94cd667e7f67345034987ff560c13eb20bcd69cd..f836db66731d4ef9b6bdda81d0b7ac26a3f5aa62 100644 (file)
                        <name>com.woltlab.wcf.like.likeableObject</name>
                        <interfacename>wcf\data\like\ILikeObjectTypeProvider</interfacename>
                </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.comment.commentableContent</name>
+                       <interfacename>wcf\system\comment\manager\ICommentManager</interfacename>
+               </definition>
        </import>
 </data>
index cf426faa96112f2b4ff6485ee8bf1f0f496441e8..58570a2b86e607cf928f42f647989b68e4550193 100644 (file)
                                <defaultvalue>1</defaultvalue>
                        </option>
                        
+                       <option name="module_user_profile_wall">
+                               <categoryname>module.user</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       
                        <!-- general.page -->
                        <option name="page_title">
                                <categoryname>general.page</categoryname>
diff --git a/com.woltlab.wcf/template/__commentJavaScript.tpl b/com.woltlab.wcf/template/__commentJavaScript.tpl
new file mode 100644 (file)
index 0000000..a65ddf6
--- /dev/null
@@ -0,0 +1,30 @@
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Comment{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Moderation{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript">
+       //<![CDATA[
+       $(function() {
+               WCF.Language.addObject({
+                       'wcf.comment.add': '{lang}wcf.comment.add{/lang}',
+                       'wcf.comment.button.response.add': '{lang}wcf.comment.button.response.add{/lang}',
+                       'wcf.comment.delete.confirmMessage': '{lang}wcf.comment.delete.confirmMessage{/lang}',
+                       'wcf.comment.description': '{lang}wcf.comment.description{/lang}',
+                       'wcf.comment.more': '{lang}wcf.comment.more{/lang}',
+                       'wcf.comment.response.add': '{lang}wcf.comment.response.add{/lang}',
+                       'wcf.comment.response.more': '{lang}wcf.comment.response.more{/lang}',
+                       'wcf.moderation.report.reportContent': '{lang}wcf.moderation.report.reportContent{/lang}',
+                       'wcf.moderation.report.success': '{lang}wcf.moderation.report.success{/lang}'
+               });
+               
+               new {if $commentHandlerClass|isset}{@$commentHandlerClass}{else}WCF.Comment.Handler{/if}('{$commentContainerID}', '{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(32)}');
+               {if MODULE_LIKE && $__wcf->getSession()->getPermission('user.like.canViewLike')}
+                       new WCF.Comment.Like({if $__wcf->getUser()->userID && $__wcf->getSession()->getPermission('user.like.canLike')}1{else}0{/if}, {@LIKE_ENABLE_DISLIKE}, false, {@LIKE_ALLOW_FOR_OWN_CONTENT});
+                       new WCF.Comment.Response.Like({if $__wcf->getUser()->userID && $__wcf->getSession()->getPermission('user.like.canLike')}1{else}0{/if}, {@LIKE_ENABLE_DISLIKE}, false, {@LIKE_ALLOW_FOR_OWN_CONTENT});
+               {/if}
+               
+               new WCF.Moderation.Report.Content('com.woltlab.wcf.comment.comment', '.jsReportCommentComment');
+               new WCF.Moderation.Report.Content('com.woltlab.wcf.comment.response', '.jsReportCommentResponse');
+       });
+       //]]>
+</script>
+
+{event name='javascriptInclude'}
diff --git a/com.woltlab.wcf/template/commentList.tpl b/com.woltlab.wcf/template/commentList.tpl
new file mode 100644 (file)
index 0000000..42d2534
--- /dev/null
@@ -0,0 +1,33 @@
+{foreach from=$commentList item=comment}
+       <li class="comment jsComment" data-comment-id="{@$comment->commentID}" data-object-type="com.woltlab.wcf.comment" data-like-liked="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->liked}{/if}" data-like-likes="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->likes}{else}0{/if}" data-like-dislikes="{if $likeData[comment][$comment->commentID]|isset}{@$likeData[comment][$comment->commentID]->dislikes}{else}0{/if}" data-like-users='{if $likeData[comment][$comment->commentID]|isset}{ {implode from=$likeData[comment][$comment->commentID]->getUsers() item=likeUser}"{@$likeUser->userID}": { "username": "{$likeUser->username|encodeJSON}" }{/implode} }{else}{ }{/if}' data-can-edit="{if $comment->isEditable()}true{else}false{/if}" data-can-delete="{if $comment->isDeletable()}true{else}false{/if}" data-responses="{@$comment->responses}" data-last-response-time="{@$comment->getLastResponseTime()}" data-user-id="{@$comment->userID}">
+               <div class="box32">
+                       <a href="{link controller='User' object=$comment->getUserProfile()}{/link}" title="{$comment->getUserProfile()->username}" class="framed">
+                               {@$comment->getUserProfile()->getAvatar()->getImageTag(32)}
+                       </a>
+                       
+                       <div>
+                               <div class="commentContent">
+                                       <div class="containerHeadline">
+                                               <h3><a href="{link controller='User' object=$comment->getUserProfile()}{/link}">{$comment->username}</a><small> - {@$comment->time|time}</small></h3> 
+                                       </div>
+                                       
+                                       <p class="userMessage">{@$comment->getFormattedMessage()}</p>
+                                       
+                                       <nav class="jsMobileNavigation buttonGroupNavigation">
+                                               <ul class="commentOptions">
+                                                       <li class="jsReportCommentComment jsOnly" data-object-id="{@$comment->commentID}"><a title="{lang}wcf.moderation.report.reportContent{/lang}" class="jsTooltip"><span class="icon icon16 icon-warning-sign"></span> <span class="invisible">{lang}wcf.moderation.report.reportContent{/lang}</span></a></li>
+                                                       
+                                                       {event name='commentOptions'}
+                                               </ul>
+                                       </nav>
+                               </div>
+                               
+                               <ul data-responses="{@$comment->responses}" class="commentResponseList">
+                                       {if $comment|count}
+                                               {include file='commentResponseList' responseList=$comment}
+                                       {/if}
+                               </ul>
+                       </div>
+               </div>
+       </li>
+{/foreach}
diff --git a/com.woltlab.wcf/template/commentResponseList.tpl b/com.woltlab.wcf/template/commentResponseList.tpl
new file mode 100644 (file)
index 0000000..9ecc824
--- /dev/null
@@ -0,0 +1,27 @@
+{foreach from=$responseList item=response}
+       <li class="commentResponse jsCommentResponse" data-response-id="{@$response->responseID}" data-object-type="com.woltlab.wcf.comment.response" data-like-liked="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->liked}{/if}" data-like-likes="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->likes}{else}0{/if}" data-like-dislikes="{if $likeData[response][$response->responseID]|isset}{@$likeData[response][$response->responseID]->dislikes}{else}0{/if}" data-like-users='{if $likeData[response][$response->responseID]|isset}{ {implode from=$likeData[response][$response->responseID]->getUsers() item=likeUser}"{@$likeUser->userID}": { "username": "{$likeUser->username|encodeJSON}" }{/implode} }{else}{ }{/if}' data-can-edit="{if $response->isEditable()}true{else}false{/if}" data-can-delete="{if $response->isDeletable()}true{else}false{/if}" data-user-id="{@$response->userID}">
+               <div class="box32">
+                       <a href="{link controller='User' object=$response->getUserProfile()}{/link}" title="{$response->getUserProfile()->username}" class="framed">
+                               {if $response->getUserProfile()->getAvatar()}
+                                       {@$response->getUserProfile()->getAvatar()->getImageTag(32)}
+                               {/if}
+                       </a>
+                       
+                       <div class="commentContent commentResponseContent">
+                               <div class="containerHeadline">
+                                       <h3><a href="{link controller='User' object=$response->getUserProfile()}{/link}">{$response->username}</a><small> - {@$response->time|time}</small></h3> 
+                               </div>
+                               
+                               <p class="userMessage">{@$response->getFormattedMessage()}</p>
+                               
+                               <nav class="jsMobileNavigation buttonGroupNavigation">
+                                       <ul class="commentOptions">
+                                               <li class="jsReportCommentResponse jsOnly" data-object-id="{@$response->responseID}"><a title="{lang}wcf.moderation.report.reportContent{/lang}" class="jsTooltip"><span class="icon icon16 icon-warning-sign"></span> <span class="invisible">{lang}wcf.moderation.report.reportContent{/lang}</span></a></li>
+                                               
+                                               {event name='commentOptions'}
+                                       </ul>
+                               </nav>
+                       </div>
+               </div>
+       </li>
+{/foreach}
diff --git a/com.woltlab.wcf/template/moderationComment.tpl b/com.woltlab.wcf/template/moderationComment.tpl
new file mode 100644 (file)
index 0000000..becf299
--- /dev/null
@@ -0,0 +1,29 @@
+<article class="message messageReduced">
+       <div>
+               <section class="messageContent">
+                       <div>
+                               <header class="messageHeader">
+                                       <div class="box32">
+                                               <a href="{link controller='User' object=$message->getUserProfile()->getDecoratedObject()}{/link}" class="framed">{@$message->getUserProfile()->getAvatar()->getImageTag(32)}</a>
+                                               
+                                               <div class="messageHeadline">
+                                                       <h1><a href="{@$message->getLink()}">{$message->getTitle()}</a></h1>
+                                                       <p>
+                                                               <span class="username"><a href="{link controller='User' object=$message->getUserProfile()->getDecoratedObject()}{/link}">{$message->getUsername()}</a></span>
+                                                               {@$message->getTime()|time}
+                                                       </p>
+                                               </div>
+                                       </div>
+                               </header>
+                               
+                               <div class="messageBody">
+                                       <div>
+                                               <div class="messageText">
+                                                       {@$message->getFormattedMessage()}
+                                               </div>
+                                       </div>
+                               </div>
+                       </div>
+               </section>
+       </div>
+</article>
\ No newline at end of file
diff --git a/com.woltlab.wcf/template/userProfileCommentList.tpl b/com.woltlab.wcf/template/userProfileCommentList.tpl
new file mode 100644 (file)
index 0000000..ba0d970
--- /dev/null
@@ -0,0 +1,19 @@
+{include file='__commentJavaScript' commentContainerID='userProfileCommentList'}
+
+{if $commentCanAdd}
+       <ul id="userProfileCommentList" class="commentList containerList" data-can-add="true" data-object-id="{@$userID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+               {include file='commentList'}
+       </ul>
+{else}
+       {hascontent}
+               <ul id="userProfileCommentList" class="commentList containerList" data-can-add="false" data-object-id="{@$userID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+                       {content}
+                               {include file='commentList'}
+                       {/content}
+               </ul>
+       {hascontentelse}
+               <div class="containerPadding">
+                       {lang}wcf.user.profile.content.wall.noEntries{/lang}
+               </div>
+       {/hascontent}
+{/if}
\ No newline at end of file
index 50bcbbf3ffc9dfeabb8065c0179b2fa2f421306a..dc89504aa2b1ce49a7a56b477ba89a044ce7363b 100644 (file)
@@ -15,6 +15,9 @@
                        <category name="user.profile">
                                <parent>user</parent>
                        </category>
+                       <category name="user.profileComment">
+                               <parent>user.profile</parent>
+                       </category>
                        
                        <category name="user.signature">
                                <parent>user.profile</parent>
@@ -28,6 +31,9 @@
                        <category name="mod.general">
                                <parent>mod</parent>
                        </category>
+                       <category name="mod.profileComment">
+                               <parent>mod.general</parent>
+                       </category>
                        
                        <category name="admin"></category>
                        <category name="admin.general">
@@ -487,6 +493,48 @@ png]]></defaultvalue>
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
                        </option>
+                       
+                       <!-- mod.general -->
+                       <option name="mod.profileComment.canEditComment">
+                               <categoryname>mod.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
+                       <option name="mod.profileComment.canDeleteComment">
+                               <categoryname>mod.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
+                       <option name="mod.profileComment.canModerateComment">
+                               <categoryname>mod.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
+                       <!-- /mod.general -->
+                       
+                       <!-- user.profileComment -->
+                       <option name="user.profileComment.canAddComment">
+                               <categoryname>user.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       
+                       <option name="user.profileComment.canEditComment">
+                               <categoryname>user.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       
+                       <option name="user.profileComment.canDeleteComment">
+                               <categoryname>user.profileComment</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
+                       <!-- /user.profileComment -->
                </options>
        </import>
 </data>
index 31513410eb3050a3047cba11732544385016d547..9821ee78161a8cbc5025b4d3a9b41282cfe84260 100644 (file)
@@ -6,5 +6,24 @@
                        <objecttype>com.woltlab.wcf.user.follow</objecttype>
                        <classname>wcf\system\user\notification\event\UserFollowFollowingUserNotificationEvent</classname>
                </event>
+               
+               <event>
+                       <name>comment</name>
+                       <objecttype>com.woltlab.wcf.user.profileComment.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\UserProfileCommentUserNotificationEvent</classname>
+                       <preset>1</preset>
+               </event>
+               <event>
+                       <name>commentResponse</name>
+                       <objecttype>com.woltlab.wcf.user.profileComment.response.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\UserProfileCommentResponseUserNotificationEvent</classname>
+                       <preset>1</preset>
+               </event>
+               <event>
+                       <name>commentResponseOwner</name>
+                       <objecttype>com.woltlab.wcf.user.profileComment.response.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\UserProfileCommentResponseOwnerUserNotificationEvent</classname>
+                       <preset>1</preset>
+               </event>
        </import>
 </data>
index 3edcbe88178cffafb3ae97b084f0f277498c6f5b..1bda37ea4f96c9e39e3e34714394cd4ae8ffe5e7 100644 (file)
                                <defaultvalue>1</defaultvalue>
                        </option>
                        <!-- /settings -->
+                       
+                       <option name="canWriteProfileComments">
+                               <categoryname>settings.privacy.messaging</categoryname>
+                               <optiontype>select</optiontype>
+                               <editable>3</editable>
+                               <selectoptions><![CDATA[1:wcf.user.access.registered
+2:wcf.user.access.following
+3:wcf.user.access.nobody]]></selectoptions>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                </options>
        </import>
 </data>
index 59b608ba586363445f8ff9bcd02c92bbd4b0b58b..7c3eabc69da281104e8cbf203ac588e7c717a6d6 100644 (file)
                        <classname>wcf\system\menu\user\profile\content\RecentActivityUserProfileMenuContent</classname>
                        <showorder>1</showorder>
                </userprofilemenuitem>
+               
+               <userprofilemenuitem name="wall">
+                       <classname>wcf\system\menu\user\profile\content\CommentUserProfileMenuContent</classname>
+                       <showorder>1</showorder>
+                       <options>module_user_profile_wall</options>
+               </userprofilemenuitem>
        </import>
 </data>
diff --git a/wcfsetup/install/files/js/WCF.Comment.js b/wcfsetup/install/files/js/WCF.Comment.js
new file mode 100644 (file)
index 0000000..61a3073
--- /dev/null
@@ -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 <http://opensource.org/licenses/lgpl-license.php>
+ */
+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 = $('<li class="commentLoadNext"><button class="buttonPrimary small">' + WCF.Language.get('wcf.comment.more') + '</button></li>').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] = $('<div class="responseLoadNext"><button class="small">' + WCF.Language.get('wcf.comment.response.more') + '</button></div>').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 = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 icon-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
+                       $editButton.data('commentID', commentID).appendTo(comment.find('ul.commentOptions:eq(0)')).click($.proxy(this._prepareEdit, this));
+               }
+               
+               if (comment.data('canDelete')) {
+                       var $deleteButton = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 icon-remove" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
+                       $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 = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.edit') + '"><span class="icon icon16 icon-pencil" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.edit') + '</span></a></li>');
+                       
+                       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 = $('<li><a class="jsTooltip" title="' + WCF.Language.get('wcf.global.button.delete') + '"><span class="icon icon16 icon-remove" /> <span class="invisible">' + WCF.Language.get('wcf.global.button.delete') + '</span></a></li>');
+                       
+                       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 = $('<li class="box32 jsCommentAdd"><span class="framed">' + this._userAvatar + '</span><div /></li>').prependTo(this._container);
+               var $inputContainer = this._commentAdd.children('div');
+               var $input = $('<input type="text" placeholder="' + WCF.Language.get('wcf.comment.add') + '" maxlength="65535" class="long" />').appendTo($inputContainer);
+               $('<small>' + WCF.Language.get('wcf.comment.description') + '</small>').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 = $('<div class="commentResponseAdd jsCommentResponseAddPlaceholder"><a class="button small">' + WCF.Language.get('wcf.comment.button.response.add') + '</a></div>').insertBefore(comment.find('ul.commentResponseList'));
+               $placeholder.data('commentID', commentID).click($.proxy(this._showAddResponse, this));
+               
+               var $listItem = $('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">' + this._userAvatar + '</span><div /></div>').hide().insertAfter($placeholder);
+               var $inputContainer = $listItem.children('div');
+               var $input = $('<input type="text" placeholder="' + WCF.Language.get('wcf.comment.response.add') + '" maxlength="65535" class="long" />').data('commentID', commentID).appendTo($inputContainer);
+               $('<small>' + WCF.Language.get('wcf.comment.description') + '</small>').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 = $('<input type="text" class="long" maxlength="65535" /><small>' + WCF.Language.get('wcf.comment.description') + '</small>').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 (file)
index 0000000..af7456d
--- /dev/null
@@ -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<this._container.data("comments")){if(this._loadNextComments===null){this._loadNextComments=$('<li class="commentLoadNext"><button class="buttonPrimary small">'+WCF.Language.get("wcf.comment.more")+"</button></li>").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")<b.data("responses")){if(this._loadNextResponses[a]===undefined){this._loadNextResponses[a]=$('<div class="responseLoadNext"><button class="small">'+WCF.Language.get("wcf.comment.response.more")+"</button></div>").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=$('<li><a class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 icon-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>");b.data("commentID",a).appendTo(d.find("ul.commentOptions:eq(0)")).click($.proxy(this._prepareEdit,this))}if(d.data("canDelete")){var c=$('<li><a class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 icon-remove" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>");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=$('<li><a class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.edit")+'"><span class="icon icon16 icon-pencil" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.edit")+"</span></a></li>");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=$('<li><a class="jsTooltip" title="'+WCF.Language.get("wcf.global.button.delete")+'"><span class="icon icon16 icon-remove" /> <span class="invisible">'+WCF.Language.get("wcf.global.button.delete")+"</span></a></li>");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=$('<li class="box32 jsCommentAdd"><span class="framed">'+this._userAvatar+"</span><div /></li>").prependTo(this._container);var a=this._commentAdd.children("div");var b=$('<input type="text" placeholder="'+WCF.Language.get("wcf.comment.add")+'" maxlength="65535" class="long" />').appendTo(a);$("<small>"+WCF.Language.get("wcf.comment.description")+"</small>").appendTo(a);b.keyup($.proxy(this._keyUp,this))},_initAddResponse:function(d,g){var c=$('<div class="commentResponseAdd jsCommentResponseAddPlaceholder"><a class="button small">'+WCF.Language.get("wcf.comment.button.response.add")+"</a></div>").insertBefore(g.find("ul.commentResponseList"));c.data("commentID",d).click($.proxy(this._showAddResponse,this));var e=$('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">'+this._userAvatar+"</span><div /></div>").hide().insertAfter(c);var a=e.children("div");var f=$('<input type="text" placeholder="'+WCF.Language.get("wcf.comment.response.add")+'" maxlength="65535" class="long" />').data("commentID",d).appendTo(a);$("<small>"+WCF.Language.get("wcf.comment.description")+"</small>").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=$('<input type="text" class="long" maxlength="65535" /><small>'+WCF.Language.get("wcf.comment.description")+"</small>").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
index c7fb332530333394b42f53a2307909a12df279e5..311717d65b91377ccc1a4775deb15e6fd9efacfc 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\acp\form;
+use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\user\UserAction;
 use wcf\form\AbstractForm;
 use wcf\system\clipboard\ClipboardHandler;
@@ -106,6 +107,35 @@ class UserMergeForm extends AbstractForm {
                
                parent::save();
                
+               // comment
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->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 (file)
index 0000000..73f122b
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\DatabaseObject;
+use wcf\data\IMessage;
+use wcf\system\bbcode\SimpleMessageParser;
+use wcf\system\comment\CommentHandler;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a comment.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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<integer>
+        */
+       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 (file)
index 0000000..c758a1e
--- /dev/null
@@ -0,0 +1,576 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\CommentResponseAction;
+use wcf\data\comment\response\CommentResponseEditor;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\data\comment\response\StructuredCommentResponse;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\UserProfile;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\comment\CommentHandler;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\like\LikeHandler;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
+use wcf\system\user\notification\object\CommentUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Executes comment-related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..4000a93
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\DatabaseObjectEditor;
+use wcf\system\WCF;
+
+/**
+ * Provides functions to edit comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..b454f26
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..386849b
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\like\object\AbstractLikeObject;
+use wcf\data\object\type\ObjectTypeCache;
+
+/**
+ * Likeable object implementation for comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..b5ec9d3
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\like\object\ILikeObject;
+use wcf\data\like\ILikeObjectTypeProvider;
+use wcf\data\object\type\AbstractObjectTypeProvider;
+use wcf\system\comment\CommentHandler;
+
+/**
+ * Object type provider for comments
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..5a13c6d
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\comment\response\StructuredCommentResponse;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Provides methods to handle responses for this comment.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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<wcf\data\comment\response\StructuredCommentResponse>
+        */
+       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<wcf\data\comment\response\StructuredCommentReponse>
+        */
+       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 (file)
index 0000000..de6b99d
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\data\comment\response\StructuredCommentResponse;
+use wcf\data\user\UserProfile;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\like\LikeHandler;
+
+/**
+ * Provides a structured comment list fetching last responses for every comment.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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<integer>
+        */
+       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 (file)
index 0000000..ca0f341
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\data\comment;
+use wcf\data\user\User;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Represents a viewable comment.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..53ddc96
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\comment\Comment;
+use wcf\data\DatabaseObject;
+use wcf\data\IMessage;
+use wcf\system\bbcode\SimpleMessageParser;
+use wcf\system\comment\CommentHandler;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a comment response.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..8795aee
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentEditor;
+use wcf\data\comment\CommentList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+
+/**
+ * Executes comment response-related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..c391bdf
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..86b165b
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..016501c
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\like\object\AbstractLikeObject;
+use wcf\data\object\type\ObjectTypeCache;
+
+/**
+ * Likeable object implementation for comment reponses.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..b95a93e
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\comment\Comment;
+use wcf\data\like\object\ILikeObject;
+use wcf\data\like\ILikeObjectTypeProvider;
+use wcf\data\object\type\AbstractObjectTypeProvider;
+use wcf\system\comment\CommentHandler;
+
+/**
+ * Object type provider for likeable comment responses.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..7e56d80
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Provides methods to handle response data.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..2ebfa06
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\comment\Comment;
+use wcf\data\user\UserProfile;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\like\LikeHandler;
+
+/**
+ * Provides a structured comment response list.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..04a49ab
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\data\comment\response;
+use wcf\data\user\User;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Represents a viewable comment response.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..62c677e
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+namespace wcf\system\comment;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\data\comment\CommentEditor;
+use wcf\data\comment\CommentList;
+use wcf\data\comment\StructuredCommentList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\exception\SystemException;
+use wcf\system\like\LikeHandler;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\SingletonFactory;
+
+/**
+ * Provides methods for comment object handling.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.comment
+ * @subpackage system.comment
+ * @category   Community Framework
+ */
+class CommentHandler extends SingletonFactory {
+       /**
+        * cached object types
+        * @var array<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<integer>  $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 (file)
index 0000000..a881dbe
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+namespace wcf\system\comment\manager;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\Comment;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Default implementation for comment managers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..6e47956
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace wcf\system\comment\manager;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\Comment;
+
+/**
+ * Default interface for comment managers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..545fa4a
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+namespace wcf\system\comment\manager;
+use wcf\data\user\UserProfile;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * User profile comment manager implementation.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..e629c38
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+namespace wcf\system\menu\user\profile\content;
+use wcf\system\comment\CommentHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles user profile comment content.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..c2562b8
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+namespace wcf\system\moderation\queue\report;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentAction;
+use wcf\data\comment\CommentList;
+use wcf\data\comment\ViewableComment;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\moderation\queue\AbstractModerationQueueHandler;
+use wcf\system\moderation\queue\ModerationQueueManager;
+use wcf\system\WCF;
+
+/**
+ * An implementation of IModerationQueueReportHandler for comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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<wcf\data\comment\Comment>
+        */
+       protected static $comments = array();
+       
+       /**
+        * list of comment managers
+        * @var array<wcf\system\comment\manager\ICommentManager>
+        */
+       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 (file)
index 0000000..d06ffc0
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+namespace wcf\system\moderation\queue\report;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\CommentResponseAction;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\data\comment\response\ViewableCommentResponse;
+use wcf\data\comment\Comment;
+use wcf\data\comment\CommentList;
+use wcf\data\moderation\queue\ModerationQueue;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\moderation\queue\ModerationQueueManager;
+use wcf\system\WCF;
+
+/**
+ * An implementation of IModerationQueueReportHandler for comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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<wcf\data\comment\response\CommentResponse>
+        */
+       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 (file)
index 0000000..e6d9f2f
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+namespace wcf\system\user\activity\event;
+use wcf\data\comment\response\CommentResponseList;
+use wcf\data\comment\CommentList;
+use wcf\data\user\UserList;
+use wcf\system\user\activity\event\IUserActivityEvent;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * User activity event implementation for profile comment responses.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..971f424
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+namespace wcf\system\user\activity\event;
+use wcf\data\comment\CommentList;
+use wcf\data\user\UserList;
+use wcf\system\user\activity\event\IUserActivityEvent;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * User activity event implementation for profile comments.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..5436520
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\comment\Comment;
+use wcf\data\user\User;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\event\AbstractUserNotificationEvent;
+use wcf\system\WCF;
+
+/**
+ * User notification event for profile's owner for commment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..e5d4b8c
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\comment\Comment;
+use wcf\data\user\User;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\event\AbstractUserNotificationEvent;
+
+/**
+ * User notification event for profile commment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..0c1f7cf
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\user\User;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\event\AbstractUserNotificationEvent;
+use wcf\system\WCF;
+
+/**
+ * User notification event for profile commments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..f37d292
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Notification object for comment responses.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..5761d1f
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Notification object for comments.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..edc0ceb
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+
+/**
+ * Default interface for comment user notification object types.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..3c5279e
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+
+/**
+ * Represents a comment response notification object type.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..be08d04
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\system\WCF;
+
+/**
+ * Represents a comment notification object type.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.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 (file)
index 0000000..a450df1
--- /dev/null
@@ -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;
+}
index ea86530fe5bd277f70581a103a798ebed74c1afa..d6d4bf29315ffae31b726f69864464be851ff697 100644 (file)
                <item name="wcf.acp.group.option.category.user.like"><![CDATA[Like-System]]></item>
                <item name="wcf.acp.group.option.user.like.canViewLike"><![CDATA[Kann vergebene Likes sehen]]></item>
                <item name="wcf.acp.group.option.user.like.canLike"><![CDATA[Kann Inhalte liken]]></item>
+               <item name="wcf.acp.group.option.category.user.profileComment"><![CDATA[Benutzerprofil-Pinnwand]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canAddComment"><![CDATA[Kann Pinnwand-Kommentare erstellen]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canEditComment"><![CDATA[Kann eigene Pinnwand-Kommentare bearbeiten]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canDeleteComment"><![CDATA[Kann eigene Pinnwand-Kommentare löschen]]></item>
+               <item name="wcf.acp.group.option.mod.profileComment.canEditComment"><![CDATA[Kann Pinnwand-Kommentare bearbeiten]]></item>
+               <item name="wcf.acp.group.option.mod.profileComment.canDeleteComment"><![CDATA[Kann Pinnwand-Kommentare löschen]]></item>
+               <item name="wcf.acp.group.option.mod.profileComment.canModerateComment"><![CDATA[Kann Pinnwand-Kommentare moderieren]]></item>
+               <item name="wcf.acp.group.option.category.mod.profileComment"><![CDATA[Benutzerprofil-Pinnwand]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.option.like_allow_for_own_content"><![CDATA[Benutzer können eigene Inhalte liken]]></item>
                <item name="wcf.acp.option.like_enable_dislike"><![CDATA[Benutzer können Inhalte disliken]]></item>
                <item name="wcf.acp.option.like_show_summary"><![CDATA[Zusammenfassung anzeigen]]></item>
+               <item name="wcf.acp.option.module_user_profile_wall"><![CDATA[Benutzerprofil-Pinnwand]]></item>
        </category>
        
        <category name="wcf.acp.package">
@@ -1190,7 +1199,6 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.bbcode.spoiler.text"><![CDATA[(Versteckter Text)]]></item>
        </category>
        
-       
        <category name="wcf.category">
                <item name="wcf.category.add"><![CDATA[Kategorie hinzufügen]]></item>
                <item name="wcf.category.button.list"><![CDATA[Kategorien auflisten]]></item>
@@ -1219,6 +1227,16 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Aktivieren]]></item>
        </category>
        
+       <category name="wcf.comment">
+               <item name="wcf.comment.add"><![CDATA[Kommentar schreiben …]]></item>
+               <item name="wcf.comment.delete.confirmMessage"><![CDATA[Wollen Sie diesen Kommentar wirklich löschen?]]></item>
+               <item name="wcf.comment.description"><![CDATA[Drücken Sie die Eingabetaste, um abzusenden oder Escape, um abzubrechen.]]></item>
+               <item name="wcf.comment.more"><![CDATA[Weitere Kommentare]]></item>
+               <item name="wcf.comment.response.add"><![CDATA[Antworten …]]></item>
+               <item name="wcf.comment.response.more"><![CDATA[Weitere Antworten]]></item>
+               <item name="wcf.comment.button.response.add"><![CDATA[Antworten]]></item>
+       </category>
+       
        <category name="wcf.dashboard">
                <item name="wcf.dashboard.box.availableBoxes"><![CDATA[Deaktivierte Boxen]]></item>
                <item name="wcf.dashboard.box.enabledBoxes"><![CDATA[Aktive Boxen]]></item>
@@ -1504,6 +1522,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.like.details"><![CDATA[Details]]></item>
                <item name="wcf.like.details.like"><![CDATA[Likes]]></item>
                <item name="wcf.like.details.dislike"><![CDATA[Dislikes]]></item>
+               <item name="wcf.like.objectType.com.woltlab.wcf.comment"><![CDATA[Kommentar]]></item>
+               <item name="wcf.like.objectType.com.woltlab.wcf.comment.response"><![CDATA[Kommentar-Antwort]]></item>
        </category>
        
        <category name="wcf.message">
@@ -1965,6 +1985,8 @@ Sollten Sie sich nicht auf der Website: {@PAGE_TITLE|language} angemeldet haben,
                <item name="wcf.user.recentActivity.noMoreEntries"><![CDATA[Keine weiteren Aktivitäten]]></item>
                <item name="wcf.user.recentActivity.noEntries"><![CDATA[Es sind keine Aktivitäten vorhanden.]]></item>
                <item name="wcf.user.recentActivity.com.woltlab.wcf.user.recentActivityEvent.follow"><![CDATA[Folgen]]></item>
+               <item name="wcf.user.recentActivity.com.woltlab.wcf.user.profileComment.recentActivityEvent"><![CDATA[Pinnwand-Kommentar]]></item>
+               <item name="wcf.user.recentActivity.com.woltlab.wcf.user.profileComment.response.recentActivityEvent"><![CDATA[Pinnwand-Antwort]]></item>
        </category>
        
        <category name="wcf.user.3rdparty">
@@ -2065,6 +2087,21 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn
                <item name="wcf.user.notification.showAll"><![CDATA[Alle Benachrichtigungen anzeigen]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[Benutzer-Profile]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.follow.following"><![CDATA[Jemand folgt Ihnen]]></item>
+               <item name="wcf.user.notification.comment.title"><![CDATA[Neuer Kommentar (Pinnwand)]]></item>
+               <item name="wcf.user.notification.comment.message"><![CDATA[Hat einen Kommentar an Ihrer Pinnwand verfasst.]]></item>
+               <item name="wcf.user.notification.comment.mail"><![CDATA[{@$author->username} hat einen Kommentar an Ihrer Pinnwand verfasst:
+{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]></item>
+               <item name="wcf.user.notification.commentResponse.title"><![CDATA[Neue Antwort (Pinnwand)]]></item>
+               <item name="wcf.user.notification.commentResponse.message"><![CDATA[Hat eine Antwort zu Ihrem Kommentar an der Pinnwand von {$owner->username} verfasst.]]></item>
+               <item name="wcf.user.notification.commentResponse.mail"><![CDATA[{@$author->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]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.title"><![CDATA[Neue Antwort (Pinnwand)]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.message"><![CDATA[Hat eine Antwort zum Kommentar von {$commentAuthor->username} an Ihrer Pinnwand verfasst.]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.mail"><![CDATA[{@$author->username} hat eine Antwort zum Kommentar von "{@$commentAuthor->username}" an Ihrer Pinnwand verfasst:
+{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.notification.comment"><![CDATA[Neuer Kommentar an Ihrer Pinnwand]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponse"><![CDATA[Neue Antwort auf einen Kommentar an Ihrer Pinnwand]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponseOwner"><![CDATA[Neue Antwort auf einen Kommentar von Ihnen]]></item>
        </category>
        
        <category name="wcf.user.profile">
@@ -2079,6 +2116,12 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn
                <item name="wcf.user.profile.oldUsername"><![CDATA[Hieß früher „{$user->getOldUsername()}“]]></item>
                <item name="wcf.user.profile.recentActivity.follow"><![CDATA[Folgt nun <a href="{link controller='User' object=$user}{/link}">{$user->username}</a>.]]></item>
                <item name="wcf.user.profile.visitors"><![CDATA[Profil-Besucher]]></item>
+               <item name="wcf.user.profile.content.wall.comment"><![CDATA[Kommentar an der Pinnwand]]></item>
+               <item name="wcf.user.profile.content.wall.commentResponse"><![CDATA[Antwort auf einen Pinnwand-Kommentar]]></item>
+               <item name="wcf.user.profile.content.wall.noEntries"><![CDATA[Es wurden noch keine Einträge an der Pinnwand verfasst.]]></item>
+               <item name="wcf.user.profile.menu.wall"><![CDATA[Pinnwand]]></item>
+               <item name="wcf.user.profile.recentActivity.profileComment"><![CDATA[Hat einen Kommentar an die <a href="{link controller='User' object=$user}{/link}#wall">Pinnwand von {$user->username}</a> geschrieben.]]></item>
+               <item name="wcf.user.profile.recentActivity.profileCommentResponse"><![CDATA[Hat auf einen Kommentar von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> an der <a href="{link controller='User' object=$user}{/link}#wall">Pinnwand von {$user->username}</a> geantwortet.]]></item>
        </category>
        
        <category name="wcf.user.objectWatch">
@@ -2123,6 +2166,7 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn
                <item name="wcf.user.option.twitter"><![CDATA[Twitter]]></item>
                <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
                <item name="wcf.user.option.googlePlus.description"><![CDATA[Geben Sie Ihre 21-stellige Google+-ID an.]]></item>
+               <item name="wcf.user.option.canWriteProfileComments"><![CDATA[Kann Pinnwand-Kommentare schreiben]]></item>
        </category>
        
        <category name="wcf.user.mail">
index a7fe5f246c6825b33a7f84f36e1eb6349d3455eb..d703d0e12db10a282cfa70ea1e41cd9541637f6c 100644 (file)
@@ -295,6 +295,14 @@ Examples for medium ID detection:
                <item name="wcf.acp.group.option.category.user.like"><![CDATA[Like System]]></item>
                <item name="wcf.acp.group.option.user.like.canViewLike"><![CDATA[Can view likes]]></item>
                <item name="wcf.acp.group.option.user.like.canLike"><![CDATA[Can like content]]></item>
+               <item name="wcf.acp.group.option.category.user.profileComment"><![CDATA[User Profile Wall]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canAddComment"><![CDATA[Can create comments]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canEditComment"><![CDATA[Cam edit own comments]]></item>
+               <item name="wcf.acp.group.option.user.profileComment.canDeleteComment"><![CDATA[Cam delete own comments]]></item>
+               <item name="wcf.acp.group.option.mod.profileComment.canEditComment"><![CDATA[Cam edit comments]]></item>
+               <item name="wcf.acp.group.option.mod.profileComment.canDeleteComment"><![CDATA[Can delete comments]]></item>
+               <item name="wcf.acp.group.option.mod.profileComment.canModerateComment"><![CDATA[Can moderate comments]]></item>
+               <item name="wcf.acp.group.option.category.mod.profileComment"><![CDATA[User Profile Wall]]></item>
        </category>
        
        <category name="wcf.acp.index">
@@ -707,6 +715,7 @@ Examples for medium ID detection:
                <item name="wcf.acp.option.like_allow_for_own_content"><![CDATA[Users can like their own content]]></item>
                <item name="wcf.acp.option.like_enable_dislike"><![CDATA[Users can dislike content]]></item>
                <item name="wcf.acp.option.like_show_summary"><![CDATA[Show like summary]]></item>
+               <item name="wcf.acp.option.module_user_profile_wall"><![CDATA[User Profile Wall]]></item>
        </category>
        
        <category name="wcf.acp.package">
@@ -1216,6 +1225,16 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Approve]]></item>
        </category>
        
+       <category name="wcf.comment">
+               <item name="wcf.comment.add"><![CDATA[Write a comment …]]></item>
+               <item name="wcf.comment.delete.confirmMessage"><![CDATA[Do you really want to delete this comment?]]></item>
+               <item name="wcf.comment.description"><![CDATA[Press Enter to send or Escape to cancel.]]></item>
+               <item name="wcf.comment.more"><![CDATA[More Comments]]></item>
+               <item name="wcf.comment.response.add"><![CDATA[Reply …]]></item>
+               <item name="wcf.comment.response.more"><![CDATA[More Replies]]></item>
+               <item name="wcf.comment.button.response.add"><![CDATA[Reply]]></item>
+       </category>
+       
        <category name="wcf.dashboard">
                <item name="wcf.dashboard.box.availableBoxes"><![CDATA[Disabled Boxes]]></item>
                <item name="wcf.dashboard.box.enabledBoxes"><![CDATA[Active Boxes]]></item>
@@ -1501,6 +1520,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <item name="wcf.like.details"><![CDATA[Details]]></item>
                <item name="wcf.like.details.like"><![CDATA[Likes]]></item>
                <item name="wcf.like.details.dislike"><![CDATA[Dislikes]]></item>
+               <item name="wcf.like.objectType.com.woltlab.wcf.comment"><![CDATA[Comment]]></item>
+               <item name="wcf.like.objectType.com.woltlab.wcf.comment.response"><![CDATA[Comment Reply]]></item>
        </category>
        
        <category name="wcf.message">
@@ -1928,6 +1949,8 @@ You can safely ignore this email if you did not register with the website: {@PAG
                <item name="wcf.user.recentActivity.noMoreEntries"><![CDATA[No more activities]]></item>
                <item name="wcf.user.recentActivity.noEntries"><![CDATA[There are no activities.]]></item>
                <item name="wcf.user.recentActivity.com.woltlab.wcf.user.recentActivityEvent.follow"><![CDATA[Follow]]></item>
+               <item name="wcf.user.recentActivity.com.woltlab.wcf.user.profileComment.recentActivityEvent"><![CDATA[Wall Comment]]></item>
+               <item name="wcf.user.recentActivity.com.woltlab.wcf.user.profileComment.response.recentActivityEvent"><![CDATA[Wall Reply]]></item>
        </category>
        
        <category name="wcf.user.3rdparty">
@@ -2028,6 +2051,21 @@ If you do not want to receive further email notifications for this event, you ca
                <item name="wcf.user.notification.showAll"><![CDATA[Show All Notifications]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[User Profiles]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.follow.following"><![CDATA[New follower]]></item>
+               <item name="wcf.user.notification.comment.title"><![CDATA[New Comment (Wall)]]></item>
+               <item name="wcf.user.notification.comment.message"><![CDATA[Wrote a comment on your wall.]]></item>
+               <item name="wcf.user.notification.comment.mail"><![CDATA[{@$author->username} wrote a comment on your wall:
+{link controller='User' object=$owner encode=false forceFrontend=true}#wall{/link}]]></item>
+               <item name="wcf.user.notification.commentResponse.title"><![CDATA[New Reply (Wall)]]></item>
+               <item name="wcf.user.notification.commentResponse.message"><![CDATA[Wrote a reply to your comment on {$owner->username}’s wall.]]></item>
+               <item name="wcf.user.notification.commentResponse.mail"><![CDATA[{@$author->username} wrote a reply to your comment on {@$owner->username}’s wall:
+{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.title"><![CDATA[New Reply (Wall)]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.message"><![CDATA[Wrote a reply to {$commentAuthor->username}’s comment on your wall.]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.mail"><![CDATA[{@$author->username} wrote a reply to {@$commentAuthor->username}’s comment on your wall:
+{link controller='User' object=$owner encode=false forceFrontend=true}{/link}#wall]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.notification.comment"><![CDATA[New comment on your wall]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponse"><![CDATA[New reply to a comment on your wall]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponseOwner"><![CDATA[New reply to one of your comments]]></item>
        </category>
        
        <category name="wcf.user.profile">
@@ -2042,6 +2080,12 @@ If you do not want to receive further email notifications for this event, you ca
                <item name="wcf.user.profile.oldUsername"><![CDATA[Used to be called “{$user->getOldUsername()}”]]></item>
                <item name="wcf.user.profile.recentActivity.follow"><![CDATA[Now follows <a href="{link controller='User' object=$user}{/link}">{$user->username}</a>.]]></item>
                <item name="wcf.user.profile.visitors"><![CDATA[Profile Visitors]]></item>
+               <item name="wcf.user.profile.content.wall.comment"><![CDATA[Wall Comment]]></item>
+               <item name="wcf.user.profile.content.wall.commentResponse"><![CDATA[Reply to wall comment]]></item>
+               <item name="wcf.user.profile.content.wall.noEntries"><![CDATA[There are no comments yet.]]></item>
+               <item name="wcf.user.profile.menu.wall"><![CDATA[Wall]]></item>
+               <item name="wcf.user.profile.recentActivity.profileComment"><![CDATA[Wrote a comment on <a href="{link controller='User' object=$user}{/link}#wall">{$user->username}’s wall</a>.]]></item>
+               <item name="wcf.user.profile.recentActivity.profileCommentResponse"><![CDATA[Replied to a comment by <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a> on <a href="{link controller='User' object=$user}{/link}#wall">{$user->username}’s wall</a>.]]></item>
        </category>
        
        <category name="wcf.user.objectWatch">
@@ -2086,6 +2130,7 @@ If you do not want to receive further email notifications for this event, you ca
                <item name="wcf.user.option.twitter"><![CDATA[Twitter]]></item>
                <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
                <item name="wcf.user.option.googlePlus.description"><![CDATA[Enter your Google Plus user ID with a length of 21 digits.]]></item>
+               <item name="wcf.user.option.canWriteProfileComments"><![CDATA[Can Write Comments on My Wall]]></item>
        </category>
        
        <category name="wcf.user.mail">
index f9a4b92d964bd8009d80f389389612bd76d68483..edf04c52c7412eeff2321d6ba19ab2f6b08af019 100644 (file)
@@ -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