2 namespace wcf\data\conversation\message
;
3 use wcf\data\conversation\Conversation
;
4 use wcf\data\conversation\ConversationAction
;
5 use wcf\data\conversation\ConversationEditor
;
6 use wcf\data\smiley\SmileyCache
;
7 use wcf\data\AbstractDatabaseObjectAction
;
8 use wcf\data\DatabaseObject
;
9 use wcf\data\IAttachmentMessageQuickReplyAction
;
10 use wcf\data\IMessageInlineEditorAction
;
11 use wcf\data\IMessageQuoteAction
;
12 use wcf\system\attachment\AttachmentHandler
;
13 use wcf\system\bbcode\BBCodeHandler
;
14 use wcf\system\bbcode\BBCodeParser
;
15 use wcf\system\bbcode\PreParser
;
16 use wcf\system\exception\PermissionDeniedException
;
17 use wcf\system\exception\UserInputException
;
18 use wcf\system\html\input\HtmlInputProcessor
;
19 use wcf\system\message\censorship\Censorship
;
20 use wcf\system\message\embedded\
object\MessageEmbeddedObjectManager
;
21 use wcf\system\message\quote\MessageQuoteManager
;
22 use wcf\system\message\QuickReplyManager
;
23 use wcf\system\moderation\queue\ModerationQueueManager
;
24 use wcf\system\request\LinkHandler
;
25 use wcf\system\search\SearchIndexManager
;
26 use wcf\system\user\notification\
object\ConversationMessageUserNotificationObject
;
27 use wcf\system\user\notification\UserNotificationHandler
;
28 use wcf\system\user\storage\UserStorageHandler
;
30 use wcf\util\StringUtil
;
33 * Executes conversation message-related actions.
36 * @copyright 2001-2016 WoltLab GmbH
37 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
38 * @package WoltLabSuite\Core\Data\Conversation\Message
40 * @method ConversationMessageEditor[] getObjects()
41 * @method ConversationMessageEditor getSingleObject()
43 class ConversationMessageAction
extends AbstractDatabaseObjectAction
implements IAttachmentMessageQuickReplyAction
, IMessageInlineEditorAction
, IMessageQuoteAction
{
47 protected $className = ConversationMessageEditor
::class;
56 * @var HtmlInputProcessor
58 public $htmlInputProcessor;
61 * conversation message object
62 * @var ConversationMessage
68 * @return ConversationMessage
70 public function create() {
71 if (!isset($this->parameters
['data']['enableHtml'])) $this->parameters
['data']['enableHtml'] = 1;
74 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
75 $this->parameters
['data']['attachments'] = count($this->parameters
['attachmentHandler']);
80 if (!isset($this->parameters
['data']['ipAddress'])) {
81 $this->parameters
['data']['ipAddress'] = WCF
::getSession()->ipAddress
;
85 // do not track ip address
86 if (isset($this->parameters
['data']['ipAddress'])) {
87 unset($this->parameters
['data']['ipAddress']);
91 if (!empty($this->parameters
['htmlInputProcessor'])) {
92 /** @noinspection PhpUndefinedMethodInspection */
93 $this->parameters
['data']['message'] = $this->parameters
['htmlInputProcessor']->getHtml();
97 /** @var ConversationMessage $message */
98 $message = parent
::create();
99 $messageEditor = new ConversationMessageEditor($message);
102 $conversation = (isset($this->parameters
['converation']) ?
$this->parameters
['converation'] : new Conversation($message->conversationID
));
103 $conversationEditor = new ConversationEditor($conversation);
105 if (empty($this->parameters
['isFirstPost'])) {
106 // update last message
107 $conversationEditor->addMessage($message);
109 // fire notification event
110 if (!$conversation->isDraft
) {
111 $notificationRecipients = array_diff($conversation->getParticipantIDs(true), [$message->userID
]); // don't notify message author
112 if (!empty($notificationRecipients)) {
113 UserNotificationHandler
::getInstance()->fireEvent('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', new ConversationMessageUserNotificationObject($message), $notificationRecipients);
117 $userConversation = Conversation
::getUserConversation($conversation->conversationID
, $message->userID
);
118 if ($userConversation !== null && $userConversation->isInvisible
) {
119 // make invisible participant visible
120 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
122 WHERE participantID = ?
123 AND conversationID = ?";
124 $statement = WCF
::getDB()->prepareStatement($sql);
125 $statement->execute([$message->userID
, $conversation->conversationID
]);
127 $conversationEditor->updateParticipantSummary();
128 $conversationEditor->updateParticipantCount();
131 // reset visibility if it was hidden but not left
132 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
133 SET hideConversation = ?
134 WHERE conversationID = ?
135 AND hideConversation = ?";
136 $statement = WCF
::getDB()->prepareStatement($sql);
137 $statement->execute([
138 Conversation
::STATE_DEFAULT
,
139 $conversation->conversationID
,
140 Conversation
::STATE_HIDDEN
145 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
147 // update search index
148 SearchIndexManager
::getInstance()->set('com.woltlab.wcf.conversation.message', $message->messageID
, $message->message
, (!empty($this->parameters
['isFirstPost']) ?
$conversation->subject
: ''), $message->time
, $message->userID
, $message->username
);
150 // update attachments
151 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
152 /** @noinspection PhpUndefinedMethodInspection */
153 $this->parameters
['attachmentHandler']->updateObjectID($message->messageID
);
156 // save embedded objects
157 if (!empty($this->parameters
['htmlInputProcessor'])) {
158 /** @noinspection PhpUndefinedMethodInspection */
159 $this->parameters
['htmlInputProcessor']->setObjectID($message->messageID
);
161 if (MessageEmbeddedObjectManager
::getInstance()->registerObjects($this->parameters
['htmlInputProcessor'])) {
162 $messageEditor->update(['hasEmbeddedObjects' => 1]);
167 if (isset($this->parameters
['removeQuoteIDs']) && !empty($this->parameters
['removeQuoteIDs'])) {
168 MessageQuoteManager
::getInstance()->markQuotesForRemoval($this->parameters
['removeQuoteIDs']);
170 MessageQuoteManager
::getInstance()->removeMarkedQuotes();
172 // return new message
179 public function update() {
181 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
182 $this->parameters
['data']['attachments'] = count($this->parameters
['attachmentHandler']);
185 if (!empty($this->parameters
['htmlInputProcessor'])) {
186 /** @noinspection PhpUndefinedMethodInspection */
187 $this->parameters
['data']['message'] = $this->parameters
['htmlInputProcessor']->getHtml();
192 // update search index / embedded objects
193 if (isset($this->parameters
['data']) && isset($this->parameters
['data']['message'])) {
194 foreach ($this->getObjects() as $message) {
195 $conversation = $message->getConversation();
196 SearchIndexManager
::getInstance()->set('com.woltlab.wcf.conversation.message', $message->messageID
, $this->parameters
['data']['message'], ($conversation->firstMessageID
== $message->messageID ?
$conversation->subject
: ''), $message->time
, $message->userID
, $message->username
);
198 if (!empty($this->parameters
['htmlInputProcessor'])) {
199 /** @noinspection PhpUndefinedMethodInspection */
200 $this->parameters
['htmlInputProcessor']->setObjectID($message->messageID
);
202 if ($message->hasEmbeddedObjects
!= MessageEmbeddedObjectManager
::getInstance()->registerObjects($this->parameters
['htmlInputProcessor'])) {
203 $message->update(['hasEmbeddedObjects' => ($message->hasEmbeddedObjects ?
0 : 1)]);
213 public function delete() {
214 $count = parent
::delete();
216 $attachmentMessageIDs = $conversationIDs = [];
217 foreach ($this->getObjects() as $message) {
218 if (!in_array($message->conversationID
, $conversationIDs)) {
219 $conversationIDs[] = $message->conversationID
;
222 if ($message->attachments
) {
223 $attachmentMessageIDs[] = $message->messageID
;
227 // rebuild conversations
228 if (!empty($conversationIDs)) {
229 $conversationAction = new ConversationAction($conversationIDs, 'rebuild');
230 $conversationAction->executeAction();
233 if (!empty($this->objectIDs
)) {
234 // delete notifications
235 UserNotificationHandler
::getInstance()->deleteNotifications('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [], $this->objectIDs
);
237 // update search index
238 SearchIndexManager
::getInstance()->delete('com.woltlab.wcf.conversation.message', $this->objectIDs
);
240 // update embedded objects
241 MessageEmbeddedObjectManager
::getInstance()->removeObjects('com.woltlab.wcf.conversation.message', $this->objectIDs
);
243 // remove moderation queues
244 ModerationQueueManager
::getInstance()->removeQueues('com.woltlab.wcf.conversation.message', $this->objectIDs
);
247 // remove attachments
248 if (!empty($attachmentMessageIDs)) {
249 AttachmentHandler
::removeAttachments('com.woltlab.wcf.conversation.message', $attachmentMessageIDs);
258 public function validateQuickReply() {
259 QuickReplyManager
::getInstance()->setAllowedBBCodes(explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
260 QuickReplyManager
::getInstance()->validateParameters($this, $this->parameters
, Conversation
::class);
266 public function quickReply() {
267 return QuickReplyManager
::getInstance()->createMessage(
270 ConversationAction
::class,
271 CONVERSATION_LIST_DEFAULT_SORT_ORDER
,
272 'conversationMessageList'
279 public function validateJumpToExtended() {
280 $this->readInteger('containerID');
281 $this->readString('message', true);
282 $this->readString('tmpHash', true);
284 $this->conversation
= new Conversation($this->parameters
['containerID']);
285 if (!$this->conversation
->conversationID
) {
286 throw new UserInputException('containerID');
288 else if ($this->conversation
->isClosed ||
!Conversation
::isParticipant([$this->conversation
->conversationID
])) {
289 throw new PermissionDeniedException();
292 // editing existing message
293 if (isset($this->parameters
['messageID'])) {
294 $this->message
= new ConversationMessage(intval($this->parameters
['messageID']));
295 if (!$this->message
->messageID ||
($this->message
->conversationID
!= $this->conversation
->conversationID
)) {
296 throw new UserInputException('messageID');
299 if (!$this->message
->canEdit()) {
300 throw new PermissionDeniedException();
308 public function jumpToExtended() {
310 if ($this->message
=== null) {
311 QuickReplyManager
::getInstance()->setMessage('conversation', $this->conversation
->conversationID
, $this->parameters
['message']);
312 $url = LinkHandler
::getInstance()->getLink('ConversationMessageAdd', ['id' => $this->conversation
->conversationID
]);
316 QuickReplyManager
::getInstance()->setMessage('conversationMessage', $this->message
->messageID
, $this->parameters
['message']);
317 $url = LinkHandler
::getInstance()->getLink('ConversationMessageEdit', ['id' => $this->message
->messageID
]);
320 if (!empty($this->parameters
['tmpHash'])) {
321 QuickReplyManager
::getInstance()->setTmpHash($this->parameters
['tmpHash']);
333 public function validateBeginEdit() {
334 $this->readInteger('containerID');
335 $this->readInteger('objectID');
337 $this->conversation
= new Conversation($this->parameters
['containerID']);
338 if (!$this->conversation
->conversationID
) {
339 throw new UserInputException('containerID');
342 if ($this->conversation
->isClosed ||
!Conversation
::isParticipant([$this->conversation
->conversationID
])) {
343 throw new PermissionDeniedException();
346 $this->message
= new ConversationMessage($this->parameters
['objectID']);
347 if (!$this->message
->messageID
) {
348 throw new UserInputException('objectID');
351 if (!$this->message
->canEdit()) {
352 throw new PermissionDeniedException();
359 public function beginEdit() {
360 BBCodeHandler
::getInstance()->setAllowedBBCodes(explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
362 WCF
::getTPL()->assign([
363 'defaultSmilies' => SmileyCache
::getInstance()->getCategorySmilies(),
364 'message' => $this->message
,
365 'permissionCanUseSmilies' => 'user.message.canUseSmilies',
366 'wysiwygSelector' => 'messageEditor'.$this->message
->messageID
369 if (MODULE_ATTACHMENT
) {
370 $tmpHash = StringUtil
::getRandomID();
371 $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', $this->message
->messageID
, $tmpHash);
372 $attachmentList = $attachmentHandler->getAttachmentList();
374 WCF
::getTPL()->assign([
375 'attachmentHandler' => $attachmentHandler,
376 'attachmentList' => $attachmentList->getObjects(),
377 'attachmentObjectID' => $this->message
->messageID
,
378 'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
379 'attachmentParentObjectID' => 0,
380 'tmpHash' => $tmpHash
385 'actionName' => 'beginEdit',
386 'template' => WCF
::getTPL()->fetch('conversationMessageInlineEditor')
393 public function validateSave() {
394 $this->readString('message', true, 'data');
396 if (empty($this->parameters
['data']['message'])) {
397 throw new UserInputException('message', WCF
::getLanguage()->get('wcf.global.form.error.empty'));
400 $this->validateBeginEdit();
402 $this->validateMessage($this->conversation
, $this->getHtmlInputProcessor($this->parameters
['data']['message'], $this->message
->messageID
));
408 public function save() {
411 if (!$this->message
->getConversation()->isDraft
) {
412 $data['lastEditTime'] = TIME_NOW
;
413 $data['editCount'] = $this->message
->editCount +
1;
415 // execute update action
416 $action = new ConversationMessageAction([$this->message
], 'update', [
418 'htmlInputProcessor' => $this->getHtmlInputProcessor()
420 $action->executeAction();
423 $this->message
= new ConversationMessage($this->message
->messageID
);
424 $this->message
->getAttachments();
426 $attachmentList = null;
427 if (MODULE_ATTACHMENT
) {
428 $attachmentList = $this->message
->getAttachments(true);
430 if ($attachmentList !== null) {
432 $attachmentList->setPermissions([
433 'canDownload' => true,
434 'canViewPreview' => true
437 $count = count($attachmentList);
440 // update count to reflect number of attachments after edit
441 if ($count != $this->message
->attachments
) {
442 $messageEditor = new ConversationMessageEditor($this->message
);
443 $messageEditor->update(['attachments' => $count]);
447 // load embedded objects
448 MessageEmbeddedObjectManager
::getInstance()->loadObjects('com.woltlab.wcf.conversation.message', [$this->message
->messageID
]);
451 'actionName' => 'save',
452 'message' => $this->message
->getFormattedMessage()
455 if (MODULE_ATTACHMENT
) {
456 WCF
::getTPL()->assign([
457 'attachmentList' => $attachmentList,
458 'objectID' => $this->message
->messageID
460 $data['attachmentList'] = WCF
::getTPL()->fetch('attachments');
469 public function validateContainer(DatabaseObject
$conversation) {
470 /** @var Conversation $conversation */
472 if (!$conversation->conversationID
) {
473 throw new UserInputException('objectID');
475 if ($conversation->isClosed
) {
476 throw new PermissionDeniedException();
478 $conversation->loadUserParticipation();
479 if (!$conversation->canRead()) {
480 throw new PermissionDeniedException();
487 public function validateMessage(DatabaseObject
$container, HtmlInputProcessor
$htmlInputProcessor) {
488 /*if (mb_strlen($message) > WCF::getSession()->getPermission('user.conversation.maxLength')) {
489 throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', ['maxTextLength' => WCF::getSession()->getPermission('user.conversation.maxLength')]));
492 // search for disallowed bbcodes
493 $disallowedBBCodes = BBCodeParser::getInstance()->validateBBCodes($message, explode(',', WCF::getSession()->getPermission('user.message.allowedBBCodes')));
494 if (!empty($disallowedBBCodes)) {
495 throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
498 // search for censored words
499 if (ENABLE_CENSORSHIP) {
500 $result = Censorship::getInstance()->test($message);
502 throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', ['censoredWords' => $result]));
510 public function getMessageList(DatabaseObject
$conversation, $lastMessageTime) {
511 /** @var Conversation $conversation */
513 $messageList = new ViewableConversationMessageList();
514 $messageList->setConversation($conversation);
515 $messageList->getConditionBuilder()->add("conversation_message.conversationID = ?", [$conversation->conversationID
]);
516 $messageList->getConditionBuilder()->add("conversation_message.time > ?", [$lastMessageTime]);
517 $messageList->sqlOrderBy
= "conversation_message.time ".CONVERSATION_LIST_DEFAULT_SORT_ORDER
;
518 $messageList->readObjects();
526 public function getPageNo(DatabaseObject
$conversation) {
527 /** @var Conversation $conversation */
529 $sql = "SELECT COUNT(*) AS count
530 FROM wcf".WCF_N
."_conversation_message
531 WHERE conversationID = ?";
532 $statement = WCF
::getDB()->prepareStatement($sql);
533 $statement->execute([$conversation->conversationID
]);
534 $count = $statement->fetchArray();
536 return [intval(ceil($count['count'] / CONVERSATION_MESSAGES_PER_PAGE
)), $count['count']];
542 public function getRedirectUrl(DatabaseObject
$conversation, DatabaseObject
$message) {
543 /** @var ConversationMessage $message */
544 return LinkHandler
::getInstance()->getLink('Conversation', [
545 'object' => $conversation,
546 'messageID' => $message->messageID
547 ]).'#message'.$message->messageID
;
553 public function validateSaveFullQuote() {
554 $this->message
= $this->getSingleObject();
556 if (!Conversation
::isParticipant([$this->message
->conversationID
])) {
557 throw new PermissionDeniedException();
564 public function saveFullQuote() {
565 $quoteID = MessageQuoteManager
::getInstance()->addQuote(
566 'com.woltlab.wcf.conversation.message',
567 $this->message
->conversationID
,
568 $this->message
->messageID
,
569 $this->message
->getExcerpt(),
570 $this->message
->getMessage()
573 if ($quoteID === false) {
574 $removeQuoteID = MessageQuoteManager
::getInstance()->getQuoteID('com.woltlab.wcf.conversation.message', $this->message
->messageID
, $this->message
->getExcerpt(), $this->message
->getMessage());
575 MessageQuoteManager
::getInstance()->removeQuote($removeQuoteID);
579 'count' => MessageQuoteManager
::getInstance()->countQuotes(),
580 'fullQuoteMessageIDs' => MessageQuoteManager
::getInstance()->getFullQuoteObjectIDs(['com.woltlab.wcf.conversation.message'])
584 $returnValues['renderedQuote'] = MessageQuoteManager
::getInstance()->getQuoteComponents($quoteID);
587 return $returnValues;
593 public function validateSaveQuote() {
594 $this->readString('message');
595 $this->readBoolean('renderQuote', true);
596 $this->message
= $this->getSingleObject();
598 if (!Conversation
::isParticipant([$this->message
->conversationID
])) {
599 throw new PermissionDeniedException();
606 public function saveQuote() {
607 $quoteID = MessageQuoteManager
::getInstance()->addQuote('com.woltlab.wcf.conversation.message', $this->message
->conversationID
, $this->message
->messageID
, $this->parameters
['message'], false);
610 'count' => MessageQuoteManager
::getInstance()->countQuotes(),
611 'fullQuoteMessageIDs' => MessageQuoteManager
::getInstance()->getFullQuoteObjectIDs(['com.woltlab.wcf.conversation.message'])
614 if ($this->parameters
['renderQuote']) {
615 $returnValues['renderedQuote'] = MessageQuoteManager
::getInstance()->getQuoteComponents($quoteID);
618 return $returnValues;
624 public function validateGetRenderedQuotes() {
625 $this->readInteger('parentObjectID');
627 $this->conversation
= new Conversation($this->parameters
['parentObjectID']);
628 if (!$this->conversation
->conversationID
) {
629 throw new UserInputException('parentObjectID');
636 public function getRenderedQuotes() {
637 $quotes = MessageQuoteManager
::getInstance()->getQuotesByParentObjectID('com.woltlab.wcf.conversation.message', $this->conversation
->conversationID
);
640 'template' => implode("\n\n", $quotes)
647 public function getAttachmentHandler(DatabaseObject
$conversation) {
648 return new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $this->parameters
['tmpHash']);
654 public function getHtmlInputProcessor($message = null, $objectID = 0) {
655 if ($message === null) {
656 return $this->htmlInputProcessor
;
659 $this->htmlInputProcessor
= new HtmlInputProcessor();
660 $this->htmlInputProcessor
->process($message, 'com.woltlab.wcf.conversation.message', $objectID);
662 return $this->htmlInputProcessor
;