Add clipboard support for moderation (#4121)
authorMatthias Schmidt <gravatronics@live.com>
Wed, 14 Apr 2021 12:02:33 +0000 (14:02 +0200)
committerGitHub <noreply@github.com>
Wed, 14 Apr 2021 12:02:33 +0000 (14:02 +0200)
* Add clipboard support for moderation

Close  #3774

* Apply suggestions from code review

Co-authored-by: Tim Düsterhus <duesterhus@woltlab.com>
* Use `defaultChecked` property in `Ui/Moderation/Clipboard/AssignUser`

Co-authored-by: Tim Düsterhus <duesterhus@woltlab.com>
16 files changed:
com.woltlab.wcf/clipboardAction.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/templates/moderationList.tpl
com.woltlab.wcf/templates/moderationReportRemoveContent.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/moderationReportRemoveReport.tpl [new file with mode: 0644]
ts/WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser.js [new file with mode: 0644]
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueActivationAction.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueReportAction.class.php
wcfsetup/install/files/lib/page/ModerationListPage.class.php
wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueActivationClipboardAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueClipboardAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueReportClipboardAction.class.php [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 3f48d7992503e9b84e506db96b41ea8b1b4fae19..1e4b32527a95ee97f715c247b135ae9ccf831fbc 100644 (file)
                                <page>wcf\acp\page\ArticleListPage</page>
                        </pages>
                </action>
+               <action name="assignUserByClipboard">
+                       <actionclassname>wcf\system\clipboard\action\ModerationQueueClipboardAction</actionclassname>
+                       <showorder>1</showorder>
+                       <pages>
+                               <page>wcf\page\ModerationListPage</page>
+                       </pages>
+               </action>
+               <action name="enableContent">
+                       <actionclassname>wcf\system\clipboard\action\ModerationQueueActivationClipboardAction</actionclassname>
+                       <showorder>2</showorder>
+                       <pages>
+                               <page>wcf\page\ModerationListPage</page>
+                       </pages>
+               </action>
+               <action name="removeActivationContent">
+                       <actionclassname>wcf\system\clipboard\action\ModerationQueueActivationClipboardAction</actionclassname>
+                       <showorder>3</showorder>
+                       <pages>
+                               <page>wcf\page\ModerationListPage</page>
+                       </pages>
+               </action>
+               <action name="removeReport">
+                       <actionclassname>wcf\system\clipboard\action\ModerationQueueReportClipboardAction</actionclassname>
+                       <showorder>4</showorder>
+                       <pages>
+                               <page>wcf\page\ModerationListPage</page>
+                       </pages>
+               </action>
+               <action name="removeReportContent">
+                       <actionclassname>wcf\system\clipboard\action\ModerationQueueReportClipboardAction</actionclassname>
+                       <showorder>5</showorder>
+                       <pages>
+                               <page>wcf\page\ModerationListPage</page>
+                       </pages>
+               </action>
        </import>
 </data>
index 78b99d56b64cbc7278a61c65edc581e0b8a678bf..8b6b752f54708314dcaea60e0e85e79ab5afb629 100644 (file)
                        <definitionname>com.woltlab.wcf.clipboardItem</definitionname>
                        <listclassname>wcf\data\article\ArticleList</listclassname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.moderation.queue</name>
+                       <definitionname>com.woltlab.wcf.clipboardItem</definitionname>
+                       <listclassname>wcf\data\moderation\queue\ViewableModerationQueueList</listclassname>
+               </type>
                <!-- /clipboard items -->
                <!-- articles -->
                <type>
index a417d26de268197d31c7be30b4a00ad624d781c1..72637c619a1e87e10ecddade2333992a25ea1b67 100644 (file)
 {/hascontent}
 
 {if $objects|count}
-       <div class="section tabularBox messageGroupList moderationList moderationQueueEntryList">
+       <div class="section tabularBox messageGroupList moderationList moderationQueueEntryList jsClipboardContainer" data-type="com.woltlab.wcf.moderation.queue">
                <ol class="tabularList">
                        <li class="tabularListRow tabularListRowHead">
                                <ol class="tabularListColumns">
+                                       <li class="columnMark jsOnly"><label><input type="checkbox" class="jsClipboardMarkAll"></label></li>
+                                       
                                        <li class="columnSort">
                                                <ul class="inlineList">
                                                        <li>
@@ -33,7 +35,7 @@
                                                        <li>
                                                                <div class="dropdown">
                                                                        <span class="dropdownToggle">{lang}wcf.moderation.{$sortField}{/lang}</span>
-               
+                                                                       
                                                                        <ul class="dropdownMenu">
                                                                                {foreach from=$validSortFields item=_sortField}
                                                                                        <li{if $_sortField === $sortField} class="active"{/if}><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField={$_sortField}&sortOrder={if $sortField == $_sortField && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.moderation.{$_sortField}{/lang}</a></li>
                        
                        {foreach from=$objects item=entry}
                                <li class="tabularListRow">
-                                       <ol class="tabularListColumns messageGroup moderationQueueEntry{if $entry->isNew()} new{/if}" data-queue-id="{@$entry->queueID}">
+                                       <ol class="tabularListColumns messageGroup moderationQueueEntry jsClipboardObject{if $entry->isNew()} new{/if}" data-queue-id="{@$entry->queueID}">
+                                               <li class="columnMark jsOnly">
+                                                       <label><input type="checkbox" class="jsClipboardItem" data-object-id="{@$entry->getObjectID()}"></label>
+                                               </li>
                                                <li class="columnIcon columnAvatar">
                                                        <div>
                                                                <p{if $entry->isNew()} title="{lang}wcf.moderation.markAsRead.doubleClick{/lang}"{/if}>{@$entry->getUserProfile()->getAvatar()->getImageTag(48)}</p>
 </div>
 
 <script data-relocate="true">
-       $(function() {
+       require([
+               'Language',
+               'WoltLabSuite/Core/Controller/Clipboard',
+               'WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser'
+       ], (
+               Language,
+               ControllerClipboard,
+               UiModerationClipboardAssignUser
+       ) => {
+               Language.addObject({
+                       'wcf.moderation.assignedUser': '{jslang}wcf.moderation.assignedUser{/jslang}',
+                       'wcf.moderation.assignedUser.change': '{jslang}wcf.moderation.assignedUser.change{/jslang}',
+                       'wcf.moderation.assignedUser.error.notAffected': '{jslang}wcf.moderation.assignedUser.error.notAffected{/jslang}',
+                       'wcf.moderation.assignedUser.nobody': '{jslang}wcf.moderation.assignedUser.nobody{/jslang}',
+                       'wcf.user.username.error.notFound': '{jslang __literal=true}wcf.user.username.error.notFound{/jslang}',
+               });
+               
+               ControllerClipboard.setup({
+                       hasMarkedItems: {if $hasMarkedItems}true{else}false{/if},
+                       pageClassName: 'wcf\\page\\ModerationListPage',
+               });
+               
+               UiModerationClipboardAssignUser.setup();
+               
                new WCF.Moderation.Queue.MarkAsRead();
                new WCF.Moderation.Queue.MarkAllAsRead();
        });
diff --git a/com.woltlab.wcf/templates/moderationReportRemoveContent.tpl b/com.woltlab.wcf/templates/moderationReportRemoveContent.tpl
new file mode 100644 (file)
index 0000000..1e3b12e
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="section">
+    <dl>
+        <dt>
+            <label for="message">{lang}wcf.moderation.report.removeContent.reason{/lang}</label>
+        </dt>
+        <dd>
+            <textarea name="message" cols="40" rows="3"></textarea>
+        </dd>
+    </dl>
+</div>
diff --git a/com.woltlab.wcf/templates/moderationReportRemoveReport.tpl b/com.woltlab.wcf/templates/moderationReportRemoveReport.tpl
new file mode 100644 (file)
index 0000000..8e5bfd1
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="section">
+    <dl>
+        <dt></dt>
+        <dd>
+            <label>
+                <input type="checkbox" name="markAsJustified" id="markAsJustified" value="1">
+                {lang}wcf.moderation.report.removeReport.markAsJustified{/lang}
+            </label>
+        </dd>
+    </dl>
+</div>
diff --git a/ts/WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser.ts b/ts/WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser.ts
new file mode 100644 (file)
index 0000000..f028510
--- /dev/null
@@ -0,0 +1,189 @@
+/**
+ * Handles the dialog to select the user when assigning a user to multiple moderation queue entries
+ * via clipboard.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2021 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser
+ */
+
+import * as EventHandler from "../../../Event/Handler";
+import { AjaxResponse, ClipboardActionData } from "../../../Controller/Clipboard/Data";
+import * as UiNotification from "../../Notification";
+import User from "../../../User";
+import * as StringUtil from "../../../StringUtil";
+import * as Language from "../../../Language";
+import UiUserSearchInput from "../../User/Search/Input";
+import * as DomTraverse from "../../../Dom/Traverse";
+import * as Ajax from "../../../Ajax";
+import { AjaxCallbackObject, AjaxCallbackSetup, ResponseData } from "../../../Ajax/Data";
+import DomUtil from "../../../Dom/Util";
+import { DialogCallbackObject, DialogCallbackSetup } from "../../Dialog/Data";
+import UiDialog from "../../Dialog";
+
+interface EventData {
+  data: ClipboardActionData;
+  listItem: HTMLLIElement;
+  responseData: AjaxResponse;
+}
+
+class UiModerationClipboardAssignUser implements AjaxCallbackObject, DialogCallbackObject {
+  /**
+   * ids of the moderation queue entries currently being handled
+   */
+  protected queueIds: number[] = [];
+
+  public _ajaxFailure(data: ResponseData): boolean {
+    if (data.returnValues?.fieldName === "assignedUsername") {
+      let errorMessage = "";
+
+      const dialog = UiDialog.getDialog(this)!.content;
+      const assignedUsername = dialog.querySelector("input[name=assignedUsername]") as HTMLInputElement;
+
+      const errorType: string = data.returnValues.errorType;
+      switch (errorType) {
+        case "empty":
+          errorMessage = Language.get("wcf.global.form.error.empty");
+          break;
+
+        case "notAffected":
+          errorMessage = Language.get("wcf.moderation.assignedUser.error.notAffected");
+          break;
+
+        default:
+          errorMessage = Language.get(`wcf.user.username.error.${errorType}`, {
+            username: assignedUsername.value,
+          });
+          break;
+      }
+
+      DomUtil.innerError(assignedUsername, errorMessage);
+
+      return false;
+    }
+
+    return true;
+  }
+
+  public _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
+    return {
+      data: {
+        actionName: "assignUserByClipboard",
+        className: "wcf\\data\\moderation\\queue\\ModerationQueueAction",
+      },
+    };
+  }
+
+  public _ajaxSuccess(): void {
+    UiDialog.close(this);
+
+    UiNotification.show(undefined, () => window.location.reload());
+  }
+
+  public _dialogSetup(): ReturnType<DialogCallbackSetup> {
+    const submitCallback = () => this.submitDialog();
+
+    return {
+      id: "moderationQueueClipboardAssignUser",
+      options: {
+        onSetup(content: HTMLElement): void {
+          const username = content.querySelector("input[name=assignedUsername]") as HTMLInputElement;
+          new UiUserSearchInput(username, {});
+
+          username.addEventListener("click", (event) => {
+            const assignedUserId = DomTraverse.prevBySel(
+              event.currentTarget as HTMLElement,
+              "input[name=assignedUserID]",
+            ) as HTMLInputElement;
+            assignedUserId.click();
+          });
+
+          content.querySelector("button[data-type=submit]")!.addEventListener("click", submitCallback);
+        },
+        onShow(content: HTMLElement): void {
+          // Reset dialog to initial state.
+          const assignedUsername = content.querySelector("input[name=assignedUsername]") as HTMLInputElement;
+          content
+            .querySelectorAll("input[name=assignedUserID]")
+            .forEach((el: HTMLInputElement) => (el.checked = el.defaultChecked));
+
+          assignedUsername.value = "";
+
+          DomUtil.innerError(assignedUsername, "");
+        },
+        title: Language.get("wcf.moderation.assignedUser.change"),
+      },
+      source: `
+<div class="section">
+  <dl>
+    <dt>${Language.get("wcf.moderation.assignedUser")}</dt>
+    <dd>
+      <ul>
+        <li>
+          <label>
+            <input type="radio" name="assignedUserID" value="${User.userId}" checked>
+            ${StringUtil.escapeHTML(User.username)}
+          </label>
+        </li>
+        <li>
+          <label>
+            <input type="radio" name="assignedUserID" value="0">
+            ${Language.get("wcf.moderation.assignedUser.nobody")}
+          </label>
+        </li>
+        <li>
+          <input type="radio" name="assignedUserID" value="-1">
+          <input type="text" name="assignedUsername" value="">
+        </li>
+      </ul>
+    </dd>
+  </dl>
+</div>
+<div class="formSubmit">
+  <button class="buttonPrimary" data-type="submit">${Language.get("wcf.global.button.save")}</button>
+</div>`,
+    };
+  }
+
+  public showDialog(queueIds: number[]): void {
+    this.queueIds = queueIds;
+
+    UiDialog.open(this);
+  }
+
+  public submitDialog(): void {
+    const dialog = UiDialog.getDialog(this)!.content;
+    const assignedUserId = dialog.querySelector("input[name=assignedUserID]:checked") as HTMLInputElement;
+    const assignedUsername = dialog.querySelector("input[name=assignedUsername]") as HTMLInputElement;
+
+    Ajax.api(this, {
+      objectIDs: this.queueIds,
+      parameters: {
+        assignedUserID: assignedUserId.value,
+        assignedUsername: assignedUsername.value,
+      },
+    });
+  }
+}
+
+let isSetUp = false;
+
+export function setup(): void {
+  if (isSetUp) {
+    return;
+  }
+
+  const handler = new UiModerationClipboardAssignUser();
+
+  EventHandler.add("com.woltlab.wcf.clipboard", "com.woltlab.wcf.moderation.queue", (data: EventData) => {
+    if (
+      data.data.actionName === "com.woltlab.wcf.moderation.queue.assignUserByClipboard" &&
+      data.responseData === null
+    ) {
+      handler.showDialog(data.data.parameters.objectIDs);
+    }
+  });
+
+  isSetUp = true;
+}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser.js
new file mode 100644 (file)
index 0000000..5a44a46
--- /dev/null
@@ -0,0 +1,156 @@
+/**
+ * Handles the dialog to select the user when assigning a user to multiple moderation queue entries
+ * via clipboard.
+ *
+ * @author  Matthias Schmidt
+ * @copyright  2001-2021 WoltLab GmbH
+ * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module  WoltLabSuite/Core/Ui/Moderation/Clipboard/AssignUser
+ */
+define(["require", "exports", "tslib", "../../../Event/Handler", "../../Notification", "../../../User", "../../../StringUtil", "../../../Language", "../../User/Search/Input", "../../../Dom/Traverse", "../../../Ajax", "../../../Dom/Util", "../../Dialog"], function (require, exports, tslib_1, EventHandler, UiNotification, User_1, StringUtil, Language, Input_1, DomTraverse, Ajax, Util_1, Dialog_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.setup = void 0;
+    EventHandler = tslib_1.__importStar(EventHandler);
+    UiNotification = tslib_1.__importStar(UiNotification);
+    User_1 = tslib_1.__importDefault(User_1);
+    StringUtil = tslib_1.__importStar(StringUtil);
+    Language = tslib_1.__importStar(Language);
+    Input_1 = tslib_1.__importDefault(Input_1);
+    DomTraverse = tslib_1.__importStar(DomTraverse);
+    Ajax = tslib_1.__importStar(Ajax);
+    Util_1 = tslib_1.__importDefault(Util_1);
+    Dialog_1 = tslib_1.__importDefault(Dialog_1);
+    class UiModerationClipboardAssignUser {
+        constructor() {
+            /**
+             * ids of the moderation queue entries currently being handled
+             */
+            this.queueIds = [];
+        }
+        _ajaxFailure(data) {
+            var _a;
+            if (((_a = data.returnValues) === null || _a === void 0 ? void 0 : _a.fieldName) === "assignedUsername") {
+                let errorMessage = "";
+                const dialog = Dialog_1.default.getDialog(this).content;
+                const assignedUsername = dialog.querySelector("input[name=assignedUsername]");
+                const errorType = data.returnValues.errorType;
+                switch (errorType) {
+                    case "empty":
+                        errorMessage = Language.get("wcf.global.form.error.empty");
+                        break;
+                    case "notAffected":
+                        errorMessage = Language.get("wcf.moderation.assignedUser.error.notAffected");
+                        break;
+                    default:
+                        errorMessage = Language.get(`wcf.user.username.error.${errorType}`, {
+                            username: assignedUsername.value,
+                        });
+                        break;
+                }
+                Util_1.default.innerError(assignedUsername, errorMessage);
+                return false;
+            }
+            return true;
+        }
+        _ajaxSetup() {
+            return {
+                data: {
+                    actionName: "assignUserByClipboard",
+                    className: "wcf\\data\\moderation\\queue\\ModerationQueueAction",
+                },
+            };
+        }
+        _ajaxSuccess() {
+            Dialog_1.default.close(this);
+            UiNotification.show(undefined, () => window.location.reload());
+        }
+        _dialogSetup() {
+            const submitCallback = () => this.submitDialog();
+            return {
+                id: "moderationQueueClipboardAssignUser",
+                options: {
+                    onSetup(content) {
+                        const username = content.querySelector("input[name=assignedUsername]");
+                        new Input_1.default(username, {});
+                        username.addEventListener("click", (event) => {
+                            const assignedUserId = DomTraverse.prevBySel(event.currentTarget, "input[name=assignedUserID]");
+                            assignedUserId.click();
+                        });
+                        content.querySelector("button[data-type=submit]").addEventListener("click", submitCallback);
+                    },
+                    onShow(content) {
+                        // Reset dialog to initial state.
+                        const assignedUsername = content.querySelector("input[name=assignedUsername]");
+                        content
+                            .querySelectorAll("input[name=assignedUserID]")
+                            .forEach((el) => (el.checked = el.defaultChecked));
+                        assignedUsername.value = "";
+                        Util_1.default.innerError(assignedUsername, "");
+                    },
+                    title: Language.get("wcf.moderation.assignedUser.change"),
+                },
+                source: `
+<div class="section">
+  <dl>
+    <dt>${Language.get("wcf.moderation.assignedUser")}</dt>
+    <dd>
+      <ul>
+        <li>
+          <label>
+            <input type="radio" name="assignedUserID" value="${User_1.default.userId}" checked>
+            ${StringUtil.escapeHTML(User_1.default.username)}
+          </label>
+        </li>
+        <li>
+          <label>
+            <input type="radio" name="assignedUserID" value="0">
+            ${Language.get("wcf.moderation.assignedUser.nobody")}
+          </label>
+        </li>
+        <li>
+          <input type="radio" name="assignedUserID" value="-1">
+          <input type="text" name="assignedUsername" value="">
+        </li>
+      </ul>
+    </dd>
+  </dl>
+</div>
+<div class="formSubmit">
+  <button class="buttonPrimary" data-type="submit">${Language.get("wcf.global.button.save")}</button>
+</div>`,
+            };
+        }
+        showDialog(queueIds) {
+            this.queueIds = queueIds;
+            Dialog_1.default.open(this);
+        }
+        submitDialog() {
+            const dialog = Dialog_1.default.getDialog(this).content;
+            const assignedUserId = dialog.querySelector("input[name=assignedUserID]:checked");
+            const assignedUsername = dialog.querySelector("input[name=assignedUsername]");
+            Ajax.api(this, {
+                objectIDs: this.queueIds,
+                parameters: {
+                    assignedUserID: assignedUserId.value,
+                    assignedUsername: assignedUsername.value,
+                },
+            });
+        }
+    }
+    let isSetUp = false;
+    function setup() {
+        if (isSetUp) {
+            return;
+        }
+        const handler = new UiModerationClipboardAssignUser();
+        EventHandler.add("com.woltlab.wcf.clipboard", "com.woltlab.wcf.moderation.queue", (data) => {
+            if (data.data.actionName === "com.woltlab.wcf.moderation.queue.assignUserByClipboard" &&
+                data.responseData === null) {
+                handler.showDialog(data.data.parameters.objectIDs);
+            }
+        });
+        isSetUp = true;
+    }
+    exports.setup = setup;
+});
index 1cd5ba8db8ad7625d88eabef04420e0f225a7d91..a75f47942a503e385fee58840e7ee08b5dc0e83b 100644 (file)
@@ -5,9 +5,11 @@ namespace wcf\data\moderation\queue;
 use wcf\data\AbstractDatabaseObjectAction;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\user\User;
+use wcf\system\clipboard\ClipboardHandler;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
+use wcf\system\moderation\queue\IModerationQueueHandler;
 use wcf\system\moderation\queue\ModerationQueueManager;
 use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\visitTracker\VisitTracker;
@@ -380,4 +382,109 @@ class ModerationQueueAction extends AbstractDatabaseObjectAction
     {
         // does nothing
     }
+
+    /**
+     * Validates the `assignUserByClipboard` action.
+     *
+     * @since   5.4
+     */
+    public function validateAssignUserByClipboard(): void
+    {
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
+        }
+
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            if (!$moderationQueueEditor->canEdit()) {
+                throw new PermissionDeniedException();
+            }
+        }
+
+        $this->readInteger('assignedUserID', false);
+
+        if ($this->parameters['assignedUserID'] < -1) {
+            throw new UserInputException('assignedUserID');
+        } elseif ($this->parameters['assignedUserID'] == -1) {
+            $this->readString('assignedUsername', false);
+
+            $this->user = User::getUserByUsername($this->parameters['assignedUsername']);
+            if (!$this->user->userID) {
+                throw new UserInputException('assignedUsername', 'notFound');
+            }
+
+            foreach ($this->getObjects() as $moderationQueueEditor) {
+                /** @var IModerationQueueHandler $processor */
+                $processor = ObjectTypeCache::getInstance()->getObjectType(
+                    $moderationQueueEditor->objectTypeID
+                )->getProcessor();
+
+                $isAffected = $processor->isAffectedUser(
+                    $moderationQueueEditor->getDecoratedObject(),
+                    $this->user->userID
+                );
+                if (!$isAffected) {
+                    throw new UserInputException('assignedUsername', 'notAffected');
+                }
+            }
+
+            $this->parameters['assignedUserID'] = $this->user->userID;
+            $this->parameters['assignedUsername'] = '';
+        } elseif ($this->parameters['assignedUserID'] == WCF::getUser()->userID) {
+            $this->user = WCF::getUser();
+        }
+    }
+
+    /**
+     * Assigns a user to multiple moderation queue entries via clipboard.
+     *
+     * @since   5.4
+     */
+    public function assignUserByClipboard(): void
+    {
+        WCF::getDB()->beginTransaction();
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            $data = [
+                'assignedUserID' => $this->user ? $this->user->userID : null,
+            ];
+            if ($this->user) {
+                if ($moderationQueueEditor->status == ModerationQueue::STATUS_OUTSTANDING) {
+                    $data['status'] = ModerationQueue::STATUS_PROCESSING;
+                }
+            } else {
+                if ($moderationQueueEditor->status == ModerationQueue::STATUS_PROCESSING) {
+                    $data['status'] = ModerationQueue::STATUS_OUTSTANDING;
+                }
+            }
+
+            $moderationQueueEditor->update($data);
+        }
+        WCF::getDB()->commitTransaction();
+
+        $this->unmarkItems();
+    }
+
+    /**
+     * Unmarks the moderation queue entries with the given ids or all currently handled entries if
+     * no argument is given.
+     *
+     * @param   int[]   $queueIDs
+     * @since   5.4
+     */
+    protected function unmarkItems(array $queueIDs = []): void
+    {
+        if (empty($queueIDs)) {
+            $queueIDs = $this->objectIDs;
+        }
+
+        if (!empty($queueIDs)) {
+            ClipboardHandler::getInstance()->unmark(
+                $queueIDs,
+                ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue')
+            );
+        }
+    }
 }
index 6b53cfaa1358efe2d6f4a8a80c7a0e5ae961626f..4e112a11342e63197531336f371919f65a90cf0a 100644 (file)
@@ -3,7 +3,10 @@
 namespace wcf\data\moderation\queue;
 
 use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
 use wcf\system\moderation\queue\ModerationQueueActivationManager;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Executes actions for reports.
@@ -31,9 +34,18 @@ class ModerationQueueActivationAction extends ModerationQueueAction
      */
     public function validateEnableContent()
     {
-        $this->queue = $this->getSingleObject();
-        if (!$this->queue->canEdit()) {
-            throw new PermissionDeniedException();
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
+        }
+
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            if (!$moderationQueueEditor->canEdit()) {
+                throw new PermissionDeniedException();
+            }
         }
     }
 
@@ -42,10 +54,17 @@ class ModerationQueueActivationAction extends ModerationQueueAction
      */
     public function enableContent()
     {
-        // enable content
-        ModerationQueueActivationManager::getInstance()->enableContent($this->queue->getDecoratedObject());
+        WCF::getDB()->beginTransaction();
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            ModerationQueueActivationManager::getInstance()->enableContent(
+                $moderationQueueEditor->getDecoratedObject()
+            );
 
-        $this->queue->markAsConfirmed();
+            $moderationQueueEditor->markAsConfirmed();
+        }
+        WCF::getDB()->commitTransaction();
+
+        $this->unmarkItems();
     }
 
     /**
@@ -69,5 +88,55 @@ class ModerationQueueActivationAction extends ModerationQueueAction
         );
 
         $this->queue->markAsRejected();
+
+        $this->unmarkItems();
+    }
+
+    /**
+     * Validates the `removeActivationContent` action.
+     *
+     * @since   5.4
+     */
+    public function validateRemoveActivationContent(): void
+    {
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
+        }
+
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            if (
+                !$moderationQueueEditor->canEdit()
+                || !ModerationQueueActivationManager::getInstance()->canRemoveContent($moderationQueueEditor->getDecoratedObject())
+            ) {
+                throw new PermissionDeniedException();
+            }
+        }
+
+        $this->parameters['message'] = StringUtil::trim($this->parameters['message'] ?? '');
+    }
+
+    /**
+     * Deletes disabled content via clipboard.
+     *
+     * @since   5.4
+     */
+    public function removeActivationContent(): void
+    {
+        WCF::getDB()->beginTransaction();
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            ModerationQueueActivationManager::getInstance()->removeContent(
+                $moderationQueueEditor->getDecoratedObject(),
+                $this->parameters['message']
+            );
+
+            $moderationQueueEditor->markAsConfirmed();
+        }
+        WCF::getDB()->commitTransaction();
+
+        $this->unmarkItems();
     }
 }
index ef63c38366a9edecaca89a30687b4f53f38ffb8e..049c5e399dabad593130ee55c1f06ca26b204acc 100644 (file)
@@ -51,6 +51,8 @@ class ModerationQueueReportAction extends ModerationQueueAction
         );
 
         $this->queue->markAsConfirmed();
+
+        $this->unmarkItems();
     }
 
     /**
@@ -58,12 +60,28 @@ class ModerationQueueReportAction extends ModerationQueueAction
      */
     public function validateRemoveReport()
     {
-        $this->queue = $this->getSingleObject();
-        if (!$this->queue->canEdit()) {
-            throw new PermissionDeniedException();
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
         }
 
-        $this->readBoolean('markAsJustified', true);
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            if (!$moderationQueueEditor->canEdit()) {
+                throw new PermissionDeniedException();
+            }
+        }
+
+        if (isset($this->parameters['data']['markAsJustified'])) {
+            $this->readBoolean('markAsJustified', true, 'data');
+
+            // Clipboard passes `markAsJustified` in the `data` array.
+            $this->parameters['markAsJustified'] = $this->parameters['data']['markAsJustified'];
+        } else {
+            $this->readBoolean('markAsJustified', true);
+        }
     }
 
     /**
@@ -71,7 +89,13 @@ class ModerationQueueReportAction extends ModerationQueueAction
      */
     public function removeReport()
     {
-        $this->queue->markAsRejected(($this->parameters['markAsJustified'] ?? false));
+        WCF::getDB()->beginTransaction();
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            $moderationQueueEditor->markAsRejected($this->parameters['markAsJustified'] ?? false);
+        }
+        WCF::getDB()->commitTransaction();
+
+        $this->unmarkItems();
     }
 
     /**
@@ -205,4 +229,52 @@ class ModerationQueueReportAction extends ModerationQueueAction
             'additionalData' => \serialize($additionalData),
         ]);
     }
+
+    /**
+     * Validates the `removeReportContent` action.
+     *
+     * @since   5.4
+     */
+    public function validateRemoveReportContent(): void
+    {
+        if (empty($this->objects)) {
+            $this->readObjects();
+
+            if (empty($this->objects)) {
+                throw new UserInputException('objectIDs');
+            }
+        }
+
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            if (
+                !$moderationQueueEditor->canEdit()
+                || !ModerationQueueReportManager::getInstance()->canRemoveContent($moderationQueueEditor->getDecoratedObject())
+            ) {
+                throw new PermissionDeniedException();
+            }
+        }
+
+        $this->parameters['message'] = StringUtil::trim($this->parameters['message'] ?? '');
+    }
+
+    /**
+     * Deletes reported content via clipboard.
+     *
+     * @since   5.4
+     */
+    public function removeReportContent(): void
+    {
+        WCF::getDB()->beginTransaction();
+        foreach ($this->getObjects() as $moderationQueueEditor) {
+            ModerationQueueReportManager::getInstance()->removeContent(
+                $moderationQueueEditor->getDecoratedObject(),
+                $this->parameters['message']
+            );
+
+            $moderationQueueEditor->markAsConfirmed();
+        }
+        WCF::getDB()->commitTransaction();
+
+        $this->unmarkItems();
+    }
 }
index f53f94e2dc2b24b6f00aaf0052f75d442d94345d..295dc1fff5d6e59708fd1f9f4e895882c0dbb2ff 100644 (file)
@@ -4,6 +4,7 @@ namespace wcf\page;
 
 use wcf\data\moderation\queue\ModerationQueue;
 use wcf\data\moderation\queue\ViewableModerationQueueList;
+use wcf\system\clipboard\ClipboardHandler;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\moderation\queue\ModerationQueueManager;
 use wcf\system\request\LinkHandler;
@@ -172,6 +173,9 @@ class ModerationListPage extends SortablePage
             'status' => $this->status,
             'validSortFields' => $this->validSortFields,
             'hasActiveFilter' => $this->hasActiveFilter,
+            'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(
+                ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue')
+            ),
         ]);
     }
 }
diff --git a/wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueActivationClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueActivationClipboardAction.class.php
new file mode 100644 (file)
index 0000000..c19c8f5
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+
+namespace wcf\system\clipboard\action;
+
+use wcf\data\clipboard\action\ClipboardAction;
+use wcf\data\moderation\queue\ModerationQueueActivationAction;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\moderation\queue\report\IModerationQueueReportHandler;
+use wcf\system\WCF;
+
+/**
+ * Clipboard action implementation for activation moderation queue entries.
+ *
+ * @author  Matthias Schmidt
+ * @copyright   2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Clipboard\Action
+ * @since   5.4
+ */
+class ModerationQueueActivationClipboardAction extends AbstractClipboardAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $actionClassActions = [
+        'enableContent',
+        'removeActivationContent',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    protected $reloadPageOnSuccess = [
+        'enableContent',
+        'removeActivationContent',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    protected $supportedActions = [
+        'enableContent',
+        'removeActivationContent',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    public function execute(array $objects, ClipboardAction $action)
+    {
+        $item = parent::execute($objects, $action);
+
+        if ($item === null) {
+            return;
+        }
+
+        switch ($action->actionName) {
+            case 'enableContent':
+                $item->addInternalData(
+                    'confirmMessage',
+                    WCF::getLanguage()->getDynamicVariable(
+                        'wcf.moderation.activation.enableContent.confirmMessage',
+                        [
+                            'moderationQueueCount' => $item->getCount(),
+                        ]
+                    )
+                );
+                break;
+
+            case 'removeActivationContent':
+                $item->addInternalData(
+                    'confirmMessage',
+                    WCF::getLanguage()->getDynamicVariable(
+                        'wcf.moderation.activation.removeContent.confirmMessage',
+                        [
+                            'moderationQueueCount' => $item->getCount(),
+                        ]
+                    )
+                );
+                break;
+        }
+
+        return $item;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getClassName()
+    {
+        return ModerationQueueActivationAction::class;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTypeName()
+    {
+        return 'com.woltlab.wcf.moderation.queue';
+    }
+
+    /**
+     * Returns the ids of the ids of the marked moderation queue entries that are activation
+     * moderation queue entries whose content the active user can delete.
+     *
+     * @return  int[]
+     */
+    public function validateRemoveActivationContent(): array
+    {
+        return \array_values(\array_filter(\array_map(static function (ViewableModerationQueue $queue) {
+            $objectType = ObjectTypeCache::getInstance()->getObjectType($queue->objectTypeID);
+            /** @var IModerationQueueReportHandler $processor */
+            $processor = $objectType->getProcessor();
+            $definition = $objectType->getDefinition();
+            if (
+                $definition->definitionName === 'com.woltlab.wcf.moderation.activation'
+                && $queue->canEdit()
+                && $processor->canRemoveContent($queue->getDecoratedObject())
+                && !$queue->isDone()
+            ) {
+                return $queue->queueID;
+            }
+        }, $this->objects)));
+    }
+
+    /**
+     * Returns the ids of the ids of the marked moderation queue entries that are activation
+     * moderation queue entries whose content the active user can enable.
+     *
+     * @return  int[]
+     */
+    public function validateEnableContent(): array
+    {
+        return \array_values(\array_filter(\array_map(static function (ViewableModerationQueue $queue) {
+            $definition = ObjectTypeCache::getInstance()->getObjectType($queue->objectTypeID)->getDefinition();
+            if (
+                $definition->definitionName === 'com.woltlab.wcf.moderation.activation'
+                && $queue->canEdit()
+                && !$queue->isDone()
+            ) {
+                return $queue->queueID;
+            }
+        }, $this->objects)));
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueClipboardAction.class.php
new file mode 100644 (file)
index 0000000..d2ae708
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace wcf\system\clipboard\action;
+
+use wcf\data\moderation\queue\ModerationQueueAction;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+
+/**
+ * Clipboard action implementation for moderation queue entries.
+ *
+ * @author  Matthias Schmidt
+ * @copyright   2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Clipboard\Action
+ * @since   5.4
+ */
+class ModerationQueueClipboardAction extends AbstractClipboardAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $supportedActions = [
+        'assignUserByClipboard',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    public function getClassName()
+    {
+        return ModerationQueueAction::class;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTypeName()
+    {
+        return 'com.woltlab.wcf.moderation.queue';
+    }
+
+    /**
+     * Returns the ids of the ids of the marked moderation queue entries for which the active user
+     * can assign users.
+     *
+     * @return  int[]
+     */
+    public function validateAssignUserByClipboard(): array
+    {
+        return \array_values(\array_filter(\array_map(static function (ViewableModerationQueue $queue) {
+            if ($queue->canEdit()) {
+                return $queue->queueID;
+            }
+        }, $this->objects)));
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueReportClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/ModerationQueueReportClipboardAction.class.php
new file mode 100644 (file)
index 0000000..2f2dcfd
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+
+namespace wcf\system\clipboard\action;
+
+use wcf\data\clipboard\action\ClipboardAction;
+use wcf\data\moderation\queue\ModerationQueueReportAction;
+use wcf\data\moderation\queue\ViewableModerationQueue;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\moderation\queue\report\IModerationQueueReportHandler;
+use wcf\system\WCF;
+
+/**
+ * Clipboard action implementation for report moderation queue entries.
+ *
+ * @author  Matthias Schmidt
+ * @copyright   2001-2021 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Clipboard\Action
+ * @since   5.4
+ */
+class ModerationQueueReportClipboardAction extends AbstractClipboardAction
+{
+    /**
+     * @inheritDoc
+     */
+    protected $actionClassActions = [
+        'removeReport',
+        'removeReportContent',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    protected $reloadPageOnSuccess = [
+        'removeReport',
+        'removeReportContent',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    protected $supportedActions = [
+        'removeReport',
+        'removeReportContent',
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    public function execute(array $objects, ClipboardAction $action)
+    {
+        $item = parent::execute($objects, $action);
+
+        if ($item === null) {
+            return;
+        }
+
+        switch ($action->actionName) {
+            case 'removeReportContent':
+                $item->addInternalData(
+                    'confirmMessage',
+                    WCF::getLanguage()->getDynamicVariable(
+                        'wcf.moderation.report.removeContent.confirmMessage',
+                        [
+                            'moderationQueueCount' => $item->getCount(),
+                        ]
+                    )
+                );
+                $item->addInternalData(
+                    'template',
+                    WCF::getTPL()->fetch('moderationReportRemoveContent')
+                );
+                break;
+
+            case 'removeReport':
+                $item->addInternalData(
+                    'confirmMessage',
+                    WCF::getLanguage()->getDynamicVariable(
+                        'wcf.moderation.report.removeReport.confirmMessage',
+                        [
+                            'moderationQueueCount' => $item->getCount(),
+                        ]
+                    )
+                );
+                $item->addInternalData(
+                    'template',
+                    WCF::getTPL()->fetch('moderationReportRemoveReport')
+                );
+                break;
+        }
+
+        return $item;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getClassName()
+    {
+        return ModerationQueueReportAction::class;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTypeName()
+    {
+        return 'com.woltlab.wcf.moderation.queue';
+    }
+
+    /**
+     * Returns the ids of the ids of the marked moderation queue entries that are report
+     * moderation queue entries whose content the active user can delete.
+     *
+     * @return  int[]
+     */
+    public function validateRemoveReportContent(): array
+    {
+        return \array_values(\array_filter(\array_map(static function (ViewableModerationQueue $queue) {
+            $objectType = ObjectTypeCache::getInstance()->getObjectType($queue->objectTypeID);
+            /** @var IModerationQueueReportHandler $processor */
+            $processor = $objectType->getProcessor();
+            $definition = $objectType->getDefinition();
+            if (
+                $definition->definitionName === 'com.woltlab.wcf.moderation.report'
+                && $queue->canEdit()
+                && $processor->canRemoveContent($queue->getDecoratedObject())
+                && !$queue->isDone()
+            ) {
+                return $queue->queueID;
+            }
+        }, $this->objects)));
+    }
+
+    /**
+     * Returns the ids of the ids of the marked moderation queue entries that are report
+     * moderation queue entries the active user can close.
+     *
+     * @return  int[]
+     */
+    public function validateRemoveReport(): array
+    {
+        return \array_values(\array_filter(\array_map(static function (ViewableModerationQueue $queue) {
+            $definition = ObjectTypeCache::getInstance()->getObjectType($queue->objectTypeID)->getDefinition();
+            if (
+                $definition->definitionName === 'com.woltlab.wcf.moderation.report'
+                && $queue->canEdit()
+                && !$queue->isDone()
+            ) {
+                return $queue->queueID;
+            }
+        }, $this->objects)));
+    }
+}
index 00bab9111a23019480490fbcdf72e02c4b406282..050269ed0678b2c42cdedfb0b794f20649299e72 100644 (file)
@@ -3446,10 +3446,16 @@ Fehler sind beispielsweise:
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.confirmEmail"><![CDATA[E-Mail-Adresse bestätigen ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.unconfirmEmail"><![CDATA[Bestätigung der E-Mail-Adresse widerrufen ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.deleteUserContent"><![CDATA[Inhalte der Benutzer löschen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.assignUserByClipboard"><![CDATA[Zugewiesenen Benutzer ändern ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.enableContent"><![CDATA[Inhalt freischalten ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.removeActivationContent"><![CDATA[Freizuschaltenden Inhalt löschen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.removeReport"><![CDATA[Meldung schließen ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.removeReportContent"><![CDATA[Gemeldeten Inhalt löschen ({#$count})]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.article.marked"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Artikel]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.media.marked"><![CDATA[{if $count == 1}Eine Datei{else}{#$count} Dateien{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{if $count == 1}Ein Tag{else}{#$count} Tags{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Benutzer]]></item>
+               <item name="wcf.clipboard.label.com.woltlab.wcf.moderation.queue.marked"><![CDATA[{plural value=$count 1='Ein Moderationseintrag' other='# Moderationseinträge'}]]></item>
        </category>
        <category name="wcf.comment">
                <item name="wcf.comment.add"><![CDATA[Kommentar schreiben …]]></item>
@@ -4284,7 +4290,7 @@ Dateianhänge:
                <item name="wcf.moderation.activation.details"><![CDATA[Informationen]]></item>
                <item name="wcf.moderation.activation.content"><![CDATA[Freizuschaltender Inhalt]]></item>
                <item name="wcf.moderation.activation.enableContent"><![CDATA[Inhalt freischalten]]></item>
-               <item name="wcf.moderation.activation.enableContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Inhalt wirklich freischalten?]]></item>
+               <item name="wcf.moderation.activation.enableContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if !$moderationQueueCount|isset || $moderationQueueCount == 1}diesen Inhalt{else}diese Inhalte{/if} wirklich freischalten?]]></item>
                <item name="wcf.moderation.activation.notification.comment.title"><![CDATA[Neuer Kommentar (Freischaltung)]]></item>
                <item name="wcf.moderation.activation.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Freischaltung)]]></item>
                <item name="wcf.moderation.activation.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar zum freizuschaltenden Inhalt <strong>{$moderationQueue}</strong> verfasst.]]></item>
@@ -4298,7 +4304,7 @@ Dateianhänge:
                <item name="wcf.moderation.activation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$commentAuthor} zum freizuschaltenden Inhalt {@$moderationQueue->getTitle()} [URL:{@$moderationQueue->getLink()}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von <strong>{$commentAuthor} zum freizuschaltenden Inhalt <a href="{$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.activation.removeContent"><![CDATA[Inhalt löschen]]></item>
-               <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Inhalt wirklich löschen?]]></item>
+               <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if !$moderationQueueCount|isset || $moderationQueueCount == 1}diesen Inhalt{else}diese Inhalte{/if} wirklich löschen?]]></item>
        </category>
        <category name="wcf.moderation.report">
                <item name="wcf.moderation.report"><![CDATA[Meldung]]></item>
@@ -4319,10 +4325,10 @@ Dateianhänge:
                <item name="wcf.moderation.report.reason"><![CDATA[Grund der Meldung]]></item>
                <item name="wcf.moderation.report.reason.description"><![CDATA[Diese Funktion ist ausschließlich zu verwenden bei: Spam, Werbung und anderen problematischen (rassistischen, gewaltverherrlichenden, aggressiven, beleidigenden oder sexistischen) Inhalten.]]></item>
                <item name="wcf.moderation.report.removeContent"><![CDATA[Inhalt löschen]]></item>
-               <item name="wcf.moderation.report.removeContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den gemeldeten Inhalt wirklich löschen?]]></item>
+               <item name="wcf.moderation.report.removeContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} {if !$moderationQueueCount|isset || $moderationQueueCount == 1}den gemeldeten Inhalt{else}die gemeldeten Inhalte{/if} wirklich löschen?]]></item>
                <item name="wcf.moderation.report.removeContent.reason"><![CDATA[Begründung (optional)]]></item>
                <item name="wcf.moderation.report.removeReport"><![CDATA[Meldung schließen]]></item>
-               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diese Meldung wirklich schließen?]]></item>
+               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diese Meldung{if $moderationQueueCount|isset && $moderationQueueCount > 1}en{/if} wirklich schließen?]]></item>
                <item name="wcf.moderation.report.removeReport.markAsJustified"><![CDATA[Meldung zusätzlich als „Berechtigt“ markieren]]></item>
                <item name="wcf.moderation.report.reportContent"><![CDATA[Inhalt melden]]></item>
                <item name="wcf.moderation.report.reportedBy"><![CDATA[Gemeldet von]]></item>
index 9ab65127584ce9a62078c3c252c7da6dd8b5e2ab..af82c4052d7b6f75de7fed63f94a2afbe0999530 100644 (file)
@@ -3372,10 +3372,16 @@ Errors are:
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.confirmEmail"><![CDATA[Confirm Email Address ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.unconfirmEmail"><![CDATA[Unconfirm Email Address ({#$count})]]></item>
                <item name="wcf.clipboard.item.com.woltlab.wcf.user.deleteUserContent"><![CDATA[Delete User Contents ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.assignUserByClipboard"><![CDATA[Change Assigned User ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.enableContent"><![CDATA[Approve Content ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.removeActivationContent"><![CDATA[Delete Pending Content ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.removeReport"><![CDATA[Close Report ({#$count})]]></item>
+               <item name="wcf.clipboard.item.com.woltlab.wcf.moderation.queue.removeReportContent"><![CDATA[Delete Repored Content ({#$count})]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.article.marked"><![CDATA[{if $count == 1}One Article{else}{#$count} Articles{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.media.marked"><![CDATA[{if $count == 1}One File{else}{#$count} Files{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{if $count == 1}One Tag{else}{#$count} Tags{/if}]]></item>
                <item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{if $count == 1}One User{else}{#$count} Users{/if}]]></item>
+               <item name="wcf.clipboard.label.com.woltlab.wcf.moderation.queue.marked"><![CDATA[{plural value=$count 1='One Moderation Item' other='# Moderation Items'}]]></item>
        </category>
        <category name="wcf.comment">
                <item name="wcf.comment.add"><![CDATA[Write a comment …]]></item>
@@ -4231,7 +4237,7 @@ Attachments:
                <item name="wcf.moderation.activation.details"><![CDATA[Information]]></item>
                <item name="wcf.moderation.activation.content"><![CDATA[Content awaiting approval]]></item>
                <item name="wcf.moderation.activation.enableContent"><![CDATA[Approve]]></item>
-               <item name="wcf.moderation.activation.enableContent.confirmMessage"><![CDATA[Do you really want to approve this content?]]></item>
+               <item name="wcf.moderation.activation.enableContent.confirmMessage"><![CDATA[Do you really want to approve {if !$moderationQueueCount|isset || $moderationQueueCount == 1}this content{else}these contents{/if}?]]></item>
                <item name="wcf.moderation.activation.notification.comment.title"><![CDATA[New comment (Approval)]]></item>
                <item name="wcf.moderation.activation.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} new comments (Approval)]]></item>
                <item name="wcf.moderation.activation.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on <strong>{$moderationQueue}</strong> waiting for approval.]]></item>
@@ -4245,7 +4251,7 @@ Attachments:
                <item name="wcf.moderation.activation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} replied to {@$commentAuthor}’s comment on {@$moderationQueue->getTitle()} [URL:{@$moderationQueue->getLink()}] waiting for approval{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} replied to <strong>{$commentAuthor}</strong>’s comment on <a href="{$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a>  waiting for approval:</p>]]></item>
                <item name="wcf.moderation.activation.removeContent"><![CDATA[Delete Content]]></item>
-               <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[Do you really want to delete this content?]]></item>
+               <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[Do you really want to delete {if !$moderationQueueCount|isset || $moderationQueueCount == 1}this content{else}these contents{/if}?]]></item>
        </category>
        <category name="wcf.moderation.report">
                <item name="wcf.moderation.report"><![CDATA[Report]]></item>
@@ -4266,10 +4272,10 @@ Attachments:
                <item name="wcf.moderation.report.reason"><![CDATA[Reason]]></item>
                <item name="wcf.moderation.report.reason.description"><![CDATA[This function is reserved for: spam, advertisement and other questionable (racism, glorification of violence, offending, or sexist) content.]]></item>
                <item name="wcf.moderation.report.removeContent"><![CDATA[Delete Content]]></item>
-               <item name="wcf.moderation.report.removeContent.confirmMessage"><![CDATA[Do you really want to delete the reported content?]]></item>
+               <item name="wcf.moderation.report.removeContent.confirmMessage"><![CDATA[Do you really want to delete the reported content{if $moderationQueueCount|isset && $moderationQueueCount > 1}s{/if}?]]></item>
                <item name="wcf.moderation.report.removeContent.reason"><![CDATA[Reason (optional)]]></item>
                <item name="wcf.moderation.report.removeReport"><![CDATA[Close Report]]></item>
-               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[Do you really want to close this report?]]></item>
+               <item name="wcf.moderation.report.removeReport.confirmMessage"><![CDATA[Do you really want to close {if !$moderationQueueCount|isset || $moderationQueueCount == 1}this report{else}these reports{/if}?]]></item>
                <item name="wcf.moderation.report.removeReport.markAsJustified"><![CDATA[Also mark report as “Justified”]]></item>
                <item name="wcf.moderation.report.reportContent"><![CDATA[Report Content]]></item>
                <item name="wcf.moderation.report.reportedBy"><![CDATA[Reported by]]></item>