Merge branch '3.1' into 5.2
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / moderation / queue / ModerationQueueAction.class.php
1 <?php
2 namespace wcf\data\moderation\queue;
3 use wcf\data\object\type\ObjectTypeCache;
4 use wcf\data\user\User;
5 use wcf\data\AbstractDatabaseObjectAction;
6 use wcf\system\database\util\PreparedStatementConditionBuilder;
7 use wcf\system\exception\PermissionDeniedException;
8 use wcf\system\exception\UserInputException;
9 use wcf\system\moderation\queue\ModerationQueueManager;
10 use wcf\system\request\LinkHandler;
11 use wcf\system\user\storage\UserStorageHandler;
12 use wcf\system\visitTracker\VisitTracker;
13 use wcf\system\WCF;
14
15 /**
16 * Executes moderation queue-related actions.
17 *
18 * @author Alexander Ebert
19 * @copyright 2001-2019 WoltLab GmbH
20 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
21 * @package WoltLabSuite\Core\Data\Moderation\Queue
22 *
23 * @method ModerationQueueEditor[] getObjects()
24 * @method ModerationQueueEditor getSingleObject()
25 */
26 class ModerationQueueAction extends AbstractDatabaseObjectAction {
27 /**
28 * @inheritDoc
29 */
30 protected $className = ModerationQueueEditor::class;
31
32 /**
33 * moderation queue editor object
34 * @var ModerationQueueEditor
35 */
36 public $moderationQueueEditor = null;
37
38 /**
39 * user object
40 * @var User
41 */
42 public $user = null;
43
44 /**
45 * @inheritDoc
46 * @return ModerationQueue
47 */
48 public function create() {
49 if (!isset($this->parameters['data']['lastChangeTime'])) {
50 $this->parameters['data']['lastChangeTime'] = TIME_NOW;
51 }
52
53 /** @noinspection PhpIncompatibleReturnTypeInspection */
54 return parent::create();
55 }
56
57 /**
58 * @inheritDoc
59 */
60 public function update() {
61 if (!isset($this->parameters['data']['lastChangeTime'])) {
62 $this->parameters['data']['lastChangeTime'] = TIME_NOW;
63 }
64
65 parent::update();
66 }
67
68 /**
69 * Marks a list of objects as done.
70 */
71 public function markAsDone() {
72 if (empty($this->objects)) {
73 $this->readObjects();
74 }
75
76 $queueIDs = [];
77 foreach ($this->getObjects() as $queue) {
78 $queueIDs[] = $queue->queueID;
79 }
80
81 $conditions = new PreparedStatementConditionBuilder();
82 $conditions->add("queueID IN (?)", [$queueIDs]);
83
84 $sql = "UPDATE wcf".WCF_N."_moderation_queue
85 SET status = ".ModerationQueue::STATUS_DONE."
86 ".$conditions;
87 $statement = WCF::getDB()->prepareStatement($sql);
88 $statement->execute($conditions->getParameters());
89
90 // reset number of active moderation queue items
91 ModerationQueueManager::getInstance()->resetModerationCount();
92 }
93
94 /**
95 * Validates parameters to fetch a list of outstanding queues.
96 */
97 public function validateGetOutstandingQueues() {
98 WCF::getSession()->checkPermissions(['mod.general.canUseModeration']);
99 }
100
101 /**
102 * Returns a list of outstanding queues.
103 *
104 * @return string[]
105 */
106 public function getOutstandingQueues() {
107 // Maximum cardinality of the returned array
108 static $MAX_ITEMS = 10;
109
110 $conditions = new PreparedStatementConditionBuilder();
111 $conditions->add("moderation_queue_to_user.userID = ?", [WCF::getUser()->userID]);
112 $conditions->add("moderation_queue_to_user.isAffected = ?", [1]);
113 $conditions->add("moderation_queue.status IN (?)", [[ModerationQueue::STATUS_OUTSTANDING, ModerationQueue::STATUS_PROCESSING]]);
114 $conditions->add("moderation_queue.time > ?", [VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.moderation.queue')]);
115 $conditions->add("(moderation_queue.time > tracked_visit.visitTime OR tracked_visit.visitTime IS NULL)");
116
117 $sql = "SELECT moderation_queue.queueID
118 FROM wcf".WCF_N."_moderation_queue_to_user moderation_queue_to_user
119 LEFT JOIN wcf".WCF_N."_moderation_queue moderation_queue
120 ON (moderation_queue.queueID = moderation_queue_to_user.queueID)
121 LEFT JOIN wcf".WCF_N."_tracked_visit tracked_visit
122 ON (tracked_visit.objectTypeID = ".VisitTracker::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue')." AND tracked_visit.objectID = moderation_queue.queueID AND tracked_visit.userID = ".WCF::getUser()->userID.")
123 ".$conditions."
124 ORDER BY moderation_queue.lastChangeTime DESC";
125 $statement = WCF::getDB()->prepareStatement($sql, $MAX_ITEMS);
126 $statement->execute($conditions->getParameters());
127 $queueIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
128
129 $queues = [];
130 if (!empty($queueIDs)) {
131 $queueList = new ViewableModerationQueueList();
132 $queueList->getConditionBuilder()->add("moderation_queue.queueID IN (?)", [$queueIDs]);
133 $queueList->sqlOrderBy = "moderation_queue.lastChangeTime DESC";
134 $queueList->readObjects();
135 foreach ($queueList as $queue) {
136 $queues[] = $queue;
137 }
138 }
139
140 // check if user storage is outdated
141 $totalCount = ModerationQueueManager::getInstance()->getUnreadModerationCount();
142 $count = count($queues);
143 if ($count < $MAX_ITEMS) {
144 // load more entries to fill up list
145 $queueList = new ViewableModerationQueueList();
146 $queueList->getConditionBuilder()->add("moderation_queue.status IN (?)", [[ModerationQueue::STATUS_OUTSTANDING, ModerationQueue::STATUS_PROCESSING]]);
147 if (!empty($queueIDs)) $queueList->getConditionBuilder()->add("moderation_queue.queueID NOT IN (?)", [$queueIDs]);
148 $queueList->sqlOrderBy = "moderation_queue.lastChangeTime DESC";
149 $queueList->sqlLimit = $MAX_ITEMS - $count;
150 $queueList->readObjects();
151 foreach ($queueList as $queue) {
152 $queues[] = $queue;
153 }
154
155 // check if stored count is out of sync
156 if ($count < $totalCount) {
157 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'outstandingModerationCount');
158
159 // check for orphaned queues
160 $queueCount = ModerationQueueManager::getInstance()->getUnreadModerationCount();
161 if (count($queues) < $queueCount) {
162 ModerationQueueManager::getInstance()->identifyOrphans();
163 }
164 }
165 }
166
167 WCF::getTPL()->assign([
168 'queues' => $queues
169 ]);
170
171 return [
172 'template' => WCF::getTPL()->fetch('moderationQueueList'),
173 'totalCount' => $totalCount
174 ];
175 }
176
177 /**
178 * Validates parameters to show the user assign form.
179 */
180 public function validateGetAssignUserForm() {
181 $this->moderationQueueEditor = $this->getSingleObject();
182
183 // check if queue is accessible for current user
184 if (!$this->moderationQueueEditor->canEdit()) {
185 throw new PermissionDeniedException();
186 }
187 }
188
189 /**
190 * Returns the user assign form.
191 *
192 * @return string[]
193 */
194 public function getAssignUserForm() {
195 $assignedUser = $this->moderationQueueEditor->assignedUserID ? new User($this->moderationQueueEditor->assignedUserID) : null;
196
197 WCF::getTPL()->assign([
198 'assignedUser' => $assignedUser,
199 'queue' => $this->moderationQueueEditor
200 ]);
201
202 return [
203 'template' => WCF::getTPL()->fetch('moderationQueueAssignUser')
204 ];
205 }
206
207 /**
208 * Validates parameters to assign a user.
209 */
210 public function validateAssignUser() {
211 // To assign a user we need to be able to fetch the assignment form.
212 $this->validateGetAssignUserForm();
213
214 $this->readInteger('assignedUserID', true);
215
216 if ($this->parameters['assignedUserID'] && $this->parameters['assignedUserID'] != -1) {
217 if ($this->parameters['assignedUserID'] != WCF::getUser()->userID && $this->parameters['assignedUserID'] != $this->moderationQueueEditor->assignedUserID) {
218 // user id is either faked or changed during viewing, use database value instead
219 $this->parameters['assignedUserID'] = $this->moderationQueueEditor->assignedUserID;
220 }
221 }
222
223 if ($this->parameters['assignedUserID'] == -1) {
224 $this->readString('assignedUsername');
225
226 $this->user = User::getUserByUsername($this->parameters['assignedUsername']);
227 if (!$this->user->userID) {
228 throw new UserInputException('assignedUsername', 'notFound');
229 }
230
231 // get handler
232 $objectType = ObjectTypeCache::getInstance()->getObjectType($this->moderationQueueEditor->objectTypeID);
233 if (!$objectType->getProcessor()->isAffectedUser($this->moderationQueueEditor->getDecoratedObject(), $this->user->userID)) {
234 throw new UserInputException('assignedUsername', 'notAffected');
235 }
236
237 $this->parameters['assignedUserID'] = $this->user->userID;
238 $this->parameters['assignedUsername'] = '';
239 }
240 else {
241 $this->user = new User($this->parameters['assignedUserID']);
242 }
243 }
244
245 /**
246 * Returns the data for the newly assigned user.
247 *
248 * @return string[]
249 */
250 public function assignUser() {
251 $data = ['assignedUserID' => $this->parameters['assignedUserID'] ?: null];
252 if ($this->user->userID) {
253 if ($this->moderationQueueEditor->status == ModerationQueue::STATUS_OUTSTANDING) {
254 $data['status'] = ModerationQueue::STATUS_PROCESSING;
255 }
256 }
257 else {
258 if ($this->moderationQueueEditor->status == ModerationQueue::STATUS_PROCESSING) {
259 $data['status'] = ModerationQueue::STATUS_OUTSTANDING;
260 }
261 }
262
263 $this->moderationQueueEditor->update($data);
264
265 $username = $this->user->userID ? $this->user->username : WCF::getLanguage()->get('wcf.moderation.assignedUser.nobody');
266 $link = '';
267 if ($this->user->userID) {
268 $link = LinkHandler::getInstance()->getLink('User', ['object' => $this->user]);
269 }
270
271 $newStatus = '';
272 if (isset($data['status'])) {
273 $newStatus = ($data['status'] == ModerationQueue::STATUS_OUTSTANDING) ? 'outstanding' : 'processing';
274 }
275
276 return [
277 'link' => $link,
278 'newStatus' => $newStatus,
279 'userID' => $this->user->userID,
280 'username' => $username
281 ];
282 }
283
284 /**
285 * Marks queue entries as read.
286 */
287 public function markAsRead() {
288 if (empty($this->parameters['visitTime'])) {
289 $this->parameters['visitTime'] = TIME_NOW;
290 }
291
292 if (empty($this->objects)) {
293 $this->readObjects();
294 }
295
296 foreach ($this->getObjects() as $queue) {
297 VisitTracker::getInstance()->trackObjectVisit('com.woltlab.wcf.moderation.queue', $queue->queueID, $this->parameters['visitTime']);
298 }
299
300 // reset storage
301 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadModerationCount');
302
303 if (count($this->objects) == 1) {
304 $queue = reset($this->objects);
305
306 return [
307 'markAsRead' => $queue->queueID,
308 'totalCount' => ModerationQueueManager::getInstance()->getUnreadModerationCount(true)
309 ];
310 }
311 }
312
313 /**
314 * @inheritDoc
315 */
316 public function validateMarkAsRead() {
317 if (empty($this->objects)) {
318 $this->readObjects();
319 }
320
321 foreach ($this->getObjects() as $queue) {
322 if (!$queue->canEdit()) {
323 throw new PermissionDeniedException();
324 }
325 }
326 }
327
328 /**
329 * Marks all queue entries as read.
330 */
331 public function markAllAsRead() {
332 VisitTracker::getInstance()->trackTypeVisit('com.woltlab.wcf.moderation.queue');
333
334 // reset storage
335 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadModerationCount');
336
337 return [
338 'markAllAsRead' => true
339 ];
340 }
341
342 /**
343 * Validates the mark all as read action.
344 */
345 public function validateMarkAllAsRead() {
346 // does nothing
347 }
348 }