Fix warnings about undefined properties and methods
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / data / conversation / ConversationAction.class.php
CommitLineData
9544b6b4
MW
1<?php
2namespace wcf\data\conversation;
5e279c42 3use wcf\data\conversation\label\ConversationLabel;
9544b6b4 4use wcf\data\conversation\message\ConversationMessageAction;
5d7f0df0 5use wcf\data\conversation\message\ConversationMessageList;
a0c1a541 6use wcf\data\conversation\message\SimplifiedViewableConversationMessageList;
b475685e 7use wcf\data\AbstractDatabaseObjectAction;
232cdc4b 8use wcf\data\IClipboardAction;
d8963ec2 9use wcf\data\IVisitableObjectAction;
e8fe47c2 10use wcf\system\clipboard\ClipboardHandler;
7f07124d 11use wcf\system\conversation\ConversationHandler;
5d7f0df0 12use wcf\system\database\util\PreparedStatementConditionBuilder;
5e279c42 13use wcf\system\exception\PermissionDeniedException;
e8fe47c2 14use wcf\system\exception\UserInputException;
65a160b7 15use wcf\system\log\modification\ConversationModificationLogHandler;
b2e0a2ad 16use wcf\system\request\LinkHandler;
8b467fcd
MW
17use wcf\system\user\notification\object\ConversationUserNotificationObject;
18use wcf\system\user\notification\UserNotificationHandler;
9544b6b4
MW
19use wcf\system\user\storage\UserStorageHandler;
20use wcf\system\WCF;
21
22/**
23 * Executes conversation-related actions.
24 *
25 * @author Marcel Werk
a2df5242 26 * @copyright 2001-2016 WoltLab GmbH
9544b6b4
MW
27 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
28 * @package com.woltlab.wcf.conversation
29 * @subpackage data.conversation
61f754e0 30 * @category Community Framework
5def88a8
MS
31 *
32 * @method ConversationEditor[] getObjects()
33 * @method ConversationEditor getSingleObject()
9544b6b4 34 */
d8963ec2 35class ConversationAction extends AbstractDatabaseObjectAction implements IClipboardAction, IVisitableObjectAction {
9544b6b4 36 /**
76f12d13 37 * @inheritDoc
9544b6b4 38 */
76f12d13 39 protected $className = ConversationEditor::class;
9544b6b4 40
65b37bf6
AE
41 /**
42 * conversation object
76f12d13 43 * @var ConversationEditor
65b37bf6
AE
44 */
45 protected $conversation = null;
46
ecc8c621
AE
47 /**
48 * list of conversation data modifications
03043c3c 49 * @var mixed[][]
ecc8c621 50 */
76f12d13 51 protected $conversationData = [];
ecc8c621 52
9544b6b4 53 /**
76f12d13 54 * @inheritDoc
5def88a8 55 * @return Conversation
9544b6b4
MW
56 */
57 public function create() {
58 // create conversation
59 $data = $this->parameters['data'];
60 $data['lastPosterID'] = $data['userID'];
61 $data['lastPoster'] = $data['username'];
62 $data['lastPostTime'] = $data['time'];
63 // count participants
64 if (!empty($this->parameters['participants'])) {
65 $data['participants'] = count($this->parameters['participants']);
66 }
67 // count attachments
68 if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
69 $data['attachments'] = count($this->parameters['attachmentHandler']);
70 }
76f12d13 71 $conversation = call_user_func([$this->className, 'create'], $data);
f34884b9 72 $conversationEditor = new ConversationEditor($conversation);
9544b6b4 73
f34884b9 74 if (!$conversation->isDraft) {
11cde7d7 75 // save participants
76f12d13 76 $conversationEditor->updateParticipants((!empty($this->parameters['participants']) ? $this->parameters['participants'] : []), (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []));
f34884b9
MW
77
78 // add author
76f12d13 79 $conversationEditor->updateParticipants([$data['userID']]);
3bbcb27d
MW
80
81 // update conversation count
462d532a 82 UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
db864366 83
1920cd3d
MW
84 // mark conversation as read for the author
85 $sql = "UPDATE wcf".WCF_N."_conversation_to_user
86 SET lastVisitTime = ?
87 WHERE participantID = ?
88 AND conversationID = ?";
89 $statement = WCF::getDB()->prepareStatement($sql);
76f12d13 90 $statement->execute([$data['time'], $data['userID'], $conversation->conversationID]);
3bbcb27d
MW
91 }
92 else {
93 // update conversation count
76f12d13 94 UserStorageHandler::getInstance()->reset([$data['userID']], 'conversationCount');
9544b6b4 95 }
9544b6b4
MW
96
97 // update participant summary
9544b6b4
MW
98 $conversationEditor->updateParticipantSummary();
99
100 // create message
1920cd3d
MW
101 $messageData = $this->parameters['messageData'];
102 $messageData['conversationID'] = $conversation->conversationID;
103 $messageData['time'] = $this->parameters['data']['time'];
104 $messageData['userID'] = $this->parameters['data']['userID'];
105 $messageData['username'] = $this->parameters['data']['username'];
9544b6b4 106
76f12d13 107 $messageAction = new ConversationMessageAction([], 'create', [
1920cd3d 108 'data' => $messageData,
9544b6b4
MW
109 'conversation' => $conversation,
110 'isFirstPost' => true,
5ff95003
AE
111 'attachmentHandler' => (isset($this->parameters['attachmentHandler']) ? $this->parameters['attachmentHandler'] : null),
112 'htmlInputProcessor' => (isset($this->parameters['htmlInputProcessor']) ? $this->parameters['htmlInputProcessor'] : null)
76f12d13 113 ]);
9544b6b4
MW
114 $resultValues = $messageAction->executeAction();
115
ba1ab5e5 116 // update first message id
76f12d13 117 $conversationEditor->update([
f34884b9 118 'firstMessageID' => $resultValues['returnValues']->messageID
76f12d13 119 ]);
f34884b9 120
11cde7d7
MW
121 $conversation->setFirstMessage($resultValues['returnValues']);
122 if (!$conversation->isDraft) {
123 // fire notification event
76f12d13 124 $notificationRecipients = array_merge((!empty($this->parameters['participants']) ? $this->parameters['participants'] : []), (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []));
11cde7d7
MW
125 UserNotificationHandler::getInstance()->fireEvent('conversation', 'com.woltlab.wcf.conversation.notification', new ConversationUserNotificationObject($conversation), $notificationRecipients);
126 }
127
9544b6b4
MW
128 return $conversation;
129 }
130
5d7f0df0 131 /**
76f12d13 132 * @inheritDoc
5d7f0df0
MW
133 */
134 public function delete() {
135 // deletes messages
136 $messageList = new ConversationMessageList();
76f12d13 137 $messageList->getConditionBuilder()->add('conversation_message.conversationID IN (?)', [$this->objectIDs]);
5d7f0df0
MW
138 $messageList->readObjectIDs();
139 $action = new ConversationMessageAction($messageList->getObjectIDs(), 'delete');
140 $action->executeAction();
141
142 // delete conversations
143 parent::delete();
d0cc88e1 144
5d7f0df0
MW
145 if (!empty($this->objectIDs)) {
146 // delete notifications
76f12d13 147 UserNotificationHandler::getInstance()->deleteNotifications('conversation', 'com.woltlab.wcf.conversation.notification', [], $this->objectIDs);
3b37e024
AE
148
149 // remove modification logs
76f12d13 150 ConversationModificationLogHandler::getInstance()->deleteLogs($this->objectIDs);
5d7f0df0
MW
151 }
152 }
153
f34884b9 154 /**
76f12d13 155 * @inheritDoc
f34884b9
MW
156 */
157 public function update() {
76f12d13
MS
158 if (!isset($this->parameters['participants'])) $this->parameters['participants'] = [];
159 if (!isset($this->parameters['invisibleParticipants'])) $this->parameters['invisibleParticipants'] = [];
f73a10ed 160
f34884b9
MW
161 // count participants
162 if (!empty($this->parameters['participants'])) {
163 $this->parameters['data']['participants'] = count($this->parameters['participants']);
164 }
165
166 parent::update();
167
c9ee8de8 168 foreach ($this->getObjects() as $conversation) {
bf720d62 169 // participants
f34884b9 170 if (!empty($this->parameters['participants']) || !empty($this->parameters['invisibleParticipants'])) {
f73a10ed
AE
171 // get current participants
172 $participantIDs = $conversation->getParticipantIDs();
173
76f12d13 174 $conversation->updateParticipants((!empty($this->parameters['participants']) ? $this->parameters['participants'] : []), (!empty($this->parameters['invisibleParticipants']) ? $this->parameters['invisibleParticipants'] : []));
f34884b9 175 $conversation->updateParticipantSummary();
f73a10ed
AE
176
177 // check if new participants have been added
1773c844
MW
178 $newParticipantIDs = array_diff(array_merge($this->parameters['participants'], $this->parameters['invisibleParticipants']), $participantIDs);
179 if (!empty($newParticipantIDs)) {
f73a10ed 180 // update conversation count
1846a5f4 181 UserStorageHandler::getInstance()->reset($newParticipantIDs, 'unreadConversationCount');
1773c844 182 UserStorageHandler::getInstance()->reset($newParticipantIDs, 'conversationCount');
f73a10ed
AE
183
184 // fire notification event
1773c844 185 UserNotificationHandler::getInstance()->fireEvent('conversation', 'com.woltlab.wcf.conversation.notification', new ConversationUserNotificationObject($conversation->getDecoratedObject()), $newParticipantIDs);
f73a10ed 186 }
f34884b9
MW
187 }
188
189 // draft status
190 if (isset($this->parameters['data']['isDraft'])) {
191 if ($conversation->isDraft && !$this->parameters['data']['isDraft']) {
192 // add author
76f12d13 193 $conversation->updateParticipants([$conversation->userID]);
3bbcb27d
MW
194
195 // update conversation count
1846a5f4 196 UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
462d532a 197 UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
f34884b9
MW
198 }
199 }
200 }
201 }
202
9544b6b4 203 /**
76f12d13 204 * @inheritDoc
9544b6b4
MW
205 */
206 public function markAsRead() {
207 if (empty($this->parameters['visitTime'])) {
208 $this->parameters['visitTime'] = TIME_NOW;
209 }
210
ac18582b 211 if (empty($this->objects)) {
9544b6b4
MW
212 $this->readObjects();
213 }
214
76f12d13 215 $conversationIDs = [];
9544b6b4
MW
216 $sql = "UPDATE wcf".WCF_N."_conversation_to_user
217 SET lastVisitTime = ?
218 WHERE participantID = ?
219 AND conversationID = ?";
220 $statement = WCF::getDB()->prepareStatement($sql);
c2b184f9 221 WCF::getDB()->beginTransaction();
c9ee8de8 222 foreach ($this->getObjects() as $conversation) {
76f12d13 223 $statement->execute([
c2b184f9
AE
224 $this->parameters['visitTime'],
225 WCF::getUser()->userID,
226 $conversation->conversationID
76f12d13 227 ]);
d0051596 228 $conversationIDs[] = $conversation->conversationID;
9544b6b4 229 }
c2b184f9 230 WCF::getDB()->commitTransaction();
9544b6b4
MW
231
232 // reset storage
76f12d13 233 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
d0051596 234
c61f0f25 235 // mark notifications as confirmed
d0051596
MW
236 if (!empty($conversationIDs)) {
237 // conversation start notification
238 $conditionBuilder = new PreparedStatementConditionBuilder();
76f12d13 239 $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler::getInstance()->getEvent('com.woltlab.wcf.conversation.notification', 'conversation')->eventID]);
d0051596 240 $conditionBuilder->add('notification.objectID = conversation.conversationID');
76f12d13
MS
241 $conditionBuilder->add('notification.userID = ?', [WCF::getUser()->userID]);
242 $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
243 $conditionBuilder->add('conversation.time <= ?', [$this->parameters['visitTime']]);
d0051596
MW
244
245 $sql = "SELECT conversation.conversationID
246 FROM wcf".WCF_N."_conversation conversation,
c61f0f25 247 wcf".WCF_N."_user_notification notification
d0051596
MW
248 ".$conditionBuilder;
249 $statement = WCF::getDB()->prepareStatement($sql);
250 $statement->execute($conditionBuilder->getParameters());
c9d69e7c 251 $notificationObjectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
d0051596
MW
252
253 if (!empty($notificationObjectIDs)) {
76f12d13 254 UserNotificationHandler::getInstance()->markAsConfirmed('conversation', 'com.woltlab.wcf.conversation.notification', [WCF::getUser()->userID], $notificationObjectIDs);
d0051596
MW
255 }
256
257 // conversation reply notification
258 $conditionBuilder = new PreparedStatementConditionBuilder();
76f12d13 259 $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler::getInstance()->getEvent('com.woltlab.wcf.conversation.message.notification', 'conversationMessage')->eventID]);
d0051596 260 $conditionBuilder->add('notification.objectID = conversation_message.messageID');
76f12d13
MS
261 $conditionBuilder->add('notification.userID = ?', [WCF::getUser()->userID]);
262 $conditionBuilder->add('conversation_message.conversationID IN (?)', [$conversationIDs]);
263 $conditionBuilder->add('conversation_message.time <= ?', [$this->parameters['visitTime']]);
a480521b 264
d0051596
MW
265 $sql = "SELECT conversation_message.messageID
266 FROM wcf".WCF_N."_conversation_message conversation_message,
c61f0f25 267 wcf".WCF_N."_user_notification notification
d0051596
MW
268 ".$conditionBuilder;
269 $statement = WCF::getDB()->prepareStatement($sql);
270 $statement->execute($conditionBuilder->getParameters());
c9d69e7c 271 $notificationObjectIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
a480521b 272
d0051596 273 if (!empty($notificationObjectIDs)) {
76f12d13 274 UserNotificationHandler::getInstance()->markAsConfirmed('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [WCF::getUser()->userID], $notificationObjectIDs);
d0051596
MW
275 }
276 }
4ca3137c
AE
277
278 if (!empty($conversationIDs)) {
279 $this->unmarkItems($conversationIDs);
280 }
c61f0f25 281
76f12d13 282 $returnValues = [
c61f0f25 283 'totalCount' => ConversationHandler::getInstance()->getUnreadConversationCount(null, true)
76f12d13 284 ];
c61f0f25
AE
285
286 if (count($conversationIDs) == 1) {
287 $returnValues['markAsRead'] = reset($conversationIDs);
288 }
289
290 return $returnValues;
9544b6b4
MW
291 }
292
293 /**
76f12d13 294 * @inheritDoc
9544b6b4
MW
295 */
296 public function validateMarkAsRead() {
c2b184f9
AE
297 // visitTime might not be in the future
298 if (isset($this->parameters['visitTime'])) {
299 $this->parameters['visitTime'] = intval($this->parameters['visitTime']);
300 if ($this->parameters['visitTime'] > TIME_NOW) {
301 $this->parameters['visitTime'] = TIME_NOW;
302 }
303 }
304
305 if (empty($this->objects)) {
306 $this->readObjects();
307 }
308
309 // check participation
76f12d13 310 $conversationIDs = [];
c9ee8de8 311 foreach ($this->getObjects() as $conversation) {
c2b184f9
AE
312 $conversationIDs[] = $conversation->conversationID;
313 }
314
315 if (empty($conversationIDs)) {
316 throw new UserInputException('objectIDs');
317 }
318
319 if (!Conversation::isParticipant($conversationIDs)) {
320 throw new PermissionDeniedException();
321 }
9544b6b4 322 }
5e279c42 323
38cc68ad
MW
324 /**
325 * Marks all conversations as read.
326 */
327 public function markAllAsRead() {
328 $sql = "UPDATE wcf".WCF_N."_conversation_to_user
329 SET lastVisitTime = ?
330 WHERE participantID = ?";
331 $statement = WCF::getDB()->prepareStatement($sql);
76f12d13 332 $statement->execute([
38cc68ad
MW
333 TIME_NOW,
334 WCF::getUser()->userID
76f12d13 335 ]);
38cc68ad
MW
336
337 // reset storage
76f12d13 338 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
38cc68ad
MW
339
340 // delete obsolete notifications
76f12d13
MS
341 UserNotificationHandler::getInstance()->deleteNotifications('conversation', 'com.woltlab.wcf.conversation.notification', [WCF::getUser()->userID]);
342 UserNotificationHandler::getInstance()->deleteNotifications('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [WCF::getUser()->userID]);
c61f0f25 343
76f12d13 344 return [
c61f0f25 345 'markAllAsRead' => true
76f12d13 346 ];
38cc68ad
MW
347 }
348
349 /**
350 * Validates the markAllAsRead action.
351 */
76f12d13
MS
352 public function validateMarkAllAsRead() {
353 // does nothing
354 }
38cc68ad 355
5e279c42
AE
356 /**
357 * Validates user access for label management.
358 */
bf720d62 359 public function validateGetLabelManagement() {
5e279c42
AE
360 if (!WCF::getSession()->getPermission('user.conversation.canUseConversation')) {
361 throw new PermissionDeniedException();
362 }
363 }
364
365 /**
366 * Returns the conversation label management.
367 *
368 * @return array
369 */
370 public function getLabelManagement() {
76f12d13 371 WCF::getTPL()->assign([
5e279c42
AE
372 'cssClassNames' => ConversationLabel::getLabelCssClassNames(),
373 'labelList' => ConversationLabel::getLabelsByUser()
76f12d13 374 ]);
5e279c42 375
76f12d13 376 return [
5e279c42 377 'actionName' => 'getLabelManagement',
d8c52eb0
MS
378 'template' => WCF::getTPL()->fetch('conversationLabelManagement'),
379 'maxLabels' => WCF::getSession()->getPermission('user.conversation.maxLabels'),
380 'labelCount' => count(ConversationLabel::getLabelsByUser())
76f12d13 381 ];
5e279c42 382 }
e2196ce0
MW
383
384 /**
385 * Validates the get message preview action.
386 */
387 public function validateGetMessagePreview() {
953b3e7b 388 $this->conversation = $this->getSingleObject();
76f12d13 389 if (!Conversation::isParticipant([$this->conversation->conversationID])) {
953b3e7b 390 throw new PermissionDeniedException();
e2196ce0 391 }
e2196ce0
MW
392 }
393
394 /**
a480521b 395 * Returns a preview of a message in a specific conversation.
e2196ce0 396 *
76f12d13 397 * @return string[]
e2196ce0
MW
398 */
399 public function getMessagePreview() {
a0c1a541 400 $messageList = new SimplifiedViewableConversationMessageList();
e2196ce0 401
76f12d13 402 $messageList->getConditionBuilder()->add("conversation_message.messageID = ?", [$this->conversation->firstMessageID]);
e2196ce0
MW
403 $messageList->readObjects();
404 $messages = $messageList->getObjects();
405
76f12d13 406 WCF::getTPL()->assign([
e2196ce0 407 'message' => reset($messages)
76f12d13
MS
408 ]);
409 return [
e2196ce0 410 'template' => WCF::getTPL()->fetch('conversationMessagePreview')
76f12d13 411 ];
e2196ce0 412 }
ecc8c621
AE
413
414 /**
415 * Validates parameters to close conversations.
416 */
417 public function validateClose() {
418 // read objects
419 if (empty($this->objects)) {
420 $this->readObjects();
eb6d1db7
AE
421
422 if (empty($this->objects)) {
423 throw new UserInputException('objectIDs');
424 }
ecc8c621
AE
425 }
426
427 // validate ownership
c9ee8de8 428 foreach ($this->getObjects() as $conversation) {
ecc8c621
AE
429 if ($conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) {
430 throw new PermissionDeniedException();
431 }
432 }
433 }
434
435 /**
436 * Closes conversations.
437 *
03043c3c 438 * @return mixed[][]
ecc8c621
AE
439 */
440 public function close() {
c9ee8de8 441 foreach ($this->getObjects() as $conversation) {
76f12d13 442 $conversation->update(['isClosed' => 1]);
2e0bc870 443 $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 1);
953b3e7b
AE
444
445 ConversationModificationLogHandler::getInstance()->close($conversation->getDecoratedObject());
ecc8c621
AE
446 }
447
e8fe47c2
AE
448 $this->unmarkItems();
449
ecc8c621
AE
450 return $this->getConversationData();
451 }
452
453 /**
454 * Validates parameters to open conversations.
455 */
456 public function validateOpen() {
457 // read objects
458 if (empty($this->objects)) {
459 $this->readObjects();
eb6d1db7
AE
460
461 if (empty($this->objects)) {
462 throw new UserInputException('objectIDs');
463 }
ecc8c621 464 }
db864366 465
ecc8c621 466 // validate ownership
c9ee8de8 467 foreach ($this->getObjects() as $conversation) {
ecc8c621
AE
468 if (!$conversation->isClosed || ($conversation->userID != WCF::getUser()->userID)) {
469 throw new PermissionDeniedException();
470 }
471 }
472 }
473
474 /**
475 * Opens conversations.
ba1ab5e5 476 *
03043c3c 477 * @return mixed[][]
ecc8c621
AE
478 */
479 public function open() {
c9ee8de8 480 foreach ($this->getObjects() as $conversation) {
76f12d13 481 $conversation->update(['isClosed' => 0]);
2e0bc870 482 $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 0);
953b3e7b 483
518ad17c 484 ConversationModificationLogHandler::getInstance()->open($conversation->getDecoratedObject());
ecc8c621 485 }
e8fe47c2
AE
486
487 $this->unmarkItems();
db864366 488
ecc8c621
AE
489 return $this->getConversationData();
490 }
491
50cd21a1
AE
492 /**
493 * Validates conversations for leave form.
494 */
495 public function validateGetLeaveForm() {
2f5b4859
AE
496 if (empty($this->objectIDs)) {
497 throw new UserInputException('objectIDs');
50cd21a1
AE
498 }
499
500 // validate participation
2f5b4859 501 if (!Conversation::isParticipant($this->objectIDs)) {
50cd21a1
AE
502 throw new PermissionDeniedException();
503 }
504 }
505
506 /**
507 * Returns dialog form to leave conversations.
508 *
509 * @return array
510 */
511 public function getLeaveForm() {
512 // get hidden state from first conversation (all others have the same state)
513 $sql = "SELECT hideConversation
514 FROM wcf".WCF_N."_conversation_to_user
515 WHERE conversationID = ?
516 AND participantID = ?";
517 $statement = WCF::getDB()->prepareStatement($sql);
76f12d13 518 $statement->execute([
2f5b4859 519 current($this->objectIDs),
50cd21a1 520 WCF::getUser()->userID
76f12d13 521 ]);
50cd21a1
AE
522 $row = $statement->fetchArray();
523
524 WCF::getTPL()->assign('hideConversation', $row['hideConversation']);
525
76f12d13 526 return [
50cd21a1
AE
527 'actionName' => 'getLeaveForm',
528 'template' => WCF::getTPL()->fetch('conversationLeave')
76f12d13 529 ];
50cd21a1
AE
530 }
531
532 /**
533 * Validates parameters to hide conversations.
534 */
535 public function validateHideConversation() {
536 $this->parameters['hideConversation'] = (isset($this->parameters['hideConversation'])) ? intval($this->parameters['hideConversation']) : null;
76f12d13 537 if ($this->parameters['hideConversation'] === null || !in_array($this->parameters['hideConversation'], [Conversation::STATE_DEFAULT, Conversation::STATE_HIDDEN, Conversation::STATE_LEFT])) {
50cd21a1
AE
538 throw new UserInputException('hideConversation');
539 }
540
2f5b4859
AE
541 if (empty($this->objectIDs)) {
542 throw new UserInputException('objectIDs');
50cd21a1
AE
543 }
544
545 // validate participation
2f5b4859 546 if (!Conversation::isParticipant($this->objectIDs)) {
50cd21a1
AE
547 throw new PermissionDeniedException();
548 }
549 }
550
551 /**
552 * Hides or restores conversations.
553 *
76f12d13 554 * @return string[]
50cd21a1
AE
555 */
556 public function hideConversation() {
557 $sql = "UPDATE wcf".WCF_N."_conversation_to_user
558 SET hideConversation = ?
559 WHERE conversationID = ?
560 AND participantID = ?";
561 $statement = WCF::getDB()->prepareStatement($sql);
562
563 WCF::getDB()->beginTransaction();
2f5b4859 564 foreach ($this->objectIDs as $conversationID) {
76f12d13 565 $statement->execute([
50cd21a1
AE
566 $this->parameters['hideConversation'],
567 $conversationID,
568 WCF::getUser()->userID
76f12d13 569 ]);
50cd21a1
AE
570 }
571 WCF::getDB()->commitTransaction();
572
c26a69bc
MS
573 // reset user's conversation counters if user leaves conversation
574 // permanently
a056f309 575 if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
76f12d13
MS
576 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'conversationCount');
577 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
8bbdb36d
MS
578 }
579
e829b862
MW
580 // add modification log entry
581 if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
582 if (empty($this->objects)) $this->readObjects();
583
c9ee8de8 584 foreach ($this->getObjects() as $conversation) {
e829b862
MW
585 ConversationModificationLogHandler::getInstance()->leave($conversation->getDecoratedObject());
586 }
587 }
588
02d63678 589 // unmark items
e8fe47c2
AE
590 $this->unmarkItems();
591
5d7f0df0 592 if ($this->parameters['hideConversation'] == Conversation::STATE_LEFT) {
0320065e
MS
593 // update participants count and participant summary
594 ConversationEditor::updateParticipantCounts($this->objectIDs);
1920cd3d
MW
595 ConversationEditor::updateParticipantSummaries($this->objectIDs);
596
597 // delete conversation if all users have left it
5d7f0df0 598 $conditionBuilder = new PreparedStatementConditionBuilder();
76f12d13 599 $conditionBuilder->add('conversation.conversationID IN (?)', [$this->objectIDs]);
5d7f0df0 600 $conditionBuilder->add('conversation_to_user.conversationID IS NULL');
5d7f0df0
MW
601 $sql = "SELECT DISTINCT conversation.conversationID
602 FROM wcf".WCF_N."_conversation conversation
603 LEFT JOIN wcf".WCF_N."_conversation_to_user conversation_to_user
24f6c489
TD
604 ON ( conversation_to_user.conversationID = conversation.conversationID
605 AND conversation_to_user.hideConversation <> ".Conversation::STATE_LEFT."
606 AND conversation_to_user.participantID IS NOT NULL)
5d7f0df0
MW
607 ".$conditionBuilder;
608 $statement = WCF::getDB()->prepareStatement($sql);
609 $statement->execute($conditionBuilder->getParameters());
c9d69e7c 610 $conversationIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
0e86a288 611
5d7f0df0
MW
612 if (!empty($conversationIDs)) {
613 $action = new ConversationAction($conversationIDs, 'delete');
614 $action->executeAction();
615 }
616 }
617
76f12d13 618 return [
b2e0a2ad
AE
619 'actionName' => 'hideConversation',
620 'redirectURL' => LinkHandler::getInstance()->getLink('ConversationList')
76f12d13 621 ];
50cd21a1
AE
622 }
623
742b8736 624 /**
b17ff425 625 * Validates parameters to return the mixed conversation list.
742b8736 626 */
b17ff425 627 public function validateGetMixedConversationList() {
a480521b
MS
628 // does nothing
629 }
742b8736
AE
630
631 /**
582481f3 632 * Returns a mixed conversation list with up to 10 unread conversations.
742b8736 633 *
03043c3c 634 * @return mixed[][]
742b8736 635 */
b17ff425 636 public function getMixedConversationList() {
df3404c7
MW
637 $sqlSelect = ' , (SELECT participantID FROM wcf'.WCF_N.'_conversation_to_user WHERE conversationID = conversation.conversationID AND participantID <> conversation.userID AND isInvisible = 0 ORDER BY username, participantID LIMIT 1) AS otherParticipantID
638 , (SELECT username FROM wcf'.WCF_N.'_conversation_to_user WHERE conversationID = conversation.conversationID AND participantID <> conversation.userID AND isInvisible = 0 ORDER BY username, participantID LIMIT 1) AS otherParticipant';
639
b17ff425 640 $unreadConversationList = new UserConversationList(WCF::getUser()->userID);
df3404c7 641 $unreadConversationList->sqlSelects .= $sqlSelect;
b17ff425 642 $unreadConversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime < conversation.lastPostTime');
582481f3 643 $unreadConversationList->sqlLimit = 10;
b17ff425
AE
644 $unreadConversationList->sqlOrderBy = 'conversation.lastPostTime DESC';
645 $unreadConversationList->readObjects();
646
76f12d13 647 $conversations = [];
b17ff425
AE
648 $count = 0;
649 foreach ($unreadConversationList as $conversation) {
650 $conversations[] = $conversation;
651 $count++;
652 }
653
582481f3 654 if ($count < 10) {
b17ff425 655 $conversationList = new UserConversationList(WCF::getUser()->userID);
df3404c7 656 $conversationList->sqlSelects .= $sqlSelect;
b17ff425 657 $conversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime >= conversation.lastPostTime');
582481f3 658 $conversationList->sqlLimit = (10 - $count);
b17ff425
AE
659 $conversationList->sqlOrderBy = 'conversation.lastPostTime DESC';
660 $conversationList->readObjects();
661
662 foreach ($conversationList as $conversation) {
663 $conversations[] = $conversation;
664 }
665 }
742b8736 666
76f12d13 667 WCF::getTPL()->assign([
b17ff425 668 'conversations' => $conversations
76f12d13 669 ]);
742b8736 670
acd16520 671 $totalCount = ConversationHandler::getInstance()->getUnreadConversationCount();
582481f3 672 if ($count < 10 && $count < $totalCount) {
76f12d13 673 UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadConversationCount');
acd16520
AE
674 }
675
76f12d13 676 return [
c61f0f25 677 'template' => WCF::getTPL()->fetch('conversationListUserPanel'),
acd16520 678 'totalCount' => $totalCount
76f12d13 679 ];
742b8736
AE
680 }
681
0035c8ea 682 /**
a480521b 683 * Validates the 'unmarkAll' action.
0035c8ea 684 */
a480521b
MS
685 public function validateUnmarkAll() {
686 // does nothing
687 }
0035c8ea
AE
688
689 /**
690 * Unmarks all conversations.
691 */
692 public function unmarkAll() {
693 ClipboardHandler::getInstance()->removeItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
694 }
695
65b37bf6
AE
696 /**
697 * Validates parameters to display the 'add participants' form.
698 */
699 public function validateGetAddParticipantsForm() {
700 $this->conversation = $this->getSingleObject();
76f12d13 701 if (!Conversation::isParticipant([$this->conversation->conversationID]) || !$this->conversation->canAddParticipants()) {
65b37bf6
AE
702 throw new PermissionDeniedException();
703 }
704 }
705
706 /**
707 * Shows the 'add participants' form.
708 *
709 * @return array
710 */
711 public function getAddParticipantsForm() {
76f12d13 712 return [
368540c1
AE
713 'excludedSearchValues' => $this->conversation->getParticipantNames(),
714 'maxItems' => WCF::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation->participants,
65b37bf6 715 'template' => WCF::getTPL()->fetch('conversationAddParticipants')
76f12d13 716 ];
65b37bf6
AE
717 }
718
719 /**
720 * Validates parameters to add new participants.
721 */
722 public function validateAddParticipants() {
723 $this->validateGetAddParticipantsForm();
724
725 // validate participants
368540c1 726 $this->readStringArray('participants');
65b37bf6
AE
727 }
728
729 /**
730 * Adds new participants.
731 *
732 * @return array
733 */
734 public function addParticipants() {
094ca1e9 735 try {
b5b88a93 736 $participantIDs = Conversation::validateParticipants($this->parameters['participants'], 'participants', $this->conversation->getParticipantIDs(true));
094ca1e9
AE
737 }
738 catch (UserInputException $e) {
739 $errorMessage = '';
740 foreach ($e->getType() as $type) {
0bca0d8c 741 if (!empty($errorMessage)) $errorMessage .= ' ';
76f12d13 742 $errorMessage .= WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.'.$type['type'], ['errorData' => ['username' => $type['username']]]);
094ca1e9
AE
743 }
744
76f12d13 745 return [
094ca1e9
AE
746 'actionName' => 'addParticipants',
747 'errorMessage' => $errorMessage
76f12d13 748 ];
094ca1e9
AE
749 }
750
ee3847e4
AE
751 // validate limit
752 $newCount = $this->conversation->participants + count($participantIDs);
753 if ($newCount > WCF::getSession()->getPermission('user.conversation.maxParticipants')) {
76f12d13 754 return [
ee3847e4 755 'actionName' => 'addParticipants',
3846d50e 756 'errorMessage' => WCF::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.tooManyParticipants')
76f12d13 757 ];
ee3847e4
AE
758 }
759
65b37bf6
AE
760 $count = 0;
761 $successMessage = '';
65b37bf6
AE
762 if (!empty($participantIDs)) {
763 // check for already added participants
b5b88a93
MW
764 if ($this->conversation->isDraft) {
765 $draftData = unserialize($this->conversation->draftData);
766 $draftData['participants'] = array_merge($draftData['participants'], $participantIDs);
76f12d13 767 $data = ['data' => ['draftData' => serialize($draftData)]];
b5b88a93
MW
768 }
769 else {
76f12d13 770 $data = ['participants' => $participantIDs];
b5b88a93
MW
771 }
772
76f12d13 773 $conversationAction = new ConversationAction([$this->conversation], 'update', $data);
b5b88a93
MW
774 $conversationAction->executeAction();
775
776 $count = count($participantIDs);
76f12d13 777 $successMessage = WCF::getLanguage()->getDynamicVariable('wcf.conversation.edit.addParticipants.success', ['count' => $count]);
b5b88a93
MW
778
779 ConversationModificationLogHandler::getInstance()->addParticipants($this->conversation->getDecoratedObject(), $participantIDs);
780
781 if (!$this->conversation->isDraft) {
782 // update participant summary
783 $this->conversation->updateParticipantSummary();
65b37bf6
AE
784 }
785 }
786
76f12d13 787 return [
65b37bf6
AE
788 'count' => $count,
789 'successMessage' => $successMessage
76f12d13 790 ];
65b37bf6
AE
791 }
792
a208d1f4
AE
793 /**
794 * Validates parameters to remove a participant from a conversation.
795 */
796 public function validateRemoveParticipant() {
797 $this->readInteger('userID');
798
799 // validate conversation
800 $this->conversation = $this->getSingleObject();
801 if (!$this->conversation->conversationID) {
802 throw new UserInputException('objectIDs');
803 }
804
805 // check ownership
806 if ($this->conversation->userID != WCF::getUser()->userID) {
807 throw new PermissionDeniedException();
808 }
809
810 // validate participants
76f12d13 811 if ($this->parameters['userID'] == WCF::getUser()->userID || !Conversation::isParticipant([$this->conversation->conversationID]) || !Conversation::isParticipant([$this->conversation->conversationID], $this->parameters['userID'])) {
a208d1f4
AE
812 throw new PermissionDeniedException();
813 }
814
815 }
816
817 /**
818 * Removes a participant from a conversation.
819 */
820 public function removeParticipant() {
821 $this->conversation->removeParticipant($this->parameters['userID']);
822 $this->conversation->updateParticipantSummary();
823
824 ConversationModificationLogHandler::getInstance()->removeParticipant($this->conversation->getDecoratedObject(), $this->parameters['userID']);
825
92e66692 826 // reset storage
76f12d13 827 UserStorageHandler::getInstance()->reset([$this->parameters['userID']], 'unreadConversationCount');
92e66692 828
76f12d13 829 return [
a208d1f4 830 'userID' => $this->parameters['userID']
76f12d13 831 ];
a208d1f4
AE
832 }
833
3b34d39f
MS
834 /**
835 * Rebuilds the conversation data of the relevant conversations.
836 */
837 public function rebuild() {
838 if (empty($this->objects)) {
839 $this->readObjects();
840 }
841
842 // collect number of messages for each conversation
843 $conditionBuilder = new PreparedStatementConditionBuilder();
76f12d13 844 $conditionBuilder->add('conversation_message.conversationID IN (?)', [$this->objectIDs]);
487b839f 845 $sql = "SELECT conversationID, COUNT(messageID) AS messages, SUM(attachments) AS attachments
3b34d39f
MS
846 FROM wcf".WCF_N."_conversation_message conversation_message
847 ".$conditionBuilder."
848 GROUP BY conversationID";
849 $statement = WCF::getDB()->prepareStatement($sql);
850 $statement->execute($conditionBuilder->getParameters());
851
76f12d13 852 $objectIDs = [];
d5a7543e 853 while (($row = $statement->fetchArray())) {
3b34d39f
MS
854 if (!$row['messages']) {
855 continue;
856 }
857 $objectIDs[] = $row['conversationID'];
858
76f12d13 859 $conversationEditor = new ConversationEditor(new Conversation(null, [
3b34d39f 860 'conversationID' => $row['conversationID']
76f12d13
MS
861 ]));
862 $conversationEditor->update([
487b839f 863 'attachments' => $row['attachments'],
3b34d39f 864 'replies' => $row['messages'] - 1
76f12d13 865 ]);
3b34d39f
MS
866 $conversationEditor->updateFirstMessage();
867 $conversationEditor->updateLastMessage();
868 }
869
870 // delete conversations without messages
871 $deleteConversationIDs = array_diff($this->objectIDs, $objectIDs);
872 if (!empty($deleteConversationIDs)) {
873 $conversationAction = new ConversationAction($deleteConversationIDs, 'delete');
874 $conversationAction->executeAction();
875 }
876 }
877
ecc8c621
AE
878 /**
879 * Adds conversation modification data.
880 *
76f12d13
MS
881 * @param Conversation $conversation
882 * @param string $key
883 * @param mixed $value
ecc8c621
AE
884 */
885 protected function addConversationData(Conversation $conversation, $key, $value) {
886 if (!isset($this->conversationData[$conversation->conversationID])) {
76f12d13 887 $this->conversationData[$conversation->conversationID] = [];
ecc8c621
AE
888 }
889
890 $this->conversationData[$conversation->conversationID][$key] = $value;
891 }
892
893 /**
db864366
MS
894 * Returns conversation data.
895 *
03043c3c 896 * @return mixed[][]
ecc8c621
AE
897 */
898 protected function getConversationData() {
76f12d13 899 return [
ecc8c621 900 'conversationData' => $this->conversationData
76f12d13 901 ];
ecc8c621 902 }
e8fe47c2
AE
903
904 /**
905 * Unmarks conversations.
906 *
76f12d13 907 * @param integer[] $conversationIDs
e8fe47c2 908 */
76f12d13 909 protected function unmarkItems(array $conversationIDs = []) {
e8fe47c2
AE
910 if (empty($conversationIDs)) {
911 $conversationIDs = $this->objectIDs;
912 }
913
914 ClipboardHandler::getInstance()->unmark($conversationIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
915 }
9544b6b4 916}