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\IPopoverAction
;
10 use wcf\data\IVisitableObjectAction
;
11 use wcf\system\clipboard\ClipboardHandler
;
12 use wcf\system\conversation\ConversationHandler
;
13 use wcf\system\database\util\PreparedStatementConditionBuilder
;
14 use wcf\system\event\EventHandler
;
15 use wcf\system\exception\PermissionDeniedException
;
16 use wcf\system\exception\UserInputException
;
17 use wcf\system\log\modification\ConversationModificationLogHandler
;
18 use wcf\system\request\LinkHandler
;
19 use wcf\system\user\notification\
object\ConversationUserNotificationObject
;
20 use wcf\system\user\notification\UserNotificationHandler
;
21 use wcf\system\user\storage\UserStorageHandler
;
25 * Executes conversation-related actions.
28 * @copyright 2001-2019 WoltLab GmbH
29 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
30 * @package WoltLabSuite\Core\Data\Conversation
32 * @method ConversationEditor[] getObjects()
33 * @method ConversationEditor getSingleObject()
35 class ConversationAction
extends AbstractDatabaseObjectAction
implements IClipboardAction
, IPopoverAction
, IVisitableObjectAction
{
39 protected $className = ConversationEditor
::class;
43 * @var ConversationEditor
48 * list of conversation data modifications
51 protected $conversationData = [];
53 /** @noinspection PhpMissingParentCallCommonInspection */
56 * @return Conversation
58 public function create() {
59 // create conversation
60 $data = $this->parameters
['data'];
61 $data['lastPosterID'] = $data['userID'];
62 $data['lastPoster'] = $data['username'];
63 $data['lastPostTime'] = $data['time'];
65 if (!empty($this->parameters
['participants'])) {
66 $data['participants'] = count($this->parameters
['participants']);
69 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
70 $data['attachments'] = count($this->parameters
['attachmentHandler']);
72 $conversation = call_user_func([$this->className
, 'create'], $data);
73 $conversationEditor = new ConversationEditor($conversation);
75 if (!$conversation->isDraft
) {
77 $conversationEditor->updateParticipants(
78 (!empty($this->parameters
['participants']) ?
$this->parameters
['participants'] : []),
79 (!empty($this->parameters
['invisibleParticipants']) ?
$this->parameters
['invisibleParticipants'] : []),
84 if ($data['userID'] !== null) {
85 $conversationEditor->updateParticipants([$data['userID']], [], 'all');
88 // update conversation count
89 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
91 // mark conversation as read for the author
92 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
94 WHERE participantID = ?
95 AND conversationID = ?";
96 $statement = WCF
::getDB()->prepareStatement($sql);
97 $statement->execute([$data['time'], $data['userID'], $conversation->conversationID
]);
100 // update conversation count
101 UserStorageHandler
::getInstance()->reset([$data['userID']], 'conversationCount');
104 // update participant summary
105 $conversationEditor->updateParticipantSummary();
108 $messageData = $this->parameters
['messageData'];
109 $messageData['conversationID'] = $conversation->conversationID
;
110 $messageData['time'] = $this->parameters
['data']['time'];
111 $messageData['userID'] = $this->parameters
['data']['userID'];
112 $messageData['username'] = $this->parameters
['data']['username'];
114 $messageAction = new ConversationMessageAction([], 'create', [
115 'data' => $messageData,
116 'conversation' => $conversation,
117 'isFirstPost' => true,
118 'attachmentHandler' => isset($this->parameters
['attachmentHandler']) ?
$this->parameters
['attachmentHandler'] : null,
119 'htmlInputProcessor' => isset($this->parameters
['htmlInputProcessor']) ?
$this->parameters
['htmlInputProcessor'] : null
121 $resultValues = $messageAction->executeAction();
123 // update first message id
124 $conversationEditor->update([
125 'firstMessageID' => $resultValues['returnValues']->messageID
128 $conversation->setFirstMessage($resultValues['returnValues']);
129 if (!$conversation->isDraft
) {
130 // fire notification event
131 $notificationRecipients = array_merge((!empty($this->parameters
['participants']) ?
$this->parameters
['participants'] : []), (!empty($this->parameters
['invisibleParticipants']) ?
$this->parameters
['invisibleParticipants'] : []));
132 UserNotificationHandler
::getInstance()->fireEvent(
134 'com.woltlab.wcf.conversation.notification',
135 new ConversationUserNotificationObject($conversation),
136 $notificationRecipients
140 return $conversation;
146 public function delete() {
148 $messageList = new ConversationMessageList();
149 $messageList->getConditionBuilder()->add('conversation_message.conversationID IN (?)', [$this->objectIDs
]);
150 $messageList->readObjectIDs();
151 $action = new ConversationMessageAction($messageList->getObjectIDs(), 'delete');
152 $action->executeAction();
154 // get the list of participants in order to reset the 'unread conversation'-counter
155 $participantIDs = [];
156 if (!empty($this->objectIDs
)) {
157 $conditions = new PreparedStatementConditionBuilder();
158 $conditions->add("conversationID IN (?)", [$this->objectIDs
]);
159 $sql = "SELECT DISTINCT participantID
160 FROM wcf" . WCF_N
. "_conversation_to_user
162 $statement = WCF
::getDB()->prepareStatement($sql);
163 $statement->execute($conditions->getParameters());
165 while ($participantID = $statement->fetchColumn()) {
166 $participantIDs[] = $participantID;
170 // delete conversations
173 if (!empty($this->objectIDs
)) {
174 // delete notifications
175 UserNotificationHandler
::getInstance()->removeNotifications('com.woltlab.wcf.conversation.notification', $this->objectIDs
);
177 // remove modification logs
178 ConversationModificationLogHandler
::getInstance()->deleteLogs($this->objectIDs
);
180 // reset the number of unread conversations
181 if (!empty($participantIDs)) {
182 UserStorageHandler
::getInstance()->reset($participantIDs, 'unreadConversationCount');
190 public function update() {
191 if (!isset($this->parameters
['participants'])) $this->parameters
['participants'] = [];
192 if (!isset($this->parameters
['invisibleParticipants'])) $this->parameters
['invisibleParticipants'] = [];
194 // count participants
195 if (!empty($this->parameters
['participants'])) {
196 $this->parameters
['data']['participants'] = count($this->parameters
['participants']);
201 foreach ($this->getObjects() as $conversation) {
203 if (!empty($this->parameters
['participants']) ||
!empty($this->parameters
['invisibleParticipants'])) {
204 // get current participants
205 $participantIDs = $conversation->getParticipantIDs();
207 $conversation->updateParticipants(
208 (!empty($this->parameters
['participants']) ?
$this->parameters
['participants'] : []),
209 (!empty($this->parameters
['invisibleParticipants']) ?
$this->parameters
['invisibleParticipants'] : []),
210 (!empty($this->parameters
['visibility']) ?
$this->parameters
['visibility'] : 'all')
212 $conversation->updateParticipantSummary();
214 // check if new participants have been added
215 $newParticipantIDs = array_diff(array_merge($this->parameters
['participants'], $this->parameters
['invisibleParticipants']), $participantIDs);
216 if (!empty($newParticipantIDs)) {
217 // update conversation count
218 UserStorageHandler
::getInstance()->reset($newParticipantIDs, 'unreadConversationCount');
219 UserStorageHandler
::getInstance()->reset($newParticipantIDs, 'conversationCount');
221 // fire notification event
222 UserNotificationHandler
::getInstance()->fireEvent(
224 'com.woltlab.wcf.conversation.notification',
225 new ConversationUserNotificationObject($conversation->getDecoratedObject()),
232 if (isset($this->parameters
['data']['isDraft'])) {
233 if ($conversation->isDraft
&& !$this->parameters
['data']['isDraft']) {
235 $conversation->updateParticipants([$conversation->userID
], [], 'all');
237 // update conversation count
238 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
239 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'conversationCount');
248 public function markAsRead() {
249 if (empty($this->parameters
['visitTime'])) {
250 $this->parameters
['visitTime'] = TIME_NOW
;
253 // in case this is a call via PHP and the userID parameter is missing, set it to the userID of the current user
254 if (!isset($this->parameters
['userID'])) {
255 $this->parameters
['userID'] = WCF
::getUser()->userID
;
258 if (empty($this->objects
)) {
259 $this->readObjects();
262 $conversationIDs = [];
263 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
264 SET lastVisitTime = ?
265 WHERE participantID = ?
266 AND conversationID = ?";
267 $statement = WCF
::getDB()->prepareStatement($sql);
268 WCF
::getDB()->beginTransaction();
269 foreach ($this->getObjects() as $conversation) {
270 $statement->execute([
271 $this->parameters
['visitTime'],
272 $this->parameters
['userID'],
273 $conversation->conversationID
275 $conversationIDs[] = $conversation->conversationID
;
277 WCF
::getDB()->commitTransaction();
280 UserStorageHandler
::getInstance()->reset([$this->parameters
['userID']], 'unreadConversationCount');
282 // mark notifications as confirmed
283 if (!empty($conversationIDs)) {
284 // conversation start notification
285 $conditionBuilder = new PreparedStatementConditionBuilder();
286 $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler
::getInstance()->getEvent('com.woltlab.wcf.conversation.notification', 'conversation')->eventID
]);
287 $conditionBuilder->add('notification.objectID = conversation.conversationID');
288 $conditionBuilder->add('notification.userID = ?', [$this->parameters
['userID']]);
289 $conditionBuilder->add('conversation.conversationID IN (?)', [$conversationIDs]);
290 $conditionBuilder->add('conversation.time <= ?', [$this->parameters
['visitTime']]);
292 $sql = "SELECT conversation.conversationID
293 FROM wcf".WCF_N
."_conversation conversation,
294 wcf".WCF_N
."_user_notification notification
296 $statement = WCF
::getDB()->prepareStatement($sql);
297 $statement->execute($conditionBuilder->getParameters());
298 $notificationObjectIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
300 if (!empty($notificationObjectIDs)) {
301 UserNotificationHandler
::getInstance()->markAsConfirmed('conversation', 'com.woltlab.wcf.conversation.notification', [$this->parameters
['userID']], $notificationObjectIDs);
304 // conversation reply notification
305 $conditionBuilder = new PreparedStatementConditionBuilder();
306 $conditionBuilder->add('notification.eventID = ?', [UserNotificationHandler
::getInstance()->getEvent('com.woltlab.wcf.conversation.message.notification', 'conversationMessage')->eventID
]);
307 $conditionBuilder->add('notification.objectID = conversation_message.messageID');
308 $conditionBuilder->add('notification.userID = ?', [$this->parameters
['userID']]);
309 $conditionBuilder->add('conversation_message.conversationID IN (?)', [$conversationIDs]);
310 $conditionBuilder->add('conversation_message.time <= ?', [$this->parameters
['visitTime']]);
312 $sql = "SELECT conversation_message.messageID
313 FROM wcf".WCF_N
."_conversation_message conversation_message,
314 wcf".WCF_N
."_user_notification notification
316 $statement = WCF
::getDB()->prepareStatement($sql);
317 $statement->execute($conditionBuilder->getParameters());
318 $notificationObjectIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
320 if (!empty($notificationObjectIDs)) {
321 UserNotificationHandler
::getInstance()->markAsConfirmed('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [$this->parameters
['userID']], $notificationObjectIDs);
325 if (!empty($conversationIDs)) {
326 $this->unmarkItems($conversationIDs);
330 'totalCount' => ConversationHandler
::getInstance()->getUnreadConversationCount($this->parameters
['userID'], true)
333 if (count($conversationIDs) == 1) {
334 $returnValues['markAsRead'] = reset($conversationIDs);
337 return $returnValues;
343 public function validateMarkAsRead() {
344 // visitTime might not be in the future
345 if (isset($this->parameters
['visitTime'])) {
346 $this->parameters
['visitTime'] = intval($this->parameters
['visitTime']);
347 if ($this->parameters
['visitTime'] > TIME_NOW
) {
348 $this->parameters
['visitTime'] = TIME_NOW
;
352 // userID should always be equal to the userID of the current user when called via AJAX
353 $this->parameters
['userID'] = WCF
::getUser()->userID
;
355 if (empty($this->objects
)) {
356 $this->readObjects();
359 // check participation
360 $conversationIDs = [];
361 foreach ($this->getObjects() as $conversation) {
362 $conversationIDs[] = $conversation->conversationID
;
365 if (empty($conversationIDs)) {
366 throw new UserInputException('objectIDs');
369 if (!Conversation
::isParticipant($conversationIDs)) {
370 throw new PermissionDeniedException();
375 * Marks all conversations as read.
377 public function markAllAsRead() {
378 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
379 SET lastVisitTime = ?
380 WHERE participantID = ?";
381 $statement = WCF
::getDB()->prepareStatement($sql);
382 $statement->execute([
384 WCF
::getUser()->userID
388 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'unreadConversationCount');
390 // confirm obsolete notifications
391 UserNotificationHandler
::getInstance()->markAsConfirmed('conversation', 'com.woltlab.wcf.conversation.notification', [WCF
::getUser()->userID
]);
392 UserNotificationHandler
::getInstance()->markAsConfirmed('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [WCF
::getUser()->userID
]);
395 'markAllAsRead' => true
400 * Validates the markAllAsRead action.
402 public function validateMarkAllAsRead() {
407 * Validates user access for label management.
409 * @throws PermissionDeniedException
411 public function validateGetLabelManagement() {
412 if (!WCF
::getSession()->getPermission('user.conversation.canUseConversation')) {
413 throw new PermissionDeniedException();
418 * Returns the conversation label management.
422 public function getLabelManagement() {
423 WCF
::getTPL()->assign([
424 'cssClassNames' => ConversationLabel
::getLabelCssClassNames(),
425 'labelList' => ConversationLabel
::getLabelsByUser()
429 'actionName' => 'getLabelManagement',
430 'template' => WCF
::getTPL()->fetch('conversationLabelManagement'),
431 'maxLabels' => WCF
::getSession()->getPermission('user.conversation.maxLabels'),
432 'labelCount' => count(ConversationLabel
::getLabelsByUser())
439 public function validateGetPopover() {
440 $this->conversation
= $this->getSingleObject();
441 if (!Conversation
::isParticipant([$this->conversation
->conversationID
])) {
442 throw new PermissionDeniedException();
449 public function getPopover() {
450 $messageList = new SimplifiedViewableConversationMessageList();
451 $messageList->getConditionBuilder()->add("conversation_message.messageID = ?", [$this->conversation
->firstMessageID
]);
452 $messageList->readObjects();
455 'template' => WCF
::getTPL()->fetch('conversationMessagePreview', 'wcf', [
456 'message' => $messageList->getSingleObject(),
462 * Validates the get message preview action.
464 * @throws PermissionDeniedException
465 * @deprecated 5.3 Use `validateGetPopover()` instead.
467 public function validateGetMessagePreview() {
468 $this->validateGetPopover();
472 * Returns a preview of a message in a specific conversation.
475 * @deprecated 5.3 Use `getPopover()` instead.
477 public function getMessagePreview() {
478 return $this->getPopover();
482 * Validates parameters to close conversations.
484 * @throws PermissionDeniedException
485 * @throws UserInputException
487 public function validateClose() {
489 if (empty($this->objects
)) {
490 $this->readObjects();
492 if (empty($this->objects
)) {
493 throw new UserInputException('objectIDs');
497 // validate ownership
498 foreach ($this->getObjects() as $conversation) {
499 if ($conversation->isClosed ||
($conversation->userID
!= WCF
::getUser()->userID
)) {
500 throw new PermissionDeniedException();
506 * Closes conversations.
510 public function close() {
511 foreach ($this->getObjects() as $conversation) {
512 $conversation->update(['isClosed' => 1]);
513 $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 1);
515 ConversationModificationLogHandler
::getInstance()->close($conversation->getDecoratedObject());
518 $this->unmarkItems();
520 return $this->getConversationData();
524 * Validates parameters to open conversations.
526 * @throws PermissionDeniedException
527 * @throws UserInputException
529 public function validateOpen() {
531 if (empty($this->objects
)) {
532 $this->readObjects();
534 if (empty($this->objects
)) {
535 throw new UserInputException('objectIDs');
539 // validate ownership
540 foreach ($this->getObjects() as $conversation) {
541 if (!$conversation->isClosed ||
($conversation->userID
!= WCF
::getUser()->userID
)) {
542 throw new PermissionDeniedException();
548 * Opens conversations.
552 public function open() {
553 foreach ($this->getObjects() as $conversation) {
554 $conversation->update(['isClosed' => 0]);
555 $this->addConversationData($conversation->getDecoratedObject(), 'isClosed', 0);
557 ConversationModificationLogHandler
::getInstance()->open($conversation->getDecoratedObject());
560 $this->unmarkItems();
562 return $this->getConversationData();
566 * Validates conversations for leave form.
568 * @throws PermissionDeniedException
569 * @throws UserInputException
571 public function validateGetLeaveForm() {
572 if (empty($this->objectIDs
)) {
573 throw new UserInputException('objectIDs');
576 // validate participation
577 if (!Conversation
::isParticipant($this->objectIDs
)) {
578 throw new PermissionDeniedException();
583 * Returns dialog form to leave conversations.
587 public function getLeaveForm() {
588 // get hidden state from first conversation (all others have the same state)
589 $sql = "SELECT hideConversation
590 FROM wcf".WCF_N
."_conversation_to_user
591 WHERE conversationID = ?
592 AND participantID = ?";
593 $statement = WCF
::getDB()->prepareStatement($sql);
594 $statement->execute([
595 current($this->objectIDs
),
596 WCF
::getUser()->userID
598 $row = $statement->fetchArray();
600 WCF
::getTPL()->assign('hideConversation', ($row !== false ?
$row['hideConversation'] : 0));
603 'actionName' => 'getLeaveForm',
604 'template' => WCF
::getTPL()->fetch('conversationLeave')
609 * Validates parameters to hide conversations.
611 * @throws PermissionDeniedException
612 * @throws UserInputException
614 public function validateHideConversation() {
615 $this->parameters
['hideConversation'] = isset($this->parameters
['hideConversation']) ?
intval($this->parameters
['hideConversation']) : null;
616 if ($this->parameters
['hideConversation'] === null ||
!in_array($this->parameters
['hideConversation'], [Conversation
::STATE_DEFAULT
, Conversation
::STATE_HIDDEN
, Conversation
::STATE_LEFT
])) {
617 throw new UserInputException('hideConversation');
620 if (empty($this->objectIDs
)) {
621 throw new UserInputException('objectIDs');
624 // validate participation
625 if (!Conversation
::isParticipant($this->objectIDs
)) {
626 throw new PermissionDeniedException();
631 * Hides or restores conversations.
635 public function hideConversation() {
636 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
637 SET hideConversation = ?
638 WHERE conversationID = ?
639 AND participantID = ?";
640 $statement = WCF
::getDB()->prepareStatement($sql);
642 WCF
::getDB()->beginTransaction();
643 foreach ($this->objectIDs
as $conversationID) {
644 $statement->execute([
645 $this->parameters
['hideConversation'],
647 WCF
::getUser()->userID
650 WCF
::getDB()->commitTransaction();
652 // reset user's conversation counters if user leaves conversation
654 if ($this->parameters
['hideConversation'] == Conversation
::STATE_LEFT
) {
655 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'conversationCount');
656 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'unreadConversationCount');
659 // add modification log entry
660 if ($this->parameters
['hideConversation'] == Conversation
::STATE_LEFT
) {
661 if (empty($this->objects
)) $this->readObjects();
663 foreach ($this->getObjects() as $conversation) {
664 ConversationModificationLogHandler
::getInstance()->leave($conversation->getDecoratedObject());
669 $this->unmarkItems();
671 if ($this->parameters
['hideConversation'] == Conversation
::STATE_LEFT
) {
672 // update participants count and participant summary
673 ConversationEditor
::updateParticipantCounts($this->objectIDs
);
674 ConversationEditor
::updateParticipantSummaries($this->objectIDs
);
676 // delete conversation if all users have left it
677 $conditionBuilder = new PreparedStatementConditionBuilder();
678 $conditionBuilder->add('conversation.conversationID IN (?)', [$this->objectIDs
]);
679 $conditionBuilder->add('conversation_to_user.conversationID IS NULL');
680 $sql = "SELECT DISTINCT conversation.conversationID
681 FROM wcf".WCF_N
."_conversation conversation
682 LEFT JOIN wcf".WCF_N
."_conversation_to_user conversation_to_user
683 ON ( conversation_to_user.conversationID = conversation.conversationID
684 AND conversation_to_user.hideConversation <> ".Conversation
::STATE_LEFT
."
685 AND conversation_to_user.participantID IS NOT NULL)
687 $statement = WCF
::getDB()->prepareStatement($sql);
688 $statement->execute($conditionBuilder->getParameters());
689 $conversationIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
691 if (!empty($conversationIDs)) {
692 $action = new ConversationAction($conversationIDs, 'delete');
693 $action->executeAction();
698 'actionName' => 'hideConversation',
699 'redirectURL' => LinkHandler
::getInstance()->getLink('ConversationList')
704 * Validates parameters to return the mixed conversation list.
706 public function validateGetMixedConversationList() {
711 * Returns a mixed conversation list with up to 10 unread conversations.
715 public function getMixedConversationList() {
716 $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
717 , (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';
719 $unreadConversationList = new UserConversationList(WCF
::getUser()->userID
);
720 $unreadConversationList->sqlSelects
.= $sqlSelect;
721 $unreadConversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime < lastPostTime');
722 $unreadConversationList->sqlLimit
= 10;
723 $unreadConversationList->sqlOrderBy
= 'lastPostTime DESC';
724 $unreadConversationList->readObjects();
728 foreach ($unreadConversationList as $conversation) {
729 $conversations[] = $conversation;
734 $conversationList = new UserConversationList(WCF
::getUser()->userID
);
735 $conversationList->sqlSelects
.= $sqlSelect;
736 $conversationList->getConditionBuilder()->add('conversation_to_user.lastVisitTime >= lastPostTime');
737 $conversationList->sqlLimit
= (10 - $count);
738 $conversationList->sqlOrderBy
= 'lastPostTime DESC';
739 $conversationList->readObjects();
741 foreach ($conversationList as $conversation) {
742 $conversations[] = $conversation;
746 WCF
::getTPL()->assign([
747 'conversations' => $conversations
750 $totalCount = ConversationHandler
::getInstance()->getUnreadConversationCount();
751 if ($count < 10 && $count < $totalCount) {
752 UserStorageHandler
::getInstance()->reset([WCF
::getUser()->userID
], 'unreadConversationCount');
756 'template' => WCF
::getTPL()->fetch('conversationListUserPanel'),
757 'totalCount' => $totalCount
762 * Validates the 'unmarkAll' action.
764 public function validateUnmarkAll() {
769 * Unmarks all conversations.
771 public function unmarkAll() {
772 ClipboardHandler
::getInstance()->removeItems(ClipboardHandler
::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));
776 * Validates parameters to display the 'add participants' form.
778 * @throws PermissionDeniedException
780 public function validateGetAddParticipantsForm() {
781 $this->conversation
= $this->getSingleObject();
782 if (!Conversation
::isParticipant([$this->conversation
->conversationID
]) ||
!$this->conversation
->canAddParticipants()) {
783 throw new PermissionDeniedException();
788 * Shows the 'add participants' form.
792 public function getAddParticipantsForm() {
794 'excludedSearchValues' => $this->conversation
->getParticipantNames(false, true),
795 'maxItems' => WCF
::getSession()->getPermission('user.conversation.maxParticipants') - $this->conversation
->participants
,
796 'canAddGroupParticipants' => WCF
::getSession()->getPermission('user.conversation.canAddGroupParticipants'),
797 'template' => WCF
::getTPL()->fetch('conversationAddParticipants', 'wcf', ['conversation' => $this->conversation
])
802 * Validates parameters to add new participants.
804 public function validateAddParticipants() {
805 $this->validateGetAddParticipantsForm();
807 // validate participants
808 $this->readStringArray('participants', true);
809 $this->readIntegerArray('participantsGroupIDs', true);
811 if (!$this->conversation
->getDecoratedObject()->isDraft
) {
812 $this->readString('visibility');
813 if (!in_array($this->parameters
['visibility'], ['all', 'new'])) {
814 throw new UserInputException('visibility');
817 if ($this->parameters
['visibility'] === 'all' && !$this->conversation
->canAddParticipantsUnrestricted()) {
818 throw new UserInputException('visibility');
824 * Adds new participants.
828 public function addParticipants() {
830 $participantIDs = Conversation
::validateParticipants($this->parameters
['participants'], 'participants', $this->conversation
->getParticipantIDs(true));
831 if (!empty($this->parameters
['participantsGroupIDs']) && WCF
::getSession()->getPermission('user.conversation.canAddGroupParticipants')) {
832 $participantIDs = array_merge($participantIDs, Conversation
::validateGroupParticipants($this->parameters
['participantsGroupIDs'], 'participants', $this->conversation
->getParticipantIDs(true)));
833 $participantIDs = array_unique($participantIDs);
837 'participantIDs' => $participantIDs,
839 EventHandler
::getInstance()->fireAction($this, 'addParticipants_validateParticipants', $parameters);
840 $participantIDs = $parameters['participantIDs'];
842 catch (UserInputException
$e) {
844 foreach ($e->getType() as $type) {
845 if (!empty($errorMessage)) $errorMessage .= ' ';
846 $errorMessage .= WCF
::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.'.$type['type'], ['errorData' => ['username' => $type['username']]]);
850 'actionName' => 'addParticipants',
851 'errorMessage' => $errorMessage
856 $newCount = $this->conversation
->participants +
count($participantIDs);
857 if ($newCount > WCF
::getSession()->getPermission('user.conversation.maxParticipants')) {
859 'actionName' => 'addParticipants',
860 'errorMessage' => WCF
::getLanguage()->getDynamicVariable('wcf.conversation.participants.error.tooManyParticipants')
865 $successMessage = '';
866 if (!empty($participantIDs)) {
867 // check for already added participants
868 if ($this->conversation
->isDraft
) {
869 $draftData = unserialize($this->conversation
->draftData
);
870 $draftData['participants'] = array_merge($draftData['participants'], $participantIDs);
871 $data = ['data' => ['draftData' => serialize($draftData)]];
875 'participants' => $participantIDs,
876 'visibility' => (isset($this->parameters
['visibility'])) ?
$this->parameters
['visibility'] : 'all'
880 $conversationAction = new ConversationAction([$this->conversation
], 'update', $data);
881 $conversationAction->executeAction();
883 $count = count($participantIDs);
884 $successMessage = WCF
::getLanguage()->getDynamicVariable('wcf.conversation.edit.addParticipants.success', ['count' => $count]);
886 ConversationModificationLogHandler
::getInstance()->addParticipants($this->conversation
->getDecoratedObject(), $participantIDs);
888 if (!$this->conversation
->isDraft
) {
889 // update participant summary
890 $this->conversation
->updateParticipantSummary();
896 'successMessage' => $successMessage
901 * Validates parameters to remove a participant from a conversation.
903 * @throws PermissionDeniedException
904 * @throws UserInputException
906 public function validateRemoveParticipant() {
907 $this->readInteger('userID');
909 // validate conversation
910 $this->conversation
= $this->getSingleObject();
911 if (!$this->conversation
->conversationID
) {
912 throw new UserInputException('objectIDs');
916 if ($this->conversation
->userID
!= WCF
::getUser()->userID
) {
917 throw new PermissionDeniedException();
920 // validate participants
921 if ($this->parameters
['userID'] == WCF
::getUser()->userID ||
!Conversation
::isParticipant([$this->conversation
->conversationID
]) ||
!Conversation
::isParticipant([$this->conversation
->conversationID
], $this->parameters
['userID'])) {
922 throw new PermissionDeniedException();
928 * Removes a participant from a conversation.
930 public function removeParticipant() {
931 $this->conversation
->removeParticipant($this->parameters
['userID']);
932 $this->conversation
->updateParticipantSummary();
934 ConversationModificationLogHandler
::getInstance()->removeParticipant($this->conversation
->getDecoratedObject(), $this->parameters
['userID']);
937 UserStorageHandler
::getInstance()->reset([$this->parameters
['userID']], 'unreadConversationCount');
940 'userID' => $this->parameters
['userID']
945 * Rebuilds the conversation data of the relevant conversations.
947 public function rebuild() {
948 if (empty($this->objects
)) {
949 $this->readObjects();
952 // collect number of messages for each conversation
953 $conditionBuilder = new PreparedStatementConditionBuilder();
954 $conditionBuilder->add('conversation_message.conversationID IN (?)', [$this->objectIDs
]);
955 $sql = "SELECT conversationID, COUNT(messageID) AS messages, SUM(attachments) AS attachments
956 FROM wcf".WCF_N
."_conversation_message conversation_message
957 ".$conditionBuilder."
958 GROUP BY conversationID";
959 $statement = WCF
::getDB()->prepareStatement($sql);
960 $statement->execute($conditionBuilder->getParameters());
963 while ($row = $statement->fetchArray()) {
964 if (!$row['messages']) {
967 $objectIDs[] = $row['conversationID'];
969 $conversationEditor = new ConversationEditor(new Conversation(null, [
970 'conversationID' => $row['conversationID']
972 $conversationEditor->update([
973 'attachments' => $row['attachments'],
974 'replies' => $row['messages'] - 1
976 $conversationEditor->updateFirstMessage();
977 $conversationEditor->updateLastMessage();
980 // delete conversations without messages
981 $deleteConversationIDs = array_diff($this->objectIDs
, $objectIDs);
982 if (!empty($deleteConversationIDs)) {
983 $conversationAction = new ConversationAction($deleteConversationIDs, 'delete');
984 $conversationAction->executeAction();
989 * Validates the parameters to edit a conversation's subject.
991 * @throws PermissionDeniedException
993 public function validateEditSubject() {
994 $this->readString('subject');
996 $this->conversation
= $this->getSingleObject();
997 if ($this->conversation
->userID
!= WCF
::getUser()->userID
) {
998 throw new PermissionDeniedException();
1003 * Edits a conversation's subject.
1007 public function editSubject() {
1008 $this->conversation
->update([
1009 'subject' => mb_substr($this->parameters
['subject'], 0, 255)
1013 'subject' => $this->parameters
['subject']
1018 * Adds conversation modification data.
1020 * @param Conversation $conversation
1021 * @param string $key
1022 * @param mixed $value
1024 protected function addConversationData(Conversation
$conversation, $key, $value) {
1025 if (!isset($this->conversationData
[$conversation->conversationID
])) {
1026 $this->conversationData
[$conversation->conversationID
] = [];
1029 $this->conversationData
[$conversation->conversationID
][$key] = $value;
1033 * Returns conversation data.
1037 protected function getConversationData() {
1039 'conversationData' => $this->conversationData
1044 * Unmarks conversations.
1046 * @param integer[] $conversationIDs
1048 protected function unmarkItems(array $conversationIDs = []) {
1049 if (empty($conversationIDs)) {
1050 $conversationIDs = $this->objectIDs
;
1053 ClipboardHandler
::getInstance()->unmark($conversationIDs, ClipboardHandler
::getInstance()->getObjectTypeID('com.woltlab.wcf.conversation.conversation'));