Commit | Line | Data |
---|---|---|
9544b6b4 | 1 | <?php |
fea86294 | 2 | |
9544b6b4 | 3 | namespace wcf\data\conversation; |
fea86294 TD |
4 | |
5 | use wcf\data\AbstractDatabaseObjectAction; | |
5e279c42 | 6 | use wcf\data\conversation\label\ConversationLabel; |
9544b6b4 | 7 | use wcf\data\conversation\message\ConversationMessageAction; |
5d7f0df0 | 8 | use wcf\data\conversation\message\ConversationMessageList; |
a0c1a541 | 9 | use wcf\data\conversation\message\SimplifiedViewableConversationMessageList; |
232cdc4b | 10 | use wcf\data\IClipboardAction; |
ce8c322f | 11 | use wcf\data\IPopoverAction; |
d8963ec2 | 12 | use wcf\data\IVisitableObjectAction; |
265e4e9f | 13 | use wcf\data\user\group\UserGroup; |
b297c618 | 14 | use wcf\page\ConversationPage; |
e8fe47c2 | 15 | use wcf\system\clipboard\ClipboardHandler; |
7f07124d | 16 | use wcf\system\conversation\ConversationHandler; |
5d7f0df0 | 17 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
c5a889cc | 18 | use wcf\system\event\EventHandler; |
9908db5b | 19 | use wcf\system\exception\IllegalLinkException; |
5e279c42 | 20 | use wcf\system\exception\PermissionDeniedException; |
e8fe47c2 | 21 | use wcf\system\exception\UserInputException; |
65a160b7 | 22 | use wcf\system\log\modification\ConversationModificationLogHandler; |
b2e0a2ad | 23 | use wcf\system\request\LinkHandler; |
87de5988 | 24 | use wcf\system\search\SearchIndexManager; |
3f3d496b | 25 | use wcf\system\style\FontAwesomeIcon; |
8b467fcd MW |
26 | use wcf\system\user\notification\object\ConversationUserNotificationObject; |
27 | use wcf\system\user\notification\UserNotificationHandler; | |
9544b6b4 MW |
28 | use wcf\system\user\storage\UserStorageHandler; |
29 | use wcf\system\WCF; | |
725b10a8 | 30 | use wcf\util\StringUtil; |
9544b6b4 MW |
31 | |
32 | /** | |
33 | * Executes conversation-related actions. | |
fea86294 TD |
34 | * |
35 | * @author Marcel Werk | |
36 | * @copyright 2001-2019 WoltLab GmbH | |
37 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
fea86294 TD |
38 | * |
39 | * @method ConversationEditor[] getObjects() | |
40 | * @method ConversationEditor getSingleObject() | |
9544b6b4 | 41 | */ |
fea86294 TD |
42 | class ConversationAction extends AbstractDatabaseObjectAction implements |
43 | IClipboardAction, | |
44 | IPopoverAction, | |
45 | IVisitableObjectAction | |
46 | { | |
47 | /** | |
48 | * @inheritDoc | |
49 | */ | |
50 | protected $className = ConversationEditor::class; | |
51 | ||
52 | /** | |
53 | * conversation object | |
54 | * @var ConversationEditor | |
55 | */ | |
56 | public $conversation; | |
57 | ||
58 | /** | |
59 | * list of conversation data modifications | |
60 | * @var mixed[][] | |
61 | */ | |
62 | protected $conversationData = []; | |
63 | ||
fea86294 TD |
64 | /** |
65 | * @inheritDoc | |
66 | * @return Conversation | |
67 | */ | |
68 | public function create() | |
69 | { | |
70 | // create conversation | |
71 | $data = $this->parameters['data']; | |
72 | $data['lastPosterID'] = $data['userID']; | |
73 | $data['lastPoster'] = $data['username']; | |
74 | $data['lastPostTime'] = $data['time']; | |
75 | // count participants | |
76 | if (!empty($this->parameters['participants'])) { | |
77 | $data['participants'] = \count($this->parameters['participants']); | |
78 | } | |
79 | // count attachments | |
80 | if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) { | |
81 | $data['attachments'] = \count($this->parameters['attachmentHandler']); | |
82 | } | |
83 | $conversation = \call_user_func([$this->className, 'create'], $data); | |
84 | $conversationEditor = new ConversationEditor($conversation); | |
85 | ||
86 | if (!$conversation->isDraft) { | |
87 | // save participants | |
88 | $conversationEditor->updateParticipants( | |
89 | (!empty($this->parameters['participants']) ? $this->parameters['participants'] : []), | |
90 | (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []), | |
91 | 'all' | |
92 | ); | |
93 | ||
94 | // add author | |
95 | if ($data['userID'] !== null) { | |
96 | $conversationEditor->updateParticipants([$data['userID']], [], 'all'); | |
97 | } | |
98 | ||
99 | // update conversation count | |
100 | UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount'); | |
101 | ||
102 | // mark conversation as read for the author | |
8fbd8b01 MS |
103 | $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user |
104 | SET lastVisitTime = ? | |
105 | WHERE participantID = ? | |
106 | AND conversationID = ?"; | |
fea86294 TD |
107 | $statement = WCF::getDB()->prepareStatement($sql); |
108 | $statement->execute([$data['time'], $data['userID'], $conversation->conversationID]); | |
109 | } else { | |
110 | // update conversation count | |
111 | UserStorageHandler::getInstance()->reset([$data['userID']], 'conversationCount'); | |
112 | } | |
113 | ||
114 | // update participant summary | |
115 | $conversationEditor->updateParticipantSummary(); | |
116 | ||
117 | // create message | |
118 | $messageData = $this->parameters['messageData']; | |
119 | $messageData['conversationID'] = $conversation->conversationID; | |
120 | $messageData['time'] = $this->parameters['data']['time']; | |
121 | $messageData['userID'] = $this->parameters['data']['userID']; | |
122 | $messageData['username'] = $this->parameters['data']['username']; | |
123 | ||
124 | $messageAction = new ConversationMessageAction([], 'create', [ | |
125 | 'data' => $messageData, | |
126 | 'conversation' => $conversation, | |
127 | 'isFirstPost' => true, | |
128 | 'attachmentHandler' => $this->parameters['attachmentHandler'] ?? null, | |
129 | 'htmlInputProcessor' => $this->parameters['htmlInputProcessor'] ?? null, | |
130 | ]); | |
131 | $resultValues = $messageAction->executeAction(); | |
132 | ||
133 | // update first message id | |
134 | $conversationEditor->update([ | |
135 | 'firstMessageID' => $resultValues['returnValues']->messageID, | |
136 | ]); | |
137 | ||
138 | $conversation->setFirstMessage($resultValues['returnValues']); | |
139 | if (!$conversation->isDraft) { | |
140 | // fire notification event | |
141 | $notificationRecipients = \array_merge( | |
d8ca8c92 | 142 | !empty($this->parameters['participants']) ? $this->parameters['participants'] : [], |
2a512cd0 | 143 | !empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : [] |
fea86294 TD |
144 | ); |
145 | UserNotificationHandler::getInstance()->fireEvent( | |
146 | 'conversation', | |
147 | 'com.woltlab.wcf.conversation.notification', | |
148 | new ConversationUserNotificationObject($conversation), | |
149 | $notificationRecipients | |
150 | ); | |
151 | } | |
152 | ||
153 | return $conversation; | |
154 | } | |
155 | ||
156 | /** | |
157 | * @inheritDoc | |
158 | */ | |
159 | public function delete() | |
160 | { | |
161 | // deletes messages | |
162 | $messageList = new ConversationMessageList(); | |
163 | $messageList->getConditionBuilder()->add('conversation_message.conversationID IN (?)', [$this->objectIDs]); | |
164 | $messageList->readObjectIDs(); | |
165 | $action = new ConversationMessageAction($messageList->getObjectIDs(), 'delete'); | |
166 | $action->executeAction(); | |
167 | ||
168 | // get the list of participants in order to reset the 'unread conversation'-counter | |
169 | $participantIDs = []; | |
170 | if (!empty($this->objectIDs)) { | |
171 | $conditions = new PreparedStatementConditionBuilder(); | |
172 | $conditions->add("conversationID IN (?)", [$this->objectIDs]); | |
173 | $sql = "SELECT DISTINCT participantID | |
8fbd8b01 MS |
174 | FROM wcf" . WCF_N . "_conversation_to_user |
175 | " . $conditions; | |
fea86294 TD |
176 | $statement = WCF::getDB()->prepareStatement($sql); |
177 | $statement->execute($conditions->getParameters()); | |
178 | ||
179 | while ($participantID = $statement->fetchColumn()) { | |
180 | $participantIDs[] = $participantID; | |
181 | } | |
182 | } | |
183 | ||
184 | // delete conversations | |
185 | parent::delete(); | |
186 | ||
187 | if (!empty($this->objectIDs)) { | |
188 | // delete notifications | |
189 | UserNotificationHandler::getInstance() | |
190 | ->removeNotifications('com.woltlab.wcf.conversation.notification', $this->objectIDs); | |
191 | ||
192 | // remove modification logs | |
193 | ConversationModificationLogHandler::getInstance()->deleteLogs($this->objectIDs); | |
194 | ||
195 | // reset the number of unread conversations | |
196 | if (!empty($participantIDs)) { | |
197 | UserStorageHandler::getInstance()->reset($participantIDs, 'unreadConversationCount'); | |
198 | } | |
199 | } | |
200 | } | |
201 | ||
202 | /** | |
203 | * @inheritDoc | |
204 | */ | |
205 | public function update() | |
206 | { | |
207 | if (!isset($this->parameters['participants'])) { | |
208 | $this->parameters['participants'] = []; | |
209 | } | |
210 | if (!isset($this->parameters['invisibleParticipants'])) { | |
211 | $this->parameters['invisibleParticipants'] = []; | |
212 | } | |
213 | ||
214 | // count participants | |
215 | if (!empty($this->parameters['participants'])) { | |
216 | $this->parameters['data']['participants'] = \count($this->parameters['participants']); | |
217 | } | |
218 | ||
219 | parent::update(); | |
220 | ||
221 | foreach ($this->getObjects() as $conversation) { | |
222 | // participants | |
223 | if (!empty($this->parameters['participants']) || !empty($this->parameters['invisibleParticipants'])) { | |
224 | // get current participants | |
225 | $participantIDs = $conversation->getParticipantIDs(); | |
226 | ||
227 | $conversation->updateParticipants( | |
228 | (!empty($this->parameters['participants']) ? $this->parameters['participants'] : []), | |
229 | (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []), | |
230 | (!empty($this->parameters['visibility']) ? $this->parameters['visibility'] : 'all') | |
231 | ); | |
232 | $conversation->updateParticipantSummary(); | |
233 | ||
234 | // check if new participants have been added | |
235 | $newParticipantIDs = \array_diff(\array_merge( | |
236 | $this->parameters['participants'], | |
237 | $this->parameters['invisibleParticipants'] | |
238 | ), $participantIDs); | |
239 | if (!empty($newParticipantIDs)) { | |
240 | // update conversation count | |
241 | UserStorageHandler::getInstance()->reset($newParticipantIDs, 'unreadConversationCount'); | |
242 | UserStorageHandler::getInstance()->reset($newParticipantIDs, 'conversationCount'); | |
243 | ||
244 | // fire notification event | |
245 | UserNotificationHandler::getInstance()->fireEvent( | |
246 | 'conversation', | |
247 | 'com.woltlab.wcf.conversation.notification', | |
248 | new ConversationUserNotificationObject($conversation->getDecoratedObject()), | |
249 | $newParticipantIDs | |
250 | ); | |
251 | } | |
252 | } | |
253 | ||
254 | // draft status | |
255 | if (isset($this->parameters['data']['isDraft'])) { | |
256 | if ($conversation->isDraft && !$this->parameters['data']['isDraft']) { | |
257 | // add author | |
258 | $conversation->updateParticipants([$conversation->userID], [], 'all'); | |
259 | ||
260 | // update conversation count | |
261 | UserStorageHandler::getInstance() | |
262 | ->reset($conversation->getParticipantIDs(), 'unreadConversationCount'); | |
263 | UserStorageHandler::getInstance() | |
264 | ->reset($conversation->getParticipantIDs(), 'conversationCount'); | |
265 | } | |
266 | } | |
267 | } | |
268 | } | |
269 | ||
270 | /** | |
271 | * @inheritDoc | |
272 | */ | |
273 | public function markAsRead() | |
274 | { | |
275 | if (empty($this->parameters['visitTime'])) { | |
276 | $this->parameters['visitTime'] = TIME_NOW; | |
277 | } | |
278 | ||
279 | // in case this is a call via PHP and the userID parameter is missing, set it to the userID of the current user | |
280 | if (!isset($this->parameters['userID'])) { | |
281 | $this->parameters['userID'] = WCF::getUser()->userID; | |
282 | } | |
283 | ||
284 | if (empty($this->objects)) { | |
285 | $this->readObjects(); | |
286 | } | |
287 | ||
288 | $conversationIDs = []; | |
8fbd8b01 MS |
289 | $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user |
290 | SET lastVisitTime = ? | |
291 | WHERE participantID = ? | |
292 | AND conversationID = ?"; | |
fea86294 TD |
293 | $statement = WCF::getDB()->prepareStatement($sql); |
294 | WCF::getDB()->beginTransaction(); | |
295 | foreach ($this->getObjects() as $conversation) { | |
296 | $statement->execute([ | |
297 | $this->parameters['visitTime'], | |
298 | $this->parameters['userID'], | |
299 | $conversation->conversationID, | |
300 | ]); | |
301 | $conversationIDs[] = $conversation->conversationID; | |
302 | } | |
303 | WCF::getDB()->commitTransaction(); | |
304 | ||
305 | // reset storage | |
306 | UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount'); | |
307 | ||
308 | // mark notifications as confirmed | |
309 | if (!empty($conversationIDs)) { | |
d5669d80 TD |
310 | // 1) Mark notifications about new conversations as read. |
311 | UserNotificationHandler::getInstance()->markAsConfirmed( | |
312 | 'conversation', | |
313 | 'com.woltlab.wcf.conversation.notification', | |
314 | [$this->parameters['userID']], | |
315 | $conversationIDs | |
316 | ); | |
fea86294 | 317 | |
d5669d80 TD |
318 | // 2) Mark notifications about new replies as read. |
319 | $eventID = UserNotificationHandler::getInstance() | |
320 | ->getEvent('com.woltlab.wcf.conversation.message.notification', 'conversationMessage') | |
321 | ->eventID; | |
322 | ||
323 | $condition = new PreparedStatementConditionBuilder(); | |
324 | $condition->add('notification.userID = ?', [$this->parameters['userID']]); | |
325 | $condition->add('notification.confirmTime = ?', [0]); | |
326 | $condition->add('notification.eventID = ?', [$eventID]); | |
327 | $condition->add("notification.objectID IN ( | |
328 | SELECT messageID | |
329 | FROM wcf" . WCF_N . "_conversation_message | |
330 | WHERE conversationID IN (?) | |
331 | AND time <= ? | |
332 | )", [ | |
333 | $conversationIDs, | |
334 | $this->parameters['visitTime'], | |
fea86294 | 335 | ]); |
d5669d80 TD |
336 | |
337 | $sql = "SELECT notificationID | |
338 | FROM wcf" . WCF_N . "_user_notification notification | |
339 | {$condition}"; | |
fea86294 | 340 | $statement = WCF::getDB()->prepareStatement($sql); |
d5669d80 TD |
341 | $statement->execute($condition->getParameters()); |
342 | ||
343 | UserNotificationHandler::getInstance()->markAsConfirmedByIDs( | |
344 | $statement->fetchAll(\PDO::FETCH_COLUMN) | |
345 | ); | |
fea86294 TD |
346 | } |
347 | ||
348 | if (!empty($conversationIDs)) { | |
349 | $this->unmarkItems($conversationIDs); | |
350 | } | |
351 | ||
352 | $returnValues = [ | |
353 | 'totalCount' => ConversationHandler::getInstance() | |
354 | ->getUnreadConversationCount($this->parameters['userID'], true), | |
355 | ]; | |
356 | ||
357 | if (\count($conversationIDs) == 1) { | |
358 | $returnValues['markAsRead'] = \reset($conversationIDs); | |
359 | } | |
360 | ||
361 | return $returnValues; | |
362 | } | |
363 | ||
364 | /** | |
365 | * @inheritDoc | |
366 | */ | |
367 | public function validateMarkAsRead() | |
368 | { | |
369 | // visitTime might not be in the future | |
370 | if (isset($this->parameters['visitTime'])) { | |
371 | $this->parameters['visitTime'] = \intval($this->parameters['visitTime']); | |
372 | if ($this->parameters['visitTime'] > TIME_NOW) { | |
373 | $this->parameters['visitTime'] = TIME_NOW; | |
374 | } | |
375 | } | |
376 | ||
377 | // userID should always be equal to the userID of the current user when called via AJAX | |
378 | $this->parameters['userID'] = WCF::getUser()->userID; | |
379 | ||
380 | if (empty($this->objects)) { | |
381 | $this->readObjects(); | |
382 | } | |
383 | ||
384 | // check participation | |
385 | $conversationIDs = []; | |
386 | foreach ($this->getObjects() as $conversation) { | |
387 | $conversationIDs[] = $conversation->conversationID; | |
388 | } | |
389 | ||
390 | if (empty($conversationIDs)) { | |
391 | throw new UserInputException('objectIDs'); | |
392 | } | |
393 | ||
394 | if (!Conversation::isParticipant($conversationIDs)) { | |
395 | throw new PermissionDeniedException(); | |
396 | } | |
397 | } | |
398 | ||
399 | /** | |
400 | * Marks all conversations as read. | |
401 | */ | |
402 | public function markAllAsRead() | |
403 | { | |
8fbd8b01 MS |
404 | $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user |
405 | SET lastVisitTime = ? | |
406 | WHERE participantID = ?"; | |
fea86294 TD |
407 | $statement = WCF::getDB()->prepareStatement($sql); |
408 | $statement->execute([ | |
409 | TIME_NOW, | |
410 | WCF::getUser()->userID, | |
411 | ]); | |
412 | ||
413 | // reset storage | |
414 | UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount'); | |
415 | ||
416 | // confirm obsolete notifications | |
417 | UserNotificationHandler::getInstance()->markAsConfirmed( | |
418 | 'conversation', | |
419 | 'com.woltlab.wcf.conversation.notification', | |
420 | [WCF::getUser()->userID] | |
421 | ); | |
422 | UserNotificationHandler::getInstance()->markAsConfirmed( | |
423 | 'conversationMessage', | |
424 | 'com.woltlab.wcf.conversation.message.notification', | |
425 | [WCF::getUser()->userID] | |
426 | ); | |
427 | ||
428 | return [ | |
429 | 'markAllAsRead' => true, | |
430 | ]; | |
431 | } | |
432 | ||
433 | /** | |
434 | * Validates the markAllAsRead action. | |
435 | */ | |
436 | public function validateMarkAllAsRead() | |
437 | { | |
438 | // does nothing | |
439 | } | |
440 | ||
441 | /** | |
442 | * Validates user access for label management. | |
443 | * | |
444 | * @throws PermissionDeniedException | |
445 | */ | |
446 | public function validateGetLabelManagement() | |
447 | { | |
448 | if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) { | |
449 | throw new PermissionDeniedException(); | |
450 | } | |
451 | } | |
452 | ||
453 | /** | |
454 | * Returns the conversation label management. | |
455 | * | |
456 | * @return array | |
457 | */ | |
458 | public function getLabelManagement() | |
459 | { | |
460 | WCF::getTPL()->assign([ | |
461 | 'cssClassNames' => ConversationLabel::getLabelCssClassNames(), | |
462 | 'labelList' => ConversationLabel::getLabelsByUser(), | |
463 | ]); | |
464 | ||
465 | return [ | |
466 | 'actionName' => 'getLabelManagement', | |
467 | 'template' => WCF::getTPL()->fetch('conversationLabelManagement'), | |
468 | 'maxLabels' => WCF::getSession()->getPermission('user.conversation.maxLabels'), | |
469 | 'labelCount' => \count(ConversationLabel::getLabelsByUser()), | |
470 | ]; | |
471 | } | |
472 | ||
473 | /** | |
474 | * @inheritDoc | |
475 | */ | |
476 | public function validateGetPopover() | |
477 | { | |
478 | $this->conversation = $this->getSingleObject(); | |
479 | if (!Conversation::isParticipant([$this->conversation->conversationID])) { | |
480 | throw new PermissionDeniedException(); | |
481 | } | |
482 | } | |
483 | ||
484 | /** | |
485 | * @inheritDoc | |
486 | */ | |
487 | public function getPopover() | |
488 | { | |
489 | $messageList = new SimplifiedViewableConversationMessageList(); | |
490 | $messageList->getConditionBuilder() | |
491 | ->add("conversation_message.messageID = ?", [$this->conversation->firstMessageID]); | |
492 | $messageList->readObjects(); | |
493 | ||
494 | return [ | |
495 | 'template' => WCF::getTPL()->fetch('conversationMessagePreview', 'wcf', [ | |
496 | 'message' => $messageList->getSingleObject(), | |
497 | ]), | |
498 | ]; | |
499 | } | |
500 | ||
501 | /** | |
502 | * Validates the get message preview action. | |
503 | * | |
504 | * @throws PermissionDeniedException | |
505 | * @deprecated 5.3 Use `validateGetPopover()` instead. | |
506 | */ | |
507 | public function validateGetMessagePreview() | |
508 | { | |
509 | $this->validateGetPopover(); | |
510 | } | |
511 | ||
512 | /** | |
513 | * Returns a preview of a message in a specific conversation. | |
514 | * | |
515 | * @return string[] | |
516 | * @deprecated 5.3 Use `getPopover()` instead. | |
517 | */ | |
518 | public function getMessagePreview() | |
519 | { | |
520 | return $this->getPopover(); | |
521 | } | |
522 | ||
523 | /** | |
524 | * Validates parameters to close conversations. | |
525 | * | |
526 | * @throws PermissionDeniedException | |
527 | * @throws UserInputException | |
528 | */ | |
529 | public function validateClose() | |
530 | { | |
531 | // read objects | |
532 | if (empty($this->objects)) { | |
533 | $this->readObjects(); | |
534 | ||
535 | if (empty($this->objects)) { | |
536 | throw new UserInputException('objectIDs'); | |
537 | } | |
538 | } | |
539 | ||
540 | // validate ownership | |
541 | foreach ($this->getObjects() as $conversation) { | |
542 | if ($conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) { | |
543 | throw new PermissionDeniedException(); | |
544 | } | |
545 | } | |
546 | } | |
547 | ||
548 | /** | |
549 | * Closes conversations. | |
550 | * | |
551 | * @return mixed[][] | |
552 | */ | |
553 | public function close() | |
554 | { | |
555 | foreach ($this->getObjects() as $conversation) { | |
556 | $conversation->update(['isClosed' => 1]); | |
557 | $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 1); | |
558 | ||
559 | ConversationModificationLogHandler::getInstance()->close($conversation->getDecoratedObject()); | |
560 | } | |
561 | ||
562 | $this->unmarkItems(); | |
563 | ||
564 | return $this->getConversationData(); | |
565 | } | |
566 | ||
567 | /** | |
568 | * Validates parameters to open conversations. | |
569 | * | |
570 | * @throws PermissionDeniedException | |
571 | * @throws UserInputException | |
572 | */ | |
573 | public function validateOpen() | |
574 | { | |
575 | // read objects | |
576 | if (empty($this->objects)) { | |
577 | $this->readObjects(); | |
578 | ||
579 | if (empty($this->objects)) { | |
580 | throw new UserInputException('objectIDs'); | |
581 | } | |
582 | } | |
583 | ||
584 | // validate ownership | |
585 | foreach ($this->getObjects() as $conversation) { | |
586 | if (!$conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) { | |
587 | throw new PermissionDeniedException(); | |
588 | } | |
589 | } | |
590 | } | |
591 | ||
592 | /** | |
593 | * Opens conversations. | |
594 | * | |
595 | * @return mixed[][] | |
596 | */ | |
597 | public function open() | |
598 | { | |
599 | foreach ($this->getObjects() as $conversation) { | |
600 | $conversation->update(['isClosed' => 0]); | |
601 | $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 0); | |
602 | ||
603 | ConversationModificationLogHandler::getInstance()->open($conversation->getDecoratedObject()); | |
604 | } | |
605 | ||
606 | $this->unmarkItems(); | |
607 | ||
608 | return $this->getConversationData(); | |
609 | } | |
610 | ||
611 | /** | |
612 | * Validates conversations for leave form. | |
613 | * | |
614 | * @throws PermissionDeniedException | |
615 | * @throws UserInputException | |
616 | */ | |
617 | public function validateGetLeaveForm() | |
618 | { | |
619 | if (empty($this->objectIDs)) { | |
620 | throw new UserInputException('objectIDs'); | |
621 | } | |
622 | ||
623 | // validate participation | |
624 | if (!Conversation::isParticipant($this->objectIDs)) { | |
625 | throw new PermissionDeniedException(); | |
626 | } | |
627 | } | |
628 | ||
629 | /** | |
630 | * Returns dialog form to leave conversations. | |
631 | * | |
632 | * @return array | |
633 | */ | |
634 | public function getLeaveForm() | |
635 | { | |
636 | // get hidden state from first conversation (all others have the same state) | |
8fbd8b01 MS |
637 | $sql = "SELECT hideConversation |
638 | FROM wcf" . WCF_N . "_conversation_to_user | |
639 | WHERE conversationID = ? | |
640 | AND participantID = ?"; | |
fea86294 TD |
641 | $statement = WCF::getDB()->prepareStatement($sql); |
642 | $statement->execute([ | |
643 | \current($this->objectIDs), | |
644 | WCF::getUser()->userID, | |
645 | ]); | |
646 | $row = $statement->fetchArray(); | |
647 | ||
648 | WCF::getTPL()->assign('hideConversation', ($row !== false ? $row['hideConversation'] : 0)); | |
649 | ||
650 | return [ | |
651 | 'actionName' => 'getLeaveForm', | |
652 | 'template' => WCF::getTPL()->fetch('conversationLeave'), | |
653 | ]; | |
654 | } | |
655 | ||
656 | /** | |
657 | * Validates parameters to hide conversations. | |
658 | * | |
659 | * @throws PermissionDeniedException | |
660 | * @throws UserInputException | |
661 | */ | |
662 | public function validateHideConversation() | |
663 | { | |
664 | $this->parameters['hideConversation'] = isset($this->parameters['hideConversation']) ? \intval($this->parameters['hideConversation']) : null; | |
665 | if ( | |
666 | $this->parameters['hideConversation'] === null | |
667 | || !\in_array( | |
668 | $this->parameters['hideConversation'], | |
669 | [Conversation::STATE_DEFAULT, Conversation::STATE_HIDDEN, Conversation::STATE_LEFT] | |
670 | ) | |
671 | ) { | |
672 | throw new UserInputException('hideConversation'); | |
673 | } | |
674 | ||
675 | if (empty($this->objectIDs)) { | |
676 | throw new UserInputException('objectIDs'); | |
677 | } | |
678 | ||
679 | // validate participation | |
680 | if (!Conversation::isParticipant($this->objectIDs)) { | |
681 | throw new PermissionDeniedException(); | |
682 | } | |
683 | } | |
684 | ||
685 | /** | |
686 | * Hides or restores conversations. | |
687 | * | |
688 | * @return string[] | |
689 | */ | |
690 | public function hideConversation() | |
691 | { | |
8fbd8b01 MS |
692 | $sql = "UPDATE wcf" . WCF_N . "_conversation_to_user |
693 | SET hideConversation = ? | |
694 | WHERE conversationID = ? | |
695 | AND participantID = ?"; | |
fea86294 TD |
696 | $statement = WCF::getDB()->prepareStatement($sql); |
697 | ||
698 | WCF::getDB()->beginTransaction(); | |
699 | foreach ($this->objectIDs as $conversationID) { | |
700 | $statement->execute([ | |
701 | $this->parameters['hideConversation'], | |
702 | $conversationID, | |
703 | WCF::getUser()->userID, | |
704 | ]); | |
705 | } | |
706 | WCF::getDB()->commitTransaction(); | |
707 | ||
708 | // reset user's conversation counters if user leaves conversation | |
709 | // permanently | |
710 | if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) { | |
711 | UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'conversationCount'); | |
712 | UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount'); | |
713 | } | |
714 | ||
715 | // add modification log entry | |
716 | if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) { | |
717 | if (empty($this->objects)) { | |
718 | $this->readObjects(); | |
719 | } | |
720 | ||
721 | foreach ($this->getObjects() as $conversation) { | |
722 | ConversationModificationLogHandler::getInstance()->leave($conversation->getDecoratedObject()); | |
723 | } | |
724 | } | |
725 | ||
726 | // unmark items | |
727 | $this->unmarkItems(); | |
728 | ||
729 | if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) { | |
730 | // update participants count and participant summary | |
731 | ConversationEditor::updateParticipantCounts($this->objectIDs); | |
732 | ConversationEditor::updateParticipantSummaries($this->objectIDs); | |
733 | ||
734 | // delete conversation if all users have left it | |
735 | $conditionBuilder = new PreparedStatementConditionBuilder(); | |
736 | $conditionBuilder->add('conversation.conversationID IN (?)', [$this->objectIDs]); | |
737 | $conditionBuilder->add('conversation_to_user.conversationID IS NULL'); | |
8fbd8b01 MS |
738 | $sql = "SELECT DISTINCT conversation.conversationID |
739 | FROM wcf" . WCF_N . "_conversation conversation | |
740 | LEFT JOIN wcf" . WCF_N . "_conversation_to_user conversation_to_user | |
6124508f MS |
741 | ON conversation_to_user.conversationID = conversation.conversationID |
742 | AND conversation_to_user.hideConversation <> " . Conversation::STATE_LEFT . " | |
743 | AND conversation_to_user.participantID IS NOT NULL | |
8fbd8b01 | 744 | " . $conditionBuilder; |
fea86294 TD |
745 | $statement = WCF::getDB()->prepareStatement($sql); |
746 | $statement->execute($conditionBuilder->getParameters()); | |
747 | $conversationIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); | |
748 | ||
749 | if (!empty($conversationIDs)) { | |
750 | $action = new self($conversationIDs, 'delete'); | |
751 | $action->executeAction(); | |
752 | } | |
753 | } | |
754 | ||
755 | return [ | |
756 | 'actionName' => 'hideConversation', | |
757 | 'redirectURL' => LinkHandler::getInstance()->getLink('ConversationList'), | |
758 | ]; | |
759 | } | |
760 | ||
b297c618 AE |
761 | /** |
762 | * @since 5.5 | |
763 | */ | |
764 | public function validateGetConversations(): void | |
765 | { | |
9908db5b AE |
766 | if (!\MODULE_CONVERSATION) { |
767 | throw new IllegalLinkException(); | |
768 | } | |
769 | ||
770 | if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) { | |
771 | throw new PermissionDeniedException(); | |
772 | } | |
b297c618 AE |
773 | } |
774 | ||
775 | /** | |
776 | * @since 5.5 | |
777 | */ | |
778 | public function getConversations(): array | |
779 | { | |
780 | $sqlSelect = ' , ( | |
781 | SELECT participantID | |
782 | FROM wcf' . WCF_N . '_conversation_to_user | |
783 | WHERE conversationID = conversation.conversationID | |
784 | AND participantID <> conversation.userID | |
785 | AND isInvisible = 0 | |
786 | ORDER BY username, participantID | |
787 | LIMIT 1 | |
788 | ) AS otherParticipantID | |
789 | , ( | |
790 | SELECT username | |
791 | FROM wcf' . WCF_N . '_conversation_to_user | |
792 | WHERE conversationID = conversation.conversationID | |
793 | AND participantID <> conversation.userID | |
794 | AND isInvisible = 0 | |
795 | ORDER BY username, participantID | |
796 | LIMIT 1 | |
797 | ) AS otherParticipant'; | |
798 | ||
799 | $unreadConversationList = new UserConversationList(WCF::getUser()->userID); | |
800 | $unreadConversationList->sqlSelects .= $sqlSelect; | |
801 | $unreadConversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime < lastPostTime'); | |
802 | $unreadConversationList->sqlLimit = 10; | |
803 | $unreadConversationList->sqlOrderBy = 'lastPostTime DESC'; | |
804 | $unreadConversationList->readObjects(); | |
805 | ||
806 | $conversations = []; | |
807 | $count = 0; | |
808 | foreach ($unreadConversationList as $conversation) { | |
809 | $conversations[] = $conversation; | |
810 | $count++; | |
811 | } | |
812 | ||
813 | if ($count < 10) { | |
814 | $conversationList = new UserConversationList(WCF::getUser()->userID); | |
815 | $conversationList->sqlSelects .= $sqlSelect; | |
816 | $conversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime >= lastPostTime'); | |
817 | $conversationList->sqlLimit = (10 - $count); | |
818 | $conversationList->sqlOrderBy = 'lastPostTime DESC'; | |
819 | $conversationList->readObjects(); | |
820 | ||
821 | foreach ($conversationList as $conversation) { | |
822 | $conversations[] = $conversation; | |
823 | } | |
824 | } | |
825 | ||
826 | $totalCount = ConversationHandler::getInstance()->getUnreadConversationCount(); | |
827 | if ($count < 10 && $count < $totalCount) { | |
828 | UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount'); | |
829 | } | |
830 | ||
05e725cc | 831 | $conversations = \array_map(static function (ViewableConversation $conversation) { |
8261584b AE |
832 | if ($conversation->userID === WCF::getUser()->userID) { |
833 | if ($conversation->participants > 1) { | |
3f3d496b | 834 | $image = FontAwesomeIcon::fromValues('users')->toHtml(48); |
016adb32 | 835 | $usernames = \array_column($conversation->getParticipantSummary(), 'username'); |
8261584b AE |
836 | } else { |
837 | $image = $conversation->getOtherParticipantProfile()->getAvatar()->getImageTag(48); | |
016adb32 | 838 | $usernames = [$conversation->getOtherParticipantProfile()->username]; |
8261584b | 839 | } |
b297c618 | 840 | } else { |
46a48921 | 841 | if ($conversation->participants > 1) { |
3f3d496b | 842 | $image = FontAwesomeIcon::fromValues('users')->toHtml(48); |
cfb465b7 | 843 | $usernames = \array_filter($conversation->getParticipantNames(), static function ($username) use ($conversation) { |
24195627 | 844 | return $username !== $conversation->getUserProfile()->username; |
46a48921 AE |
845 | }); |
846 | } else { | |
847 | $image = $conversation->getUserProfile()->getAvatar()->getImageTag(48); | |
848 | $usernames = [$conversation->getUserProfile()->username]; | |
849 | } | |
b297c618 AE |
850 | } |
851 | ||
852 | $link = LinkHandler::getInstance()->getControllerLink( | |
853 | ConversationPage::class, | |
854 | [ | |
855 | 'object' => $conversation, | |
856 | 'action' => 'firstNew', | |
857 | ] | |
858 | ); | |
859 | ||
b297c618 | 860 | return [ |
725b10a8 | 861 | 'content' => StringUtil::encodeHTML($conversation->getTitle()), |
b297c618 AE |
862 | 'image' => $image, |
863 | 'isUnread' => $conversation->isNew(), | |
864 | 'link' => $link, | |
865 | 'objectId' => $conversation->conversationID, | |
866 | 'time' => $conversation->lastPostTime, | |
867 | 'usernames' => $usernames, | |
868 | ]; | |
869 | }, $conversations); | |
05e725cc AE |
870 | |
871 | return [ | |
872 | 'items' => $conversations, | |
873 | 'totalCount' => $totalCount, | |
874 | ]; | |
b297c618 AE |
875 | } |
876 | ||
fea86294 TD |
877 | /** |
878 | * Validates the 'unmarkAll' action. | |
879 | */ | |
880 | public function validateUnmarkAll() | |
881 | { | |
882 | // does nothing | |
883 | } | |
884 | ||
885 | /** | |
886 | * Unmarks all conversations. | |
887 | */ | |
888 | public function unmarkAll() | |
889 | { | |
890 | ClipboardHandler::getInstance()->removeItems( | |
891 | ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation') | |
892 | ); | |
893 | } | |
894 | ||
895 | /** | |
896 | * Validates parameters to display the 'add participants' form. | |
897 | * | |
898 | * @throws PermissionDeniedException | |
899 | */ | |
900 | public function validateGetAddParticipantsForm() | |
901 | { | |
902 | $this->conversation = $this->getSingleObject(); | |
903 | if ( | |
904 | !Conversation::isParticipant([$this->conversation->conversationID]) | |
905 | || !$this->conversation->canAddParticipants() | |
906 | ) { | |
907 | throw new PermissionDeniedException(); | |
908 | } | |
909 | } | |
910 | ||
911 | /** | |
912 | * Shows the 'add participants' form. | |
913 | * | |
914 | * @return array | |
915 | */ | |
916 | public function getAddParticipantsForm() | |
917 | { | |
918 | $restrictUserGroupIDs = []; | |
919 | foreach (UserGroup::getAllGroups() as $group) { | |
920 | if ($group->canBeAddedAsConversationParticipant) { | |
921 | $restrictUserGroupIDs[] = $group->groupID; | |
922 | } | |
923 | } | |
924 | ||
925 | return [ | |
926 | 'excludedSearchValues' => $this->conversation->getParticipantNames(false, true), | |
927 | 'maxItems' => WCF::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation->participants, | |
928 | 'canAddGroupParticipants' => WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants'), | |
929 | 'template' => WCF::getTPL()->fetch( | |
930 | 'conversationAddParticipants', | |
931 | 'wcf', | |
932 | ['conversation' => $this->conversation] | |
933 | ), | |
934 | 'restrictUserGroupIDs' => $restrictUserGroupIDs, | |
935 | ]; | |
936 | } | |
937 | ||
938 | /** | |
939 | * Validates parameters to add new participants. | |
940 | */ | |
941 | public function validateAddParticipants() | |
942 | { | |
943 | $this->validateGetAddParticipantsForm(); | |
944 | ||
945 | // validate participants | |
946 | $this->readStringArray('participants', true); | |
947 | $this->readIntegerArray('participantsGroupIDs', true); | |
948 | ||
949 | if (!$this->conversation->getDecoratedObject()->isDraft) { | |
950 | $this->readString('visibility'); | |
951 | if (!\in_array($this->parameters['visibility'], ['all', 'new'])) { | |
952 | throw new UserInputException('visibility'); | |
953 | } | |
954 | ||
955 | if ($this->parameters['visibility'] === 'all' && !$this->conversation->canAddParticipantsUnrestricted()) { | |
956 | throw new UserInputException('visibility'); | |
957 | } | |
958 | } | |
959 | } | |
960 | ||
961 | /** | |
962 | * Adds new participants. | |
963 | * | |
964 | * @return array | |
965 | */ | |
966 | public function addParticipants() | |
967 | { | |
968 | try { | |
969 | $existingParticipants = $this->conversation->getParticipantIDs(true); | |
970 | $participantIDs = Conversation::validateParticipants( | |
971 | $this->parameters['participants'], | |
972 | 'participants', | |
973 | $existingParticipants | |
974 | ); | |
975 | if ( | |
976 | !empty($this->parameters['participantsGroupIDs']) | |
977 | && WCF::getSession()->getPermission('user.conversation.canAddGroupParticipants') | |
978 | ) { | |
979 | $validGroupParticipants = Conversation::validateGroupParticipants( | |
980 | $this->parameters['participantsGroupIDs'], | |
981 | 'participants', | |
982 | $existingParticipants | |
983 | ); | |
984 | $validGroupParticipants = \array_diff($validGroupParticipants, $participantIDs); | |
985 | if (empty($validGroupParticipants)) { | |
986 | throw new UserInputException('participants', 'emptyGroup'); | |
987 | } | |
988 | $participantIDs = \array_merge($participantIDs, $validGroupParticipants); | |
989 | } | |
990 | ||
991 | $parameters = [ | |
992 | 'participantIDs' => $participantIDs, | |
993 | ]; | |
994 | EventHandler::getInstance()->fireAction($this, 'addParticipants_validateParticipants', $parameters); | |
995 | $participantIDs = $parameters['participantIDs']; | |
996 | } catch (UserInputException $e) { | |
997 | $errorMessage = ''; | |
998 | $errors = \is_array($e->getType()) ? $e->getType() : [['type' => $e->getType()]]; | |
999 | foreach ($errors as $type) { | |
1000 | if (!empty($errorMessage)) { | |
1001 | $errorMessage .= ' '; | |
1002 | } | |
1003 | $errorMessage .= WCF::getLanguage()->getDynamicVariable( | |
1004 | 'wcf.conversation.participants.error.' . $type['type'], | |
1005 | ['errorData' => $type] | |
1006 | ); | |
1007 | } | |
1008 | ||
1009 | return [ | |
1010 | 'actionName' => 'addParticipants', | |
1011 | 'errorMessage' => $errorMessage, | |
1012 | ]; | |
1013 | } | |
1014 | ||
1015 | // validate limit | |
1016 | $newCount = $this->conversation->participants + \count($participantIDs); | |
1017 | if ($newCount > WCF::getSession()->getPermission('user.conversation.maxParticipants')) { | |
1018 | return [ | |
1019 | 'actionName' => 'addParticipants', | |
1020 | 'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.tooManyParticipants'), | |
1021 | ]; | |
1022 | } | |
1023 | ||
1024 | $count = 0; | |
1025 | $successMessage = ''; | |
1026 | if (!empty($participantIDs)) { | |
1027 | // check for already added participants | |
1028 | if ($this->conversation->isDraft) { | |
1029 | $draftData = \unserialize($this->conversation->draftData); | |
1030 | $draftData['participants'] = \array_merge($draftData['participants'], $participantIDs); | |
1031 | $data = ['data' => ['draftData' => \serialize($draftData)]]; | |
1032 | } else { | |
1033 | $data = [ | |
1034 | 'participants' => $participantIDs, | |
1035 | 'visibility' => (isset($this->parameters['visibility'])) ? $this->parameters['visibility'] : 'all', | |
1036 | ]; | |
1037 | } | |
1038 | ||
1039 | $conversationAction = new self([$this->conversation], 'update', $data); | |
1040 | $conversationAction->executeAction(); | |
1041 | ||
1042 | $count = \count($participantIDs); | |
1043 | $successMessage = WCF::getLanguage()->getDynamicVariable( | |
1044 | 'wcf.conversation.edit.addParticipants.success', | |
1045 | ['count' => $count] | |
1046 | ); | |
1047 | ||
1048 | ConversationModificationLogHandler::getInstance() | |
1049 | ->addParticipants($this->conversation->getDecoratedObject(), $participantIDs); | |
1050 | ||
1051 | if (!$this->conversation->isDraft) { | |
1052 | // update participant summary | |
1053 | $this->conversation->updateParticipantSummary(); | |
1054 | } | |
1055 | } | |
1056 | ||
1057 | return [ | |
1058 | 'count' => $count, | |
1059 | 'successMessage' => $successMessage, | |
1060 | ]; | |
1061 | } | |
1062 | ||
1063 | /** | |
1064 | * Validates parameters to remove a participant from a conversation. | |
1065 | * | |
1066 | * @throws PermissionDeniedException | |
1067 | * @throws UserInputException | |
1068 | */ | |
1069 | public function validateRemoveParticipant() | |
1070 | { | |
8aecc0ed MS |
1071 | // The previous request from `WCF.Action.Delete` used `userID`, while the new `Ui/Object/Action` |
1072 | // module passes `userId`. | |
1073 | try { | |
1074 | $this->readInteger('userID'); | |
1075 | } catch (UserInputException $e) { | |
1076 | $this->readInteger('userId'); | |
1077 | $this->parameters['userID'] = $this->parameters['userId']; | |
1078 | } | |
fea86294 TD |
1079 | |
1080 | // validate conversation | |
1081 | $this->conversation = $this->getSingleObject(); | |
1082 | if (!$this->conversation->conversationID) { | |
1083 | throw new UserInputException('objectIDs'); | |
1084 | } | |
1085 | ||
1086 | // check ownership | |
1087 | if ($this->conversation->userID != WCF::getUser()->userID) { | |
1088 | throw new PermissionDeniedException(); | |
1089 | } | |
1090 | ||
1091 | // validate participants | |
1092 | if ( | |
1093 | $this->parameters['userID'] == WCF::getUser()->userID | |
1094 | || !Conversation::isParticipant([$this->conversation->conversationID]) | |
1095 | || !Conversation::isParticipant([$this->conversation->conversationID], $this->parameters['userID']) | |
1096 | ) { | |
1097 | throw new PermissionDeniedException(); | |
1098 | } | |
1099 | } | |
1100 | ||
1101 | /** | |
1102 | * Removes a participant from a conversation. | |
1103 | */ | |
1104 | public function removeParticipant() | |
1105 | { | |
1106 | $this->conversation->removeParticipant($this->parameters['userID']); | |
1107 | $this->conversation->updateParticipantSummary(); | |
1108 | ||
4def2a52 TD |
1109 | $userConversation = Conversation::getUserConversation( |
1110 | $this->conversation->conversationID, | |
1111 | $this->parameters['userID'] | |
1112 | ); | |
1113 | ||
1114 | if (!$userConversation->isInvisible) { | |
1115 | ConversationModificationLogHandler::getInstance() | |
1116 | ->removeParticipant($this->conversation->getDecoratedObject(), $this->parameters['userID']); | |
1117 | } | |
fea86294 TD |
1118 | |
1119 | // reset storage | |
1120 | UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount'); | |
1121 | ||
1122 | return [ | |
1123 | 'userID' => $this->parameters['userID'], | |
1124 | ]; | |
1125 | } | |
1126 | ||
1127 | /** | |
1128 | * Rebuilds the conversation data of the relevant conversations. | |
1129 | */ | |
1130 | public function rebuild() | |
1131 | { | |
1132 | if (empty($this->objects)) { | |
1133 | $this->readObjects(); | |
1134 | } | |
1135 | ||
1136 | // collect number of messages for each conversation | |
1137 | $conditionBuilder = new PreparedStatementConditionBuilder(); | |
1138 | $conditionBuilder->add('conversation_message.conversationID IN (?)', [$this->objectIDs]); | |
8fbd8b01 MS |
1139 | $sql = "SELECT conversationID, COUNT(messageID) AS messages, SUM(attachments) AS attachments |
1140 | FROM wcf" . WCF_N . "_conversation_message conversation_message | |
1141 | " . $conditionBuilder . " | |
1142 | GROUP BY conversationID"; | |
fea86294 TD |
1143 | $statement = WCF::getDB()->prepareStatement($sql); |
1144 | $statement->execute($conditionBuilder->getParameters()); | |
1145 | ||
1146 | $objectIDs = []; | |
1147 | while ($row = $statement->fetchArray()) { | |
1148 | if (!$row['messages']) { | |
1149 | continue; | |
1150 | } | |
1151 | $objectIDs[] = $row['conversationID']; | |
1152 | ||
1153 | $conversationEditor = new ConversationEditor(new Conversation(null, [ | |
1154 | 'conversationID' => $row['conversationID'], | |
1155 | ])); | |
1156 | $conversationEditor->update([ | |
1157 | 'attachments' => $row['attachments'], | |
1158 | 'replies' => $row['messages'] - 1, | |
1159 | ]); | |
1160 | $conversationEditor->updateFirstMessage(); | |
1161 | $conversationEditor->updateLastMessage(); | |
1162 | } | |
1163 | ||
1164 | // delete conversations without messages | |
1165 | $deleteConversationIDs = \array_diff($this->objectIDs, $objectIDs); | |
1166 | if (!empty($deleteConversationIDs)) { | |
1167 | $conversationAction = new self($deleteConversationIDs, 'delete'); | |
1168 | $conversationAction->executeAction(); | |
1169 | } | |
1170 | } | |
1171 | ||
1172 | /** | |
1173 | * Validates the parameters to edit a conversation's subject. | |
1174 | * | |
1175 | * @throws PermissionDeniedException | |
1176 | */ | |
1177 | public function validateEditSubject() | |
1178 | { | |
1179 | $this->readString('subject'); | |
1180 | ||
1181 | $this->conversation = $this->getSingleObject(); | |
1182 | if ($this->conversation->userID != WCF::getUser()->userID) { | |
1183 | throw new PermissionDeniedException(); | |
1184 | } | |
1185 | } | |
1186 | ||
1187 | /** | |
1188 | * Edits a conversation's subject. | |
1189 | * | |
1190 | * @return string[] | |
1191 | */ | |
1192 | public function editSubject() | |
1193 | { | |
1194 | $subject = \mb_substr($this->parameters['subject'], 0, 255); | |
1195 | ||
1196 | $this->conversation->update([ | |
1197 | 'subject' => $subject, | |
1198 | ]); | |
1199 | ||
1200 | $message = $this->conversation->getFirstMessage(); | |
1201 | ||
1202 | SearchIndexManager::getInstance()->set( | |
1203 | 'com.woltlab.wcf.conversation.message', | |
1204 | $message->messageID, | |
1205 | $message->message, | |
1206 | $subject, | |
1207 | $message->time, | |
1208 | $message->userID, | |
1209 | $message->username | |
1210 | ); | |
1211 | ||
1212 | return [ | |
1213 | 'subject' => $subject, | |
1214 | ]; | |
1215 | } | |
1216 | ||
1217 | /** | |
1218 | * Adds conversation modification data. | |
1219 | * | |
1220 | * @param Conversation $conversation | |
1221 | * @param string $key | |
1222 | * @param mixed $value | |
1223 | */ | |
1224 | protected function addConversationData(Conversation $conversation, $key, $value) | |
1225 | { | |
1226 | if (!isset($this->conversationData[$conversation->conversationID])) { | |
1227 | $this->conversationData[$conversation->conversationID] = []; | |
1228 | } | |
1229 | ||
1230 | $this->conversationData[$conversation->conversationID][$key] = $value; | |
1231 | } | |
1232 | ||
1233 | /** | |
1234 | * Returns conversation data. | |
1235 | * | |
1236 | * @return mixed[][] | |
1237 | */ | |
1238 | protected function getConversationData() | |
1239 | { | |
1240 | return [ | |
1241 | 'conversationData' => $this->conversationData, | |
1242 | ]; | |
1243 | } | |
1244 | ||
1245 | /** | |
1246 | * Unmarks conversations. | |
1247 | * | |
c85e9df8 | 1248 | * @param int[] $conversationIDs |
fea86294 TD |
1249 | */ |
1250 | protected function unmarkItems(array $conversationIDs = []) | |
1251 | { | |
1252 | if (empty($conversationIDs)) { | |
1253 | $conversationIDs = $this->objectIDs; | |
1254 | } | |
1255 | ||
1256 | ClipboardHandler::getInstance()->unmark( | |
1257 | $conversationIDs, | |
1258 | ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation') | |
1259 | ); | |
1260 | } | |
9544b6b4 | 1261 | } |