Fixed time zone calculation issue
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / data / comment / CommentAction.class.php
1 <?php
2 namespace wcf\data\comment;
3 use wcf\data\comment\response\CommentResponse;
4 use wcf\data\comment\response\CommentResponseAction;
5 use wcf\data\comment\response\CommentResponseEditor;
6 use wcf\data\comment\response\CommentResponseList;
7 use wcf\data\comment\response\StructuredCommentResponse;
8 use wcf\data\object\type\ObjectTypeCache;
9 use wcf\data\user\UserProfile;
10 use wcf\data\AbstractDatabaseObjectAction;
11 use wcf\system\comment\CommentHandler;
12 use wcf\system\exception\PermissionDeniedException;
13 use wcf\system\exception\UserInputException;
14 use wcf\system\like\LikeHandler;
15 use wcf\system\user\activity\event\UserActivityEventHandler;
16 use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
17 use wcf\system\user\notification\object\CommentUserNotificationObject;
18 use wcf\system\user\notification\UserNotificationHandler;
19 use wcf\system\WCF;
20 use wcf\util\MessageUtil;
21
22 /**
23 * Executes comment-related actions.
24 *
25 * @author Alexander Ebert
26 * @copyright 2001-2014 WoltLab GmbH
27 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
28 * @package com.woltlab.wcf
29 * @subpackage data.comment
30 * @category Community Framework
31 */
32 class CommentAction extends AbstractDatabaseObjectAction {
33 /**
34 * @see \wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
35 */
36 protected $allowGuestAccess = array('loadComments');
37
38 /**
39 * @see \wcf\data\AbstractDatabaseObjectAction::$className
40 */
41 protected $className = 'wcf\data\comment\CommentEditor';
42
43 /**
44 * comment object
45 * @var \wcf\data\comment\Comment
46 */
47 protected $comment = null;
48
49 /**
50 * comment processor
51 * @var \wcf\system\comment\manager\ICommentManager
52 */
53 protected $commentProcessor = null;
54
55 /**
56 * response object
57 * @var \wcf\data\comment\response\CommentResponse
58 */
59 protected $response = null;
60
61 /**
62 * @see \wcf\data\AbstractDatabaseObjectAction::delete()
63 */
64 public function delete() {
65 if (empty($this->objects)) {
66 $this->readObjects();
67 }
68
69 // update counters
70 $processors = array();
71 $groupCommentIDs = $commentIDs = array();
72 foreach ($this->objects as $comment) {
73 if (!isset($processors[$comment->objectTypeID])) {
74 $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
75 $processors[$comment->objectTypeID] = $objectType->getProcessor();
76
77 $groupCommentIDs[$comment->objectTypeID] = array();
78 }
79
80 $processors[$comment->objectTypeID]->updateCounter($comment->objectID, -1 * ($comment->responses + 1));
81 $groupCommentIDs[$comment->objectTypeID][] = $comment->commentID;
82 $commentIDs[] = $comment->commentID;
83 }
84
85 if (!empty($groupCommentIDs)) {
86 $likeObjectIDs = array();
87 foreach ($groupCommentIDs as $objectTypeID => $objectIDs) {
88 // remove activity events
89 $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
90 if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.recentActivityEvent')) {
91 UserActivityEventHandler::getInstance()->removeEvents($objectType->objectType.'.recentActivityEvent', $objectIDs);
92 }
93
94 $likeObjectIDs = array_merge($likeObjectIDs, $objectIDs);
95
96 // delete notifications
97 $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
98 if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
99 UserNotificationHandler::getInstance()->deleteNotifications('comment', $objectType->objectType.'.notification', array(), $objectIDs);
100 }
101 }
102
103 // remove likes
104 LikeHandler::getInstance()->removeLikes('com.woltlab.wcf.comment', $likeObjectIDs);
105 }
106
107 // delete responses
108 if (!empty($commentIDs)) {
109 $commentResponseList = new CommentResponseList();
110 $commentResponseList->getConditionBuilder()->add('comment_response.commentID IN (?)', array($commentIDs));
111 $commentResponseList->readObjectIDs();
112 if (count($commentResponseList->getObjectIDs())) {
113 $action = new CommentResponseAction($commentResponseList->getObjectIDs(), 'delete', array(
114 'ignoreCounters' => true
115 ));
116 $action->executeAction();
117 }
118 }
119
120 return parent::delete();
121 }
122
123 /**
124 * Validates parameters to load comments.
125 */
126 public function validateLoadComments() {
127 $this->readInteger('lastCommentTime', false, 'data');
128 $this->readInteger('objectID', false, 'data');
129
130 $objectType = $this->validateObjectType();
131 $this->commentProcessor = $objectType->getProcessor();
132 if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
133 throw new PermissionDeniedException();
134 }
135 }
136
137 /**
138 * Returns parsed comments.
139 *
140 * @return array
141 */
142 public function loadComments() {
143 $commentList = CommentHandler::getInstance()->getCommentList($this->commentProcessor, $this->parameters['data']['objectTypeID'], $this->parameters['data']['objectID'], false);
144 $commentList->getConditionBuilder()->add("comment.time < ?", array($this->parameters['data']['lastCommentTime']));
145 $commentList->readObjects();
146
147 WCF::getTPL()->assign(array(
148 'commentList' => $commentList,
149 'likeData' => (MODULE_LIKE ? $commentList->getLikeData() : array())
150 ));
151
152 return array(
153 'lastCommentTime' => $commentList->getMinCommentTime(),
154 'template' => WCF::getTPL()->fetch('commentList')
155 );
156 }
157
158 /**
159 * Validates parameters to add a comment.
160 */
161 public function validateAddComment() {
162 CommentHandler::enforceFloodControl();
163
164 $this->readInteger('objectID', false, 'data');
165 $this->validateMessage();
166 $objectType = $this->validateObjectType();
167
168 // validate object id and permissions
169 $this->commentProcessor = $objectType->getProcessor();
170 if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
171 throw new PermissionDeniedException();
172 }
173 }
174
175 /**
176 * Adds a comment.
177 *
178 * @return array
179 */
180 public function addComment() {
181 // create comment
182 $comment = CommentEditor::create(array(
183 'objectTypeID' => $this->parameters['data']['objectTypeID'],
184 'objectID' => $this->parameters['data']['objectID'],
185 'time' => TIME_NOW,
186 'userID' => WCF::getUser()->userID,
187 'username' => WCF::getUser()->username,
188 'message' => $this->parameters['data']['message'],
189 'responses' => 0,
190 'responseIDs' => serialize(array())
191 ));
192
193 // update counter
194 $this->commentProcessor->updateCounter($this->parameters['data']['objectID'], 1);
195
196 // fire activity event
197 $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['data']['objectTypeID']);
198 if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.recentActivityEvent')) {
199 UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.recentActivityEvent', $comment->commentID);
200 }
201
202 // fire notification event
203 if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
204 $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification');
205 $userID = $notificationObjectType->getOwnerID($comment->commentID);
206 if ($userID != WCF::getUser()->userID) {
207 $notificationObject = new CommentUserNotificationObject($comment);
208
209 UserNotificationHandler::getInstance()->fireEvent('comment', $objectType->objectType.'.notification', $notificationObject, array($userID));
210 }
211 }
212
213 return array(
214 'template' => $this->renderComment($comment)
215 );
216 }
217
218 /**
219 * Validates parameters to add a response.
220 */
221 public function validateAddResponse() {
222 CommentHandler::enforceFloodControl();
223
224 $this->readInteger('objectID', false, 'data');
225 $this->validateMessage();
226
227 // validate comment id
228 $this->validateCommentID();
229
230 // validate object type id
231 $objectType = $this->validateObjectType();
232
233 // validate object id and permissions
234 $this->commentProcessor = $objectType->getProcessor();
235 if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
236 throw new PermissionDeniedException();
237 }
238 }
239
240 /**
241 * Adds a response.
242 *
243 * @return array
244 */
245 public function addResponse() {
246 // create response
247 $response = CommentResponseEditor::create(array(
248 'commentID' => $this->comment->commentID,
249 'time' => TIME_NOW,
250 'userID' => WCF::getUser()->userID,
251 'username' => WCF::getUser()->username,
252 'message' => $this->parameters['data']['message']
253 ));
254
255 // update response data
256 $responseIDs = $this->comment->getResponseIDs();
257 if (count($responseIDs) < 3) {
258 $responseIDs[] = $response->responseID;
259 }
260 $responses = $this->comment->responses + 1;
261
262 // update comment
263 $commentEditor = new CommentEditor($this->comment);
264 $commentEditor->update(array(
265 'responseIDs' => serialize($responseIDs),
266 'responses' => $responses
267 ));
268
269 // update counter
270 $this->commentProcessor->updateCounter($this->parameters['data']['objectID'], 1);
271
272 // fire activity event
273 $objectType = ObjectTypeCache::getInstance()->getObjectType($this->comment->objectTypeID);
274 if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) {
275 UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.response.recentActivityEvent', $response->responseID);
276 }
277
278 // fire notification event
279 if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification')) {
280 $notificationObject = new CommentResponseUserNotificationObject($response);
281 if ($this->comment->userID != WCF::getUser()->userID) {
282 UserNotificationHandler::getInstance()->fireEvent('commentResponse', $objectType->objectType.'.response.notification', $notificationObject, array($this->comment->userID));
283 }
284
285 // notify the container owner
286 if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
287 $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification');
288 $userID = $notificationObjectType->getOwnerID($this->comment->commentID);
289
290 if ($userID != $this->comment->userID && $userID != WCF::getUser()->userID) {
291 UserNotificationHandler::getInstance()->fireEvent('commentResponseOwner', $objectType->objectType.'.response.notification', $notificationObject, array($userID));
292 }
293 }
294 }
295
296 return array(
297 'commentID' => $this->comment->commentID,
298 'template' => $this->renderResponse($response),
299 'responses' => $responses
300 );
301 }
302
303 /**
304 * Validates parameters to edit a comment or a response.
305 */
306 public function validatePrepareEdit() {
307 // validate comment id or response id
308 try {
309 $this->validateCommentID();
310 }
311 catch (UserInputException $e) {
312 try {
313 $this->validateResponseID();
314 }
315 catch (UserInputException $e) {
316 throw new UserInputException('objectIDs');
317 }
318 }
319
320 // validate object type id
321 $objectType = $this->validateObjectType();
322
323 // validate object id and permissions
324 $this->commentProcessor = $objectType->getProcessor();
325 if ($this->comment !== null) {
326 if (!$this->commentProcessor->canEditComment($this->comment)) {
327 throw new PermissionDeniedException();
328 }
329 }
330 else {
331 if (!$this->commentProcessor->canEditResponse($this->response)) {
332 throw new PermissionDeniedException();
333 }
334 }
335 }
336
337 /**
338 * Prepares editing of a comment or a response.
339 *
340 * @return array
341 */
342 public function prepareEdit() {
343 $message = '';
344 if ($this->comment !== null) {
345 $message = $this->comment->message;
346 }
347 else {
348 $message = $this->response->message;
349 }
350
351 $returnValues = array(
352 'action' => 'prepare',
353 'message' => $message
354 );
355 if ($this->comment !== null) {
356 $returnValues['commentID'] = $this->comment->commentID;
357 }
358 else {
359 $returnValues['responseID'] = $this->response->responseID;
360 }
361
362 return $returnValues;
363 }
364
365 /**
366 * @see \wcf\data\comment\CommentAction::validatePrepareEdit()
367 */
368 public function validateEdit() {
369 $this->validatePrepareEdit();
370
371 $this->validateMessage();
372 }
373
374 /**
375 * Edits a comment or response.
376 *
377 * @return array
378 */
379 public function edit() {
380 $returnValues = array(
381 'action' => 'saved',
382 );
383
384 if ($this->response === null) {
385 $editor = new CommentEditor($this->comment);
386 $editor->update(array(
387 'message' => $this->parameters['data']['message']
388 ));
389 $comment = new Comment($this->comment->commentID);
390 $returnValues['commentID'] = $this->comment->commentID;
391 $returnValues['message'] = $comment->getFormattedMessage();
392 }
393 else {
394 $editor = new CommentResponseEditor($this->response);
395 $editor->update(array(
396 'message' => $this->parameters['data']['message']
397 ));
398 $response = new CommentResponse($this->response->responseID);
399 $returnValues['responseID'] = $this->response->responseID;
400 $returnValues['message'] = $response->getFormattedMessage();
401 }
402
403 return $returnValues;
404 }
405
406 /**
407 * Validates parameters to remove a comment or response.
408 */
409 public function validateRemove() {
410 // validate comment id or response id
411 try {
412 $this->validateCommentID();
413 }
414 catch (UserInputException $e) {
415 try {
416 $this->validateResponseID();
417 }
418 catch (UserInputException $e) {
419 throw new UserInputException('objectIDs');
420 }
421 }
422
423 // validate object type id
424 $objectType = $this->validateObjectType();
425
426 // validate object id and permissions
427 $this->commentProcessor = $objectType->getProcessor();
428 if ($this->comment !== null) {
429 if (!$this->commentProcessor->canDeleteComment($this->comment)) {
430 throw new PermissionDeniedException();
431 }
432 }
433 else {
434 if (!$this->commentProcessor->canDeleteResponse($this->response)) {
435 throw new PermissionDeniedException();
436 }
437 }
438 }
439
440 /**
441 * Removes a comment or response.
442 *
443 * @return array
444 */
445 public function remove() {
446 if ($this->comment !== null) {
447 $objectAction = new CommentAction(array($this->comment), 'delete');
448 $objectAction->executeAction();
449
450 return array(
451 'commentID' => $this->comment->commentID
452 );
453 }
454 else {
455 $objectAction = new CommentResponseAction(array($this->response), 'delete');
456 $objectAction->executeAction();
457
458 return array(
459 'responseID' => $this->response->responseID
460 );
461 }
462 }
463
464 /**
465 * Renders a comment.
466 *
467 * @param \wcf\data\comment\Comment $comment
468 * @return string
469 */
470 protected function renderComment(Comment $comment) {
471 $comment = new StructuredComment($comment);
472 $comment->setIsDeletable($this->commentProcessor->canDeleteComment($comment->getDecoratedObject()));
473 $comment->setIsEditable($this->commentProcessor->canEditComment($comment->getDecoratedObject()));
474
475 // set user profile
476 $userProfile = UserProfile::getUserProfile($comment->userID);
477 $comment->setUserProfile($userProfile);
478
479 WCF::getTPL()->assign(array(
480 'commentList' => array($comment)
481 ));
482 return WCF::getTPL()->fetch('commentList');
483 }
484
485 /**
486 * Renders a response.
487 *
488 * @param \wcf\data\comment\response\CommentResponse $response
489 * @return string
490 */
491 protected function renderResponse(CommentResponse $response) {
492 $response = new StructuredCommentResponse($response);
493 $response->setIsDeletable($this->commentProcessor->canDeleteResponse($response->getDecoratedObject()));
494 $response->setIsEditable($this->commentProcessor->canEditResponse($response->getDecoratedObject()));
495
496 // set user profile
497 $userProfile = UserProfile::getUserProfile($response->userID);
498 $response->setUserProfile($userProfile);
499
500 // render response
501 WCF::getTPL()->assign(array(
502 'responseList' => array($response)
503 ));
504 return WCF::getTPL()->fetch('commentResponseList');
505 }
506
507 /**
508 * Validates message parameter.
509 */
510 protected function validateMessage() {
511 $this->readString('message', false, 'data');
512 $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
513
514 if (empty($this->parameters['data']['message'])) {
515 throw new UserInputException('message');
516 }
517 }
518
519 /**
520 * Validates object type id parameter.
521 *
522 * @return \wcf\data\object\type\ObjectType
523 */
524 protected function validateObjectType() {
525 $this->readInteger('objectTypeID', false, 'data');
526
527 $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['data']['objectTypeID']);
528 if ($objectType === null) {
529 throw new UserInputException('objectTypeID');
530 }
531
532 return $objectType;
533 }
534
535 /**
536 * Validates comment id parameter.
537 */
538 protected function validateCommentID() {
539 $this->readInteger('commentID', false, 'data');
540
541 $this->comment = new Comment($this->parameters['data']['commentID']);
542 if ($this->comment === null || !$this->comment->commentID) {
543 throw new UserInputException('commentID');
544 }
545 }
546
547 /**
548 * Validates response id parameter.
549 */
550 protected function validateResponseID() {
551 if (isset($this->parameters['data']['responseID'])) {
552 $this->response = new CommentResponse($this->parameters['data']['responseID']);
553 }
554 if ($this->response === null || !$this->response->responseID) {
555 throw new UserInputException('responseID');
556 }
557 }
558
559 /**
560 * Returns the comment object.
561 *
562 * @return \wcf\data\comment\Comment
563 */
564 public function getComment() {
565 return $this->comment;
566 }
567
568 /**
569 * Returns the comment response object.
570 *
571 * @return \wcf\data\comment\response\CommentResponse
572 */
573 public function getResponse() {
574 return $this->response;
575 }
576
577 /**
578 * Returns the comment manager.
579 *
580 * @return \wcf\system\comment\manager\ICommentManager
581 */
582 public function getCommentManager() {
583 return $this->commentProcessor;
584 }
585 }