2 namespace wcf\data\conversation
;
3 use wcf\data\conversation\label\ConversationLabel
;
4 use wcf\data\conversation\message\ConversationMessageAction
;
5 use wcf\data\conversation\message\ConversationMessageList
;
6 use wcf\data\conversation\message\SimplifiedViewableConversationMessageList
;
7 use wcf\data\AbstractDatabaseObjectAction
;
8 use wcf\data\IClipboardAction
;
9 use wcf\data\IVisitableObjectAction
;
10 use wcf\system\clipboard\ClipboardHandler
;
11 use wcf\system\conversation\ConversationHandler
;
12 use wcf\system\database\util\PreparedStatementConditionBuilder
;
13 use wcf\system\exception\PermissionDeniedException
;
14 use wcf\system\exception\UserInputException
;
15 use wcf\system\log\modification\ConversationModificationLogHandler
;
16 use wcf\system\request\LinkHandler
;
17 use wcf\system\search\SearchIndexManager
;
18 use wcf\system\user\notification\
object\ConversationUserNotificationObject
;
19 use wcf\system\user\notification\UserNotificationHandler
;
20 use wcf\system\user\storage\UserStorageHandler
;
24 * Executes conversation-related actions.
27 * @copyright 2001-2018 WoltLab GmbH
28 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
29 * @package WoltLabSuite\Core\Data\Conversation
31 * @method ConversationEditor[] getObjects()
32 * @method ConversationEditor getSingleObject()
34 class ConversationAction
extends AbstractDatabaseObjectAction
implements IClipboardAction
, IVisitableObjectAction
{
38 protected $className = ConversationEditor
::class;
42 * @var ConversationEditor
44 protected $conversation;
47 * list of conversation data modifications
50 protected $conversationData = [];
52 /** @noinspection PhpMissingParentCallCommonInspection */
55 * @return Conversation
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'];
64 if (!empty($this->parameters
['participants'])) {
65 $data['participants'] = count($this->parameters
['participants']);
68 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
69 $data['attachments'] = count($this->parameters
['attachmentHandler']);
71 $conversation = call_user_func([$this->className
, 'create'], $data);
72 $conversationEditor = new ConversationEditor($conversation);
74 if (!$conversation->isDraft
) {
76 $conversationEditor->updateParticipants(
77 (!empty($this->parameters
['participants']) ?
$this->parameters
['participants'] : []),
78 (!empty($this->parameters
['invisibleParticipants']) ?
$this->parameters
['invisibleParticipants'] : []),
83 if ($data['userID'] !== null) {
84 $conversationEditor->updateParticipants([$data['userID']], [], 'all');
87 // update conversation count
88 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
90 // mark conversation as read for the author
91 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
93 WHERE participantID = ?
94 AND conversationID = ?";
95 $statement = WCF
::getDB()->prepareStatement($sql);
96 $statement->execute([$data['time'], $data['userID'], $conversation->conversationID
]);
99 // update conversation count
100 UserStorageHandler
::getInstance()->reset([$data['userID']], 'conversationCount');
103 // update participant summary
104 $conversationEditor->updateParticipantSummary();
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'];
113 $messageAction = new ConversationMessageAction([], 'create', [
114 'data' => $messageData,
115 'conversation' => $conversation,
116 'isFirstPost' => true,
117 'attachmentHandler' => isset($this->parameters
['attachmentHandler']) ?
$this->parameters
['attachmentHandler'] : null,
118 'htmlInputProcessor' => isset($this->parameters
['htmlInputProcessor']) ?
$this->parameters
['htmlInputProcessor'] : null
120 $resultValues = $messageAction->executeAction();
122 // update first message id
123 $conversationEditor->update([
124 'firstMessageID' => $resultValues['returnValues']->messageID
127 $conversation->setFirstMessage($resultValues['returnValues']);
128 if (!$conversation->isDraft
) {
129 // fire notification event
130 $notificationRecipients = array_merge((!empty($this->parameters
['participants']) ?
$this->parameters
['participants'] : []), (!empty($this->parameters
['invisibleParticipants']) ?
$this->parameters
['invisibleParticipants'] : []));
131 UserNotificationHandler
::getInstance()->fireEvent(
133 'com.woltlab.wcf.conversation.notification',
134 new ConversationUserNotificationObject($conversation),
135 $notificationRecipients
139 return $conversation;
145 public function delete() {
147 $messageList = new ConversationMessageList();
148 $messageList->getConditionBuilder()->add('conversation_message.conversationID IN (?)', [$this->objectIDs
]);
149 $messageList->readObjectIDs();
150 $action = new ConversationMessageAction($messageList->getObjectIDs(), 'delete');
151 $action->executeAction();
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
161 $statement = WCF
::getDB()->prepareStatement($sql);
162 $statement->execute($conditions->getParameters());
164 while ($participantID = $statement->fetchColumn()) {
165 $participantIDs[] = $participantID;
169 // delete conversations
172 if (!empty($this->objectIDs
)) {
173 // delete notifications
174 UserNotificationHandler
::getInstance()->removeNotifications('com.woltlab.wcf.conversation.notification', $this->objectIDs
);
176 // remove modification logs
177 ConversationModificationLogHandler
::getInstance()->deleteLogs($this->objectIDs
);
179 // reset the number of unread conversations
180 if (!empty($participantIDs)) {
181 UserStorageHandler
::getInstance()->reset($participantIDs, 'unreadConversationCount');
189 public function update() {
190 if (!isset($this->parameters
['participants'])) $this->parameters
['participants'] = [];
191 if (!isset($this->parameters
['invisibleParticipants'])) $this->parameters
['invisibleParticipants'] = [];
193 // count participants
194 if (!empty($this->parameters
['participants'])) {
195 $this->parameters
['data']['participants'] = count($this->parameters
['participants']);
200 foreach ($this->getObjects() as $conversation) {
202 if (!empty($this->parameters
['participants']) ||
!empty($this->parameters
['invisibleParticipants'])) {
203 // get current participants
204 $participantIDs = $conversation->getParticipantIDs();
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')
211 $conversation->updateParticipantSummary();
213 // check if new participants have been added
214 $newParticipantIDs = array_diff(array_merge($this->parameters
['participants'], $this->parameters
['invisibleParticipants']), $participantIDs);
215 if (!empty($newParticipantIDs)) {
216 // update conversation count
217 UserStorageHandler
::getInstance()->reset($newParticipantIDs, 'unreadConversationCount');
218 UserStorageHandler
::getInstance()->reset($newParticipantIDs, 'conversationCount');
220 // fire notification event
221 UserNotificationHandler
::getInstance()->fireEvent(
223 'com.woltlab.wcf.conversation.notification',
224 new ConversationUserNotificationObject($conversation->getDecoratedObject()),
231 if (isset($this->parameters
['data']['isDraft'])) {
232 if ($conversation->isDraft
&& !$this->parameters
['data']['isDraft']) {
234 $conversation->updateParticipants([$conversation->userID
], [], 'all');
236 // update conversation count
237 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
238 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
247 public function markAsRead() {
248 if (empty($this->parameters
['visitTime'])) {
249 $this->parameters
['visitTime'] = TIME_NOW
;
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
;
257 if (empty($this->objects
)) {
258 $this->readObjects();
261 $conversationIDs = [];
262 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
263 SET lastVisitTime = ?
264 WHERE participantID = ?
265 AND conversationID = ?";
266 $statement = WCF
::getDB()->prepareStatement($sql);
267 WCF
::getDB()->beginTransaction();
268 foreach ($this->getObjects() as $conversation) {
269 $statement->execute([
270 $this->parameters
['visitTime'],
271 $this->parameters
['userID'],
272 $conversation->conversationID
274 $conversationIDs[] = $conversation->conversationID
;
276 WCF
::getDB()->commitTransaction();
279 UserStorageHandler
::getInstance()->reset([$this->parameters
['userID']], 'unreadConversationCount');
281 // mark notifications as confirmed
282 if (!empty($conversationIDs)) {
283 // conversation start notification
284 $conditionBuilder = new PreparedStatementConditionBuilder();
285 $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler
::getInstance()->getEvent('com.woltlab.wcf.conversation.notification', 'conversation')->eventID
]);
286 $conditionBuilder->add('notification.objectID = conversation.conversationID');
287 $conditionBuilder->add('notification.userID = ?', [$this->parameters
['userID']]);
288 $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
289 $conditionBuilder->add('conversation.time <= ?', [$this->parameters
['visitTime']]);
291 $sql = "SELECT conversation.conversationID
292 FROM wcf".WCF_N
."_conversation conversation,
293 wcf".WCF_N
."_user_notification notification
295 $statement = WCF
::getDB()->prepareStatement($sql);
296 $statement->execute($conditionBuilder->getParameters());
297 $notificationObjectIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
299 if (!empty($notificationObjectIDs)) {
300 UserNotificationHandler
::getInstance()->markAsConfirmed('conversation', 'com.woltlab.wcf.conversation.notification', [$this->parameters
['userID']], $notificationObjectIDs);
303 // conversation reply notification
304 $conditionBuilder = new PreparedStatementConditionBuilder();
305 $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler
::getInstance()->getEvent('com.woltlab.wcf.conversation.message.notification', 'conversationMessage')->eventID
]);
306 $conditionBuilder->add('notification.objectID = conversation_message.messageID');
307 $conditionBuilder->add('notification.userID = ?', [$this->parameters
['userID']]);
308 $conditionBuilder->add('conversation_message.conversationID IN (?)', [$conversationIDs]);
309 $conditionBuilder->add('conversation_message.time <= ?', [$this->parameters
['visitTime']]);
311 $sql = "SELECT conversation_message.messageID
312 FROM wcf".WCF_N
."_conversation_message conversation_message,
313 wcf".WCF_N
."_user_notification notification
315 $statement = WCF
::getDB()->prepareStatement($sql);
316 $statement->execute($conditionBuilder->getParameters());
317 $notificationObjectIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
319 if (!empty($notificationObjectIDs)) {
320 UserNotificationHandler
::getInstance()->markAsConfirmed('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [$this->parameters
['userID']], $notificationObjectIDs);
324 if (!empty($conversationIDs)) {
325 $this->unmarkItems($conversationIDs);
329 'totalCount' => ConversationHandler
::getInstance()->getUnreadConversationCount(null, true)
332 if (count($conversationIDs) == 1) {
333 $returnValues['markAsRead'] = reset($conversationIDs);
336 return $returnValues;
342 public function validateMarkAsRead() {
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
;
351 // userID should always be equal to the userID of the current user when called via AJAX
352 $this->parameters
['userID'] = WCF
::getUser()->userID
;
354 if (empty($this->objects
)) {
355 $this->readObjects();
358 // check participation
359 $conversationIDs = [];
360 foreach ($this->getObjects() as $conversation) {
361 $conversationIDs[] = $conversation->conversationID
;
364 if (empty($conversationIDs)) {
365 throw new UserInputException('objectIDs');
368 if (!Conversation
::isParticipant($conversationIDs)) {
369 throw new PermissionDeniedException();
374 * Marks all conversations as read.
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);
381 $statement->execute([
383 WCF
::getUser()->userID
387 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'unreadConversationCount');
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
]);
394 'markAllAsRead' => true
399 * Validates the markAllAsRead action.
401 public function validateMarkAllAsRead() {
406 * Validates user access for label management.
408 * @throws PermissionDeniedException
410 public function validateGetLabelManagement() {
411 if (!WCF
::getSession()->getPermission('user.conversation.canUseConversation')) {
412 throw new PermissionDeniedException();
417 * Returns the conversation label management.
421 public function getLabelManagement() {
422 WCF
::getTPL()->assign([
423 'cssClassNames' => ConversationLabel
::getLabelCssClassNames(),
424 'labelList' => ConversationLabel
::getLabelsByUser()
428 'actionName' => 'getLabelManagement',
429 'template' => WCF
::getTPL()->fetch('conversationLabelManagement'),
430 'maxLabels' => WCF
::getSession()->getPermission('user.conversation.maxLabels'),
431 'labelCount' => count(ConversationLabel
::getLabelsByUser())
436 * Validates the get message preview action.
438 * @throws PermissionDeniedException
440 public function validateGetMessagePreview() {
441 $this->conversation
= $this->getSingleObject();
442 if (!Conversation
::isParticipant([$this->conversation
->conversationID
])) {
443 throw new PermissionDeniedException();
448 * Returns a preview of a message in a specific conversation.
452 public function getMessagePreview() {
453 $messageList = new SimplifiedViewableConversationMessageList();
455 $messageList->getConditionBuilder()->add("conversation_message.messageID = ?", [$this->conversation
->firstMessageID
]);
456 $messageList->readObjects();
457 $messages = $messageList->getObjects();
459 WCF
::getTPL()->assign([
460 'message' => reset($messages)
463 'template' => WCF
::getTPL()->fetch('conversationMessagePreview')
468 * Validates parameters to close conversations.
470 * @throws PermissionDeniedException
471 * @throws UserInputException
473 public function validateClose() {
475 if (empty($this->objects
)) {
476 $this->readObjects();
478 if (empty($this->objects
)) {
479 throw new UserInputException('objectIDs');
483 // validate ownership
484 foreach ($this->getObjects() as $conversation) {
485 if ($conversation->isClosed ||
($conversation->userID
!= WCF
::getUser()->userID
)) {
486 throw new PermissionDeniedException();
492 * Closes conversations.
496 public function close() {
497 foreach ($this->getObjects() as $conversation) {
498 $conversation->update(['isClosed' => 1]);
499 $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 1);
501 ConversationModificationLogHandler
::getInstance()->close($conversation->getDecoratedObject());
504 $this->unmarkItems();
506 return $this->getConversationData();
510 * Validates parameters to open conversations.
512 * @throws PermissionDeniedException
513 * @throws UserInputException
515 public function validateOpen() {
517 if (empty($this->objects
)) {
518 $this->readObjects();
520 if (empty($this->objects
)) {
521 throw new UserInputException('objectIDs');
525 // validate ownership
526 foreach ($this->getObjects() as $conversation) {
527 if (!$conversation->isClosed ||
($conversation->userID
!= WCF
::getUser()->userID
)) {
528 throw new PermissionDeniedException();
534 * Opens conversations.
538 public function open() {
539 foreach ($this->getObjects() as $conversation) {
540 $conversation->update(['isClosed' => 0]);
541 $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 0);
543 ConversationModificationLogHandler
::getInstance()->open($conversation->getDecoratedObject());
546 $this->unmarkItems();
548 return $this->getConversationData();
552 * Validates conversations for leave form.
554 * @throws PermissionDeniedException
555 * @throws UserInputException
557 public function validateGetLeaveForm() {
558 if (empty($this->objectIDs
)) {
559 throw new UserInputException('objectIDs');
562 // validate participation
563 if (!Conversation
::isParticipant($this->objectIDs
)) {
564 throw new PermissionDeniedException();
569 * Returns dialog form to leave conversations.
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);
580 $statement->execute([
581 current($this->objectIDs
),
582 WCF
::getUser()->userID
584 $row = $statement->fetchArray();
586 WCF
::getTPL()->assign('hideConversation', ($row !== false ?
$row['hideConversation'] : 0));
589 'actionName' => 'getLeaveForm',
590 'template' => WCF
::getTPL()->fetch('conversationLeave')
595 * Validates parameters to hide conversations.
597 * @throws PermissionDeniedException
598 * @throws UserInputException
600 public function validateHideConversation() {
601 $this->parameters
['hideConversation'] = isset($this->parameters
['hideConversation']) ?
intval($this->parameters
['hideConversation']) : null;
602 if ($this->parameters
['hideConversation'] === null ||
!in_array($this->parameters
['hideConversation'], [Conversation
::STATE_DEFAULT
, Conversation
::STATE_HIDDEN
, Conversation
::STATE_LEFT
])) {
603 throw new UserInputException('hideConversation');
606 if (empty($this->objectIDs
)) {
607 throw new UserInputException('objectIDs');
610 // validate participation
611 if (!Conversation
::isParticipant($this->objectIDs
)) {
612 throw new PermissionDeniedException();
617 * Hides or restores conversations.
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);
628 WCF
::getDB()->beginTransaction();
629 foreach ($this->objectIDs
as $conversationID) {
630 $statement->execute([
631 $this->parameters
['hideConversation'],
633 WCF
::getUser()->userID
636 WCF
::getDB()->commitTransaction();
638 // reset user's conversation counters if user leaves conversation
640 if ($this->parameters
['hideConversation'] == Conversation
::STATE_LEFT
) {
641 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'conversationCount');
642 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'unreadConversationCount');
645 // add modification log entry
646 if ($this->parameters
['hideConversation'] == Conversation
::STATE_LEFT
) {
647 if (empty($this->objects
)) $this->readObjects();
649 foreach ($this->getObjects() as $conversation) {
650 ConversationModificationLogHandler
::getInstance()->leave($conversation->getDecoratedObject());
655 $this->unmarkItems();
657 if ($this->parameters
['hideConversation'] == Conversation
::STATE_LEFT
) {
658 // update participants count and participant summary
659 ConversationEditor
::updateParticipantCounts($this->objectIDs
);
660 ConversationEditor
::updateParticipantSummaries($this->objectIDs
);
662 // delete conversation if all users have left it
663 $conditionBuilder = new PreparedStatementConditionBuilder();
664 $conditionBuilder->add('conversation.conversationID IN (?)', [$this->objectIDs
]);
665 $conditionBuilder->add('conversation_to_user.conversationID IS NULL');
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
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)
673 $statement = WCF
::getDB()->prepareStatement($sql);
674 $statement->execute($conditionBuilder->getParameters());
675 $conversationIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
677 if (!empty($conversationIDs)) {
678 $action = new ConversationAction($conversationIDs, 'delete');
679 $action->executeAction();
684 'actionName' => 'hideConversation',
685 'redirectURL' => LinkHandler
::getInstance()->getLink('ConversationList')
690 * Validates parameters to return the mixed conversation list.
692 public function validateGetMixedConversationList() {
697 * Returns a mixed conversation list with up to 10 unread conversations.
701 public function getMixedConversationList() {
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';
705 $unreadConversationList = new UserConversationList(WCF
::getUser()->userID
);
706 $unreadConversationList->sqlSelects
.= $sqlSelect;
707 $unreadConversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime < lastPostTime');
708 $unreadConversationList->sqlLimit
= 10;
709 $unreadConversationList->sqlOrderBy
= 'lastPostTime DESC';
710 $unreadConversationList->readObjects();
714 foreach ($unreadConversationList as $conversation) {
715 $conversations[] = $conversation;
720 $conversationList = new UserConversationList(WCF
::getUser()->userID
);
721 $conversationList->sqlSelects
.= $sqlSelect;
722 $conversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime >= lastPostTime');
723 $conversationList->sqlLimit
= (10 - $count);
724 $conversationList->sqlOrderBy
= 'lastPostTime DESC';
725 $conversationList->readObjects();
727 foreach ($conversationList as $conversation) {
728 $conversations[] = $conversation;
732 WCF
::getTPL()->assign([
733 'conversations' => $conversations
736 $totalCount = ConversationHandler
::getInstance()->getUnreadConversationCount();
737 if ($count < 10 && $count < $totalCount) {
738 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'unreadConversationCount');
742 'template' => WCF
::getTPL()->fetch('conversationListUserPanel'),
743 'totalCount' => $totalCount
748 * Validates the 'unmarkAll' action.
750 public function validateUnmarkAll() {
755 * Unmarks all conversations.
757 public function unmarkAll() {
758 ClipboardHandler
::getInstance()->removeItems(ClipboardHandler
::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
762 * Validates parameters to display the 'add participants' form.
764 * @throws PermissionDeniedException
766 public function validateGetAddParticipantsForm() {
767 $this->conversation
= $this->getSingleObject();
768 if (!Conversation
::isParticipant([$this->conversation
->conversationID
]) ||
!$this->conversation
->canAddParticipants()) {
769 throw new PermissionDeniedException();
774 * Shows the 'add participants' form.
778 public function getAddParticipantsForm() {
780 'excludedSearchValues' => $this->conversation
->getParticipantNames(),
781 'maxItems' => WCF
::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation
->participants
,
782 'template' => WCF
::getTPL()->fetch('conversationAddParticipants', 'wcf', ['conversation' => $this->conversation
])
787 * Validates parameters to add new participants.
789 public function validateAddParticipants() {
790 $this->validateGetAddParticipantsForm();
792 // validate participants
793 $this->readStringArray('participants');
795 if (!$this->conversation
->getDecoratedObject()->isDraft
) {
796 $this->readString('visibility');
797 if (!in_array($this->parameters
['visibility'], ['all', 'new'])) {
798 throw new UserInputException('visibility');
801 if ($this->parameters
['visibility'] === 'all' && !$this->conversation
->canAddParticipantsUnrestricted()) {
802 throw new UserInputException('visibility');
808 * Adds new participants.
812 public function addParticipants() {
814 $participantIDs = Conversation
::validateParticipants($this->parameters
['participants'], 'participants', $this->conversation
->getParticipantIDs(true));
816 catch (UserInputException
$e) {
818 foreach ($e->getType() as $type) {
819 if (!empty($errorMessage)) $errorMessage .= ' ';
820 $errorMessage .= WCF
::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.'.$type['type'], ['errorData' => ['username' => $type['username']]]);
824 'actionName' => 'addParticipants',
825 'errorMessage' => $errorMessage
830 $newCount = $this->conversation
->participants +
count($participantIDs);
831 if ($newCount > WCF
::getSession()->getPermission('user.conversation.maxParticipants')) {
833 'actionName' => 'addParticipants',
834 'errorMessage' => WCF
::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.tooManyParticipants')
839 $successMessage = '';
840 if (!empty($participantIDs)) {
841 // check for already added participants
842 if ($this->conversation
->isDraft
) {
843 $draftData = unserialize($this->conversation
->draftData
);
844 $draftData['participants'] = array_merge($draftData['participants'], $participantIDs);
845 $data = ['data' => ['draftData' => serialize($draftData)]];
849 'participants' => $participantIDs,
850 'visibility' => (isset($this->parameters
['visibility'])) ?
$this->parameters
['visibility'] : 'all'
854 $conversationAction = new ConversationAction([$this->conversation
], 'update', $data);
855 $conversationAction->executeAction();
857 $count = count($participantIDs);
858 $successMessage = WCF
::getLanguage()->getDynamicVariable('wcf.conversation.edit.addParticipants.success', ['count' => $count]);
860 ConversationModificationLogHandler
::getInstance()->addParticipants($this->conversation
->getDecoratedObject(), $participantIDs);
862 if (!$this->conversation
->isDraft
) {
863 // update participant summary
864 $this->conversation
->updateParticipantSummary();
870 'successMessage' => $successMessage
875 * Validates parameters to remove a participant from a conversation.
877 * @throws PermissionDeniedException
878 * @throws UserInputException
880 public function validateRemoveParticipant() {
881 $this->readInteger('userID');
883 // validate conversation
884 $this->conversation
= $this->getSingleObject();
885 if (!$this->conversation
->conversationID
) {
886 throw new UserInputException('objectIDs');
890 if ($this->conversation
->userID
!= WCF
::getUser()->userID
) {
891 throw new PermissionDeniedException();
894 // validate participants
895 if ($this->parameters
['userID'] == WCF
::getUser()->userID ||
!Conversation
::isParticipant([$this->conversation
->conversationID
]) ||
!Conversation
::isParticipant([$this->conversation
->conversationID
], $this->parameters
['userID'])) {
896 throw new PermissionDeniedException();
902 * Removes a participant from a conversation.
904 public function removeParticipant() {
905 $this->conversation
->removeParticipant($this->parameters
['userID']);
906 $this->conversation
->updateParticipantSummary();
908 ConversationModificationLogHandler
::getInstance()->removeParticipant($this->conversation
->getDecoratedObject(), $this->parameters
['userID']);
911 UserStorageHandler
::getInstance()->reset([$this->parameters
['userID']], 'unreadConversationCount');
914 'userID' => $this->parameters
['userID']
919 * Rebuilds the conversation data of the relevant conversations.
921 public function rebuild() {
922 if (empty($this->objects
)) {
923 $this->readObjects();
926 // collect number of messages for each conversation
927 $conditionBuilder = new PreparedStatementConditionBuilder();
928 $conditionBuilder->add('conversation_message.conversationID IN (?)', [$this->objectIDs
]);
929 $sql = "SELECT conversationID, COUNT(messageID) AS messages, SUM(attachments) AS attachments
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());
937 while ($row = $statement->fetchArray()) {
938 if (!$row['messages']) {
941 $objectIDs[] = $row['conversationID'];
943 $conversationEditor = new ConversationEditor(new Conversation(null, [
944 'conversationID' => $row['conversationID']
946 $conversationEditor->update([
947 'attachments' => $row['attachments'],
948 'replies' => $row['messages'] - 1
950 $conversationEditor->updateFirstMessage();
951 $conversationEditor->updateLastMessage();
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();
963 * Validates the parameters to edit a conversation's subject.
965 * @throws PermissionDeniedException
967 public function validateEditSubject() {
968 $this->readString('subject');
970 $this->conversation
= $this->getSingleObject();
971 if ($this->conversation
->userID
!= WCF
::getUser()->userID
) {
972 throw new PermissionDeniedException();
977 * Edits a conversation's subject.
981 public function editSubject() {
982 $subject = mb_substr($this->parameters
['subject'], 0, 255);
984 $this->conversation
->update([
985 'subject' => $subject
988 $message = $this->conversation
->getFirstMessage();
990 SearchIndexManager
::getInstance()->set(
991 'com.woltlab.wcf.conversation.message',
1001 'subject' => $subject
1006 * Adds conversation modification data.
1008 * @param Conversation $conversation
1009 * @param string $key
1010 * @param mixed $value
1012 protected function addConversationData(Conversation
$conversation, $key, $value) {
1013 if (!isset($this->conversationData
[$conversation->conversationID
])) {
1014 $this->conversationData
[$conversation->conversationID
] = [];
1017 $this->conversationData
[$conversation->conversationID
][$key] = $value;
1021 * Returns conversation data.
1025 protected function getConversationData() {
1027 'conversationData' => $this->conversationData
1032 * Unmarks conversations.
1034 * @param integer[] $conversationIDs
1036 protected function unmarkItems(array $conversationIDs = []) {
1037 if (empty($conversationIDs)) {
1038 $conversationIDs = $this->objectIDs
;
1041 ClipboardHandler
::getInstance()->unmark($conversationIDs, ClipboardHandler
::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));