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