From fc69b61d9eeaba3cfd29ae68d2b058319a1207cc Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Tue, 21 May 2013 00:00:59 +0200 Subject: [PATCH] Merged com.woltlab.wcf.moderation into WCF --- com.woltlab.wcf/coreObject.xml | 4 + com.woltlab.wcf/cronjob.xml | 13 + com.woltlab.wcf/objectType.xml | 13 + com.woltlab.wcf/objectTypeDefinition.xml | 15 + .../template/moderationActivation.tpl | 113 ++++++ com.woltlab.wcf/template/moderationList.tpl | 141 +++++++ .../template/moderationQueueList.tpl | 13 + com.woltlab.wcf/template/moderationReport.tpl | 121 ++++++ .../template/moderationReportDialog.tpl | 22 + com.woltlab.wcf/template/userPanel.tpl | 23 ++ com.woltlab.wcf/userGroupOption.xml | 7 + wcfsetup/install/files/js/WCF.Moderation.js | 383 ++++++++++++++++++ .../install/files/js/WCF.Moderation.min.js | 1 + .../files/lib/acp/form/UserEditForm.class.php | 18 +- .../queue/ModerationQueue.class.php | 79 ++++ .../queue/ModerationQueueAction.class.php | 103 +++++ .../ModerationQueueActivationAction.class.php | 67 +++ .../queue/ModerationQueueEditor.class.php | 38 ++ .../queue/ModerationQueueList.class.php | 20 + .../ModerationQueueReportAction.class.php | 134 ++++++ .../queue/ViewableModerationQueue.class.php | 149 +++++++ .../ViewableModerationQueueList.class.php | 121 ++++++ .../lib/form/AbstractModerationForm.class.php | 169 ++++++++ .../form/ModerationActivationForm.class.php | 27 ++ .../lib/form/ModerationReportForm.class.php | 27 ++ .../lib/page/ModerationListPage.class.php | 134 ++++++ .../cronjob/ModerationQueueCronjob.class.php | 53 +++ .../AbstractModerationQueueHandler.class.php | 59 +++ .../AbstractModerationQueueManager.class.php | 122 ++++++ .../queue/IModerationQueueHandler.class.php | 62 +++ .../queue/IModerationQueueManager.class.php | 74 ++++ ...ModerationQueueActivationManager.class.php | 83 ++++ .../queue/ModerationQueueManager.class.php | 366 +++++++++++++++++ .../ModerationQueueReportManager.class.php | 108 +++++ ...ModerationQueueActivationHandler.class.php | 32 ++ .../IModerationQueueReportHandler.class.php | 40 ++ wcfsetup/install/lang/de.xml | 53 +++ wcfsetup/install/lang/en.xml | 53 +++ wcfsetup/setup/db/install.sql | 38 ++ 39 files changed, 3092 insertions(+), 6 deletions(-) create mode 100644 com.woltlab.wcf/template/moderationActivation.tpl create mode 100644 com.woltlab.wcf/template/moderationList.tpl create mode 100644 com.woltlab.wcf/template/moderationQueueList.tpl create mode 100644 com.woltlab.wcf/template/moderationReport.tpl create mode 100644 com.woltlab.wcf/template/moderationReportDialog.tpl create mode 100644 wcfsetup/install/files/js/WCF.Moderation.js create mode 100644 wcfsetup/install/files/js/WCF.Moderation.min.js create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ModerationQueue.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueActivationAction.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueEditor.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueList.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueReportAction.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueue.class.php create mode 100644 wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueueList.class.php create mode 100644 wcfsetup/install/files/lib/form/AbstractModerationForm.class.php create mode 100644 wcfsetup/install/files/lib/form/ModerationActivationForm.class.php create mode 100644 wcfsetup/install/files/lib/form/ModerationReportForm.class.php create mode 100644 wcfsetup/install/files/lib/page/ModerationListPage.class.php create mode 100644 wcfsetup/install/files/lib/system/cronjob/ModerationQueueCronjob.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueHandler.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueManager.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueHandler.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueManager.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueActivationManager.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueManager.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueReportManager.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/activation/IModerationQueueActivationHandler.class.php create mode 100644 wcfsetup/install/files/lib/system/moderation/queue/report/IModerationQueueReportHandler.class.php diff --git a/com.woltlab.wcf/coreObject.xml b/com.woltlab.wcf/coreObject.xml index a6642cca0e..cbf8994d9c 100644 --- a/com.woltlab.wcf/coreObject.xml +++ b/com.woltlab.wcf/coreObject.xml @@ -45,5 +45,9 @@ + + + + diff --git a/com.woltlab.wcf/cronjob.xml b/com.woltlab.wcf/cronjob.xml index 4ff071a68d..bd1ef206e0 100644 --- a/com.woltlab.wcf/cronjob.xml +++ b/com.woltlab.wcf/cronjob.xml @@ -120,5 +120,18 @@ 1 1 + + + wcf\system\cronjob\ModerationQueueCronjob + Moderation Queue Cleanup + 0 + 1 + * + * + * + 1 + 1 + 1 + \ No newline at end of file diff --git a/com.woltlab.wcf/objectType.xml b/com.woltlab.wcf/objectType.xml index 6495cc84ce..2e628f4653 100644 --- a/com.woltlab.wcf/objectType.xml +++ b/com.woltlab.wcf/objectType.xml @@ -126,5 +126,18 @@ wcf\form\SignatureEditForm wcf.user.usersOnline.location.SignatureEditForm + + + + com.woltlab.wcf.moderation.activation + com.woltlab.wcf.moderation.type + wcf\system\moderation\queue\ModerationQueueActivationManager + + + com.woltlab.wcf.moderation.report + com.woltlab.wcf.moderation.type + wcf\system\moderation\queue\ModerationQueueReportManager + + \ No newline at end of file diff --git a/com.woltlab.wcf/objectTypeDefinition.xml b/com.woltlab.wcf/objectTypeDefinition.xml index 1bddc5b386..57f677e7fd 100644 --- a/com.woltlab.wcf/objectTypeDefinition.xml +++ b/com.woltlab.wcf/objectTypeDefinition.xml @@ -74,5 +74,20 @@ com.woltlab.wcf.visitTracker.objectType + + + com.woltlab.wcf.moderation.type + wcf\system\moderation\queue\IModerationQueueManager + + + + com.woltlab.wcf.moderation.activation + wcf\system\moderation\queue\activation\IModerationQueueActivationHandler + + + + com.woltlab.wcf.moderation.report + wcf\system\moderation\queue\report\IModerationQueueReportHandler + diff --git a/com.woltlab.wcf/template/moderationActivation.tpl b/com.woltlab.wcf/template/moderationActivation.tpl new file mode 100644 index 0000000000..14dbecca6f --- /dev/null +++ b/com.woltlab.wcf/template/moderationActivation.tpl @@ -0,0 +1,113 @@ +{include file='documentHeader'} + + + {lang}wcf.moderation.activation{/lang} - {PAGE_TITLE|language} + + {include file='headInclude'} + + + + + + + +{include file='header' sidebarOrientation='left'} + +
+

{lang}wcf.moderation.activation{/lang}

+
+ +{include file='userNotice'} + +
+ +
+ +
+
+ {lang}wcf.moderation.activation.details{/lang} + +
+
{lang}wcf.global.objectID{/lang}
+
{#$queue->queueID}
+
+ {if $queue->lastChangeTime} +
+
{lang}wcf.moderation.lastChangeTime{/lang}
+
{@$queue->lastChangeTime|time}
+
+ {/if} +
+
{lang}wcf.moderation.assignedUser{/lang}
+
+
    + {if $assignedUserID && ($assignedUserID != $__wcf->getUser()->userID)} +
  • + {/if} +
  • +
  • +
+
+
+ {if $queue->assignedUser} +
+ +
{$queue->assignedUsername}
+
+ {/if} +
+
+
+
+ + {event name='detailsFields'} + +
+ +
+
+ + {event name='fieldsets'} +
+ +
+

{lang}wcf.moderation.activation.content{/lang}

+
+ +
+ {@$disabledContent} +
+ +
+ +
+ +{include file='footer'} + + + \ No newline at end of file diff --git a/com.woltlab.wcf/template/moderationList.tpl b/com.woltlab.wcf/template/moderationList.tpl new file mode 100644 index 0000000000..289cb5246f --- /dev/null +++ b/com.woltlab.wcf/template/moderationList.tpl @@ -0,0 +1,141 @@ +{include file='documentHeader'} + + + {lang}wcf.moderation.moderation{/lang} {if $pageNo > 1}- {lang}wcf.page.pageNo{/lang} {/if}- {PAGE_TITLE|language} + + {include file='headInclude'} + + + + +{capture assign='sidebar'} + {* moderation type *} +
+ {lang}wcf.moderation.filterByType{/lang} + + +
+ + {* assigned user *} +
+ {lang}wcf.moderation.filterByUser{/lang} + + +
+ + {* status *} +
+ {lang}wcf.moderation.status{/lang} + + +
+ + {event name='sidebarBoxes'} +{/capture} + +{include file='header' sidebarOrientation='left'} + +
+

{lang}wcf.moderation.moderation{/lang}

+
+ +{include file='userNotice'} + +
+ {pages print=true assign=pagesLinks controller='ModerationList' link="id=$definitionID&pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"} + + {hascontent} + + {/hascontent} +
+ +{if $objects|count} +
+
+

{if $status == 2}{lang}wcf.moderation.doneItems{/lang}{else}{lang}wcf.moderation.outstandingItems{/lang}{/if} {#$items}

+
+ + + + + + + + + + {event name='columnHeads'} + + + + + {foreach from=$objects item=entry} + + + + + + + {event name='columns'} + + {/foreach} + +
{lang}wcf.global.objectID{/lang}{lang}wcf.moderation.title{/lang}{lang}wcf.moderation.assignedUser{/lang}{lang}wcf.moderation.lastChangeTime{/lang}
{#$entry->queueID} +

+ {lang}wcf.moderation.type.{@$definitionNames[$entry->objectTypeID]}{/lang} + {$entry->getTitle()} +

+ {if $entry->userID}{$entry->username}{else}{lang}wcf.user.guest{/lang}{/if} - {@$entry->time|time} +
{if $entry->assignedUserID}{$entry->assignedUsername}{/if}{if $entry->lastChangeTime}{@$entry->lastChangeTime|time}{/if}
+
+ +
+ {@$pagesLinks} + + {hascontent} + + {/hascontent} +
+{else} +

{lang}wcf.moderation.noItems{/lang}

+{/if} + +{include file='footer'} + + + \ No newline at end of file diff --git a/com.woltlab.wcf/template/moderationQueueList.tpl b/com.woltlab.wcf/template/moderationQueueList.tpl new file mode 100644 index 0000000000..5ba8cb0e67 --- /dev/null +++ b/com.woltlab.wcf/template/moderationQueueList.tpl @@ -0,0 +1,13 @@ +{foreach from=$queues item=queue} +
  • + +
    + {@$queue->getUserProfile()->getAvatar()->getImageTag(24)} +
    +
    +

    {$queue->getAffectedObject()->getTitle()}

    + {$queue->getAffectedObject()->getUsername()} - {@$queue->getAffectedObject()->getTime()|time} +
    +
    +
  • +{/foreach} \ No newline at end of file diff --git a/com.woltlab.wcf/template/moderationReport.tpl b/com.woltlab.wcf/template/moderationReport.tpl new file mode 100644 index 0000000000..72c8a0f60b --- /dev/null +++ b/com.woltlab.wcf/template/moderationReport.tpl @@ -0,0 +1,121 @@ +{include file='documentHeader'} + + + {lang}wcf.moderation.report{/lang} - {PAGE_TITLE|language} + + {include file='headInclude'} + + + + + + + +{include file='header' sidebarOrientation='left'} + +
    +

    {lang}wcf.moderation.report{/lang}

    +
    + +{include file='userNotice'} + +
    + +
    + +
    +
    + {lang}wcf.moderation.report.details{/lang} + +
    +
    {lang}wcf.global.objectID{/lang}
    +
    {#$queue->queueID}
    +
    +
    +
    {lang}wcf.moderation.report.reportedBy{/lang}
    +
    {if $queue->userID}{$queue->username}{else}{lang}wcf.user.guest{/lang}{/if} ({@$queue->time|time})
    +
    + {if $queue->lastChangeTime} +
    +
    {lang}wcf.moderation.lastChangeTime{/lang}
    +
    {@$queue->lastChangeTime|time}
    +
    + {/if} +
    +
    {lang}wcf.moderation.assignedUser{/lang}
    +
    +
      + {if $assignedUserID && ($assignedUserID != $__wcf->getUser()->userID)} +
    • + {/if} +
    • +
    • +
    +
    +
    + {if $queue->assignedUser} +
    + +
    {$queue->assignedUsername}
    +
    + {/if} +
    +
    {lang}wcf.moderation.report.reason{/lang}
    +
    {@$queue->getFormattedMessage()}
    +
    +
    +
    +
    +
    + + {event name='detailsFields'} + +
    + +
    +
    + + {event name='fieldsets'} +
    + +
    +

    {lang}wcf.moderation.report.reportedContent{/lang}

    +
    + +
    + {@$reportedContent} +
    + +
    + +
    + +{include file='footer'} + + + \ No newline at end of file diff --git a/com.woltlab.wcf/template/moderationReportDialog.tpl b/com.woltlab.wcf/template/moderationReportDialog.tpl new file mode 100644 index 0000000000..e0d98db767 --- /dev/null +++ b/com.woltlab.wcf/template/moderationReportDialog.tpl @@ -0,0 +1,22 @@ +{if $alreadyReported} +

    {lang}wcf.moderation.report.alreadyReported{/lang}

    +{else} +
    + + +
    +
    + + {lang}wcf.moderation.report.reason.description{/lang} +
    +
    + + {event name='reasonFields'} +
    + + {event name='fieldsets'} + +
    + +
    +{/if} \ No newline at end of file diff --git a/com.woltlab.wcf/template/userPanel.tpl b/com.woltlab.wcf/template/userPanel.tpl index be0e1ea913..597c19dadc 100644 --- a/com.woltlab.wcf/template/userPanel.tpl +++ b/com.woltlab.wcf/template/userPanel.tpl @@ -174,6 +174,29 @@ {/if} {if !$__hideUserMenu|isset} + {if $__wcf->user->userID && $__wcf->session->getPermission('mod.general.canUseModeration')} +
  • + + + {lang}wcf.moderation.moderation{/lang} + {if $__wcf->getModerationQueueManager()->getOutstandingModerationCount()}{#$__wcf->getModerationQueueManager()->getOutstandingModerationCount()}{/if} + + + +
  • + {/if} + {event name='menuItems'} {/if} diff --git a/com.woltlab.wcf/userGroupOption.xml b/com.woltlab.wcf/userGroupOption.xml index 130e642ef5..d18ab007d7 100644 --- a/com.woltlab.wcf/userGroupOption.xml +++ b/com.woltlab.wcf/userGroupOption.xml @@ -466,6 +466,13 @@ jpeg png]]> + + diff --git a/wcfsetup/install/files/js/WCF.Moderation.js b/wcfsetup/install/files/js/WCF.Moderation.js new file mode 100644 index 0000000000..44c6470539 --- /dev/null +++ b/wcfsetup/install/files/js/WCF.Moderation.js @@ -0,0 +1,383 @@ +/** + * Namespace for moderation related classes. + * + * @author Alexander Ebert + * @copyright 2001-2012 WoltLab GmbH + * @license GNU Lesser General Public License + */ +WCF.Moderation = { }; + +/** + * Moderation queue management. + * + * @param integer queueID + * @param string redirectURL + */ +WCF.Moderation.Management = Class.extend({ + /** + * button selector + * @var string + */ + _buttonSelector: '', + + /** + * action class name + * @var string + */ + _className: '', + + /** + * language item pattern + * @var string + */ + _languageItem: '', + + /** + * action proxy + * @var WCF.Action.Proxy + */ + _proxy: null, + + /** + * queue id + * @var integer + */ + _queueID: 0, + + /** + * redirect URL + * @var string + */ + _redirectURL: '', + + /** + * Initializes the moderation report management. + * + * @param integer queueID + * @param string redirectURL + * @param string languageItem + */ + init: function(queueID, redirectURL, languageItem) { + if (!this._buttonSelector) { + console.debug("[WCF.Moderation.Management] Missing button selector, aborting."); + return; + } + else if (!this._className) { + console.debug("[WCF.Moderation.Management] Missing class name, aborting."); + return; + } + + this._queueID = queueID; + this._redirectURL = redirectURL; + this._languageItem = languageItem; + + this._proxy = new WCF.Action.Proxy({ + success: $.proxy(this._success, this) + }); + + $(this._buttonSelector).click($.proxy(this._click, this)); + }, + + /** + * Handles clicks on the action buttons. + * + * @param object event + */ + _click: function(event) { + var $actionName = $(event.currentTarget).wcfIdentify(); + + WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/, $actionName)), $.proxy(function(action) { + if (action === 'confirm') { + this._proxy.setOption('data', { + actionName: $actionName, + className: this._className, + objectIDs: [ this._queueID ] + }); + this._proxy.sendRequest(); + + $(this._buttonSelector).disable(); + } + }, this)); + }, + + /** + * Handles successful AJAX requests. + * + * @param object data + * @param string textStatus + * @param jQuery jqXHR + */ + _success: function(data, textStatus, jqXHR) { + var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success')); + var self = this; + $notification.show(function() { + window.location = self._redirectURL; + }); + } +}); + +/** + * Namespace for activation related classes. + */ +WCF.Moderation.Activation = { }; + +/** + * Manages disabled content within moderation. + * + * @see WCF.Moderation.Management + */ +WCF.Moderation.Activation.Management = WCF.Moderation.Management.extend({ + /** + * @see WCF.Moderation.Management.init() + */ + init: function(queueID, redirectURL) { + this._buttonSelector = '#enableContent, #removeContent'; + this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueActivationAction'; + + this._super(queueID, redirectURL, 'wcf.moderation.activation.{actionName}.confirmMessage'); + } +}); + +/** + * Namespace for report related classes. + */ +WCF.Moderation.Report = { }; + +/** + * Handles content report. + * + * @param string objectType + * @param string buttonSelector + */ +WCF.Moderation.Report.Content = Class.extend({ + /** + * list of buttons + * @var object + */ + _buttons: { }, + + /** + * button selector + * @var string + */ + _buttonSelector: '', + + /** + * dialog overlay + * @var jQuery + */ + _dialog: null, + + /** + * notification object + * @var WCF.System.Notification + */ + _notification: null, + + /** + * object id + * @var integer + */ + _objectID: 0, + + /** + * object type name + * @var string + */ + _objectType: '', + + /** + * action proxy + * @var WCF.Action.Proxy + */ + _proxy: null, + + /** + * Creates a new WCF.Moderation.Report object. + * + * @param string objectType + * @param string buttonSelector + */ + init: function(objectType, buttonSelector) { + this._objectType = objectType; + this._buttonSelector = buttonSelector; + + this._buttons = { }; + this._notification = null; + this._objectID = 0; + this._proxy = new WCF.Action.Proxy({ + success: $.proxy(this._success, this) + }); + + this._initButtons(); + + WCF.DOMNodeInsertedHandler.addCallback('WCF.Moderation.Report' + this._objectType.hashCode(), $.proxy(this._initButtons, this)); + }, + + /** + * Initializes the report feature for all matching buttons. + */ + _initButtons: function() { + var self = this; + $(this._buttonSelector).each(function(index, button) { + var $button = $(button); + var $buttonID = $button.wcfIdentify(); + + if (!self._buttons[$buttonID]) { + self._buttons[$buttonID] = $button; + $button.click($.proxy(self._click, self)); + } + }); + }, + + /** + * Handles clicks on a report button. + * + * @param object event + */ + _click: function(event) { + this._objectID = $(event.currentTarget).data('objectID'); + + this._proxy.setOption('data', { + actionName: 'prepareReport', + className: 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction', + parameters: { + objectID: this._objectID, + objectType: this._objectType + } + }); + this._proxy.sendRequest(); + }, + + /** + * Handles successful AJAX requests. + * + * @param object data + * @param string textStatus + * @param jQuery jqXHR + */ + _success: function(data, textStatus, jqXHR) { + // object has been successfully reported + if (data.returnValues.reported) { + if (this._notification === null) { + this._notification = new WCF.System.Notification(WCF.Language.get('wcf.moderation.report.success')); + } + + // show success and close dialog + this._dialog.wcfDialog('close'); + this._notification.show(); + } + else if (data.returnValues.template) { + // display template + this._showDialog(data.returnValues.template); + + if (!data.returnValues.alreadyReported) { + // bind event listener for buttons + this._dialog.find('.jsSubmitReport').click($.proxy(this._submit, this)); + } + } + }, + + /** + * Displays the dialog overlay. + * + * @param string template + */ + _showDialog: function(template) { + if (this._dialog === null) { + this._dialog = $('#moderationReport'); + if (!this._dialog.length) { + this._dialog = $('
    ').hide().appendTo(document.body); + } + } + + this._dialog.html(template).wcfDialog({ + title: WCF.Language.get('wcf.moderation.report.reportContent') + }).wcfDialog('render'); + }, + + /** + * Submits a report unless the textarea is empty. + */ + _submit: function() { + var $text = this._dialog.find('.jsReportMessage').val(); + if ($text == '') { + this._dialog.find('fieldset > dl').addClass('formError'); + + if (!this._dialog.find('.innerError').length) { + this._dialog.find('.jsReportMessage').after($('' + WCF.Language.get('wcf.global.form.error.empty') + ""));; + } + + return; + } + + this._proxy.setOption('data', { + actionName: 'report', + className: 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction', + parameters: { + message: $text, + objectID: this._objectID, + objectType: this._objectType + } + }); + this._proxy.sendRequest(); + } +}); + +/** + * Manages reported content within moderation. + * + * @see WCF.Moderation.Management + */ +WCF.Moderation.Report.Management = WCF.Moderation.Management.extend({ + /** + * @see WCF.Moderation.Management.init() + */ + init: function(queueID, redirectURL) { + this._buttonSelector = '#removeContent, #removeReport'; + this._className = 'wcf\\data\\moderation\\queue\\ModerationQueueReportAction'; + + this._super(queueID, redirectURL, 'wcf.moderation.report.{actionName}.confirmMessage'); + } +}); + +/** + * Provides a dropdown for user panel. + * + * @see WCF.UserPanel + */ +WCF.Moderation.UserPanel = WCF.UserPanel.extend({ + /** + * link to show all outstanding queues + * @var string + */ + _showAllLink: '', + + /** + * @see WCF.UserPanel.init() + */ + init: function(showAllLink) { + this._noItems = 'wcf.moderation.noMoreItems'; + this._showAllLink = showAllLink; + + this._super('outstandingModeration'); + }, + + /** + * @see WCF.UserPanel._addDefaultItems() + */ + _addDefaultItems: function(dropdownMenu) { + this._addDivider(dropdownMenu); + $('
  • ' + WCF.Language.get('wcf.moderation.showAll') + '
  • ').appendTo(dropdownMenu); + }, + + /** + * @see WCF.UserPanel._getParameters() + */ + _getParameters: function() { + return { + actionName: 'getOutstandingQueues', + className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction' + }; + } +}); diff --git a/wcfsetup/install/files/js/WCF.Moderation.min.js b/wcfsetup/install/files/js/WCF.Moderation.min.js new file mode 100644 index 0000000000..5057dc6c16 --- /dev/null +++ b/wcfsetup/install/files/js/WCF.Moderation.min.js @@ -0,0 +1 @@ +WCF.Moderation={};WCF.Moderation.Management=Class.extend({_buttonSelector:"",_className:"",_languageItem:"",_proxy:null,_queueID:0,_redirectURL:"",init:function(a,c,b){if(!this._buttonSelector){console.debug("[WCF.Moderation.Management] Missing button selector, aborting.");return}else{if(!this._className){console.debug("[WCF.Moderation.Management] Missing class name, aborting.");return}}this._queueID=a;this._redirectURL=c;this._languageItem=b;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).click($.proxy(this._click,this))},_click:function(b){var a=$(b.currentTarget).wcfIdentify();WCF.System.Confirmation.show(WCF.Language.get(this._languageItem.replace(/{actionName}/,a)),$.proxy(function(c){if(c==="confirm"){this._proxy.setOption("data",{actionName:a,className:this._className,objectIDs:[this._queueID]});this._proxy.sendRequest();$(this._buttonSelector).disable()}},this))},_success:function(c,e,b){var d=new WCF.System.Notification(WCF.Language.get("wcf.global.success"));var a=this;d.show(function(){window.location=a._redirectURL})}});WCF.Moderation.Activation={};WCF.Moderation.Activation.Management=WCF.Moderation.Management.extend({init:function(a,b){this._buttonSelector="#enableContent, #removeContent";this._className="wcf\\data\\moderation\\queue\\ModerationQueueActivationAction";this._super(a,b,"wcf.moderation.activation.{actionName}.confirmMessage")}});WCF.Moderation.Report={};WCF.Moderation.Report.Content=Class.extend({_buttons:{},_buttonSelector:"",_dialog:null,_notification:null,_objectID:0,_objectType:"",_proxy:null,init:function(a,b){this._objectType=a;this._buttonSelector=b;this._buttons={};this._notification=null;this._objectID=0;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initButtons();WCF.DOMNodeInsertedHandler.addCallback("WCF.Moderation.Report"+this._objectType.hashCode(),$.proxy(this._initButtons,this))},_initButtons:function(){var a=this;$(this._buttonSelector).each(function(c,d){var e=$(d);var b=e.wcfIdentify();if(!a._buttons[b]){a._buttons[b]=e;e.click($.proxy(a._click,a))}})},_click:function(a){this._objectID=$(a.currentTarget).data("objectID");this._proxy.setOption("data",{actionName:"prepareReport",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{objectID:this._objectID,objectType:this._objectType}});this._proxy.sendRequest()},_success:function(b,c,a){if(b.returnValues.reported){if(this._notification===null){this._notification=new WCF.System.Notification(WCF.Language.get("wcf.moderation.report.success"))}this._dialog.wcfDialog("close");this._notification.show()}else{if(b.returnValues.template){this._showDialog(b.returnValues.template);if(!b.returnValues.alreadyReported){this._dialog.find(".jsSubmitReport").click($.proxy(this._submit,this))}}}},_showDialog:function(a){if(this._dialog===null){this._dialog=$("#moderationReport");if(!this._dialog.length){this._dialog=$('
    ').hide().appendTo(document.body)}}this._dialog.html(a).wcfDialog({title:WCF.Language.get("wcf.moderation.report.reportContent")}).wcfDialog("render")},_submit:function(){var a=this._dialog.find(".jsReportMessage").val();if(a==""){this._dialog.find("fieldset > dl").addClass("formError");if(!this._dialog.find(".innerError").length){this._dialog.find(".jsReportMessage").after($(''+WCF.Language.get("wcf.global.form.error.empty")+""))}return}this._proxy.setOption("data",{actionName:"report",className:"wcf\\data\\moderation\\queue\\ModerationQueueReportAction",parameters:{message:a,objectID:this._objectID,objectType:this._objectType}});this._proxy.sendRequest()}});WCF.Moderation.Report.Management=WCF.Moderation.Management.extend({init:function(a,b){this._buttonSelector="#removeContent, #removeReport";this._className="wcf\\data\\moderation\\queue\\ModerationQueueReportAction";this._super(a,b,"wcf.moderation.report.{actionName}.confirmMessage")}});WCF.Moderation.UserPanel=WCF.UserPanel.extend({_showAllLink:"",init:function(a){this._noItems="wcf.moderation.noMoreItems";this._showAllLink=a;this._super("outstandingModeration")},_addDefaultItems:function(a){this._addDivider(a);$('
  • '+WCF.Language.get("wcf.moderation.showAll")+"
  • ").appendTo(a)},_getParameters:function(){return{actionName:"getOutstandingQueues",className:"wcf\\data\\moderation\\queue\\ModerationQueueAction"}}}); \ No newline at end of file diff --git a/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php index 2f6b8ab2ff..5527238e21 100755 --- a/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php @@ -1,13 +1,8 @@ executeAction(); } + + // remove assignments + $sql = "DELETE FROM wcf".WCF_N."_moderation_queue_to_user + WHERE userID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array($this->user->userID)); + + // reset moderation count + ModerationQueueManager::getInstance()->resetModerationCount($this->user->userID); $this->saved(); // reset password diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueue.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueue.class.php new file mode 100644 index 0000000000..36950105c4 --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueue.class.php @@ -0,0 +1,79 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ModerationQueue extends DatabaseObject { + /** + * @see wcf\data\DatabaseObject::$databaseTableName + */ + protected static $databaseTableName = 'moderation_queue'; + + /** + * @see wcf\data\DatabaseObject::$databaseIndexName + */ + protected static $databaseTableIndexName = 'queueID'; + + // states of column 'status' + const STATUS_OUTSTANDING = 0; + const STATUS_PROCESSING = 1; + const STATUS_DONE = 2; + + /** + * @see wcf\data\IStorableObject::__get() + */ + public function __get($name) { + $value = parent::__get($name); + + // treat additional data as data variables if it is an array + if ($value === null) { + if (is_array($this->data['additionalData']) && isset($this->data['additionalData'][$name])) { + $value = $this->data['additionalData'][$name]; + } + } + + return $value; + } + + /** + * @see wcf\data\DatabaseObject::handleData() + */ + protected function handleData($data) { + parent::handleData($data); + + $this->data['additionalData'] = @unserialize($this->data['additionalData']); + if (!is_array($this->data['additionalData'])) { + $this->data['additionalData'] = array(); + } + } + + /** + * Returns true if current user can edit this moderation queue. + * + * @return boolean + */ + public function canEdit() { + $sql = "SELECT isAffected + FROM wcf".WCF_N."_moderation_queue_to_user + WHERE queueID = ? + AND userID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + $this->queueID, + WCF::getUser()->userID + )); + $row = $statement->fetchArray(); + + return ($row !== false && $row['isAffected']); + } +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php new file mode 100644 index 0000000000..7dc76a6389 --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php @@ -0,0 +1,103 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ModerationQueueAction extends AbstractDatabaseObjectAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$className + */ + protected $className = 'wcf\data\moderation\queue\ModerationQueueEditor'; + + /** + * @see wcf\data\AbstractDatabaseObjectAction::create() + */ + public function create() { + if (!isset($this->parameters['data']['lastChangeTime'])) { + $this->parameters['data']['lastChangeTime'] = TIME_NOW; + } + + return parent::create(); + } + + /** + * @see wcf\data\AbstractDatabaseObjectAction::update() + */ + public function update() { + if (!isset($this->parameters['data']['lastChangeTime'])) { + $this->parameters['data']['lastChangeTime'] = TIME_NOW; + } + + parent::update(); + } + + /** + * Marks a list of objects as done. + */ + public function markAsDone() { + if (empty($this->objects)) { + $this->readObjects(); + } + + $queueIDs = array(); + foreach ($this->objects as $queue) { + $queueIDs[] = $queue->queueID; + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("queueID IN (?)", array($queueIDs)); + + $sql = "UPDATE wcf".WCF_N."_moderation_queue + SET status = ".ModerationQueue::STATUS_DONE." + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + + // reset number of active moderation queue items + ModerationQueueManager::getInstance()->resetModerationCount(); + } + + /** + * Validates parameters to fetch a list of outstanding queues. + */ + public function validateGetOutstandingQueues() { + WCF::getSession()->checkPermissions(array('mod.general.canUseModeration')); + } + + /** + * Returns a list of outstanding queues. + * + * @return array + */ + public function getOutstandingQueues() { + $objectTypeIDs = ModerationQueueManager::getInstance()->getObjectTypeIDs(array_keys(ModerationQueueManager::getInstance()->getDefinitions())); + + $queueList = new ViewableModerationQueueList(); + $queueList->getConditionBuilder()->add("moderation_queue.objectTypeID IN (?)", array($objectTypeIDs)); + $queueList->getConditionBuilder()->add("moderation_queue.status <> ?", array(ModerationQueue::STATUS_DONE)); + $queueList->sqlLimit = 5; + $queueList->loadUserProfiles = true; + $queueList->readObjects(); + + WCF::getTPL()->assign(array( + 'queues' => $queueList + )); + + return array( + 'template' => WCF::getTPL()->fetch('moderationQueueList'), + 'totalCount' => ModerationQueueManager::getInstance()->getOutstandingModerationCount() + ); + } +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueActivationAction.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueActivationAction.class.php new file mode 100644 index 0000000000..6445cc68f2 --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueActivationAction.class.php @@ -0,0 +1,67 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ModerationQueueActivationAction extends ModerationQueueAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess + */ + protected $allowGuestAccess = array('enableContent', 'removeContent'); + + /** + * moderation queue editor object + * @var wcf\data\moderation\queue\ModerationQueueEditor + */ + public $queue = null; + + /** + * Validates parameters to enable content. + */ + public function validateEnableContent() { + $this->queue = $this->getSingleObject(); + if (!$this->queue->canEdit()) { + throw new PermissionDeniedException(); + } + } + + /** + * Enables content. + */ + public function enableContent() { + // enable content + ModerationQueueActivationManager::getInstance()->enableContent($this->queue->getDecoratedObject()); + + $this->queue->markAsDone(); + } + + /** + * Validates parameters to delete reported content. + */ + public function validateRemoveContent() { + $this->readString('message', true); + $this->validateEnableContent(); + } + + /** + * Deletes reported content. + */ + public function removeContent() { + // mark content as deleted + ModerationQueueActivationManager::getInstance()->removeContent($this->queue->getDecoratedObject(), $this->parameters['message']); + + $this->queue->markAsDone(); + } +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueEditor.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueEditor.class.php new file mode 100644 index 0000000000..dabc15ead2 --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueEditor.class.php @@ -0,0 +1,38 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ModerationQueueEditor extends DatabaseObjectEditor { + /** + * @see wcf\data\DatabaseObjectEditor::$baseClass + */ + protected static $baseClass = 'wcf\data\moderation\queue\ModerationQueue'; + + /** + * Marks this entry as done. + */ + public function markAsDone() { + $this->update(array('status' => ModerationQueue::STATUS_DONE)); + + // reset moderation count + ModerationQueueManager::getInstance()->resetModerationCount(); + } + + /** + * Marks this entry as in progress. + */ + public function markAsInProgress() { + $this->update(array('status' => ModerationQueue::STATUS_PROCESSING)); + } +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueList.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueList.class.php new file mode 100644 index 0000000000..d18bf6c546 --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueList.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ModerationQueueList extends DatabaseObjectList { + /** + * @see wcf\data\DatabaseObjectList::$className + */ + public $className = 'wcf\data\moderation\queue\ModerationQueue'; +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueReportAction.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueReportAction.class.php new file mode 100644 index 0000000000..04fe279f0e --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueReportAction.class.php @@ -0,0 +1,134 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ModerationQueueReportAction extends ModerationQueueAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess + */ + protected $allowGuestAccess = array('prepareReport', 'removeContent', 'removeReport', 'report'); + + /** + * moderation queue editor object + * @var wcf\data\moderation\queue\ModerationQueueEditor + */ + public $queue = null; + + /** + * Validates parameters to delete reported content. + */ + public function validateRemoveContent() { + $this->validateRemoveReport(); + + $this->parameters['message'] = (isset($this->parameters['message']) ? StringUtil::trim($this->parameters['message']) : ''); + } + + /** + * Deletes reported content. + */ + public function removeContent() { + // mark content as deleted + ModerationQueueReportManager::getInstance()->removeContent($this->queue->getDecoratedObject(), $this->parameters['message']); + + $this->queue->markAsDone(); + } + + /** + * Validates parameters to mark this report as done. + */ + public function validateRemoveReport() { + $this->queue = $this->getSingleObject(); + if (!$this->queue->canEdit()) { + throw new PermissionDeniedException(); + } + } + + /** + * Removes this report by marking it as done without further processing. + */ + public function removeReport() { + $this->queue->markAsDone(); + } + + /** + * Validates parameters to prepare a report. + */ + public function validatePrepareReport() { + $this->readInteger('objectID'); + $this->readString('objectType'); + + if (!ModerationQueueReportManager::getInstance()->isValid($this->parameters['objectType'])) { + throw new UserInputException('objectType'); + } + + // validate the combination of object type and object id + if (!ModerationQueueReportManager::getInstance()->isValid($this->parameters['objectType'], $this->parameters['objectID'])) { + throw new UserInputException('objectID'); + } + + // validate if user may read the content (prevent information disclosure by reporting random ids) + if (!ModerationQueueReportManager::getInstance()->canReport($this->parameters['objectType'], $this->parameters['objectID'])) { + throw new PermissionDeniedException(); + } + } + + /** + * Prepares a report. + */ + public function prepareReport() { + // content was already reported + $alreadyReported = (ModerationQueueReportManager::getInstance()->isAlreadyReported($this->parameters['objectType'], $this->parameters['objectID'])) ? 1 : 0; + + WCF::getTPL()->assign(array( + 'alreadyReported' => $alreadyReported, + 'object' => ModerationQueueReportManager::getInstance()->getReportedObject($this->parameters['objectType'], $this->parameters['objectID']) + )); + + return array( + 'alreadyReported' => $alreadyReported, + 'template' => WCF::getTPL()->fetch('moderationReportDialog') + ); + } + + /** + * Validates parameters for reporting. + */ + public function validateReport() { + $this->readString('message'); + + $this->validatePrepareReport(); + } + + /** + * Reports an item. + */ + public function report() { + // if the specified content was already reported, e.g. a different user reported this + // item meanwhile, silently ignore it. Just display a success and the user is happy :) + if (!ModerationQueueReportManager::getInstance()->isAlreadyReported($this->parameters['objectType'], $this->parameters['objectID'])) { + ModerationQueueReportManager::getInstance()->addReport( + $this->parameters['objectType'], + $this->parameters['objectID'], + $this->parameters['message'] + ); + } + + return array( + 'reported' => 1 + ); + } +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueue.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueue.class.php new file mode 100644 index 0000000000..9c9893922c --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueue.class.php @@ -0,0 +1,149 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ViewableModerationQueue extends DatabaseObjectDecorator { + /** + * @see wcf\data\DatabaseObject::$baseClass + */ + protected static $baseClass = 'wcf\data\moderation\queue\ModerationQueue'; + + /** + * affected object + * @var wcf\data\IUserContent + */ + protected $affectedObject = null; + + /** + * true, if associated object was deleted + * @var boolean + */ + protected $isOrphaned = false; + + /** + * user profile object + * @var wcf\data\user\UserProfile + */ + protected $userProfile = null; + + /** + * Sets link for viewing/editing. + * + * @param wcf\data\IUserContent $object + */ + public function setAffectedObject(IUserContent $object) { + $this->affectedObject = $object; + } + + /** + * Returns the link for viewing/editing this object. + * + * @return string + */ + public function getLink() { + return ModerationQueueManager::getInstance()->getLink($this->objectTypeID, $this->queueID); + } + + /** + * Returns the title for this entry. + * + * @return string + */ + public function getTitle() { + return ($this->affectedObject === null ? '' : $this->affectedObject->getTitle()); + } + + /** + * Returns affected object. + * + * @return wcf\data\IUserContent + */ + public function getAffectedObject() { + return $this->affectedObject; + } + + /** + * Sets associated user profile object. + * + * @param wcf\data\user\UserProfile $userProfile + */ + public function setUserProfile(UserProfile $userProfile) { + if ($this->affectedObject !== null && ($userProfile->userID == $this->affectedObject->getUserID())) { + $this->userProfile = $userProfile; + } + } + + /** + * Returns associated user profile object. + * + * @return wcf\data\user\UserProfile + */ + public function getUserProfile() { + if ($this->affectedObject !== null && $this->userProfile === null) { + $this->userProfile = UserProfile::getUserProfile($this->affectedObject->getUserID()); + } + + return $this->userProfile; + } + + /** + * Returns true if associated object was removed. + * + * @return boolean + */ + public function isOrphaned() { + return $this->isOrphaned; + } + + /** + * Marks associated objects as removed. + */ + public function setIsOrphaned() { + $this->isOrphaned = true; + } + + /** + * @see wcf\data\moderation\queue\ViewableModerationQueue::getTitle() + */ + public function __toString() { + return $this->getTitle(); + } + + /** + * Returns a viewable moderation queue entry. + * + * @param integer $queueID + * @return wcf\data\moderation\queue\ViewableModerationQueue + */ + public static function getViewableModerationQueue($queueID) { + $queueList = new ViewableModerationQueueList(); + $queueList->getConditionBuilder()->add("moderation_queue.queueID = ?", array($queueID)); + $queueList->sqlLimit = 1; + $queueList->readObjects(); + $queues = $queueList->getObjects(); + + return (isset($queues[$queueID]) ? $queues[$queueID] : null); + } + + /** + * Returns formatted message text. + * + * @return string + */ + public function getFormattedMessage() { + return nl2br(htmlspecialchars($this->message)); + } +} diff --git a/wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueueList.class.php b/wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueueList.class.php new file mode 100644 index 0000000000..db160ed95e --- /dev/null +++ b/wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueueList.class.php @@ -0,0 +1,121 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage data.moderation.queue + * @category Community Framework + */ +class ViewableModerationQueueList extends ModerationQueueList { + /** + * true, if objects should be populated with associated user profiles + * @var boolean + */ + public $loadUserProfiles = false; + + /** + * @see wcf\data\DatabaseObjectList::$useQualifiedShorthand + */ + public $useQualifiedShorthand = false; + + /** + * @see wcf\data\DatabaseObjectList::__construct() + */ + public function __construct() { + parent::__construct(); + + $this->sqlSelects = "moderation_queue.*, assigned_user.username AS assignedUsername, user_table.username"; + $this->sqlConditionJoins = ", wcf".WCF_N."_moderation_queue moderation_queue"; + $this->sqlJoins = ", wcf".WCF_N."_moderation_queue moderation_queue"; + $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user assigned_user ON (assigned_user.userID = moderation_queue.assignedUserID)"; + $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = moderation_queue.userID)"; + $this->getConditionBuilder()->add("moderation_queue_to_user.queueID = moderation_queue.queueID"); + $this->getConditionBuilder()->add("moderation_queue_to_user.userID = ?", array(WCF::getUser()->userID)); + $this->getConditionBuilder()->add("moderation_queue_to_user.isAffected = ?", array(1)); + } + + /** + * @see wcf\data\DatabaseObjectList::readObjects() + */ + public function readObjects() { + parent::readObjects(); + + if (!empty($this->objects)) { + $objects = array(); + foreach ($this->objects as &$object) { + $object = new ViewableModerationQueue($object); + + if (!isset($objects[$object->objectTypeID])) { + $objects[$object->objectTypeID] = array(); + } + + $objects[$object->objectTypeID][] = $object; + } + unset($object); + + foreach ($objects as $objectTypeID => $queueItems) { + ModerationQueueManager::getInstance()->populate($objectTypeID, $queueItems); + } + + // check for non-existant items + $queueIDs = array(); + foreach ($this->objects as $index => $object) { + if ($object->isOrphaned()) { + $queueIDs[] = $object->queueID; + unset($this->objects[$index]); + } + } + + // remove orphaned queues + if (!empty($queueIDs)) { + $this->indexToObject = array_keys($this->objects); + + ModerationQueueManager::getInstance()->removeOrphans($queueIDs); + } + + if ($this->loadUserProfiles) { + $userIDs = array(); + foreach ($this->objects as $object) { + $userIDs[] = $object->getAffectedObject()->getUserID(); + } + + $userProfiles = UserProfile::getUserProfiles($userIDs); + foreach ($this->objects as $object) { + if (isset($userProfiles[$object->getAffectedObject()->getUserID()])) { + $object->setUserProfile($userProfiles[$object->getAffectedObject()->getUserID()]); + } + } + } + } + } + + /** + * Returns the name of the database table. + * + * @return string + */ + public function getDatabaseTableName() { + return parent::getDatabaseTableName() . '_to_user'; + } + + /** + * Returns the name of the database table alias. + * + * @return string + */ + public function getDatabaseTableAlias() { + return parent::getDatabaseTableAlias() . '_to_user'; + } +} diff --git a/wcfsetup/install/files/lib/form/AbstractModerationForm.class.php b/wcfsetup/install/files/lib/form/AbstractModerationForm.class.php new file mode 100644 index 0000000000..8e9912ee5a --- /dev/null +++ b/wcfsetup/install/files/lib/form/AbstractModerationForm.class.php @@ -0,0 +1,169 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage form + * @category Community Framework + */ +abstract class AbstractModerationForm extends AbstractForm { + /** + * assigned user id + * @var integer + */ + public $assignedUserID = 0; + + /** + * comment + * @var string + */ + public $comment = ''; + + /** + * data used for moderation queue update + * @var array + */ + public $data = array(); + + /** + * @see wcf\page\AbstractPage::$loginRequired + */ + public $loginRequired = true; + + /** + * moderation queue object + * @var wcf\data\moderation\queue\ViewableModerationQueue + */ + public $queue = null; + + /** + * queue id + * @var integer + */ + public $queueID = 0; + + /** + * @see wcf\page\IPage::readParameters() + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['id'])) $this->queueID = intval($_REQUEST['id']); + $this->queue = ViewableModerationQueue::getViewableModerationQueue($this->queueID); + if (!$this->queue === null) { + throw new IllegalLinkException(); + } + + if (!$this->queue->canEdit()) { + throw new PermissionDeniedException(); + } + } + + /** + * @see wcf\form\IForm::readFormParameters() + */ + public function readFormParameters() { + parent::readFormParameters(); + + if (isset($_POST['comment'])) $this->comment = StringUtil::trim($_POST['comment']); + + // verify assigned user id + if (isset($_POST['assignedUserID'])) { + $this->assignedUserID = intval($_POST['assignedUserID']); + if ($this->assignedUserID) { + if ($this->assignedUserID != WCF::getUser()->userID && $this->assignedUserID != $this->queue->assignedUserID) { + // user id is either faked or changed during viewing, use database value instead + $this->assignedUserID = $this->queue->assignedUserID; + } + } + } + } + + /** + * @see wcf\page\IPage::readData() + */ + public function readData() { + parent::readData(); + + if (empty($_POST)) { + $this->assignedUserID = $this->queue->assignedUserID; + $this->comment = $this->queue->comment; + } + + WCF::getBreadcrumbs()->add(new Breadcrumb( + WCF::getLanguage()->get('wcf.moderation.moderation'), + LinkHandler::getInstance()->getLink('ModerationList') + )); + } + + /** + * @see wcf\page\IPage::assignVariables() + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign(array( + 'assignedUserID' => $this->assignedUserID, + 'comment' => $this->comment, + 'queue' => $this->queue + )); + } + + /** + * @see wcf\form\IForm::save() + */ + public function save() { + parent::save(); + + $this->data = array( + 'assignedUserID' => ($this->assignedUserID ?: null), + 'comment' => $this->comment + ); + if ($this->assignedUserID) { + // queue item is being processed + if ($this->assignedUserID != $this->queue->assignedUserID) { + $this->data['status'] = ModerationQueue::STATUS_PROCESSING; + } + } + else { + // queue is no longer processed, mark as outstanding + if ($this->queue->assignedUserID) { + $this->data['status'] = ModerationQueue::STATUS_OUTSTANDING; + } + } + + $this->prepareSave(); + $this->objectAction = new ModerationQueueAction(array($this->queue->getDecoratedObject()), 'update', array('data' => $this->data)); + $this->objectAction->executeAction(); + + // call saved event + $this->saved(); + + // reload queue to update assignment + if ($this->assignedUserID != $this->queue->assignedUserID) { + $this->queue = ViewableModerationQueue::getViewableModerationQueue($this->queue->queueID); + } + } + + /** + * Prepares update of moderation queue item. + */ + protected function prepareSave() { + EventHandler::getInstance()->fireAction($this, 'prepareSave'); + } +} diff --git a/wcfsetup/install/files/lib/form/ModerationActivationForm.class.php b/wcfsetup/install/files/lib/form/ModerationActivationForm.class.php new file mode 100644 index 0000000000..2b67b87a8c --- /dev/null +++ b/wcfsetup/install/files/lib/form/ModerationActivationForm.class.php @@ -0,0 +1,27 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage form + * @category Community Framework + */ +class ModerationActivationForm extends AbstractModerationForm { + /** + * @see wcf\page\IPage::assignVariables() + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign(array( + 'disabledContent' => ModerationQueueActivationManager::getInstance()->getDisabledContent($this->queue) + )); + } +} diff --git a/wcfsetup/install/files/lib/form/ModerationReportForm.class.php b/wcfsetup/install/files/lib/form/ModerationReportForm.class.php new file mode 100644 index 0000000000..e1583ef587 --- /dev/null +++ b/wcfsetup/install/files/lib/form/ModerationReportForm.class.php @@ -0,0 +1,27 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage form + * @category Community Framework + */ +class ModerationReportForm extends AbstractModerationForm { + /** + * @see wcf\page\IPage::assignVariables() + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign(array( + 'reportedContent' => ModerationQueueReportManager::getInstance()->getReportedContent($this->queue) + )); + } +} diff --git a/wcfsetup/install/files/lib/page/ModerationListPage.class.php b/wcfsetup/install/files/lib/page/ModerationListPage.class.php new file mode 100644 index 0000000000..359fb55a89 --- /dev/null +++ b/wcfsetup/install/files/lib/page/ModerationListPage.class.php @@ -0,0 +1,134 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage page + * @category Community Framework + */ +class ModerationListPage extends SortablePage { + /** + * assigned user id + * @var integer + */ + public $assignedUserID = -1; + + /** + * list of available definitions + * @var array + */ + public $availableDefinitions = array(); + + /** + * @see wcf\page\SortablePage::$defaultSortField + */ + public $defaultSortField = 'lastChangeTime'; + + /** + * @see wcf\page\SortablePage::$defaultSortField + */ + public $defaultSortOrder = 'DESC'; + + /** + * definiton id for filtering + * @var integer + */ + public $definitionID = 0; + + /** + * @see wcf\page\AbstractPage::$loginRequired + */ + public $loginRequired = true; + + /** + * @see wcf\page\AbstractPage::$neededPermissions + */ + public $neededPermissions = array('mod.general.canUseModeration'); + + /** + * @see wcf\page\MultipleLinkPage::$objectListClassName + */ + public $objectListClassName = 'wcf\data\moderation\queue\ViewableModerationQueueList'; + + /** + * status bit + * @var integer + */ + public $status = -1; + + /** + * @see wcf\page\SortablePage::$validSortFields + */ + public $validSortFields = array('assignedUsername', 'lastChangeTime', 'queueID', 'time', 'username'); + + /** + * @see wcf\page\IPage::readParameters() + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['assignedUserID'])) $this->assignedUserID = intval($_REQUEST['assignedUserID']); + if (isset($_REQUEST['status'])) $this->status = intval($_REQUEST['status']); + + $this->availableDefinitions = ModerationQueueManager::getInstance()->getDefinitions(); + if (isset($_REQUEST['definitionID'])) { + $this->definitionID = intval($_REQUEST['definitionID']); + if ($this->definitionID && !isset($this->availableDefinitions[$this->definitionID])) { + throw new IllegalLinkException(); + } + } + } + + /** + * @see wcf\page\MultipleLinkPage::initObjectList() + */ + protected function initObjectList() { + parent::initObjectList(); + + // filter by object type id + $objectTypeIDs = ModerationQueueManager::getInstance()->getObjectTypeIDs( ($this->definitionID ? array($this->definitionID) : array_keys($this->availableDefinitions)) ); + if (empty($objectTypeIDs)) { + // no object type ids given? screw that, display nothing + $this->objectList->getConditionBuilder()->add("0 = 1"); + return; + } + + $this->objectList->getConditionBuilder()->add("moderation_queue.objectTypeID IN (?)", array($objectTypeIDs)); + + // filter by assigned user id + if ($this->assignedUserID == 0) $this->objectList->getConditionBuilder()->add("moderation_queue.assignedUserID IS NULL"); + else if ($this->assignedUserID > 0) $this->objectList->getConditionBuilder()->add("moderation_queue.assignedUserID = ?", array($this->assignedUserID)); + + // filter by status + if ($this->status == ModerationQueue::STATUS_DONE) { + $this->objectList->getConditionBuilder()->add("moderation_queue.status = ?", array(ModerationQueue::STATUS_DONE)); + } + else { + $this->objectList->getConditionBuilder()->add("moderation_queue.status IN (?)", array(array(ModerationQueue::STATUS_OUTSTANDING, ModerationQueue::STATUS_PROCESSING))); + } + } + + /** + * @see wcf\page\IPage::assignVariables() + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign(array( + 'assignedUserID' => $this->assignedUserID, + 'availableDefinitions' => $this->availableDefinitions, + 'definitionID' => $this->definitionID, + 'definitionNames' => ModerationQueueManager::getInstance()->getDefinitionNamesByObjectTypeIDs(), + 'status' => $this->status + )); + } +} diff --git a/wcfsetup/install/files/lib/system/cronjob/ModerationQueueCronjob.class.php b/wcfsetup/install/files/lib/system/cronjob/ModerationQueueCronjob.class.php new file mode 100644 index 0000000000..3b30686f7f --- /dev/null +++ b/wcfsetup/install/files/lib/system/cronjob/ModerationQueueCronjob.class.php @@ -0,0 +1,53 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.cronjob + * @category Community Framework + */ +class ModerationQueueCronjob extends AbstractCronjob { + /** + * @see wcf\system\cronjob\ICronjob::execute() + */ + public function execute(Cronjob $cronjob) { + parent::execute($cronjob); + + $sql = "SELECT queueID + FROM wcf".WCF_N."_moderation_queue + WHERE status = ? + AND lastChangeTime < ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + ModerationQueue::STATUS_DONE, + (TIME_NOW - (86400 * 30)) + )); + $queueIDs = array(); + while ($row = $statement->fetchArray()) { + $queueIDs[] = $row['queueID']; + } + + if (!empty($queueIDs)) { + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("queueID IN (?)", array($queueIDs)); + + $sql = "DELETE FROM wcf".WCF_N."_moderation_queue + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + + // reset moderation count for all users + ModerationQueueManager::getInstance()->resetModerationCount(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueHandler.class.php new file mode 100644 index 0000000000..83d0ef8f4e --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueHandler.class.php @@ -0,0 +1,59 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +abstract class AbstractModerationQueueHandler implements IModerationQueueHandler { + /** + * definition name + * @var string + */ + protected $definitionName = ''; + + /** + * object type + * @var string + */ + protected $objectType = ''; + + /** + * @see wcf\system\moderation\queue\IModerationQueueHandler::removeQueues() + */ + public function removeQueues(array $objectIDs) { + $objectTypeID = ModerationQueueManager::getInstance()->getObjectTypeID($this->definitionName, $this->objectType); + if ($objectTypeID === null) { + throw new SystemException("Object type '".$this->objectType."' is not valid for definition '".$this->definitionName."'"); + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("objectTypeID = ?", array($objectTypeID)); + $conditions->add("objectID IN (?)", array($objectIDs)); + + $sql = "SELECT queueID + FROM wcf".WCF_N."_moderation_queue + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + $queueIDs = array(); + while ($row = $statement->fetchArray()) { + $queueIDs[] = $row['queueID']; + } + + if (!empty($queueIDs)) { + $queueAction = new ModerationQueueAction($queueIDs, 'delete'); + $queueAction->executeAction(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueManager.class.php b/wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueManager.class.php new file mode 100644 index 0000000000..289925b0f5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/AbstractModerationQueueManager.class.php @@ -0,0 +1,122 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +abstract class AbstractModerationQueueManager extends SingletonFactory implements IModerationQueueManager { + /** + * definition name + * @var string + */ + protected $definitionName = ''; + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::assignQueues() + */ + public function assignQueues($objectTypeID, array $queues) { + ModerationQueueManager::getInstance()->getProcessor($this->definitionName, null, $objectTypeID)->assignQueues($queues); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::isValid() + */ + public function isValid($objectType, $objectID = null) { + return ModerationQueueManager::getInstance()->isValid($this->definitionName, $objectType); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::getObjectTypeID() + */ + public function getObjectTypeID($objectType) { + return ModerationQueueManager::getInstance()->getObjectTypeID($this->definitionName, $objectType); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::getProcessor() + */ + public function getProcessor($objectType, $objectTypeID = null) { + return ModerationQueueManager::getInstance()->getProcessor($this->definitionName, $objectType, $objectTypeID); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::populate() + */ + public function populate($objectTypeID, array $objects) { + ModerationQueueManager::getInstance()->getProcessor($this->definitionName, null, $objectTypeID)->populate($objects); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::removeContent() + */ + public function removeContent(ModerationQueue $queue, $message = '') { + $this->getProcessor(null, $queue->objectTypeID)->removeContent($queue, $message); + } + + /** + * Adds an entry to moderation queue. + * + * @param integer $objectTypeID + * @param integer $objectID + * @param integer $containerID + * @param array $additionalData + */ + protected function addEntry($objectTypeID, $objectID, $containerID = 0, array $additionalData = array()) { + $sql = "SELECT COUNT(*) AS count + FROM wcf".WCF_N."_moderation_queue + WHERE objectTypeID = ? + AND objectID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + $objectTypeID, + $objectID + )); + $row = $statement->fetchArray(); + + if ($row['count'] == 0) { + $objectAction = new ModerationQueueAction(array(), 'create', array( + 'data' => array( + 'objectTypeID' => $objectTypeID, + 'objectID' => $objectID, + 'containerID' => $containerID, + 'userID' => (WCF::getUser()->userID ?: null), + 'time' => TIME_NOW, + 'additionalData' => serialize($additionalData) + ) + )); + $objectAction->executeAction(); + + ModerationQueueManager::getInstance()->resetModerationCount(); + } + } + + /** + * Marks a list of moderation queue entries as done. + * + * @param integer $objectTypeID + * @param array $objectIDs + */ + protected function removeEntries($objectTypeID, array $objectIDs) { + $queueList = new ModerationQueueList(); + $queueList->getConditionBuilder()->add("moderation_queue.objectTypeID = ?", array($objectTypeID)); + $queueList->getConditionBuilder()->add("moderation_queue.objectID IN (?)", array($objectIDs)); + $queueList->readObjects(); + + if (count($queueList)) { + $objectAction = new ModerationQueueAction($queueList->getObjects(), 'markAsDone'); + $objectAction->executeAction(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueHandler.class.php new file mode 100644 index 0000000000..d310c42b0f --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueHandler.class.php @@ -0,0 +1,62 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +interface IModerationQueueHandler { + /** + * Creates queue assignments for matching object ids. + * + * @param array $queues + */ + public function assignQueues(array $queues); + + /** + * Returns the container id for current object id, may return 0. + * + * @param integer $objectID + * @return integer + */ + public function getContainerID($objectID); + + /** + * Returns true if given object id is valid. + * + * @param integer $objectID + * @return boolean + */ + public function isValid($objectID); + + /** + * Populates object properties for viewing. + * + * @param array $queues + */ + public function populate(array $queues); + + /** + * Removes affected content. It is up to the processing class to either + * soft-delete the content or remove it permanently. + * + * @param wcf\data\moderation\queue\ModerationQueue $queue + * @param string $message + */ + public function removeContent(ModerationQueue $queue, $message); + + /** + * Removes queses from database, should only be called if the referenced + * object is permanently deleted. + * + * @param array $objectIDs + */ + public function removeQueues(array $objectIDs); +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueManager.class.php b/wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueManager.class.php new file mode 100644 index 0000000000..20ee9aebd3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/IModerationQueueManager.class.php @@ -0,0 +1,74 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +interface IModerationQueueManager { + /** + * Creates queue assignments for matching object type ids. + * + * @param integer $objectTypeID + * @param array $queues + */ + public function assignQueues($objectTypeID, array $queues); + + /** + * Returns true if given object type is valid, optionally checking object id. + * + * @param string $objectType + * @param integer $objectID + * @return boolean + */ + public function isValid($objectType, $objectID = null); + + /** + * Returns link for viewing/editing objects for this moderation type. + * + * @param integer $queueID + * @return string + */ + public function getLink($queueID); + + /** + * Returns object type id for given object type. + * + * @param string $objectType + * @return integer + */ + public function getObjectTypeID($objectType); + + /** + * Returns object type processor by object type. + * + * @param string $objectType + * @param integer $objectTypeID + * @return object + */ + public function getProcessor($objectType, $objectTypeID = null); + + /** + * Populates object properties for viewing. + * + * @param integer $objectTypeID + * @param array $objects + */ + public function populate($objectTypeID, array $objects); + + /** + * Removes affected content. It is up to the processing object to use a + * soft-delete or remove the content permanently. + * + * @param wcf\data\moderation\queue\ModerationQueue $queue + * @param string $message + */ + public function removeContent(ModerationQueue $queue, $message = ''); +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueActivationManager.class.php b/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueActivationManager.class.php new file mode 100644 index 0000000000..7145eecca9 --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueActivationManager.class.php @@ -0,0 +1,83 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +class ModerationQueueActivationManager extends AbstractModerationQueueManager { + /** + * @see wcf\system\moderation\queue\AbstractModerationQueueManager::$definitionName + */ + protected $definitionName = 'com.woltlab.wcf.moderation.activation'; + + /** + * Enables affected content. + * + * @param wcf\data\moderation\queue\ModerationQueue $queue + */ + public function enableContent(ModerationQueue $queue) { + $this->getProcessor(null, $queue->objectTypeID)->enableContent($queue); + } + + /** + * Returns outstanding content. + * + * @param wcf\data\moderation\queue\ViewableModerationQueue $queue + * @return string + */ + public function getDisabledContent(ViewableModerationQueue $queue) { + return $this->getProcessor(null, $queue->objectTypeID)->getDisabledContent($queue); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::getLink() + */ + public function getLink($queueID) { + return LinkHandler::getInstance()->getLink('ModerationActivation', array('id' => $queueID)); + } + + /** + * Adds an entry for moderated content. + * + * @param string $objectType + * @param integer $objectID + * @param array $additionalData + */ + public function addModeratedContent($objectType, $objectID, array $additionalData = array()) { + if (!$this->isValid($objectType)) { + throw new SystemException("Object type '".$objectType."' is not valid for definition 'com.woltlab.wcf.moderation.activation'"); + } + + $this->addEntry( + $this->getObjectTypeID($objectType), + $objectID, + $this->getProcessor($objectType)->getContainerID($objectID), + $additionalData + ); + } + + /** + * Marks entries from moderation queue as done. + * + * @param string $objectType + * @param array $objectIDs + */ + public function removeModeratedContent($objectType, array $objectIDs) { + if (!$this->isValid($objectType)) { + throw new SystemException("Object type '".$objectType."' is not valid for definition 'com.woltlab.wcf.moderation.activation'"); + } + + $this->removeEntries($this->getObjectTypeID($objectType), $objectIDs); + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueManager.class.php b/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueManager.class.php new file mode 100644 index 0000000000..2d7dae20fd --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueManager.class.php @@ -0,0 +1,366 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +class ModerationQueueManager extends SingletonFactory { + /** + * list of definition names by definition id + * @var array + */ + protected $definitions = array(); + + /** + * list of moderation types + * @var array + */ + protected $moderationTypes = array(); + + /** + * list of object type names categorized by type + * @var array + */ + protected $objectTypeNames = array(); + + /** + * list of object types + * @var array + */ + protected $objectTypes = array(); + + /** + * @see wcf\system\SingletonFactory::init() + */ + protected function init() { + $moderationTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.moderation.type'); + if (empty($moderationTypes)) { + throw new SystemException("There are no registered moderation types"); + } + + foreach ($moderationTypes as $moderationType) { + $this->moderationTypes[$moderationType->objectType] = $moderationType; + + $definition = ObjectTypeCache::getInstance()->getDefinitionByName($moderationType->objectType); + if ($definition === null) { + throw new SystemException("Could not find corresponding definition for moderation type '".$moderationType->objectType."'"); + } + + $this->definitions[$definition->definitionID] = $definition->definitionName; + $this->objectTypeNames[$definition->definitionName] = array(); + + $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes($definition->definitionName); + foreach ($objectTypes as $objectType) { + $this->objectTypeNames[$definition->definitionName][$objectType->objectType] = $objectType->objectTypeID; + $this->objectTypes[$objectType->objectTypeID] = $objectType; + } + } + } + + /** + * Returns true if the given combination of definition and object type is valid. + * + * @param string $definitionName + * @param string $objectType + * @return boolean + */ + public function isValid($definitionName, $objectType) { + if (!isset($this->objectTypeNames[$definitionName])) { + return false; + } + else if (!isset($this->objectTypeNames[$definitionName][$objectType])) { + return false; + } + + return true; + } + + /** + * Returns the object type processor. + * + * @param string $definitionName + * @param string $objectType + * @param integer $objectTypeID + * @return object + */ + public function getProcessor($definitionName, $objectType, $objectTypeID = null) { + if ($objectType !== null) { + $objectTypeID = $this->getObjectTypeID($definitionName, $objectType); + } + + if ($objectTypeID !== null && isset($this->objectTypes[$objectTypeID])) { + return $this->objectTypes[$objectTypeID]->getProcessor(); + } + + return null; + } + + /** + * Returns link for viewing/editing an object type. + * + * @param integer $objectTypeID + * @param integer $queueID + * @return string + */ + public function getLink($objectTypeID, $queueID) { + foreach ($this->objectTypeNames as $definitionName => $objectTypeIDs) { + if (in_array($objectTypeID, $objectTypeIDs)) { + return $this->moderationTypes[$definitionName]->getProcessor()->getLink($queueID); + } + } + + return ''; + } + + /** + * Returns object type id. + * + * @param string $definitionName + * @param string $objectType + * @return integer + */ + public function getObjectTypeID($definitionName, $objectType) { + if ($this->isValid($definitionName, $objectType)) { + return $this->objectTypeNames[$definitionName][$objectType]; + } + + return null; + } + + /** + * Returns a list of moderation types. + * + * @return array + */ + public function getModerationTypes() { + return array_keys($this->objectTypeNames); + } + + /** + * Returns a list of available definitions. + * + * @return array + */ + public function getDefinitions() { + return $this->definitions; + } + + /** + * Returns a list of object type ids for given definiton ids. + * + * @param array $definitionIDs + * @return array + */ + public function getObjectTypeIDs(array $definitionIDs) { + $objectTypeIDs = array(); + foreach ($definitionIDs as $definitionID) { + if (isset($this->definitions[$definitionID])) { + foreach ($this->objectTypeNames[$this->definitions[$definitionID]] as $objectTypeID) { + $objectTypeIDs[] = $objectTypeID; + } + } + } + + return $objectTypeIDs; + } + + /** + * Populates object properties for viewing. + * + * @param integer $objectTypeID + * @param array $objects + */ + public function populate($objectTypeID, array $objects) { + $moderationType = ''; + foreach ($this->objectTypeNames as $definitionName => $data) { + if (in_array($objectTypeID, $data)) { + $moderationType = $definitionName; + break; + } + } + + if (empty($moderationType)) { + throw new SystemException("Unable to resolve object type id '".$objectTypeID."'"); + } + + // forward call to processor + $this->moderationTypes[$moderationType]->getProcessor()->populate($objectTypeID, $objects); + } + + /** + * Returns the count of outstanding moderation queue items. + * + * @return integer + */ + public function getOutstandingModerationCount() { + // load storage data + UserStorageHandler::getInstance()->loadStorage(array(WCF::getUser()->userID)); + + // get count + $data = UserStorageHandler::getInstance()->getStorage(array(WCF::getUser()->userID), 'outstandingModerationCount'); + $count = $data[WCF::getUser()->userID]; + + // cache does not exist or is outdated + if ($count === null) { + // force update of non-tracked queues for this user + $queueList = new ModerationQueueList(); + $queueList->sqlJoins = "LEFT JOIN wcf".WCF_N."_moderation_queue_to_user moderation_queue_to_user ON (moderation_queue_to_user.queueID = moderation_queue.queueID AND moderation_queue_to_user.userID = ".WCF::getUser()->userID.")"; + $queueList->getConditionBuilder()->add("moderation_queue_to_user.queueID IS NULL"); + $queueList->readObjects(); + + if (count($queueList)) { + $queues = array(); + foreach ($queueList as $queue) { + if (!isset($queues[$queue->objectTypeID])) { + $queues[$queue->objectTypeID] = array(); + } + + $queues[$queue->objectTypeID][$queue->queueID] = $queue; + } + + foreach ($this->objectTypeNames as $definitionName => $objectTypeIDs) { + foreach ($objectTypeIDs as $objectTypeID) { + if (isset($queues[$objectTypeID])) { + $this->moderationTypes[$definitionName]->getProcessor()->assignQueues($objectTypeID, $queues[$objectTypeID]); + } + } + } + } + + // count outstanding and assigned queues + $sql = "SELECT COUNT(*) AS count + FROM wcf".WCF_N."_moderation_queue_to_user moderation_queue_to_user + LEFT JOIN wcf".WCF_N."_moderation_queue moderation_queue + ON (moderation_queue.queueID = moderation_queue_to_user.queueID) + WHERE moderation_queue_to_user.userID = ? + AND moderation_queue.status <> ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + WCF::getUser()->userID, + ModerationQueue::STATUS_DONE + )); + $row = $statement->fetchArray(); + $count = $row['count']; + + // update storage data + UserStorageHandler::getInstance()->update(WCF::getUser()->userID, 'outstandingModerationCount', $count); + } + + return $count; + } + + /** + * Saves moderation queue assignments. + * + * @param array $assignments + */ + public function setAssignment(array $assignments) { + $sql = "INSERT INTO wcf".WCF_N."_moderation_queue_to_user + (queueID, userID, isAffected) + VALUES (?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + WCF::getDB()->beginTransaction(); + foreach ($assignments as $queueID => $isAffected) { + $statement->execute(array( + $queueID, + WCF::getUser()->userID, + ($isAffected ? 1 : 0) + )); + } + WCF::getDB()->commitTransaction(); + } + + /** + * Removes a list of orphaned queue ids. + * + * @param array $queueIDs + */ + public function removeOrphans(array $queueIDs) { + $queueAction = new ModerationQueueAction($queueIDs, 'markAsDone'); + $queueAction->executeAction(); + + $this->resetModerationCount(); + } + + /** + * Resets moderation count for all users or optionally only for one user. + * + * @param integer $userID + */ + public function resetModerationCount($userID = null) { + if ($userID === null) { + UserStorageHandler::getInstance()->resetAll('outstandingModerationCount'); + } + else { + UserStorageHandler::getInstance()->reset(array($userID), 'outstandingModerationCount'); + } + } + + /** + * Returns a list of object type ids and their parent definition name. + * + * @return array + */ + public function getDefinitionNamesByObjectTypeIDs() { + $definitionNames = array(); + foreach ($this->objectTypeNames as $definitionName => $objectTypes) { + foreach ($objectTypes as $objectTypeID) { + $definitionNames[$objectTypeID] = $definitionName; + } + } + + return $definitionNames; + } + + /** + * Returns a list of definition names associated with the specified object type. + * + * @param string $objectType + * @return array + */ + public function getDefinitionNamesByObjectType($objectType) { + $definitionNames = array(); + foreach ($this->objectTypeNames as $definitionName => $objectTypes) { + if (isset($objectTypes[$objectType])) { + $definitionNames[] = $definitionName; + } + } + + return $definitionNames; + } + + /** + * Removes moderation queues, should only be called if related objects are permanently deleted. + * + * @param string $objectType + * @param array $objectIDs + */ + public function removeQueues($objectType, array $objectIDs) { + $definitionNames = $this->getDefinitionNamesByObjectType($objectType); + if (empty($definitionNames)) { + throw new SystemException("Object type '".$objectType."' is invalid"); + } + + foreach ($definitionNames as $definitionName) { + $this->getProcessor($definitionName, $objectType)->removeQueues($objectIDs); + } + + $this->resetModerationCount(); + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueReportManager.class.php b/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueReportManager.class.php new file mode 100644 index 0000000000..8d7025fd9a --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/ModerationQueueReportManager.class.php @@ -0,0 +1,108 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue + * @category Community Framework + */ +class ModerationQueueReportManager extends AbstractModerationQueueManager { + /** + * @see wcf\system\moderation\queue\AbstractModerationQueueManager::$definitionName + */ + protected $definitionName = 'com.woltlab.wcf.moderation.report'; + + /** + * Returns true if given item was already reported. + * + * @param string $objectType + * @param integer $objectID + * @return boolean + */ + public function isAlreadyReported($objectType, $objectID) { + $objectTypeID = $this->getObjectTypeID($objectType); + + $sql = "SELECT COUNT(*) AS count + FROM wcf".WCF_N."_moderation_queue + WHERE objectTypeID = ? + AND objectID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + $objectTypeID, + $objectID + )); + $row = $statement->fetchArray(); + + return ($row['count'] == 0 ? false : true); + } + + /** + * Returns true if current user can report given content. + * + * @param string $objectType + * @param integer $objectID + * @return boolean + */ + public function canReport($objectType, $objectID) { + return $this->getProcessor($objectType)->canReport($objectID); + } + + /** + * @see wcf\system\moderation\queue\IModerationQueueManager::getLink() + */ + public function getLink($queueID) { + return LinkHandler::getInstance()->getLink('ModerationReport', array('id' => $queueID)); + } + + /** + * Returns rendered template for reported content. + * + * @param wcf\data\moderation\queue\ViewableModerationQueue $queue + * @return string + */ + public function getReportedContent(ViewableModerationQueue $queue) { + return $this->getProcessor(null, $queue->objectTypeID)->getReportedContent($queue); + } + + /** + * Returns the reported object. + * + * @param string $objectType + * @param integer $objectID + * @return wcf\data\IUserContent + */ + public function getReportedObject($objectType, $objectID) { + return $this->getProcessor($objectType)->getReportedObject($objectID); + } + + /** + * Adds a report for specified content. + * + * @param string $objectType + * @param integer $objectID + * @param string $message + * @param array $additionalData + */ + public function addReport($objectType, $objectID, $message, array $additionalData = array()) { + if (!$this->isValid($objectType)) { + throw new SystemException("Object type '".$objectType."' is not valid for definition 'com.woltlab.wcf.moderation.report'"); + } + + $additionalData['message'] = $message; + $this->addEntry( + $this->getObjectTypeID($objectType), + $objectID, + $this->getProcessor($objectType)->getContainerID($objectID), + $additionalData + ); + } +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/activation/IModerationQueueActivationHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/activation/IModerationQueueActivationHandler.class.php new file mode 100644 index 0000000000..ad5305e0a1 --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/activation/IModerationQueueActivationHandler.class.php @@ -0,0 +1,32 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue.activiation + * @category Community Framework + */ +interface IModerationQueueActivationHandler extends IModerationQueueHandler { + /** + * Enables affected content. + * + * @param wcf\data\moderation\queue\ModerationQueue $queue + */ + public function enableContent(ModerationQueue $queue); + + /** + * Returns rendered template for disabled content. + * + * @param wcf\data\moderation\queue\ViewableModerationQueue $queue + * @return string + */ + public function getDisabledContent(ViewableModerationQueue $queue); +} diff --git a/wcfsetup/install/files/lib/system/moderation/queue/report/IModerationQueueReportHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/report/IModerationQueueReportHandler.class.php new file mode 100644 index 0000000000..2accf164fd --- /dev/null +++ b/wcfsetup/install/files/lib/system/moderation/queue/report/IModerationQueueReportHandler.class.php @@ -0,0 +1,40 @@ + + * @package com.woltlab.wcf.moderation + * @subpackage system.moderation.queue.report + * @category Community Framework + */ +interface IModerationQueueReportHandler extends IModerationQueueHandler { + /** + * Returns true if current user can report given content. + * + * @param integer $objectID + * @return boolean + */ + public function canReport($objectID); + + /** + * Returns rendered template for reported content. + * + * @param wcf\data\moderation\queue\ViewableModerationQueue $queue + * @return string + */ + public function getReportedContent(ViewableModerationQueue $queue); + + /** + * Returns reported object. + * + * @param integer $objectID + * @return wcf\data\IUserContent + */ + public function getReportedObject($objectID); +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 50282f04f5..04a51a543a 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -292,6 +292,7 @@ + @@ -1521,6 +1522,58 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions() + + + + + + + + + getUser()->username})]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 4e8209eb8c..1433ff768b 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -291,6 +291,7 @@ Examples for medium ID detection: + @@ -1519,6 +1520,58 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]> + + + + + + + + getUser()->username})]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index b3b452e866..9283a40d73 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -339,6 +339,37 @@ CREATE TABLE wcf1_language_server ( isDisabled TINYINT(1) NOT NULL DEFAULT 0 ); +DROP TABLE IF EXISTS wcf1_moderation_queue; +CREATE TABLE wcf1_moderation_queue ( + queueID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectTypeID INT(10) NOT NULL, + objectID INT(10) NOT NULL, + containerID INT(10) NOT NULL DEFAULT 0, + userID INT(10) NULL, + time INT(10) NOT NULL DEFAULT 0, + + -- internal + assignedUserID INT(10) NULL, + status TINYINT(1) NOT NULL DEFAULT 0, + comment TEXT, + lastChangeTime INT(10) NOT NULL DEFAULT 0, + + -- additional data, e.g. message if reporting content + additionalData TEXT, + + UNIQUE KEY affectedObject (objectTypeID, objectID) +); + +DROP TABLE IF EXISTS wcf1_moderation_queue_to_user; +CREATE TABLE wcf1_moderation_queue_to_user ( + queueID INT(10) NOT NULL, + userID INT(10) NOT NULL, + isAffected TINYINT(1) NOT NULL DEFAULT 0, + + UNIQUE KEY queue (queueID, userID), + KEY affected (queueID, userID, isAffected) +); + DROP TABLE IF EXISTS wcf1_modification_log; CREATE TABLE wcf1_modification_log ( logID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, @@ -1279,6 +1310,13 @@ ALTER TABLE wcf1_user_profile_visitor ADD FOREIGN KEY (userID) REFERENCES wcf1_u ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE; +ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE; +ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL; +ALTER TABLE wcf1_moderation_queue ADD FOREIGN KEY (assignedUserID) REFERENCES wcf1_user (userID) ON DELETE SET NULL; + +ALTER TABLE wcf1_moderation_queue_to_user ADD FOREIGN KEY (queueID) REFERENCES wcf1_moderation_queue (queueID) ON DELETE CASCADE; +ALTER TABLE wcf1_moderation_queue_to_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; + /* default inserts */ -- default user groups -- 2.20.1