1 | <?php |
||
2 | |||
3 | namespace SilverStripe\Comments\Controllers; |
||
4 | |||
5 | use SilverStripe\CMS\Model\SiteTree; |
||
6 | use SilverStripe\Comments\Extensions\CommentsExtension; |
||
7 | use SilverStripe\Comments\Forms\CommentForm; |
||
8 | use SilverStripe\Comments\Model\Comment; |
||
9 | use SilverStripe\Control\Controller; |
||
10 | use SilverStripe\Control\Director; |
||
11 | use SilverStripe\Control\HTTP; |
||
12 | use SilverStripe\Control\HTTPRequest; |
||
13 | use SilverStripe\Control\HTTPResponse; |
||
14 | use SilverStripe\Control\HTTPResponse_Exception; |
||
15 | use SilverStripe\Control\RSS\RSSFeed; |
||
16 | use SilverStripe\Core\Injector\Injector; |
||
17 | use SilverStripe\Forms\Form; |
||
18 | use SilverStripe\ORM\DataObject; |
||
19 | use SilverStripe\ORM\FieldType\DBHTMLText; |
||
20 | use SilverStripe\ORM\PaginatedList; |
||
21 | use SilverStripe\Security\Security; |
||
22 | |||
23 | /** |
||
24 | * @package comments |
||
25 | */ |
||
26 | class CommentingController extends Controller |
||
27 | { |
||
28 | /** |
||
29 | * {@inheritDoc} |
||
30 | */ |
||
31 | private static $allowed_actions = [ |
||
32 | 'delete', |
||
33 | 'spam', |
||
34 | 'ham', |
||
35 | 'approve', |
||
36 | 'rss', |
||
37 | 'CommentsForm', |
||
38 | 'reply', |
||
39 | 'doPostComment', |
||
40 | 'doPreviewComment', |
||
41 | ]; |
||
42 | |||
43 | /** |
||
44 | * {@inheritDoc} |
||
45 | */ |
||
46 | private static $url_handlers = [ |
||
47 | 'reply/$ParentCommentID//$ID/$OtherID' => 'reply', |
||
48 | ]; |
||
49 | |||
50 | /** |
||
51 | * Fields required for this form |
||
52 | * |
||
53 | * @var array |
||
54 | * @config |
||
55 | */ |
||
56 | private static $required_fields = [ |
||
57 | 'Name', |
||
58 | 'Email', |
||
59 | 'Comment', |
||
60 | ]; |
||
61 | |||
62 | /** |
||
63 | * Parent class this commenting form is for |
||
64 | * |
||
65 | * @var string |
||
66 | */ |
||
67 | private $parentClass = ''; |
||
68 | |||
69 | /** |
||
70 | * The record this commenting form is for |
||
71 | * |
||
72 | * @var DataObject |
||
73 | */ |
||
74 | private $ownerRecord; |
||
75 | |||
76 | /** |
||
77 | * Parent controller record |
||
78 | * |
||
79 | * @var Controller |
||
80 | */ |
||
81 | private $ownerController; |
||
82 | |||
83 | /** |
||
84 | * Backup url to return to |
||
85 | * |
||
86 | * @var string |
||
87 | */ |
||
88 | protected $fallbackReturnURL; |
||
89 | |||
90 | /** |
||
91 | * Set the parent class name to use |
||
92 | * |
||
93 | * @param string $class |
||
94 | */ |
||
95 | public function setParentClass($class) |
||
96 | { |
||
97 | $this->parentClass = $this->encodeClassName($class); |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Get the parent class name used |
||
102 | * |
||
103 | * @return string |
||
104 | */ |
||
105 | public function getParentClass() |
||
106 | { |
||
107 | return $this->decodeClassName($this->parentClass); |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Encode a fully qualified class name to a URL-safe version |
||
112 | * |
||
113 | * @param string $input |
||
114 | * @return string |
||
115 | */ |
||
116 | public function encodeClassName($input) |
||
117 | { |
||
118 | return str_replace('\\', '-', $input); |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Decode an "encoded" fully qualified class name back to its original |
||
123 | * |
||
124 | * @param string $input |
||
125 | * @return string |
||
126 | */ |
||
127 | public function decodeClassName($input) |
||
128 | { |
||
129 | return str_replace('-', '\\', $input); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Set the record this controller is working on |
||
134 | * |
||
135 | * @param DataObject $record |
||
136 | */ |
||
137 | public function setOwnerRecord($record) |
||
138 | { |
||
139 | $this->ownerRecord = $record; |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Get the record |
||
144 | * |
||
145 | * @return DataObject |
||
146 | */ |
||
147 | public function getOwnerRecord() |
||
148 | { |
||
149 | return $this->ownerRecord; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Set the parent controller |
||
154 | * |
||
155 | * @param Controller $controller |
||
156 | */ |
||
157 | public function setOwnerController($controller) |
||
158 | { |
||
159 | $this->ownerController = $controller; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Get the parent controller |
||
164 | * |
||
165 | * @return Controller |
||
166 | */ |
||
167 | public function getOwnerController() |
||
168 | { |
||
169 | return $this->ownerController; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Get the commenting option for the current state |
||
174 | * |
||
175 | * @param string $key |
||
176 | * @return mixed Result if the setting is available, or null otherwise |
||
177 | */ |
||
178 | public function getOption($key) |
||
179 | { |
||
180 | // If possible use the current record |
||
181 | if ($record = $this->getOwnerRecord()) { |
||
182 | /** @var DataObject|CommentsExtension $record */ |
||
183 | return $record->getCommentsOption($key); |
||
184 | } |
||
185 | |||
186 | // Otherwise a singleton of that record |
||
187 | if ($class = $this->getParentClass()) { |
||
188 | return singleton($class)->getCommentsOption($key); |
||
189 | } |
||
190 | |||
191 | // Otherwise just use the default options |
||
192 | return singleton(CommentsExtension::class)->getCommentsOption($key); |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * Returns all the commenting options for the current instance. |
||
197 | * |
||
198 | * @return array |
||
199 | */ |
||
200 | public function getOptions() |
||
201 | { |
||
202 | if ($record = $this->getOwnerRecord()) { |
||
203 | /** @var DataObject|CommentsExtension $record */ |
||
204 | return $record->getCommentsOptions(); |
||
205 | } |
||
206 | |||
207 | // Otherwise a singleton of that record |
||
208 | if ($class = $this->getParentClass()) { |
||
209 | return singleton($class)->getCommentsOptions(); |
||
210 | } |
||
211 | |||
212 | // Otherwise just use the default options |
||
213 | return singleton(CommentsExtension::class)->getCommentsOptions(); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Workaround for generating the link to this controller |
||
218 | * |
||
219 | * @param string $action |
||
220 | * @param int $id |
||
221 | * @param string $other |
||
222 | * @return string |
||
223 | */ |
||
224 | public function Link($action = '', $id = '', $other = '') |
||
225 | { |
||
226 | return Controller::join_links(Director::baseURL(), 'comments', $action, $id, $other); |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * Outputs the RSS feed of comments |
||
231 | * |
||
232 | * @return DBHTMLText |
||
233 | */ |
||
234 | public function rss() |
||
235 | { |
||
236 | return $this->getFeed($this->request)->outputToBrowser(); |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * Return an RSSFeed of comments for a given set of comments or all |
||
241 | * comments on the website. |
||
242 | * |
||
243 | * @param HTTPRequest |
||
244 | * |
||
245 | * @return RSSFeed |
||
246 | */ |
||
247 | public function getFeed(HTTPRequest $request) |
||
248 | { |
||
249 | $link = $this->Link('rss'); |
||
250 | $class = $this->decodeClassName($request->param('ID')); |
||
251 | $id = $request->param('OtherID'); |
||
252 | |||
253 | // Support old pageid param |
||
254 | if (!$id && !$class && ($id = $request->getVar('pageid'))) { |
||
255 | $class = SiteTree::class; |
||
256 | } |
||
257 | |||
258 | $comments = Comment::get()->filter([ |
||
259 | 'Moderated' => 1, |
||
260 | 'IsSpam' => 0, |
||
261 | ]); |
||
262 | |||
263 | // Check if class filter |
||
264 | if ($class) { |
||
265 | if (!is_subclass_of($class, DataObject::class) || !$class::has_extension(CommentsExtension::class)) { |
||
266 | return $this->httpError(404); |
||
267 | } |
||
268 | $this->setParentClass($class); |
||
269 | $comments = $comments->filter('ParentClass', $class); |
||
270 | $link = Controller::join_links($link, $this->encodeClassName($class)); |
||
271 | |||
272 | // Check if id filter |
||
273 | if ($id) { |
||
274 | $comments = $comments->filter('ParentID', $id); |
||
275 | $link = Controller::join_links($link, $id); |
||
276 | $this->setOwnerRecord(DataObject::get_by_id($class, $id)); |
||
277 | } |
||
278 | } |
||
279 | |||
280 | $title = _t(__CLASS__ . '.RSSTITLE', "Comments RSS Feed"); |
||
281 | $comments = PaginatedList::create($comments, $request); |
||
282 | $comments->setPageLength($this->getOption('comments_per_page')); |
||
283 | |||
284 | return RSSFeed::create( |
||
285 | $comments, |
||
286 | $link, |
||
287 | $title, |
||
288 | $link, |
||
289 | 'Title', |
||
290 | 'EscapedComment', |
||
291 | 'AuthorName' |
||
292 | ); |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Deletes a given {@link Comment} via the URL. |
||
297 | */ |
||
298 | public function delete() |
||
299 | { |
||
300 | $comment = $this->getComment(); |
||
301 | if (!$comment) { |
||
302 | return $this->httpError(404); |
||
303 | } |
||
304 | if (!$comment->canDelete()) { |
||
305 | return Security::permissionFailure($this, 'You do not have permission to delete this comment'); |
||
306 | } |
||
307 | if (!$comment->getSecurityToken()->checkRequest($this->request)) { |
||
308 | return $this->httpError(400); |
||
309 | } |
||
310 | |||
311 | $comment->delete(); |
||
312 | |||
313 | return $this->request->isAjax() |
||
314 | ? true |
||
315 | : $this->redirectBack(); |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Marks a given {@link Comment} as spam. Removes the comment from display |
||
320 | */ |
||
321 | public function spam() |
||
322 | { |
||
323 | $comment = $this->getComment(); |
||
324 | if (!$comment) { |
||
325 | return $this->httpError(404); |
||
326 | } |
||
327 | if (!$comment->canEdit()) { |
||
328 | return Security::permissionFailure($this, 'You do not have permission to edit this comment'); |
||
329 | } |
||
330 | if (!$comment->getSecurityToken()->checkRequest($this->request)) { |
||
331 | return $this->httpError(400); |
||
332 | } |
||
333 | |||
334 | $comment->markSpam(); |
||
335 | return $this->renderChangedCommentState($comment); |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * Marks a given {@link Comment} as ham (not spam). |
||
340 | */ |
||
341 | public function ham() |
||
342 | { |
||
343 | $comment = $this->getComment(); |
||
344 | if (!$comment) { |
||
345 | return $this->httpError(404); |
||
346 | } |
||
347 | if (!$comment->canEdit()) { |
||
348 | return Security::permissionFailure($this, 'You do not have permission to edit this comment'); |
||
349 | } |
||
350 | if (!$comment->getSecurityToken()->checkRequest($this->request)) { |
||
351 | return $this->httpError(400); |
||
352 | } |
||
353 | |||
354 | $comment->markApproved(); |
||
355 | return $this->renderChangedCommentState($comment); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Marks a given {@link Comment} as approved. |
||
360 | */ |
||
361 | public function approve() |
||
362 | { |
||
363 | $comment = $this->getComment(); |
||
364 | if (!$comment) { |
||
365 | return $this->httpError(404); |
||
366 | } |
||
367 | if (!$comment->canEdit()) { |
||
368 | return Security::permissionFailure($this, 'You do not have permission to approve this comment'); |
||
369 | } |
||
370 | if (!$comment->getSecurityToken()->checkRequest($this->request)) { |
||
371 | return $this->httpError(400); |
||
372 | } |
||
373 | $comment->markApproved(); |
||
374 | return $this->renderChangedCommentState($comment); |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * Redirect back to referer if available, ensuring that only site URLs |
||
379 | * are allowed to avoid phishing. If it's an AJAX request render the |
||
380 | * comment in it's new state |
||
381 | * |
||
382 | * @param Comment $comment |
||
383 | * @return DBHTMLText|HTTPResponse|false |
||
384 | */ |
||
385 | private function renderChangedCommentState($comment) |
||
386 | { |
||
387 | $referer = $this->request->getHeader('Referer'); |
||
388 | |||
389 | // Render comment using AJAX |
||
390 | if ($this->request->isAjax()) { |
||
391 | return $comment->renderWith('Includes/CommentsInterface_singlecomment'); |
||
392 | } |
||
393 | |||
394 | // Redirect to either the comment or start of the page |
||
395 | if (empty($referer)) { |
||
396 | return $this->redirectBack(); |
||
397 | } |
||
398 | |||
399 | // Redirect to the comment, but check for phishing |
||
400 | $url = $referer . '#comment-' . $comment->ID; |
||
401 | // absolute redirection URLs not located on this site may cause phishing |
||
402 | if (Director::is_site_url($url)) { |
||
403 | return $this->redirect($url); |
||
404 | } |
||
405 | |||
406 | return false; |
||
407 | } |
||
408 | |||
409 | /** |
||
410 | * Returns the comment referenced in the URL (by ID). Permission checking |
||
411 | * should be done in the callee. |
||
412 | * |
||
413 | * @return Comment|false |
||
414 | */ |
||
415 | public function getComment() |
||
416 | { |
||
417 | $id = isset($this->urlParams['ID']) ? $this->urlParams['ID'] : false; |
||
418 | |||
419 | if ($id) { |
||
420 | /** @var Comment $comment */ |
||
421 | $comment = Comment::get()->byId($id); |
||
422 | if ($comment) { |
||
423 | $this->fallbackReturnURL = $comment->Link(); |
||
424 | return $comment; |
||
425 | } |
||
426 | } |
||
427 | |||
428 | return false; |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Create a reply form for a specified comment |
||
433 | * |
||
434 | * @param Comment $comment |
||
435 | * @return Form |
||
436 | */ |
||
437 | public function ReplyForm($comment) |
||
438 | { |
||
439 | // Enables multiple forms with different names to use the same handler |
||
440 | $form = $this->CommentsForm(); |
||
441 | $form->setName('ReplyForm_' . $comment->ID); |
||
442 | $form->setHTMLID(null); |
||
443 | $form->addExtraClass('reply-form'); |
||
444 | |||
445 | // Load parent into reply form |
||
446 | $form->loadDataFrom([ |
||
447 | 'ParentCommentID' => $comment->ID |
||
448 | ]); |
||
449 | |||
450 | // Customise action |
||
451 | $form->setFormAction($this->Link('reply', $comment->ID)); |
||
452 | |||
453 | $this->extend('updateReplyForm', $form); |
||
454 | |||
455 | return $form; |
||
456 | } |
||
457 | |||
458 | |||
459 | /** |
||
460 | * Request handler for reply form. |
||
461 | * |
||
462 | * This method will disambiguate multiple reply forms in the same method |
||
463 | * |
||
464 | * @param HTTPRequest $request |
||
465 | * @throws HTTPResponse_Exception |
||
466 | */ |
||
467 | public function reply(HTTPRequest $request) |
||
468 | { |
||
469 | // Extract parent comment from reply and build this way |
||
470 | if ($parentID = $request->param('ParentCommentID')) { |
||
471 | /** @var Comment $comment */ |
||
472 | $comment = DataObject::get_by_id(Comment::class, $parentID, true); |
||
473 | if ($comment) { |
||
474 | return $this->ReplyForm($comment); |
||
475 | } |
||
476 | } |
||
477 | return $this->httpError(404); |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * Post a comment form |
||
482 | * |
||
483 | * @return Form |
||
484 | */ |
||
485 | public function CommentsForm() |
||
486 | { |
||
487 | $form = Injector::inst()->create(CommentForm::class, __FUNCTION__, $this); |
||
488 | |||
489 | // hook to allow further extensions to alter the comments form |
||
490 | $this->extend('alterCommentForm', $form); |
||
491 | |||
492 | return $form; |
||
493 | } |
||
494 | |||
495 | |||
496 | /** |
||
497 | * @return HTTPResponse|false |
||
498 | */ |
||
499 | public function redirectBack() |
||
500 | { |
||
501 | // Don't cache the redirect back ever |
||
502 | HTTP::set_cache_age(0); |
||
0 ignored issues
–
show
|
|||
503 | |||
504 | $url = null; |
||
505 | |||
506 | // In edge-cases, this will be called outside of a handleRequest() context; in that case, |
||
507 | // redirect to the homepage - don't break into the global state at this stage because we'll |
||
508 | // be calling from a test context or something else where the global state is inappropraite |
||
509 | if ($this->request) { |
||
510 | if ($this->request->requestVar('BackURL')) { |
||
511 | $url = $this->request->requestVar('BackURL'); |
||
512 | } elseif ($this->request->isAjax() && $this->request->getHeader('X-Backurl')) { |
||
513 | $url = $this->request->getHeader('X-Backurl'); |
||
514 | } elseif ($this->request->getHeader('Referer')) { |
||
515 | $url = $this->request->getHeader('Referer'); |
||
516 | } |
||
517 | } |
||
518 | |||
519 | if (!$url) { |
||
520 | $url = $this->fallbackReturnURL; |
||
521 | } |
||
522 | if (!$url) { |
||
523 | $url = Director::baseURL(); |
||
524 | } |
||
525 | |||
526 | // absolute redirection URLs not located on this site may cause phishing |
||
527 | if (Director::is_site_url($url)) { |
||
528 | return $this->redirect($url); |
||
529 | } |
||
530 | |||
531 | return false; |
||
532 | } |
||
533 | } |
||
534 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.