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