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\message\censorship\Censorship
;
19 use wcf\system\message\embedded\
object\MessageEmbeddedObjectManager
;
20 use wcf\system\message\quote\MessageQuoteManager
;
21 use wcf\system\message\QuickReplyManager
;
22 use wcf\system\moderation\queue\ModerationQueueManager
;
23 use wcf\system\request\LinkHandler
;
24 use wcf\system\search\SearchIndexManager
;
25 use wcf\system\user\notification\
object\ConversationMessageUserNotificationObject
;
26 use wcf\system\user\notification\UserNotificationHandler
;
27 use wcf\system\user\storage\UserStorageHandler
;
29 use wcf\util\MessageUtil
;
30 use wcf\util\StringUtil
;
33 * Executes conversation message-related actions.
36 * @copyright 2001-2015 WoltLab GmbH
37 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
38 * @package com.woltlab.wcf.conversation
39 * @subpackage data.conversation.message
40 * @category Community Framework
42 class ConversationMessageAction
extends AbstractDatabaseObjectAction
implements IAttachmentMessageQuickReplyAction
, IMessageInlineEditorAction
, IMessageQuoteAction
{
44 * @see \wcf\data\AbstractDatabaseObjectAction::$className
46 protected $className = 'wcf\data\conversation\message\ConversationMessageEditor';
50 * @var \wcf\data\conversation\Conversation
52 public $conversation = null;
55 * conversation message object
56 * @var \wcf\data\conversation\message\ConversationMessage
58 public $message = null;
61 * @see \wcf\data\AbstractDatabaseObjectAction::create()
63 public function create() {
65 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
66 $this->parameters
['data']['attachments'] = count($this->parameters
['attachmentHandler']);
71 if (!isset($this->parameters
['data']['ipAddress'])) {
72 $this->parameters
['data']['ipAddress'] = WCF
::getSession()->ipAddress
;
76 // do not track ip address
77 if (isset($this->parameters
['data']['ipAddress'])) {
78 unset($this->parameters
['data']['ipAddress']);
83 $message = parent
::create();
84 $messageEditor = new ConversationMessageEditor($message);
87 $conversation = (isset($this->parameters
['converation']) ?
$this->parameters
['converation'] : new Conversation($message->conversationID
));
88 $conversationEditor = new ConversationEditor($conversation);
90 if (empty($this->parameters
['isFirstPost'])) {
91 // update last message
92 $conversationEditor->addMessage($message);
94 // fire notification event
95 if (!$conversation->isDraft
) {
96 $notificationRecipients = array_diff($conversation->getParticipantIDs(true), array($message->userID
)); // don't notify message author
97 if (!empty($notificationRecipients)) {
98 UserNotificationHandler
::getInstance()->fireEvent('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', new ConversationMessageUserNotificationObject($message), $notificationRecipients);
102 $userConversation = Conversation
::getUserConversation($conversation->conversationID
, $message->userID
);
103 if ($userConversation !== null && $userConversation->isInvisible
) {
104 // make invisible participant visible
105 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
107 WHERE participantID = ?
108 AND conversationID = ?";
109 $statement = WCF
::getDB()->prepareStatement($sql);
110 $statement->execute(array($message->userID
, $conversation->conversationID
));
112 $conversationEditor->updateParticipantSummary();
113 $conversationEditor->updateParticipantCount();
116 // reset visibility if it was hidden but not left
117 $sql = "UPDATE wcf".WCF_N
."_conversation_to_user
118 SET hideConversation = ?
119 WHERE conversationID = ?
120 AND hideConversation = ?";
121 $statement = WCF
::getDB()->prepareStatement($sql);
122 $statement->execute(array(
123 Conversation
::STATE_DEFAULT
,
124 $conversation->conversationID
,
125 Conversation
::STATE_HIDDEN
130 UserStorageHandler
::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
132 // update search index
133 SearchIndexManager
::getInstance()->add('com.woltlab.wcf.conversation.message', $message->messageID
, $message->message
, (!empty($this->parameters
['isFirstPost']) ?
$conversation->subject
: ''), $message->time
, $message->userID
, $message->username
);
135 // update attachments
136 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
137 $this->parameters
['attachmentHandler']->updateObjectID($message->messageID
);
140 // save embedded objects
141 if (MessageEmbeddedObjectManager
::getInstance()->registerObjects('com.woltlab.wcf.conversation.message', $message->messageID
, $message->message
)) {
142 $messageEditor->update(array(
143 'hasEmbeddedObjects' => 1
148 if (isset($this->parameters
['removeQuoteIDs']) && !empty($this->parameters
['removeQuoteIDs'])) {
149 MessageQuoteManager
::getInstance()->markQuotesForRemoval($this->parameters
['removeQuoteIDs']);
151 MessageQuoteManager
::getInstance()->removeMarkedQuotes();
153 // return new message
158 * @see \wcf\data\AbstractDatabaseObjectAction::update()
160 public function update() {
162 if (isset($this->parameters
['attachmentHandler']) && $this->parameters
['attachmentHandler'] !== null) {
163 $this->parameters
['data']['attachments'] = count($this->parameters
['attachmentHandler']);
168 // update search index / embedded objects
169 if (isset($this->parameters
['data']) && isset($this->parameters
['data']['message'])) {
170 foreach ($this->objects
as $message) {
171 $conversation = $message->getConversation();
172 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
);
174 if ($message->hasEmbeddedObjects
!= MessageEmbeddedObjectManager
::getInstance()->registerObjects('com.woltlab.wcf.conversation.message', $message->messageID
, $this->parameters
['data']['message'])) {
175 $message->update(array(
176 'hasEmbeddedObjects' => ($message->hasEmbeddedObjects ?
0 : 1)
184 * @see \wcf\data\AbstractDatabaseObjectAction::delete()
186 public function delete() {
187 $count = parent
::delete();
189 $attachmentMessageIDs = $conversationIDs = array();
190 foreach ($this->objects
as $message) {
191 if (!in_array($message->conversationID
, $conversationIDs)) {
192 $conversationIDs[] = $message->conversationID
;
195 if ($message->attachments
) {
196 $attachmentMessageIDs[] = $message->messageID
;
200 // rebuild conversations
201 if (!empty($conversationIDs)) {
202 $conversationAction = new ConversationAction($conversationIDs, 'rebuild');
203 $conversationAction->executeAction();
206 if (!empty($this->objectIDs
)) {
207 // delete notifications
208 UserNotificationHandler
::getInstance()->deleteNotifications('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', array(), $this->objectIDs
);
210 // update search index
211 SearchIndexManager
::getInstance()->delete('com.woltlab.wcf.conversation.message', $this->objectIDs
);
213 // update embedded objects
214 MessageEmbeddedObjectManager
::getInstance()->removeObjects('com.woltlab.wcf.conversation.message', $this->objectIDs
);
216 // remove moderation queues
217 ModerationQueueManager
::getInstance()->removeQueues('com.woltlab.wcf.conversation.message', $this->objectIDs
);
220 // remove attachments
221 if (!empty($attachmentMessageIDs)) {
222 AttachmentHandler
::removeAttachments('com.woltlab.wcf.conversation.message', $attachmentMessageIDs);
229 * @see \wcf\data\IMessageQuickReply::validateQuickReply()
231 public function validateQuickReply() {
232 QuickReplyManager
::getInstance()->setAllowedBBCodes(explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
233 QuickReplyManager
::getInstance()->validateParameters($this, $this->parameters
, 'wcf\data\conversation\Conversation');
237 * @see \wcf\data\IMessageQuickReply::quickReply()
239 public function quickReply() {
240 return QuickReplyManager
::getInstance()->createMessage(
243 'wcf\data\conversation\ConversationAction',
244 CONVERSATION_LIST_DEFAULT_SORT_ORDER
,
245 'conversationMessageList'
250 * @see \wcf\data\IExtendedMessageQuickReplyAction::validateJumpToExtended()
252 public function validateJumpToExtended() {
253 $this->readInteger('containerID');
254 $this->readString('message', true);
255 $this->readString('tmpHash', true);
257 $this->conversation
= new Conversation($this->parameters
['containerID']);
258 if (!$this->conversation
->conversationID
) {
259 throw new UserInputException('containerID');
261 else if ($this->conversation
->isClosed ||
!Conversation
::isParticipant(array($this->conversation
->conversationID
))) {
262 throw new PermissionDeniedException();
265 // editing existing message
266 if (isset($this->parameters
['messageID'])) {
267 $this->message
= new ConversationMessage(intval($this->parameters
['messageID']));
268 if (!$this->message
->messageID ||
($this->message
->conversationID
!= $this->conversation
->conversationID
)) {
269 throw new UserInputException('messageID');
272 if (!$this->message
->canEdit()) {
273 throw new PermissionDeniedException();
279 * @see \wcf\data\IExtendedMessageQuickReplyAction::jumpToExtended()
281 public function jumpToExtended() {
283 if ($this->message
=== null) {
284 QuickReplyManager
::getInstance()->setMessage('conversation', $this->conversation
->conversationID
, $this->parameters
['message']);
285 $url = LinkHandler
::getInstance()->getLink('ConversationMessageAdd', array('id' => $this->conversation
->conversationID
));
289 QuickReplyManager
::getInstance()->setMessage('conversationMessage', $this->message
->messageID
, $this->parameters
['message']);
290 $url = LinkHandler
::getInstance()->getLink('ConversationMessageEdit', array('id' => $this->message
->messageID
));
293 if (!empty($this->parameters
['tmpHash'])) {
294 QuickReplyManager
::getInstance()->setTmpHash($this->parameters
['tmpHash']);
304 * @see \wcf\data\IMessageInlineEditorAction::validateBeginEdit()
306 public function validateBeginEdit() {
307 $this->readInteger('containerID');
308 $this->readInteger('objectID');
310 $this->conversation
= new Conversation($this->parameters
['containerID']);
311 if (!$this->conversation
->conversationID
) {
312 throw new UserInputException('containerID');
315 if ($this->conversation
->isClosed ||
!Conversation
::isParticipant(array($this->conversation
->conversationID
))) {
316 throw new PermissionDeniedException();
319 $this->message
= new ConversationMessage($this->parameters
['objectID']);
320 if (!$this->message
->messageID
) {
321 throw new UserInputException('objectID');
324 if (!$this->message
->canEdit()) {
325 throw new PermissionDeniedException();
330 * @see \wcf\data\IMessageInlineEditorAction::beginEdit()
332 public function beginEdit() {
333 BBCodeHandler
::getInstance()->setAllowedBBCodes(explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
335 WCF
::getTPL()->assign(array(
336 'defaultSmilies' => SmileyCache
::getInstance()->getCategorySmilies(),
337 'message' => $this->message
,
338 'permissionCanUseSmilies' => 'user.message.canUseSmilies',
339 'wysiwygSelector' => 'messageEditor'.$this->message
->messageID
342 if (MODULE_ATTACHMENT
) {
343 $tmpHash = StringUtil
::getRandomID();
344 $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', $this->message
->messageID
, $tmpHash);
345 $attachmentList = $attachmentHandler->getAttachmentList();
347 WCF
::getTPL()->assign(array(
348 'attachmentHandler' => $attachmentHandler,
349 'attachmentList' => $attachmentList->getObjects(),
350 'attachmentObjectID' => $this->message
->messageID
,
351 'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
352 'attachmentParentObjectID' => 0,
353 'tmpHash' => $tmpHash
358 'actionName' => 'beginEdit',
359 'template' => WCF
::getTPL()->fetch('conversationMessageInlineEditor')
364 * @see \wcf\data\IMessageInlineEditorAction::validateSave()
366 public function validateSave() {
367 $this->readString('message', true, 'data');
369 if (empty($this->parameters
['data']['message'])) {
370 throw new UserInputException('message', WCF
::getLanguage()->get('wcf.global.form.error.empty'));
373 $this->validateBeginEdit();
374 $this->validateMessage($this->conversation
, $this->parameters
['data']['message']);
378 * @see \wcf\data\IMessageInlineEditorAction::save()
380 public function save() {
382 'message' => PreParser
::getInstance()->parse(MessageUtil
::stripCrap($this->parameters
['data']['message']), explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')))
384 if (!$this->message
->getConversation()->isDraft
) {
385 $data['lastEditTime'] = TIME_NOW
;
386 $data['editCount'] = $this->message
->editCount +
1;
388 // execute update action
389 $action = new ConversationMessageAction(array($this->message
), 'update', array('data' => $data));
390 $action->executeAction();
393 $this->message
= new ConversationMessage($this->message
->messageID
);
394 $this->message
->getAttachments();
396 if (MODULE_ATTACHMENT
) {
397 $attachmentList = $this->message
->getAttachments(true);
399 if ($attachmentList !== null) {
401 $attachmentList->setPermissions(array(
402 'canDownload' => true,
403 'canViewPreview' => true
406 $count = count($attachmentList);
409 // update count to reflect number of attachments after edit
410 if ($count != $this->message
->attachments
) {
411 $messageEditor = new ConversationMessageEditor($this->message
);
412 $messageEditor->update(array('attachments' => $count));
416 // load embedded objects
417 MessageEmbeddedObjectManager
::getInstance()->loadObjects('com.woltlab.wcf.conversation.message', array($this->message
->messageID
));
420 'actionName' => 'save',
421 'message' => $this->message
->getFormattedMessage()
424 if (MODULE_ATTACHMENT
) {
425 WCF
::getTPL()->assign(array(
426 'attachmentList' => $attachmentList,
427 'objectID' => $this->message
->messageID
429 $data['attachmentList'] = WCF
::getTPL()->fetch('attachments');
436 * @see \wcf\data\IMessageQuickReply::validateContainer()
438 public function validateContainer(DatabaseObject
$conversation) {
439 if (!$conversation->conversationID
) {
440 throw new UserInputException('objectID');
442 if ($conversation->isClosed
) {
443 throw new PermissionDeniedException();
445 $conversation->loadUserParticipation();
446 if (!$conversation->canRead()) {
447 throw new PermissionDeniedException();
452 * @see \wcf\data\IMessageQuickReplyAction::validateMessage()
454 public function validateMessage(DatabaseObject
$container, $message) {
455 if (mb_strlen($message) > WCF
::getSession()->getPermission('user.conversation.maxLength')) {
456 throw new UserInputException('message', WCF
::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', array('maxTextLength' => WCF
::getSession()->getPermission('user.conversation.maxLength'))));
459 // search for disallowed bbcodes
460 $disallowedBBCodes = BBCodeParser
::getInstance()->validateBBCodes($message, explode(',', WCF
::getSession()->getPermission('user.message.allowedBBCodes')));
461 if (!empty($disallowedBBCodes)) {
462 throw new UserInputException('text', WCF
::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', array('disallowedBBCodes' => $disallowedBBCodes)));
465 // search for censored words
466 if (ENABLE_CENSORSHIP
) {
467 $result = Censorship
::getInstance()->test($message);
469 throw new UserInputException('message', WCF
::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', array('censoredWords' => $result)));
475 * @see \wcf\data\IMessageQuickReplyAction::getMessageList()
477 public function getMessageList(DatabaseObject
$conversation, $lastMessageTime) {
478 $messageList = new ViewableConversationMessageList();
479 $messageList->setConversation($conversation);
480 $messageList->getConditionBuilder()->add("conversation_message.conversationID = ?", array($conversation->conversationID
));
481 $messageList->getConditionBuilder()->add("conversation_message.time > ?", array($lastMessageTime));
482 $messageList->sqlOrderBy
= "conversation_message.time ".CONVERSATION_LIST_DEFAULT_SORT_ORDER
;
483 $messageList->readObjects();
489 * @see \wcf\data\IMessageQuickReply::getPageNo()
491 public function getPageNo(DatabaseObject
$conversation) {
492 $sql = "SELECT COUNT(*) AS count
493 FROM wcf".WCF_N
."_conversation_message
494 WHERE conversationID = ?";
495 $statement = WCF
::getDB()->prepareStatement($sql);
496 $statement->execute(array($conversation->conversationID
));
497 $count = $statement->fetchArray();
499 return array(intval(ceil($count['count'] / CONVERSATION_MESSAGES_PER_PAGE
)), $count['count']);
503 * @see \wcf\data\IMessageQuickReply::getRedirectUrl()
505 public function getRedirectUrl(DatabaseObject
$conversation, DatabaseObject
$message) {
506 return LinkHandler
::getInstance()->getLink('Conversation', array(
507 'object' => $conversation,
508 'messageID' => $message->messageID
509 )).'#message'.$message->messageID
;
513 * @see \wcf\data\IMessageQuoteAction::validateSaveFullQuote()
515 public function validateSaveFullQuote() {
516 $this->message
= $this->getSingleObject();
518 if (!Conversation
::isParticipant(array($this->message
->conversationID
))) {
519 throw new PermissionDeniedException();
524 * @see \wcf\data\IMessageQuoteAction::saveFullQuote()
526 public function saveFullQuote() {
527 $quoteID = MessageQuoteManager
::getInstance()->addQuote(
528 'com.woltlab.wcf.conversation.message',
529 $this->message
->conversationID
,
530 $this->message
->messageID
,
531 $this->message
->getExcerpt(),
532 $this->message
->getMessage()
535 if ($quoteID === false) {
536 $removeQuoteID = MessageQuoteManager
::getInstance()->getQuoteID('com.woltlab.wcf.conversation.message', $this->message
->messageID
, $this->message
->getExcerpt(), $this->message
->getMessage());
537 MessageQuoteManager
::getInstance()->removeQuote($removeQuoteID);
540 $returnValues = array(
541 'count' => MessageQuoteManager
::getInstance()->countQuotes(),
542 'fullQuoteMessageIDs' => MessageQuoteManager
::getInstance()->getFullQuoteObjectIDs(array('com.woltlab.wcf.conversation.message'))
546 $returnValues['renderedQuote'] = MessageQuoteManager
::getInstance()->getQuoteComponents($quoteID);
549 return $returnValues;
553 * @see \wcf\data\IMessageQuoteAction::validateSaveQuote()
555 public function validateSaveQuote() {
556 $this->readString('message');
557 $this->readBoolean('renderQuote', true);
558 $this->message
= $this->getSingleObject();
560 if (!Conversation
::isParticipant(array($this->message
->conversationID
))) {
561 throw new PermissionDeniedException();
566 * @see \wcf\data\IMessageQuoteAction::saveQuote()
568 public function saveQuote() {
569 $quoteID = MessageQuoteManager
::getInstance()->addQuote('com.woltlab.wcf.conversation.message', $this->message
->conversationID
, $this->message
->messageID
, $this->parameters
['message'], false);
571 $returnValues = array(
572 'count' => MessageQuoteManager
::getInstance()->countQuotes(),
573 'fullQuoteMessageIDs' => MessageQuoteManager
::getInstance()->getFullQuoteObjectIDs(array('com.woltlab.wcf.conversation.message'))
576 if ($this->parameters
['renderQuote']) {
577 $returnValues['renderedQuote'] = MessageQuoteManager
::getInstance()->getQuoteComponents($quoteID);
580 return $returnValues;
584 * @see \wcf\data\IMessageQuoteAction::validateGetRenderedQuotes()
586 public function validateGetRenderedQuotes() {
587 $this->readInteger('parentObjectID');
589 $this->conversation
= new Conversation($this->parameters
['parentObjectID']);
590 if (!$this->conversation
->conversationID
) {
591 throw new UserInputException('parentObjectID');
596 * @see \wcf\data\IMessageQuoteAction::getRenderedQuotes()
598 public function getRenderedQuotes() {
599 $quotes = MessageQuoteManager
::getInstance()->getQuotesByParentObjectID('com.woltlab.wcf.conversation.message', $this->conversation
->conversationID
);
602 'template' => implode("\n\n", $quotes)
607 * @see \wcf\data\IAttachmentMessageQuickReplyAction::getAttachmentHandler()
609 public function getAttachmentHandler(DatabaseObject
$conversation) {
610 return new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $this->parameters
['tmpHash']);