Completed
Push — master ( 187618...8c43e0 )
by Will
12s
created

CommentingController::CommentsForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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\Model\Comment;
8
use SilverStripe\Control\Director;
9
use SilverStripe\Control\Controller;
10
use SilverStripe\Control\Email\Email;
11
use SilverStripe\Control\HTTP;
12
use SilverStripe\Control\HTTPRequest;
13
use SilverStripe\Control\RSS\RSSFeed;
14
use SilverStripe\Control\Session;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\ORM\PaginatedList;
17
use SilverStripe\Security\Member;
18
use SilverStripe\Security\Security;
19
use SilverStripe\Core\Injector\Injector;
20
use SilverStripe\Comments\Forms\CommentForm;
21
22
/**
23
 * @package comments
24
 */
25
class CommentingController extends Controller
26
{
27
    /**
28
     * {@inheritDoc}
29
     */
30
    private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by Damian Mooyman
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by Ingo Schommer
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...
31
        'delete',
32
        'spam',
33
        'ham',
34
        'approve',
35
        'rss',
36
        'CommentsForm',
37
        'reply',
38
        'doPostComment',
39
        'doPreviewComment'
40
    );
41
42
    /**
43
     * {@inheritDoc}
44
     */
45
    private static $url_handlers = array(
0 ignored issues
show
Comprehensibility introduced by Damian Mooyman
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by Damian Mooyman
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...
46
        'reply/$ParentCommentID//$ID/$OtherID' => 'reply',
47
    );
48
49
    /**
50
     * Fields required for this form
51
     *
52
     * @var array
53
     * @config
54
     */
55
    private static $required_fields = array(
0 ignored issues
show
Unused Code introduced by Damian Mooyman
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...
56
        'Name',
57
        'Email',
58
        'Comment'
59
    );
60
61
    /**
62
     * Parent class this commenting form is for
63
     *
64
     * @var string
65
     */
66
    private $parentClass = '';
67
68
    /**
69
     * The record this commenting form is for
70
     *
71
     * @var DataObject
72
     */
73
    private $ownerRecord = null;
74
75
    /**
76
     * Parent controller record
77
     *
78
     * @var Controller
79
     */
80
    private $ownerController = null;
81
82
    /**
83
     * Backup url to return to
84
     *
85
     * @var string
86
     */
87
    protected $fallbackReturnURL = null;
88
89
    /**
90
     * Set the parent class name to use
91
     *
92
     * @param string $class
93
     */
94
    public function setParentClass($class)
95
    {
96
        $this->parentClass = $this->encodeClassName($class);
97
    }
98
99
    /**
100
     * Get the parent class name used
101
     *
102
     * @return string
103
     */
104
    public function getParentClass()
105
    {
106
        return $this->decodeClassName($this->parentClass);
107
    }
108
109
    /**
110
     * Encode a fully qualified class name to a URL-safe version
111
     *
112
     * @param string $input
113
     * @return string
114
     */
115
    public function encodeClassName($input)
116
    {
117
        return str_replace('\\', '-', $input);
118
    }
119
120
    /**
121
     * Decode an "encoded" fully qualified class name back to its original
122
     *
123
     * @param string $input
124
     * @return string
125
     */
126
    public function decodeClassName($input)
127
    {
128
        return str_replace('-', '\\', $input);
129
    }
130
131
    /**
132
     * Set the record this controller is working on
133
     *
134
     * @param DataObject $record
135
     */
136
    public function setOwnerRecord($record)
137
    {
138
        $this->ownerRecord = $record;
139
    }
140
141
    /**
142
     * Get the record
143
     *
144
     * @return DataObject
145
     */
146
    public function getOwnerRecord()
147
    {
148
        return $this->ownerRecord;
149
    }
150
151
    /**
152
     * Set the parent controller
153
     *
154
     * @param Controller $controller
155
     */
156
    public function setOwnerController($controller)
157
    {
158
        $this->ownerController = $controller;
159
    }
160
161
    /**
162
     * Get the parent controller
163
     *
164
     * @return Controller
165
     */
166
    public function getOwnerController()
167
    {
168
        return $this->ownerController;
169
    }
170
171
    /**
172
     * Get the commenting option for the current state
173
     *
174
     * @param string $key
175
     * @return mixed Result if the setting is available, or null otherwise
176
     */
177 View Code Duplication
    public function getOption($key)
0 ignored issues
show
Duplication introduced by Damian Mooyman
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...
178
    {
179
        // If possible use the current record
180
        if ($record = $this->getOwnerRecord()) {
181
            return $record->getCommentsOption($key);
182
        }
183
184
        // Otherwise a singleton of that record
185
        if ($class = $this->getParentClass()) {
186
            return singleton($class)->getCommentsOption($key);
187
        }
188
189
        // Otherwise just use the default options
190
        return singleton(CommentsExtension::class)->getCommentsOption($key);
191
    }
192
193
    /**
194
     * Returns all the commenting options for the current instance.
195
     *
196
     * @return array
197
     */
198 View Code Duplication
    public function getOptions()
0 ignored issues
show
Duplication introduced by Will Rossiter
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...
199
    {
200
        if ($record = $this->getOwnerRecord()) {
201
            return $record->getCommentsOptions();
202
        }
203
204
        // Otherwise a singleton of that record
205
        if ($class = $this->getParentClass()) {
206
            return singleton($class)->getCommentsOptions();
207
        }
208
209
        // Otherwise just use the default options
210
        return singleton(CommentsExtension::class)->getCommentsOptions();
211
    }
212
213
    /**
214
     * Workaround for generating the link to this controller
215
     *
216
     * @param  string $action
217
     * @param  int    $id
0 ignored issues
show
Documentation introduced by Robbie Averill
Should the type for parameter $id not be string|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
218
     * @param  string $other
219
     * @return string
220
     */
221
    public function Link($action = '', $id = '', $other = '')
222
    {
223
        return Controller::join_links(Director::baseURL(), 'comments', $action, $id, $other);
224
    }
225
226
    /**
227
     * Outputs the RSS feed of comments
228
     *
229
     * @return HTMLText
0 ignored issues
show
Documentation introduced by Damian Mooyman
Should the return type not be \SilverStripe\ORM\FieldType\DBHTMLText?

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...
230
     */
231
    public function rss()
232
    {
233
        return $this->getFeed($this->request)->outputToBrowser();
234
    }
235
236
    /**
237
     * Return an RSSFeed of comments for a given set of comments or all
238
     * comments on the website.
239
     *
240
     * @param HTTPRequest
241
     *
242
     * @return RSSFeed
243
     */
244
    public function getFeed(HTTPRequest $request)
245
    {
246
        $link = $this->Link('rss');
247
        $class = $this->decodeClassName($request->param('ID'));
248
        $id = $request->param('OtherID');
249
250
        // Support old pageid param
251
        if (!$id && !$class && ($id = $request->getVar('pageid'))) {
252
            $class = SiteTree::class;
253
        }
254
255
        $comments = Comment::get()->filter(array(
256
            'Moderated' => 1,
257
            'IsSpam' => 0,
258
        ));
259
260
        // Check if class filter
261
        if ($class) {
262
            if (!is_subclass_of($class, DataObject::class) || !$class::has_extension(CommentsExtension::class)) {
263
                return $this->httpError(404);
264
            }
265
            $this->setParentClass($class);
266
            $comments = $comments->filter('ParentClass', $class);
267
            $link = Controller::join_links($link, $this->encodeClassName($class));
268
269
            // Check if id filter
270
            if ($id) {
271
                $comments = $comments->filter('ParentID', $id);
272
                $link = Controller::join_links($link, $id);
273
                $this->setOwnerRecord(DataObject::get_by_id($class, $id));
0 ignored issues
show
Bug introduced by Damian Mooyman
It seems like \SilverStripe\ORM\DataOb...:get_by_id($class, $id) can be null; however, setOwnerRecord() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
274
            }
275
        }
276
277
        $title = _t('SilverStripe\\Comments\\Controllers\\CommentingController.RSSTITLE', "Comments RSS Feed");
278
        $comments = new PaginatedList($comments, $request);
0 ignored issues
show
Documentation introduced by Damian Mooyman
$request is of type object<SilverStripe\Control\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...
279
        $comments->setPageLength($this->getOption('comments_per_page'));
280
281
        return new RSSFeed(
282
            $comments,
283
            $link,
284
            $title,
285
            $link,
286
            'Title',
287
            'EscapedComment',
288
            'AuthorName'
289
        );
290
    }
291
292
    /**
293
     * Deletes a given {@link Comment} via the URL.
294
     */
295 View Code Duplication
    public function delete()
0 ignored issues
show
Duplication introduced by Damian Mooyman
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...
296
    {
297
        $comment = $this->getComment();
298
        if (!$comment) {
299
            return $this->httpError(404);
300
        }
301
        if (!$comment->canDelete()) {
302
            return Security::permissionFailure($this, 'You do not have permission to delete this comment');
303
        }
304
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
305
            return $this->httpError(400);
306
        }
307
308
        $comment->delete();
309
310
        return $this->request->isAjax()
311
            ? true
312
            : $this->redirectBack();
313
    }
314
315
    /**
316
     * Marks a given {@link Comment} as spam. Removes the comment from display
317
     */
318 View Code Duplication
    public function spam()
0 ignored issues
show
Documentation introduced by Damian Mooyman
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...
Duplication introduced by Damian Mooyman
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...
319
    {
320
        $comment = $this->getComment();
321
        if (!$comment) {
322
            return $this->httpError(404);
323
        }
324
        if (!$comment->canEdit()) {
325
            return Security::permissionFailure($this, 'You do not have permission to edit this comment');
326
        }
327
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
328
            return $this->httpError(400);
329
        }
330
331
        $comment->markSpam();
332
        return $this->renderChangedCommentState($comment);
333
    }
334
335
    /**
336
     * Marks a given {@link Comment} as ham (not spam).
337
     */
338 View Code Duplication
    public function ham()
0 ignored issues
show
Documentation introduced by Damian Mooyman
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...
Duplication introduced by Damian Mooyman
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...
339
    {
340
        $comment = $this->getComment();
341
        if (!$comment) {
342
            return $this->httpError(404);
343
        }
344
        if (!$comment->canEdit()) {
345
            return Security::permissionFailure($this, 'You do not have permission to edit this comment');
346
        }
347
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
348
            return $this->httpError(400);
349
        }
350
351
        $comment->markApproved();
352
        return $this->renderChangedCommentState($comment);
353
    }
354
355
    /**
356
     * Marks a given {@link Comment} as approved.
357
     */
358 View Code Duplication
    public function approve()
0 ignored issues
show
Documentation introduced by Damian Mooyman
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...
Duplication introduced by Damian Mooyman
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...
359
    {
360
        $comment = $this->getComment();
361
        if (!$comment) {
362
            return $this->httpError(404);
363
        }
364
        if (!$comment->canEdit()) {
365
            return Security::permissionFailure($this, 'You do not have permission to approve this comment');
366
        }
367
        if (!$comment->getSecurityToken()->checkRequest($this->request)) {
368
            return $this->httpError(400);
369
        }
370
        $comment->markApproved();
371
        return $this->renderChangedCommentState($comment);
372
    }
373
374
    /**
375
     * Redirect back to referer if available, ensuring that only site URLs
376
     * are allowed to avoid phishing.  If it's an AJAX request render the
377
     * comment in it's new state
378
     */
379
    private function renderChangedCommentState($comment)
0 ignored issues
show
Documentation introduced by Damian Mooyman
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...
380
    {
381
        $referer = $this->request->getHeader('Referer');
382
383
        // Render comment using AJAX
384
        if ($this->request->isAjax()) {
385
            return $comment->renderWith('Includes/CommentsInterface_singlecomment');
386
        } else {
387
            // Redirect to either the comment or start of the page
388
            if (empty($referer)) {
389
                return $this->redirectBack();
390
            } else {
391
                // Redirect to the comment, but check for phishing
392
                $url = $referer . '#comment-' . $comment->ID;
393
                // absolute redirection URLs not located on this site may cause phishing
394
                if (Director::is_site_url($url)) {
395
                    return $this->redirect($url);
396
                } else {
397
                    return false;
398
                }
399
            }
400
        }
401
    }
402
403
    /**
404
     * Returns the comment referenced in the URL (by ID). Permission checking
405
     * should be done in the callee.
406
     *
407
     * @return Comment|false
0 ignored issues
show
Documentation introduced by Will Rossiter
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...
408
     */
409
    public function getComment()
410
    {
411
        $id = isset($this->urlParams['ID']) ? $this->urlParams['ID'] : false;
412
413
        if ($id) {
414
            $comment = Comment::get()->byId($id);
415
            if ($comment) {
416
                $this->fallbackReturnURL = $comment->Link();
417
                return $comment;
418
            }
419
        }
420
421
        return false;
422
    }
423
424
    /**
425
     * Create a reply form for a specified comment
426
     *
427
     * @param  Comment $comment
428
     * @return Form
429
     */
430
    public function ReplyForm($comment)
431
    {
432
        // Enables multiple forms with different names to use the same handler
433
        $form = $this->CommentsForm();
434
        $form->setName('ReplyForm_' . $comment->ID);
435
        $form->addExtraClass('reply-form');
436
437
        // Load parent into reply form
438
        $form->loadDataFrom(array(
439
            'ParentCommentID' => $comment->ID
440
        ));
441
442
        // Customise action
443
        $form->setFormAction($this->Link('reply', $comment->ID));
444
445
        $this->extend('updateReplyForm', $form);
446
447
        return $form;
448
    }
449
450
451
    /**
452
     * Request handler for reply form.
453
     *
454
     * This method will disambiguate multiple reply forms in the same method
455
     *
456
     * @param  HTTPRequest $request
457
     * @throws HTTPResponse_Exception
458
     */
459
    public function reply(HTTPRequest $request)
460
    {
461
        // Extract parent comment from reply and build this way
462
        if ($parentID = $request->param('ParentCommentID')) {
463
            $comment = DataObject::get_by_id(Comment::class, $parentID, true);
464
            if ($comment) {
465
                return $this->ReplyForm($comment);
0 ignored issues
show
Compatibility introduced by Damian Mooyman
$comment of type object<SilverStripe\ORM\DataObject> is not a sub-type of object<SilverStripe\Comments\Model\Comment>. It seems like you assume a child class of the class SilverStripe\ORM\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...
466
            }
467
        }
468
        return $this->httpError(404);
469
    }
470
471
    /**
472
     * Post a comment form
473
     *
474
     * @return Form
475
     */
476
    public function CommentsForm()
477
    {
478
        return Injector::inst()->create(CommentForm::class, __FUNCTION__, $this);
479
    }
480
481
482
    /**
483
     * @return HTTPResponse|false
0 ignored issues
show
Documentation introduced by Robbie Averill
Should the return type not be \SilverStripe\Control\HTTPResponse|null|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...
484
     */
485
    public function redirectBack()
486
    {
487
        // Don't cache the redirect back ever
488
        HTTP::set_cache_age(0);
489
490
        $url = null;
491
492
        // In edge-cases, this will be called outside of a handleRequest() context; in that case,
493
        // redirect to the homepage - don't break into the global state at this stage because we'll
494
        // be calling from a test context or something else where the global state is inappropraite
495
        if ($this->request) {
496
            if ($this->request->requestVar('BackURL')) {
497
                $url = $this->request->requestVar('BackURL');
498
            } elseif ($this->request->isAjax() && $this->request->getHeader('X-Backurl')) {
499
                $url = $this->request->getHeader('X-Backurl');
500
            } elseif ($this->request->getHeader('Referer')) {
501
                $url = $this->request->getHeader('Referer');
502
            }
503
        }
504
505
        if (!$url) {
506
            $url = $this->fallbackReturnURL;
507
        }
508
        if (!$url) {
509
            $url = Director::baseURL();
510
        }
511
512
        // absolute redirection URLs not located on this site may cause phishing
513
        if (Director::is_site_url($url)) {
514
            return $this->redirect($url);
515
        } else {
516
            return false;
517
        }
518
    }
519
}
520