Completed
Pull Request — master (#185)
by Janne
02:32
created

CommentingController::getFeed()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 46
rs 5.5555
cc 8
eloc 28
nc 8
nop 1
1
<?php
2
3
/**
4
 * @package comments
5
 */
6
7
class CommentingController extends Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
{
9
10
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
11
        'delete',
12
        'spam',
13
        'ham',
14
        'approve',
15
        'rss',
16
        'CommentsForm',
17
        'reply',
18
        'doPostComment',
19
        'doPreviewComment'
20
    );
21
22
    private static $url_handlers = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $url_handlers is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
23
        'reply/$ParentCommentID//$ID/$OtherID' => 'reply',
24
    );
25
26
    /**
27
     * Fields required for this form
28
     *
29
     * @var array
30
     * @config
31
     */
32
    private static $required_fields = array(
0 ignored issues
show
Unused Code introduced by
The property $required_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
        'Name',
34
        'Email',
35
        'Comment'
36
    );
37
38
    /**
39
     * Base class this commenting form is for
40
     *
41
     * @var string
42
     */
43
    private $baseClass = "";
44
45
    /**
46
     * The record this commenting form is for
47
     *
48
     * @var DataObject
49
     */
50
    private $ownerRecord = null;
51
52
    /**
53
     * Parent controller record
54
     *
55
     * @var Controller
56
     */
57
    private $ownerController = null;
58
59
    /**
60
     * Backup url to return to
61
     *
62
     * @var string
63
     */
64
    protected $fallbackReturnURL = null;
65
66
    /**
67
     * Set the base class to use
68
     *
69
     * @param string $class
70
     */
71
    public function setBaseClass($class)
72
    {
73
        $this->baseClass = $class;
74
    }
75
76
    /**
77
     * Get the base class used
78
     *
79
     * @return string
80
     */
81
    public function getBaseClass()
82
    {
83
        return $this->baseClass;
84
    }
85
86
    /**
87
     * Set the record this controller is working on
88
     *
89
     * @param DataObject $record
90
     */
91
    public function setOwnerRecord($record)
92
    {
93
        $this->ownerRecord = $record;
94
    }
95
96
    /**
97
     * Get the record
98
     *
99
     * @return DataObject
100
     */
101
    public function getOwnerRecord()
102
    {
103
        return $this->ownerRecord;
104
    }
105
106
    /**
107
     * Set the parent controller
108
     *
109
     * @param Controller $controller
110
     */
111
    public function setOwnerController($controller)
112
    {
113
        $this->ownerController = $controller;
114
    }
115
116
    /**
117
     * Get the parent controller
118
     *
119
     * @return Controller
120
     */
121
    public function getOwnerController()
122
    {
123
        return $this->ownerController;
124
    }
125
126
    /**
127
     * Get the commenting option for the current state
128
     *
129
     * @param string $key
130
     * @return mixed Result if the setting is available, or null otherwise
131
     */
132
    public function getOption($key)
133
    {
134
        // If possible use the current record
135
        if ($record = $this->getOwnerRecord()) {
136
            return $record->getCommentsOption($key);
137
        }
138
139
        // Otherwise a singleton of that record
140
        if ($class = $this->getBaseClass()) {
141
            return singleton($class)->getCommentsOption($key);
142
        }
143
144
        // Otherwise just use the default options
145
        return singleton('CommentsExtension')->getCommentsOption($key);
146
    }
147
148
    /**
149
     * Workaround for generating the link to this controller
150
     *
151
     * @return string
152
     */
153
    public function Link($action = '', $id = '', $other = '')
154
    {
155
        return Controller::join_links(Director::baseURL(), __CLASS__, $action, $id, $other);
156
    }
157
158
    /**
159
     * Outputs the RSS feed of comments
160
     *
161
     * @return HTMLText
162
     */
163
    public function rss()
164
    {
165
        return $this->getFeed($this->request)->outputToBrowser();
166
    }
167
168
    /**
169
     * Return an RSSFeed of comments for a given set of comments or all
170
     * comments on the website.
171
     *
172
     * To maintain backwards compatibility with 2.4 this supports mapping
173
     * of PageComment/rss?pageid= as well as the new RSS format for comments
174
     * of CommentingController/rss/{classname}/{id}
175
     *
176
     * @param SS_HTTPRequest
177
     *
178
     * @return RSSFeed
179
     */
180
    public function getFeed(SS_HTTPRequest $request)
181
    {
182
        $link = $this->Link('rss');
183
        $class = $request->param('ID');
184
        $id = $request->param('OtherID');
185
186
        // Support old pageid param
187
        if (!$id && !$class && ($id = $request->getVar('pageid'))) {
188
            $class = 'SiteTree';
189
        }
190
191
        $comments = Comment::get()->filter(array(
192
            'Moderated' => 1,
193
            'IsSpam' => 0,
194
        ));
195
196
        // Check if class filter
197
        if ($class) {
198
            if (!is_subclass_of($class, 'DataObject') || !$class::has_extension('CommentsExtension')) {
199
                return $this->httpError(404);
200
            }
201
            $this->setBaseClass($class);
202
            $comments = $comments->filter('BaseClass', $class);
203
            $link = Controller::join_links($link, $class);
204
205
            // Check if id filter
206
            if ($id) {
207
                $comments = $comments->filter('ParentID', $id);
208
                $link = Controller::join_links($link, $id);
209
                $this->setOwnerRecord(DataObject::get_by_id($class, $id));
210
            }
211
        }
212
213
        $title = _t('CommentingController.RSSTITLE', "Comments RSS Feed");
214
215
        $comments = new PaginatedList($comments, $request);
0 ignored issues
show
Documentation introduced by
$request is of type object<SS_HTTPRequest>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
216
        $comments->setPageLength($this->getOption('comments_per_page'));
217
218
        return new RSSFeed(
219
            $comments,
220
            $link,
221
            $title,
222
            $link,
223
            'Title', 'EscapedComment', 'AuthorName'
224
        );
225
    }
226
227
    /**
228
     * Deletes a given {@link Comment} via the URL.
229
     */
230 View Code Duplication
    public function delete()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
    {
232
        $comment = $this->getComment();
233
        if (!$comment) {
234
            return $this->httpError(404);
235
        }
236
        if (!$comment->canDelete()) {
237
            return Security::permissionFailure($this, 'You do not have permission to delete this comment');
238
        }
239
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
240
            return $this->httpError(400);
241
        }
242
243
        $comment->delete();
244
245
        return $this->request->isAjax()
246
            ? true
247
            : $this->redirectBack();
248
    }
249
250
    /**
251
     * Marks a given {@link Comment} as spam. Removes the comment from display
252
     */
253 View Code Duplication
    public function spam()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
254
    {
255
        $comment = $this->getComment();
256
        if (!$comment) {
257
            return $this->httpError(404);
258
        }
259
        if (!$comment->canEdit()) {
260
            return Security::permissionFailure($this, 'You do not have permission to edit this comment');
261
        }
262
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
263
            return $this->httpError(400);
264
        }
265
266
        $comment->markSpam();
267
        return $this->renderChangedCommentState($comment);
268
    }
269
270
    /**
271
     * Marks a given {@link Comment} as ham (not spam).
272
     */
273 View Code Duplication
    public function ham()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
274
    {
275
        $comment = $this->getComment();
276
        if (!$comment) {
277
            return $this->httpError(404);
278
        }
279
        if (!$comment->canEdit()) {
280
            return Security::permissionFailure($this, 'You do not have permission to edit this comment');
281
        }
282
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
283
            return $this->httpError(400);
284
        }
285
286
        $comment->markApproved();
287
        return $this->renderChangedCommentState($comment);
288
    }
289
290
    /**
291
     * Marks a given {@link Comment} as approved.
292
     */
293 View Code Duplication
    public function approve()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
294
    {
295
        $comment = $this->getComment();
296
        if (!$comment) {
297
            return $this->httpError(404);
298
        }
299
        if (!$comment->canEdit()) {
300
            return Security::permissionFailure($this, 'You do not have permission to approve this comment');
301
        }
302
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
303
            return $this->httpError(400);
304
        }
305
306
        $comment->markApproved();
307
        return $this->renderChangedCommentState($comment);
308
    }
309
310
    /**
311
     * Redirect back to referer if available, ensuring that only site URLs
312
     * are allowed to avoid phishing.  If it's an AJAX request render the
313
     * comment in it's new state
314
     */
315
    private function renderChangedCommentState($comment)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
316
    {
317
        $referer = $this->request->getHeader('Referer');
318
319
        // Render comment using AJAX
320
        if ($this->request->isAjax()) {
321
            return $comment->renderWith('CommentsInterface_singlecomment');
322
        } else {
323
            // Redirect to either the comment or start of the page
324
            if (empty($referer)) {
325
                return $this->redirectBack();
326
            } else {
327
                // Redirect to the comment, but check for phishing
328
                $url = $referer . '#comment-' . $comment->ID;
329
                // absolute redirection URLs not located on this site may cause phishing
330
                if (Director::is_site_url($url)) {
331
                    return $this->redirect($url);
332
                } else {
333
                    return false;
334
                }
335
            }
336
        }
337
    }
338
339
    /**
340
     * Returns the comment referenced in the URL (by ID). Permission checking
341
     * should be done in the callee.
342
     *
343
     * @return Comment|false
0 ignored issues
show
Documentation introduced by
Should the return type not be DataObject|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
344
     */
345
    public function getComment()
346
    {
347
        $id = isset($this->urlParams['ID']) ? $this->urlParams['ID'] : false;
348
349
        if ($id) {
350
            $comment = DataObject::get_by_id('Comment', $id);
351
352
            if ($comment) {
353
                $this->fallbackReturnURL = $comment->Link();
354
                return $comment;
355
            }
356
        }
357
358
        return false;
359
    }
360
361
    /**
362
     * Create a reply form for a specified comment
363
     *
364
     * @param Comment $comment
365
     */
366
    public function ReplyForm($comment)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
367
    {
368
        // Enables multiple forms with different names to use the same handler
369
        $form = $this->CommentsForm();
370
        $form->setName('ReplyForm_'.$comment->ID);
371
        $form->addExtraClass('reply-form');
372
373
        // Load parent into reply form
374
        $form->loadDataFrom(array(
375
            'ParentCommentID' => $comment->ID
376
        ));
377
378
        // Customise action
379
        $form->setFormAction($this->Link('reply', $comment->ID));
380
381
        $this->extend('updateReplyForm', $form);
382
        return $form;
383
    }
384
385
386
    /**
387
     * Request handler for reply form.
388
     * This method will disambiguate multiple reply forms in the same method
389
     *
390
     * @param SS_HTTPRequest $request
391
     */
392
    public function reply(SS_HTTPRequest $request)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
393
    {
394
        // Extract parent comment from reply and build this way
395
        if ($parentID = $request->param('ParentCommentID')) {
396
            $comment = DataObject::get_by_id('Comment', $parentID, true);
397
            if ($comment) {
398
                return $this->ReplyForm($comment);
0 ignored issues
show
Compatibility introduced by
$comment of type object<DataObject> is not a sub-type of object<Comment>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
399
            }
400
        }
401
        return $this->httpError(404);
402
    }
403
404
    /**
405
     * Post a comment form
406
     *
407
     * @return Form
408
     */
409
    public function CommentsForm()
410
    {
411
        $usePreview = $this->getOption('use_preview');
412
413
        $nameRequired = _t('CommentInterface.YOURNAME_MESSAGE_REQUIRED', 'Please enter your name');
414
        $emailRequired = _t('CommentInterface.EMAILADDRESS_MESSAGE_REQUIRED', 'Please enter your email address');
415
        $emailInvalid = _t('CommentInterface.EMAILADDRESS_MESSAGE_EMAIL', 'Please enter a valid email address');
416
        $urlInvalid = _t('CommentInterface.COMMENT_MESSAGE_URL', 'Please enter a valid URL');
417
        $commentRequired = _t('CommentInterface.COMMENT_MESSAGE_REQUIRED', 'Please enter your comment');
418
419
        $fields = new FieldList(
420
            $dataFields = new CompositeField(
421
                // Name
422
                TextField::create("Name", _t('CommentInterface.YOURNAME', 'Your name'))
423
                    ->setCustomValidationMessage($nameRequired)
424
                    ->setAttribute('data-msg-required', $nameRequired),
425
426
                // Email
427
                EmailField::create(
428
                    "Email",
429
                    _t('CommentingController.EMAILADDRESS', "Your email address (will not be published)")
430
                )
431
                    ->setCustomValidationMessage($emailRequired)
432
                    ->setAttribute('data-msg-required', $emailRequired)
433
                    ->setAttribute('data-msg-email', $emailInvalid)
434
                    ->setAttribute('data-rule-email', true),
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
435
436
                // Url
437
                TextField::create("URL", _t('CommentingController.WEBSITEURL', "Your website URL"))
438
                    ->setAttribute('data-msg-url', $urlInvalid)
439
                    ->setAttribute('data-rule-url', true),
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
440
441
                // Comment
442
                TextareaField::create("Comment", _t('CommentingController.COMMENTS', "Comments"))
443
                    ->setCustomValidationMessage($commentRequired)
444
                    ->setAttribute('data-msg-required', $commentRequired)
445
            ),
446
            HiddenField::create("ParentID"),
447
            HiddenField::create("ReturnURL"),
448
            HiddenField::create("ParentCommentID"),
449
            HiddenField::create("BaseClass")
450
        );
451
452
        // Preview formatted comment. Makes most sense when shortcodes or
453
        // limited HTML is allowed. Populated by JS/Ajax.
454
        if ($usePreview) {
455
            $fields->insertAfter(
456
                ReadonlyField::create('PreviewComment', _t('CommentInterface.PREVIEWLABEL', 'Preview'))
457
                    ->setAttribute('style', 'display: none'), // enable through JS
458
                'Comment'
0 ignored issues
show
Documentation introduced by
'Comment' is of type string, but the function expects a object<FormField>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
459
            );
460
        }
461
462
        $dataFields->addExtraClass('data-fields');
463
464
        // save actions
465
        $actions = new FieldList(
466
            new FormAction("doPostComment", _t('CommentInterface.POST', 'Post'))
467
        );
468
        if ($usePreview) {
469
            $actions->push(
470
                FormAction::create('doPreviewComment', _t('CommentInterface.PREVIEW', 'Preview'))
471
                    ->addExtraClass('action-minor')
472
                    ->setAttribute('style', 'display: none') // enable through JS
473
            );
474
        }
475
476
        // required fields for server side
477
        $required = new RequiredFields($this->config()->required_fields);
478
479
        // create the comment form
480
        $form = new Form($this, 'CommentsForm', $fields, $actions, $required);
481
482
        // if the record exists load the extra required data
483
        if ($record = $this->getOwnerRecord()) {
484
485
            // Load member data
486
            $member = Member::currentUser();
487
            if (($record->CommentsRequireLogin || $record->PostingRequiredPermission) && $member) {
488
                $fields = $form->Fields();
489
490
                $fields->removeByName('Name');
491
                $fields->removeByName('Email');
492
                $fields->insertBefore(new ReadonlyField("NameView", _t('CommentInterface.YOURNAME', 'Your name'), $member->getName()), 'URL');
0 ignored issues
show
Documentation introduced by
'URL' is of type string, but the function expects a object<FormField>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
493
                $fields->push(new HiddenField("Name", "", $member->getName()));
494
                $fields->push(new HiddenField("Email", "", $member->Email));
495
            }
496
497
            // we do not want to read a new URL when the form has already been submitted
498
            // which in here, it hasn't been.
499
            $form->loadDataFrom(array(
500
                'ParentID'      => $record->ID,
501
                'ReturnURL'     => $this->request->getURL(),
502
                'BaseClass'     => $this->getBaseClass()
503
            ));
504
        }
505
506
        // Set it so the user gets redirected back down to the form upon form fail
507
        $form->setRedirectToFormOnValidationError(true);
508
509
        // load any data from the cookies
510
        if ($data = Cookie::get('CommentsForm_UserData')) {
511
            $data = Convert::json2array($data);
512
513
            $form->loadDataFrom(array(
514
                "Name"        => isset($data['Name']) ? $data['Name'] : '',
515
                "URL"        => isset($data['URL']) ? $data['URL'] : '',
516
                "Email"        => isset($data['Email']) ? $data['Email'] : ''
517
            ));
518
            // allow previous value to fill if comment not stored in cookie (i.e. validation error)
519
            $prevComment = Cookie::get('CommentsForm_Comment');
520
            if ($prevComment && $prevComment != '') {
0 ignored issues
show
Bug Best Practice introduced by
The expression $prevComment of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
521
                $form->loadDataFrom(array("Comment" => $prevComment));
522
            }
523
        }
524
525
        if (!empty($member)) {
526
            $form->loadDataFrom($member);
527
        }
528
529
        // hook to allow further extensions to alter the comments form
530
        $this->extend('alterCommentForm', $form);
531
532
        return $form;
533
    }
534
535
    /**
536
     * Process which creates a {@link Comment} once a user submits a comment from this form.
537
     *
538
     * @param array $data
539
     * @param Form $form
540
     */
541
    public function doPostComment($data, $form)
542
    {
543
        // Load class and parent from data
544
        if (isset($data['BaseClass'])) {
545
            $this->setBaseClass($data['BaseClass']);
546
        }
547
        if (isset($data['ParentID']) && ($class = $this->getBaseClass())) {
548
            $this->setOwnerRecord($class::get()->byID($data['ParentID']));
549
        }
550
        if (!$this->getOwnerRecord()) {
551
            return $this->httpError(404);
552
        }
553
        // cache users data
554
        Cookie::set("CommentsForm_UserData", Convert::raw2json($data));
555
        Cookie::set("CommentsForm_Comment", $data['Comment']);
556
557
        // extend hook to allow extensions. Also see onAfterPostComment
558
        $this->extend('onBeforePostComment', $form);
559
560
        // If commenting can only be done by logged in users, make sure the user is logged in
561
        if (!$this->getOwnerRecord()->canPostComment()) {
562
            return Security::permissionFailure(
563
                $this,
564
                _t(
565
                    'CommentingController.PERMISSIONFAILURE',
566
                    "You're not able to post comments to this page. Please ensure you are logged in and have an "
567
                    . "appropriate permission level."
568
                )
569
            );
570
        }
571
572
        if ($member = Member::currentUser()) {
573
            $form->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID));
574
        }
575
576
        // What kind of moderation is required?
577
        switch ($this->getOwnerRecord()->ModerationRequired) {
578
            case 'Required':
579
                $requireModeration = true;
580
                break;
581
            case 'NonMembersOnly':
582
                $requireModeration = empty($member);
583
                break;
584
            case 'None':
585
            default:
586
                $requireModeration = false;
587
                break;
588
        }
589
590
        $comment = new Comment();
591
        $form->saveInto($comment);
592
593
        $comment->AllowHtml = $this->getOption('html_allowed');
594
        $comment->Moderated = !$requireModeration;
595
596
        // Save into DB, or call pre-save hooks to give accurate preview
597
        $usePreview = $this->getOption('use_preview');
598
        $isPreview = $usePreview && !empty($data['IsPreview']);
599
        if ($isPreview) {
600
            $comment->extend('onBeforeWrite');
601
        } else {
602
            $comment->write();
603
604
            // extend hook to allow extensions. Also see onBeforePostComment
605
            $this->extend('onAfterPostComment', $comment);
606
        }
607
608
        // we want to show a notification if comments are moderated
609
        if ($requireModeration && !$comment->IsSpam) {
610
            Session::set('CommentsModerated', 1);
611
        }
612
613
        // clear the users comment since it passed validation
614
        Cookie::set('CommentsForm_Comment', false);
615
616
        // Find parent link
617
        if (!empty($data['ReturnURL'])) {
618
            $url = $data['ReturnURL'];
619
        } elseif ($parent = $comment->getParent()) {
620
            $url = $parent->Link();
621
        } else {
622
            return $this->redirectBack();
623
        }
624
625
        // Given a redirect page exists, attempt to link to the correct anchor
626
        if ($comment->IsSpam) {
627
            // Link to the form with the error message contained
628
            $hash = $form->FormName();
629
        } elseif (!$comment->Moderated) {
630
            // Display the "awaiting moderation" text
631
            $hash = "moderated";
632
        } else {
633
            // Link to the moderated, non-spam comment
634
            $hash = $comment->Permalink();
635
        }
636
637
        return $this->redirect(Controller::join_links($url, "#{$hash}"));
638
    }
639
640
    public function doPreviewComment($data, $form)
641
    {
642
        $data['IsPreview'] = 1;
643
644
        return $this->doPostComment($data, $form);
645
    }
646
647
    public function redirectBack()
648
    {
649
        // Don't cache the redirect back ever
650
        HTTP::set_cache_age(0);
651
652
        $url = null;
653
654
        // In edge-cases, this will be called outside of a handleRequest() context; in that case,
655
        // redirect to the homepage - don't break into the global state at this stage because we'll
656
        // be calling from a test context or something else where the global state is inappropraite
657
        if ($this->request) {
658
            if ($this->request->requestVar('BackURL')) {
659
                $url = $this->request->requestVar('BackURL');
660
            } elseif ($this->request->isAjax() && $this->request->getHeader('X-Backurl')) {
661
                $url = $this->request->getHeader('X-Backurl');
662
            } elseif ($this->request->getHeader('Referer')) {
663
                $url = $this->request->getHeader('Referer');
664
            }
665
        }
666
667
        if (!$url) {
668
            $url = $this->fallbackReturnURL;
669
        }
670
        if (!$url) {
671
            $url = Director::baseURL();
672
        }
673
674
        // absolute redirection URLs not located on this site may cause phishing
675
        if (Director::is_site_url($url)) {
676
            return $this->redirect($url);
677
        } else {
678
            return false;
679
        }
680
    }
681
}
682