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