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\MessageUtil
;
31 use wcf\util\StringUtil
;
34 * Executes conversation message-related actions.
37 * @copyright 2001-2016 WoltLab GmbH
38 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
39 * @package com.woltlab.wcf.conversation
40 * @subpackage data.conversation.message
41 * @category Community Framework
43 * @method ConversationMessageEditor[] getObjects()
44 * @method ConversationMessageEditor getSingleObject()
46 class ConversationMessageAction
extends AbstractDatabaseObjectAction
implements IAttachmentMessageQuickReplyAction
, IMessageInlineEditorAction
, IMessageQuoteAction
{
50 protected $className = ConversationMessageEditor
::class;
59 * @var HtmlInputProcessor
61 public $htmlInputProcessor;
64 * conversation message object
65 * @var ConversationMessage
71 * @return ConversationMessage
73 public function create() {
75 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
76 $this->parameters
['data']['attachments'] = count($this->parameters
['attachmentHandler']);
81 if (!isset($this->parameters
['data']['ipAddress'])) {
82 $this->parameters
['data']['ipAddress'] = WCF
::getSession()->ipAddress
;
86 // do not track ip address
87 if (isset($this->parameters
['data']['ipAddress'])) {
88 unset($this->parameters
['data']['ipAddress']);
92 if (!empty($this->parameters
['htmlInputProcessor'])) {
93 /** @var HtmlInputProcessor $htmlInputProcessor */
94 $htmlInputProcessor = $this->parameters
['htmlInputProcessor'];
95 $this->parameters
['data']['message'] = $htmlInputProcessor->getHtml();
99 $message = parent
::create();
100 $messageEditor = new ConversationMessageEditor($message);
103 $conversation = (isset($this->parameters
['converation']) ?
$this->parameters
['converation'] : new Conversation($message->conversationID
));
104 $conversationEditor = new ConversationEditor($conversation);
106 if (empty($this->parameters
['isFirstPost'])) {
107 // update last message
108 $conversationEditor->addMessage($message);
110 // fire notification event
111 if (!$conversation->isDraft
) {
112 $notificationRecipients = array_diff($conversation->getParticipantIDs(true), [$message->userID
]); // don't notify message author
113 if (!empty($notificationRecipients)) {
114 UserNotificationHandler
::getInstance()->fireEvent('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', new ConversationMessageUserNotificationObject($message), $notificationRecipients);
118 $userConversation = Conversation
::getUserConversation($conversation->conversationID
, $message->userID
);
119 if ($userConversation !== null && $userConversation->isInvisible
) {
120 // make invisible participant visible
121 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
123 WHERE participantID = ?
124 AND conversationID = ?";
125 $statement = WCF
::getDB()->prepareStatement($sql);
126 $statement->execute([$message->userID
, $conversation->conversationID
]);
128 $conversationEditor->updateParticipantSummary();
129 $conversationEditor->updateParticipantCount();
132 // reset visibility if it was hidden but not left
133 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
134 SET hideConversation = ?
135 WHERE conversationID = ?
136 AND hideConversation = ?";
137 $statement = WCF
::getDB()->prepareStatement($sql);
138 $statement->execute([
139 Conversation
::STATE_DEFAULT
,
140 $conversation->conversationID
,
141 Conversation
::STATE_HIDDEN
146 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
148 // update search index
149 SearchIndexManager
::getInstance()->add('com.woltlab.wcf.conversation.message', $message->messageID
, $message->message
, (!empty($this->parameters
['isFirstPost']) ?
$conversation->subject
: ''), $message->time
, $message->userID
, $message->username
);
151 // update attachments
152 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
153 $this->parameters
['attachmentHandler']->updateObjectID($message->messageID
);
156 // save embedded objects
157 if (MessageEmbeddedObjectManager
::getInstance()->registerObjects('com.woltlab.wcf.conversation.message', $message->messageID
, $message->message
)) {
158 $messageEditor->update([
159 'hasEmbeddedObjects' => 1
164 if (isset($this->parameters
['removeQuoteIDs']) && !empty($this->parameters
['removeQuoteIDs'])) {
165 MessageQuoteManager
::getInstance()->markQuotesForRemoval($this->parameters
['removeQuoteIDs']);
167 MessageQuoteManager
::getInstance()->removeMarkedQuotes();
169 // return new message
176 public function update() {
178 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
179 $this->parameters
['data']['attachments'] = count($this->parameters
['attachmentHandler']);
184 // update search index / embedded objects
185 if (isset($this->parameters
['data']) && isset($this->parameters
['data']['message'])) {
186 foreach ($this->objects
as $message) {
187 $conversation = $message->getConversation();
188 SearchIndexManager
::getInstance()->update('com.woltlab.wcf.conversation.message', $message->messageID
, $this->parameters
['data']['message'], ($conversation->firstMessageID
== $message->messageID ?
$conversation->subject
: ''), $message->time
, $message->userID
, $message->username
);
190 if ($message->hasEmbeddedObjects
!= MessageEmbeddedObjectManager
::getInstance()->registerObjects('com.woltlab.wcf.conversation.message', $message->messageID
, $this->parameters
['data']['message'])) {
192 'hasEmbeddedObjects' => ($message->hasEmbeddedObjects ?
0 : 1)
202 public function delete() {
203 $count = parent
::delete();
205 $attachmentMessageIDs = $conversationIDs = [];
206 foreach ($this->objects
as $message) {
207 if (!in_array($message->conversationID
, $conversationIDs)) {
208 $conversationIDs[] = $message->conversationID
;
211 if ($message->attachments
) {
212 $attachmentMessageIDs[] = $message->messageID
;
216 // rebuild conversations
217 if (!empty($conversationIDs)) {
218 $conversationAction = new ConversationAction($conversationIDs, 'rebuild');
219 $conversationAction->executeAction();
222 if (!empty($this->objectIDs
)) {
223 // delete notifications
224 UserNotificationHandler
::getInstance()->deleteNotifications('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', [], $this->objectIDs
);
226 // update search index
227 SearchIndexManager
::getInstance()->delete('com.woltlab.wcf.conversation.message', $this->objectIDs
);
229 // update embedded objects
230 MessageEmbeddedObjectManager
::getInstance()->removeObjects('com.woltlab.wcf.conversation.message', $this->objectIDs
);
232 // remove moderation queues
233 ModerationQueueManager
::getInstance()->removeQueues('com.woltlab.wcf.conversation.message', $this->objectIDs
);
236 // remove attachments
237 if (!empty($attachmentMessageIDs)) {
238 AttachmentHandler
::removeAttachments('com.woltlab.wcf.conversation.message', $attachmentMessageIDs);
247 public function validateQuickReply() {
248 QuickReplyManager
::getInstance()->setAllowedBBCodes(explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
249 QuickReplyManager
::getInstance()->validateParameters($this, $this->parameters
, Conversation
::class);
255 public function quickReply() {
256 return QuickReplyManager
::getInstance()->createMessage(
259 ConversationAction
::class,
260 CONVERSATION_LIST_DEFAULT_SORT_ORDER
,
261 'conversationMessageList'
268 public function validateJumpToExtended() {
269 $this->readInteger('containerID');
270 $this->readString('message', true);
271 $this->readString('tmpHash', true);
273 $this->conversation
= new Conversation($this->parameters
['containerID']);
274 if (!$this->conversation
->conversationID
) {
275 throw new UserInputException('containerID');
277 else if ($this->conversation
->isClosed ||
!Conversation
::isParticipant([$this->conversation
->conversationID
])) {
278 throw new PermissionDeniedException();
281 // editing existing message
282 if (isset($this->parameters
['messageID'])) {
283 $this->message
= new ConversationMessage(intval($this->parameters
['messageID']));
284 if (!$this->message
->messageID ||
($this->message
->conversationID
!= $this->conversation
->conversationID
)) {
285 throw new UserInputException('messageID');
288 if (!$this->message
->canEdit()) {
289 throw new PermissionDeniedException();
297 public function jumpToExtended() {
299 if ($this->message
=== null) {
300 QuickReplyManager
::getInstance()->setMessage('conversation', $this->conversation
->conversationID
, $this->parameters
['message']);
301 $url = LinkHandler
::getInstance()->getLink('ConversationMessageAdd', ['id' => $this->conversation
->conversationID
]);
305 QuickReplyManager
::getInstance()->setMessage('conversationMessage', $this->message
->messageID
, $this->parameters
['message']);
306 $url = LinkHandler
::getInstance()->getLink('ConversationMessageEdit', ['id' => $this->message
->messageID
]);
309 if (!empty($this->parameters
['tmpHash'])) {
310 QuickReplyManager
::getInstance()->setTmpHash($this->parameters
['tmpHash']);
322 public function validateBeginEdit() {
323 $this->readInteger('containerID');
324 $this->readInteger('objectID');
326 $this->conversation
= new Conversation($this->parameters
['containerID']);
327 if (!$this->conversation
->conversationID
) {
328 throw new UserInputException('containerID');
331 if ($this->conversation
->isClosed ||
!Conversation
::isParticipant([$this->conversation
->conversationID
])) {
332 throw new PermissionDeniedException();
335 $this->message
= new ConversationMessage($this->parameters
['objectID']);
336 if (!$this->message
->messageID
) {
337 throw new UserInputException('objectID');
340 if (!$this->message
->canEdit()) {
341 throw new PermissionDeniedException();
348 public function beginEdit() {
349 BBCodeHandler
::getInstance()->setAllowedBBCodes(explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
351 WCF
::getTPL()->assign([
352 'defaultSmilies' => SmileyCache
::getInstance()->getCategorySmilies(),
353 'message' => $this->message
,
354 'permissionCanUseSmilies' => 'user.message.canUseSmilies',
355 'wysiwygSelector' => 'messageEditor'.$this->message
->messageID
358 if (MODULE_ATTACHMENT
) {
359 $tmpHash = StringUtil
::getRandomID();
360 $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', $this->message
->messageID
, $tmpHash);
361 $attachmentList = $attachmentHandler->getAttachmentList();
363 WCF
::getTPL()->assign([
364 'attachmentHandler' => $attachmentHandler,
365 'attachmentList' => $attachmentList->getObjects(),
366 'attachmentObjectID' => $this->message
->messageID
,
367 'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
368 'attachmentParentObjectID' => 0,
369 'tmpHash' => $tmpHash
374 'actionName' => 'beginEdit',
375 'template' => WCF
::getTPL()->fetch('conversationMessageInlineEditor')
382 public function validateSave() {
383 $this->readString('message', true, 'data');
385 if (empty($this->parameters
['data']['message'])) {
386 throw new UserInputException('message', WCF
::getLanguage()->get('wcf.global.form.error.empty'));
389 $this->validateBeginEdit();
391 $this->validateMessage($this->conversation
, $this->getHtmlInputProcessor($this->parameters
['data']['message']));
397 public function save() {
399 //'message' => PreParser::getInstance()->parse(MessageUtil::stripCrap($this->parameters['data']['message']), explode(',', WCF::getSession()->getPermission('user.message.allowedBBCodes')))
402 if (!$this->message
->getConversation()->isDraft
) {
403 $data['lastEditTime'] = TIME_NOW
;
404 $data['editCount'] = $this->message
->editCount +
1;
406 // execute update action
407 $action = new ConversationMessageAction([$this->message
], 'update', ['data' => $data]);
408 $action->executeAction();
411 $this->message
= new ConversationMessage($this->message
->messageID
);
412 $this->message
->getAttachments();
414 $attachmentList = null;
415 if (MODULE_ATTACHMENT
) {
416 $attachmentList = $this->message
->getAttachments(true);
418 if ($attachmentList !== null) {
420 $attachmentList->setPermissions([
421 'canDownload' => true,
422 'canViewPreview' => true
425 $count = count($attachmentList);
428 // update count to reflect number of attachments after edit
429 if ($count != $this->message
->attachments
) {
430 $messageEditor = new ConversationMessageEditor($this->message
);
431 $messageEditor->update(['attachments' => $count]);
435 // load embedded objects
436 MessageEmbeddedObjectManager
::getInstance()->loadObjects('com.woltlab.wcf.conversation.message', [$this->message
->messageID
]);
439 'actionName' => 'save',
440 'message' => $this->message
->getFormattedMessage()
443 if (MODULE_ATTACHMENT
) {
444 WCF
::getTPL()->assign([
445 'attachmentList' => $attachmentList,
446 'objectID' => $this->message
->messageID
448 $data['attachmentList'] = WCF
::getTPL()->fetch('attachments');
457 public function validateContainer(DatabaseObject
$conversation) {
458 if (!$conversation->conversationID
) {
459 throw new UserInputException('objectID');
461 if ($conversation->isClosed
) {
462 throw new PermissionDeniedException();
464 $conversation->loadUserParticipation();
465 if (!$conversation->canRead()) {
466 throw new PermissionDeniedException();
473 public function validateMessage(DatabaseObject
$container, HtmlInputProcessor
$htmlInputProcessor) {
474 /*if (mb_strlen($message) > WCF::getSession()->getPermission('user.conversation.maxLength')) {
475 throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', ['maxTextLength' => WCF::getSession()->getPermission('user.conversation.maxLength')]));
478 // search for disallowed bbcodes
479 $disallowedBBCodes = BBCodeParser::getInstance()->validateBBCodes($message, explode(',', WCF::getSession()->getPermission('user.message.allowedBBCodes')));
480 if (!empty($disallowedBBCodes)) {
481 throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
484 // search for censored words
485 if (ENABLE_CENSORSHIP) {
486 $result = Censorship::getInstance()->test($message);
488 throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', ['censoredWords' => $result]));
496 public function getMessageList(DatabaseObject
$conversation, $lastMessageTime) {
497 /** @var Conversation $conversation */
499 $messageList = new ViewableConversationMessageList();
500 $messageList->setConversation($conversation);
501 $messageList->getConditionBuilder()->add("conversation_message.conversationID = ?", [$conversation->conversationID
]);
502 $messageList->getConditionBuilder()->add("conversation_message.time > ?", [$lastMessageTime]);
503 $messageList->sqlOrderBy
= "conversation_message.time ".CONVERSATION_LIST_DEFAULT_SORT_ORDER
;
504 $messageList->readObjects();
512 public function getPageNo(DatabaseObject
$conversation) {
513 $sql = "SELECT COUNT(*) AS count
514 FROM wcf".WCF_N
."_conversation_message
515 WHERE conversationID = ?";
516 $statement = WCF
::getDB()->prepareStatement($sql);
517 $statement->execute([$conversation->conversationID
]);
518 $count = $statement->fetchArray();
520 return [intval(ceil($count['count'] / CONVERSATION_MESSAGES_PER_PAGE
)), $count['count']];
526 public function getRedirectUrl(DatabaseObject
$conversation, DatabaseObject
$message) {
527 return LinkHandler
::getInstance()->getLink('Conversation', [
528 'object' => $conversation,
529 'messageID' => $message->messageID
530 ]).'#message'.$message->messageID
;
536 public function validateSaveFullQuote() {
537 $this->message
= $this->getSingleObject();
539 if (!Conversation
::isParticipant([$this->message
->conversationID
])) {
540 throw new PermissionDeniedException();
547 public function saveFullQuote() {
548 $quoteID = MessageQuoteManager
::getInstance()->addQuote(
549 'com.woltlab.wcf.conversation.message',
550 $this->message
->conversationID
,
551 $this->message
->messageID
,
552 $this->message
->getExcerpt(),
553 $this->message
->getMessage()
556 if ($quoteID === false) {
557 $removeQuoteID = MessageQuoteManager
::getInstance()->getQuoteID('com.woltlab.wcf.conversation.message', $this->message
->messageID
, $this->message
->getExcerpt(), $this->message
->getMessage());
558 MessageQuoteManager
::getInstance()->removeQuote($removeQuoteID);
562 'count' => MessageQuoteManager
::getInstance()->countQuotes(),
563 'fullQuoteMessageIDs' => MessageQuoteManager
::getInstance()->getFullQuoteObjectIDs(['com.woltlab.wcf.conversation.message'])
567 $returnValues['renderedQuote'] = MessageQuoteManager
::getInstance()->getQuoteComponents($quoteID);
570 return $returnValues;
576 public function validateSaveQuote() {
577 $this->readString('message');
578 $this->readBoolean('renderQuote', true);
579 $this->message
= $this->getSingleObject();
581 if (!Conversation
::isParticipant([$this->message
->conversationID
])) {
582 throw new PermissionDeniedException();
589 public function saveQuote() {
590 $quoteID = MessageQuoteManager
::getInstance()->addQuote('com.woltlab.wcf.conversation.message', $this->message
->conversationID
, $this->message
->messageID
, $this->parameters
['message'], false);
593 'count' => MessageQuoteManager
::getInstance()->countQuotes(),
594 'fullQuoteMessageIDs' => MessageQuoteManager
::getInstance()->getFullQuoteObjectIDs(['com.woltlab.wcf.conversation.message'])
597 if ($this->parameters
['renderQuote']) {
598 $returnValues['renderedQuote'] = MessageQuoteManager
::getInstance()->getQuoteComponents($quoteID);
601 return $returnValues;
607 public function validateGetRenderedQuotes() {
608 $this->readInteger('parentObjectID');
610 $this->conversation
= new Conversation($this->parameters
['parentObjectID']);
611 if (!$this->conversation
->conversationID
) {
612 throw new UserInputException('parentObjectID');
619 public function getRenderedQuotes() {
620 $quotes = MessageQuoteManager
::getInstance()->getQuotesByParentObjectID('com.woltlab.wcf.conversation.message', $this->conversation
->conversationID
);
623 'template' => implode("\n\n", $quotes)
630 public function getAttachmentHandler(DatabaseObject
$conversation) {
631 return new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $this->parameters
['tmpHash']);
637 public function getHtmlInputProcessor($message = null) {
638 if ($message === null) {
639 return $this->htmlInputProcessor
;
642 $this->htmlInputProcessor
= new HtmlInputProcessor();
643 $this->htmlInputProcessor
->process($message);
645 return $this->htmlInputProcessor
;