Fixed smiley detection in inline editing
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / data / conversation / message / ConversationMessageAction.class.php
1 <?php
2 namespace wcf\data\conversation\message;
3 use wcf\data\conversation\Conversation;
4 use wcf\data\conversation\ConversationEditor;
5 use wcf\data\smiley\SmileyCache;
6 use wcf\data\AbstractDatabaseObjectAction;
7 use wcf\data\DatabaseObject;
8 use wcf\data\IExtendedMessageQuickReplyAction;
9 use wcf\data\IMessageInlineEditorAction;
10 use wcf\data\IMessageQuoteAction;
11 use wcf\system\attachment\AttachmentHandler;
12 use wcf\system\bbcode\BBCodeParser;
13 use wcf\system\bbcode\PreParser;
14 use wcf\system\exception\PermissionDeniedException;
15 use wcf\system\exception\UserInputException;
16 use wcf\system\message\censorship\Censorship;
17 use wcf\system\message\quote\MessageQuoteManager;
18 use wcf\system\message\QuickReplyManager;
19 use wcf\system\request\LinkHandler;
20 use wcf\system\search\SearchIndexManager;
21 use wcf\system\user\notification\object\ConversationMessageUserNotificationObject;
22 use wcf\system\user\notification\UserNotificationHandler;
23 use wcf\system\user\storage\UserStorageHandler;
24 use wcf\system\WCF;
25 use wcf\util\StringUtil;
26
27 /**
28 * Executes message-related actions.
29 *
30 * @author Marcel Werk
31 * @copyright 2001-2012 WoltLab GmbH
32 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
33 * @package com.woltlab.wcf.conversation
34 * @subpackage data.conversation.message
35 * @category Community Framework
36 */
37 class ConversationMessageAction extends AbstractDatabaseObjectAction implements IExtendedMessageQuickReplyAction, IMessageInlineEditorAction, IMessageQuoteAction {
38 /**
39 * @see wcf\data\AbstractDatabaseObjectAction::$className
40 */
41 protected $className = 'wcf\data\conversation\message\ConversationMessageEditor';
42
43 /**
44 * conversation object
45 * @var wcf\data\conversation\Conversation
46 */
47 public $conversation = null;
48
49 /**
50 * conversation message object
51 * @var wcf\data\conversation\message\ConversationMessage
52 */
53 public $message = null;
54
55 /**
56 * @see wcf\data\AbstractDatabaseObjectAction::create()
57 */
58 public function create() {
59 // count attachments
60 if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
61 $this->parameters['data']['attachments'] = count($this->parameters['attachmentHandler']);
62 }
63
64 if (LOG_IP_ADDRESS) {
65 // add ip address
66 if (!isset($this->parameters['data']['ipAddress'])) {
67 $this->parameters['data']['ipAddress'] = WCF::getSession()->ipAddress;
68 }
69 }
70 else {
71 // do not track ip address
72 if (isset($this->parameters['data']['ipAddress'])) {
73 unset($this->parameters['data']['ipAddress']);
74 }
75 }
76
77 // create message
78 $message = parent::create();
79
80 // get conversation
81 $conversation = (isset($this->parameters['converation']) ? $this->parameters['converation'] : new Conversation($message->conversationID));
82 $conversationEditor = new ConversationEditor($conversation);
83
84 if (empty($this->parameters['isFirstPost'])) {
85 // update last message
86 $conversationEditor->addMessage($message);
87
88 // fire notification event
89 if (!$conversation->isDraft) {
90 $notificationRecipients = array_diff($conversation->getParticipantIDs(true), array($message->userID)); // don't notify message author
91 if (!empty($notificationRecipients)) {
92 UserNotificationHandler::getInstance()->fireEvent('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', new ConversationMessageUserNotificationObject($message), $notificationRecipients);
93 }
94 }
95
96 // make invisible participant visible
97 $sql = "UPDATE wcf".WCF_N."_conversation_to_user
98 SET isInvisible = 0
99 WHERE participantID = ?
100 AND conversationID = ?";
101 $statement = WCF::getDB()->prepareStatement($sql);
102 $statement->execute(array($message->userID, $conversation->conversationID));
103
104 // reset visibility if it was hidden but not left
105 $sql = "UPDATE wcf".WCF_N."_conversation_to_user
106 SET hideConversation = ?
107 WHERE conversationID = ?
108 AND hideConversation = ?";
109 $statement = WCF::getDB()->prepareStatement($sql);
110 $statement->execute(array(
111 Conversation::STATE_DEFAULT,
112 $conversation->conversationID,
113 Conversation::STATE_HIDDEN
114 ));
115 }
116
117 // reset storage
118 UserStorageHandler::getInstance()->reset($conversation->getParticipantIDs(), 'unreadConversationCount');
119
120 // update search index
121 SearchIndexManager::getInstance()->add('com.woltlab.wcf.conversation.message', $message->messageID, $message->message, (!empty($this->parameters['isFirstPost']) ? $conversation->subject : ''), $message->time, $message->userID, $message->username);
122
123 // update attachments
124 if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
125 $this->parameters['attachmentHandler']->updateObjectID($message->messageID);
126 }
127
128 // clear quotes
129 if (isset($this->parameters['removeQuoteIDs']) && !empty($this->parameters['removeQuoteIDs'])) {
130 MessageQuoteManager::getInstance()->markQuotesForRemoval($this->parameters['removeQuoteIDs']);
131 }
132 MessageQuoteManager::getInstance()->removeMarkedQuotes();
133
134 // return new message
135 return $message;
136 }
137
138 /**
139 * @see wcf\data\AbstractDatabaseObjectAction::update()
140 */
141 public function update() {
142 // count attachments
143 if (isset($this->parameters['attachmentHandler']) && $this->parameters['attachmentHandler'] !== null) {
144 $this->parameters['data']['attachments'] = count($this->parameters['attachmentHandler']);
145 }
146
147 parent::update();
148
149 // update search index
150 foreach ($this->objects as $message) {
151 $conversation = $message->getConversation();
152 SearchIndexManager::getInstance()->update('com.woltlab.wcf.conversation.message', $message->messageID, $message->message, ($conversation->firstMessageID == $message->messageID ? $conversation->subject : ''), $message->time, $message->userID, $message->username);
153 }
154 }
155
156 /**
157 * @see wcf\data\AbstractDatabaseObjectAction::delete()
158 */
159 public function delete() {
160 $count = parent::delete();
161
162 $attachmentMessageIDs = array();
163 foreach ($this->objects as $message) {
164 if ($message->attachments) {
165 $attachmentMessageIDs[] = $message->messageID;
166
167 }
168 }
169
170 // @todo: modification log, reports
171
172 if (!empty($this->objectIDs)) {
173 // delete notifications
174 UserNotificationHandler::getInstance()->deleteNotifications('conversationMessage', 'com.woltlab.wcf.conversation.message.notification', array(), $this->objectIDs);
175
176 // update search index
177 SearchIndexManager::getInstance()->delete('com.woltlab.wcf.conversation.message', $this->objectIDs);
178 }
179
180 // remove attachments
181 if (!empty($attachmentMessageIDs)) {
182 AttachmentHandler::removeAttachments('com.woltlab.wcf.conversation.message', $attachmentMessageIDs);
183 }
184
185 return $count;
186 }
187
188 /**
189 * @see wcf\data\IMessageQuickReply::validateQuickReply()
190 */
191 public function validateQuickReply() {
192 QuickReplyManager::getInstance()->setAllowedBBCodes(explode(',', WCF::getSession()->getPermission('user.message.allowedBBCodes')));
193 QuickReplyManager::getInstance()->validateParameters($this, $this->parameters, 'wcf\data\conversation\Conversation');
194 }
195
196 /**
197 * @see wcf\data\IMessageQuickReply::quickReply()
198 */
199 public function quickReply() {
200 return QuickReplyManager::getInstance()->createMessage(
201 $this,
202 $this->parameters,
203 'wcf\data\conversation\ConversationAction',
204 'wcf\data\conversation\message\ViewableConversationMessageList',
205 'conversationMessageList',
206 CONVERSATION_LIST_DEFAULT_SORT_ORDER
207 );
208 }
209
210 /**
211 * @see wcf\data\IExtendedMessageQuickReplyAction::validateJumpToExtended()
212 */
213 public function validateJumpToExtended() {
214 $this->readInteger('containerID');
215 $this->readString('message', true);
216
217 $this->conversation = new Conversation($this->parameters['containerID']);
218 if (!$this->conversation->conversationID) {
219 throw new UserInputException('containerID');
220 }
221 else if ($this->conversation->isClosed || !Conversation::isParticipant(array($this->conversation->conversationID))) {
222 throw new PermissionDeniedException();
223 }
224
225 // editing existing message
226 if (isset($this->parameters['messageID'])) {
227 $this->message = new ConversationMessage(intval($this->parameters['messageID']));
228 if (!$this->message->messageID || ($this->message->conversationID != $this->conversation->conversationID)) {
229 throw new UserInputException('messageID');
230 }
231
232 if (!$this->message->canEdit()) {
233 throw new PermissionDeniedException();
234 }
235 }
236 }
237
238 /**
239 * @see wcf\data\IExtendedMessageQuickReplyAction::jumpToExtended()
240 */
241 public function jumpToExtended() {
242 // quick reply
243 if ($this->message === null) {
244 QuickReplyManager::getInstance()->setMessage('conversation', $this->conversation->conversationID, $this->parameters['message']);
245 $url = LinkHandler::getInstance()->getLink('ConversationMessageAdd', array('id' => $this->conversation->conversationID));
246 }
247 else {
248 // editing message
249 QuickReplyManager::getInstance()->setMessage('conversationMessage', $this->message->messageID, $this->parameters['message']);
250 $url = LinkHandler::getInstance()->getLink('ConversationMessageEdit', array('id' => $this->message->messageID));
251 }
252
253 // redirect
254 return array(
255 'url' => $url
256 );
257 }
258
259 /**
260 * @see wcf\data\IMessageInlineEditorAction::validateBeginEdit()
261 */
262 public function validateBeginEdit() {
263 $this->readInteger('containerID');
264 $this->readInteger('objectID');
265
266 $this->conversation = new Conversation($this->parameters['containerID']);
267 if (!$this->conversation->conversationID) {
268 throw new UserInputException('containerID');
269 }
270
271 if ($this->conversation->isClosed || !Conversation::isParticipant(array($this->conversation->conversationID))) {
272 throw new PermissionDeniedException();
273 }
274
275 $this->message = new ConversationMessage($this->parameters['objectID']);
276 if (!$this->message->messageID) {
277 throw new UserInputException('objectID');
278 }
279
280 if (!$this->message->canEdit()) {
281 throw new PermissionDeniedException();
282 }
283 }
284
285 /**
286 * @see wcf\data\IMessageInlineEditorAction::beginEdit()
287 */
288 public function beginEdit() {
289 WCF::getTPL()->assign(array(
290 'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies(),
291 'message' => $this->message,
292 'wysiwygSelector' => 'messageEditor'.$this->message->messageID
293 ));
294
295 return array(
296 'actionName' => 'beginEdit',
297 'template' => WCF::getTPL()->fetch('conversationMessageInlineEditor')
298 );
299 }
300
301 /**
302 * @see wcf\data\IMessageInlineEditorAction::validateSave()
303 */
304 public function validateSave() {
305 $this->readString('message', false, 'data');
306
307 $this->validateBeginEdit();
308 $this->validateMessage($this->conversation, $this->parameters['data']['message']);
309 }
310
311 /**
312 * @see wcf\data\IMessageInlineEditorAction::save()
313 */
314 public function save() {
315 $messageEditor = new ConversationMessageEditor($this->message);
316 $messageEditor->update(array(
317 'message' => PreParser::getInstance()->parse($this->parameters['data']['message'], explode(',', WCF::getSession()->getPermission('user.message.allowedBBCodes')))
318 ));
319
320 // load new message
321 $this->message = new ConversationMessage($this->message->messageID);
322 $this->message->getAttachments();
323
324 return array(
325 'actionName' => 'save',
326 'message' => $this->message->getFormattedMessage()
327 );
328 }
329
330 /**
331 * @see wcf\data\IMessageQuickReply::validateContainer()
332 */
333 public function validateContainer(DatabaseObject $conversation) {
334 if (!$conversation->conversationID) {
335 throw new UserInputException('objectID');
336 }
337 else if ($conversation->isClosed || !Conversation::isParticipant(array($conversation->conversationID))) {
338 throw new PermissionDeniedException();
339 }
340 }
341
342 /**
343 * @see wcf\data\IMessageQuickReplyAction::validateMessage()
344 */
345 public function validateMessage(DatabaseObject $container, $message) {
346 if (StringUtil::length($message) > WCF::getSession()->getPermission('user.conversation.maxLength')) {
347 throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', array('maxTextLength' => WCF::getSession()->getPermission('user.conversation.maxLength'))));
348 }
349
350 // search for disallowed bbcodes
351 $disallowedBBCodes = BBCodeParser::getInstance()->validateBBCodes($message, explode(',', WCF::getSession()->getPermission('user.message.allowedBBCodes')));
352 if (!empty($disallowedBBCodes)) {
353 throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', array('disallowedBBCodes' => $disallowedBBCodes)));
354 }
355
356 // search for censored words
357 if (ENABLE_CENSORSHIP) {
358 $result = Censorship::getInstance()->test($message);
359 if ($result) {
360 throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', array('censoredWords' => $result)));
361 }
362 }
363 }
364
365 /**
366 * @see wcf\data\IMessageQuickReply::getPageNo()
367 */
368 public function getPageNo(DatabaseObject $conversation) {
369 $sql = "SELECT COUNT(*) AS count
370 FROM wcf".WCF_N."_conversation_message
371 WHERE conversationID = ?";
372 $statement = WCF::getDB()->prepareStatement($sql);
373 $statement->execute(array($conversation->conversationID));
374 $count = $statement->fetchArray();
375
376 return array(intval(ceil($count['count'] / CONVERSATION_MESSAGES_PER_PAGE)), $count['count']);
377 }
378
379 /**
380 * @see wcf\data\IMessageQuickReply::getRedirectUrl()
381 */
382 public function getRedirectUrl(DatabaseObject $conversation, DatabaseObject $message) {
383 return LinkHandler::getInstance()->getLink('Conversation', array(
384 'object' => $conversation,
385 'messageID' => $message->messageID
386 )).'#message'.$message->messageID;
387 }
388
389 /**
390 * @see wcf\data\IMessageQuoteAction::validateSaveFullQuote()
391 */
392 public function validateSaveFullQuote() {
393 $this->message = $this->getSingleObject();
394
395 if (!Conversation::isParticipant(array($this->message->conversationID))) {
396 throw new PermissionDeniedException();
397 }
398 }
399
400 /**
401 * @see wcf\data\IMessageQuoteAction::saveFullQuote()
402 */
403 public function saveFullQuote() {
404 if (!MessageQuoteManager::getInstance()->addQuote('com.woltlab.wcf.conversation.message', $this->message->messageID, $this->message->getExcerpt(), $this->message->getMessage())) {
405 $quoteID = MessageQuoteManager::getInstance()->getQuoteID('com.woltlab.wcf.conversation.message', $this->message->conversationID, $this->message->messageID, $this->message->getExcerpt(), $this->message->getMessage());
406 MessageQuoteManager::getInstance()->removeQuote($quoteID);
407 }
408
409 return array(
410 'count' => MessageQuoteManager::getInstance()->countQuotes(),
411 'fullQuoteMessageIDs' => MessageQuoteManager::getInstance()->getFullQuoteObjectIDs(array('com.woltlab.wcf.conversation.message'))
412 );
413 }
414
415 /**
416 * @see wcf\data\IMessageQuoteAction::validateSaveQuote()
417 */
418 public function validateSaveQuote() {
419 $this->readString('message');
420 $this->message = $this->getSingleObject();
421
422 if (!Conversation::isParticipant(array($this->message->conversationID))) {
423 throw new PermissionDeniedException();
424 }
425 }
426
427 /**
428 * @see wcf\data\IMessageQuoteAction::saveQuote()
429 */
430 public function saveQuote() {
431 MessageQuoteManager::getInstance()->addQuote('com.woltlab.wcf.conversation.message', $this->message->conversationID, $this->message->messageID, $this->parameters['message']);
432
433 return array(
434 'count' => MessageQuoteManager::getInstance()->countQuotes(),
435 'fullQuoteMessageIDs' => MessageQuoteManager::getInstance()->getFullQuoteObjectIDs(array('com.woltlab.wcf.conversation.message'))
436 );
437 }
438
439 /**
440 * @see wcf\data\IMessageQuoteAction::validateGetRenderedQuotes()
441 */
442 public function validateGetRenderedQuotes() {
443 $this->readInteger('parentObjectID');
444
445 $this->conversation = new Conversation($this->parameters['parentObjectID']);
446 if (!$this->conversation->conversationID) {
447 throw new UserInputException('parentObjectID');
448 }
449 }
450
451 /**
452 * @see wcf\data\IMessageQuoteAction::getRenderedQuotes()
453 */
454 public function getRenderedQuotes() {
455 $quotes = MessageQuoteManager::getInstance()->getQuotesByParentObjectID('com.woltlab.wbb.conversation.message', $this->conversation->conversationID);
456
457 return array(
458 'template' => implode("\n\n", $quotes)
459 );
460 }
461 }