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