Completed
Push — master ( 82c4a1...4bf0a8 )
by Robbie
10:38
created

src/Model/Comment.php (60 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SilverStripe\Comments\Model;
4
5
use HTMLPurifier_Config;
6
use HTMLPurifier;
7
use SilverStripe\Comments\Controllers\CommentingController;
8
use SilverStripe\Comments\Extensions\CommentsExtension;
9
use SilverStripe\Comments\Model\Comment\SecurityToken;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Core\Email\Email;
13
use SilverStripe\Core\Injector\Injector;
14
use SilverStripe\Core\TempFolder;
15
use SilverStripe\Forms\CheckboxField;
16
use SilverStripe\Forms\EmailField;
17
use SilverStripe\Forms\FieldGroup;
18
use SilverStripe\Forms\FieldList;
19
use SilverStripe\Forms\HeaderField;
20
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
21
use SilverStripe\Forms\TextareaField;
22
use SilverStripe\Forms\TextField;
23
use SilverStripe\ORM\ArrayList;
24
use SilverStripe\ORM\DataObject;
25
use SilverStripe\ORM\DB;
26
use SilverStripe\ORM\PaginatedList;
27
use SilverStripe\Security\Member;
28
use SilverStripe\Security\Permission;
29
use SilverStripe\CMS\Model\SiteTree;
30
31
/**
32
 * Represents a single comment object.
33
 *
34
 * @property string  $Name
35
 * @property string  $Comment
36
 * @property string  $Email
37
 * @property string  $URL
38
 * @property string  $BaseClass
39
 * @property boolean $Moderated
40
 * @property boolean $IsSpam      True if the comment is known as spam
41
 * @property integer $ParentID    ID of the parent page / dataobject
42
 * @property boolean $AllowHtml   If true, treat $Comment as HTML instead of plain text
43
 * @property string  $SecretToken Secret admin token required to provide moderation links between sessions
44
 * @property integer $Depth       Depth of this comment in the nested chain
45
 *
46
 * @method HasManyList ChildComments() List of child comments
47
 * @method Member Author() Member object who created this comment
48
 * @method Comment ParentComment() Parent comment this is a reply to
49
 * @package comments
50
 */
51
class Comment extends DataObject
52
{
53
    /**
54
     * {@inheritDoc}
55
     */
56
    private static $db = 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...
The property $db 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...
57
        'Name' => 'Varchar(200)',
58
        'Comment' => 'Text',
59
        'Email' => 'Varchar(200)',
60
        'URL' => 'Varchar(255)',
61
        'Moderated' => 'Boolean(0)',
62
        'IsSpam' => 'Boolean(0)',
63
        'AllowHtml' => 'Boolean',
64
        'SecretToken' => 'Varchar(255)',
65
        'Depth' => 'Int'
66
    );
67
68
    /**
69
     * {@inheritDoc}
70
     */
71
    private static $has_one = 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...
The property $has_one 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...
72
        'Author' => Member::class,
73
        'ParentComment' => self::class,
74
        'Parent' => DataObject::class
75
    );
76
77
    /**
78
     * {@inheritDoc}
79
     */
80
    private static $has_many = 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...
The property $has_many 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...
81
        'ChildComments' => self::class
82
    );
83
84
    /**
85
     * {@inheritDoc}
86
     */
87
    private static $default_sort = '"Created" DESC';
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...
The property $default_sort 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...
88
89
    /**
90
     * {@inheritDoc}
91
     */
92
    private static $defaults = 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...
The property $defaults 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...
93
        'Moderated' => 0,
94
        'IsSpam' => 0,
95
    );
96
97
    /**
98
     * {@inheritDoc}
99
     */
100
    private static $casting = 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...
The property $casting 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...
101
        'Title' => 'Varchar',
102
        'ParentTitle' => 'Varchar',
103
        'ParentClassName' => 'Varchar',
104
        'AuthorName' => 'Varchar',
105
        'RSSName' => 'Varchar',
106
        'DeleteLink' => 'Varchar',
107
        'SpamLink' => 'Varchar',
108
        'HamLink' => 'Varchar',
109
        'ApproveLink' => 'Varchar',
110
        'Permalink' => 'Varchar'
111
    );
112
113
    /**
114
     * {@inheritDoc}
115
     */
116
    private static $searchable_fields = 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...
The property $searchable_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...
117
        'Name',
118
        'Email',
119
        'Comment',
120
        'Created'
121
    );
122
123
    /**
124
     * {@inheritDoc}
125
     */
126
    private static $summary_fields = 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...
The property $summary_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...
127
        'Name' => 'Submitted By',
128
        'Email' => 'Email',
129
        'Comment.LimitWordCount' => 'Comment',
130
        'Created' => 'Date Posted',
131
        'Parent.Title' => 'Post',
132
        'IsSpam' => 'Is Spam'
133
    );
134
135
    /**
136
     * {@inheritDoc}
137
     */
138
    private static $field_labels = 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...
The property $field_labels 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...
139
        'Author' => 'Author Member'
140
    );
141
142
    /**
143
     * {@inheritDoc}
144
     */
145
    private static $table_name = 'Comment';
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...
The property $table_name 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...
146
147
    /**
148
     * {@inheritDoc}
149
     */
150
    public function onBeforeWrite()
151
    {
152
        parent::onBeforeWrite();
153
154
        // Sanitize HTML, because its expected to be passed to the template unescaped later
155
        if ($this->AllowHtml) {
156
            $this->Comment = $this->purifyHtml($this->Comment);
157
        }
158
159
        // Check comment depth
160
        $this->updateDepth();
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     */
166
    public function onBeforeDelete()
167
    {
168
        parent::onBeforeDelete();
169
170
        // Delete all children
171
        foreach ($this->ChildComments() as $comment) {
172
            $comment->delete();
173
        }
174
    }
175
176
    /**
177
     * @return Comment_SecurityToken
178
     */
179
    public function getSecurityToken()
180
    {
181
        return Injector::inst()->createWithArgs(SecurityToken::class, array($this));
182
    }
183
184
    /**
185
     * Migrates the old {@link PageComment} objects to {@link Comment}
186
     */
187
    public function requireDefaultRecords()
188
    {
189
        parent::requireDefaultRecords();
190
191
        if (DB::get_schema()->hasTable('PageComment')) {
192
            $comments = DB::query('SELECT * FROM "PageComment"');
193
194
            if ($comments) {
195
                while ($pageComment = $comments->next()) {
196
                    // create a new comment from the older page comment
197
                    $comment = new Comment();
198
                    $comment->update($pageComment);
199
200
                    // set the variables which have changed
201
                    $comment->BaseClass = SiteTree::class;
202
                    $comment->URL = (isset($pageComment['CommenterURL'])) ? $pageComment['CommenterURL'] : '';
203
                    if ((int) $pageComment['NeedsModeration'] == 0) {
204
                        $comment->Moderated = true;
205
                    }
206
207
                    $comment->write();
208
                }
209
            }
210
211
            DB::alteration_message('Migrated PageComment to Comment', 'changed');
212
            DB::get_schema()->dontRequireTable('PageComment');
213
        }
214
    }
215
216
    /**
217
     * Return a link to this comment
218
     *
219
     * @param string $action
220
     *
221
     * @return string link to this comment.
0 ignored issues
show
Should the return type not be string|null?

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...
222
     */
223
    public function Link($action = '')
224
    {
225
        if ($parent = $this->Parent()) {
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
226
            return $parent->Link($action) . '#' . $this->Permalink();
227
        }
228
    }
229
230
    /**
231
     * Returns the permalink for this {@link Comment}. Inserted into
232
     * the ID tag of the comment
233
     *
234
     * @return string
235
     */
236
    public function Permalink()
237
    {
238
        $prefix = $this->getOption('comment_permalink_prefix');
239
        return $prefix . $this->ID;
240
    }
241
242
    /**
243
     * Translate the form field labels for the CMS administration
244
     *
245
     * @param boolean $includerelations
246
     *
247
     * @return array
0 ignored issues
show
Should the return type not be array|string?

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...
248
     */
249
    public function fieldLabels($includerelations = true)
250
    {
251
        $labels = parent::fieldLabels($includerelations);
252
253
        $labels['Name'] = _t('SilverStripe\\Comments\\Model\\Comment.NAME', 'Author Name');
254
        $labels['Comment'] = _t('SilverStripe\\Comments\\Model\\Comment.COMMENT', 'Comment');
255
        $labels['Email'] = _t('SilverStripe\\Comments\\Model\\Comment.EMAIL', 'Email');
256
        $labels['URL'] = _t('SilverStripe\\Comments\\Model\\Comment.URL', 'URL');
257
        $labels['IsSpam'] = _t('SilverStripe\\Comments\\Model\\Comment.ISSPAM', 'Spam?');
258
        $labels['Moderated'] = _t('SilverStripe\\Comments\\Model\\Comment.MODERATED', 'Moderated?');
259
        $labels['ParentTitle'] = _t('SilverStripe\\Comments\\Model\\Comment.PARENTTITLE', 'Parent');
260
        $labels['Created'] = _t('SilverStripe\\Comments\\Model\\Comment.CREATED', 'Date posted');
261
262
        return $labels;
263
    }
264
265
    /**
266
     * Get the commenting option
267
     *
268
     * @param string $key
269
     *
270
     * @return mixed Result if the setting is available, or null otherwise
271
     */
272
    public function getOption($key)
273
    {
274
        // If possible use the current record
275
        $record = $this->Parent();
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
276
277
        if (!$record && $this->Parent()) {
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
278
            // Otherwise a singleton of that record
279
            $record = singleton($this->Parent()->dataClass());
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
280
        } elseif (!$record) {
281
            // Otherwise just use the default options
282
            $record = singleton(CommentsExtension::class);
283
        }
284
285
        return ($record instanceof CommentsExtension || $record->hasExtension(CommentsExtension::class))
286
            ? $record->getCommentsOption($key)
287
            : null;
288
    }
289
290
    /**
291
     * Returns the parent {@link DataObject} this comment is attached too
292
     *
293
     * @deprecated 4.0.0 Use $this->Parent() instead
294
     * @return DataObject
0 ignored issues
show
Should the return type not be DataObject|null?

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...
295
     */
296
    public function getParent()
297
    {
298
        return $this->BaseClass && $this->ParentID
299
            ? DataObject::get_by_id($this->BaseClass, $this->ParentID, true)
300
            : null;
301
    }
302
303
304
    /**
305
     * Returns a string to help identify the parent of the comment
306
     *
307
     * @return string
308
     */
309
    public function getParentTitle()
310
    {
311
        if ($parent = $this->Parent()) {
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
312
            return $parent->Title ?: ($parent->ClassName . ' #' . $parent->ID);
313
        }
314
    }
315
316
    /**
317
     * Comment-parent classnames obviously vary, return the parent classname
318
     *
319
     * @return string
320
     */
321
    public function getParentClassName()
322
    {
323
        return $this->Parent()->getClassName();
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
324
    }
325
326
    /**
327
     * {@inheritDoc}
328
     */
329
    public function castingHelper($field)
0 ignored issues
show
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...
330
    {
331
        // Safely escape the comment
332
        if (in_array($field, ['EscapedComment', 'Comment'], true)) {
333
            return $this->AllowHtml ? 'HTMLText' : 'Text';
334
        }
335
        return parent::castingHelper($field);
336
    }
337
338
    /**
339
     * Content to be safely escaped on the frontend
340
     *
341
     * @return string
342
     */
343
    public function getEscapedComment()
344
    {
345
        return $this->Comment;
346
    }
347
348
    /**
349
     * Return whether this comment is a preview (has not been written to the db)
350
     *
351
     * @return boolean
352
     */
353
    public function isPreview()
354
    {
355
        return !$this->exists();
356
    }
357
358
    /**
359
     * @todo needs to compare to the new {@link Commenting} configuration API
360
     *
361
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
362
     * @param array  $context
363
     * @return bool
364
     */
365
    public function canCreate($member = null, $context = [])
366
    {
367
        return false;
368
    }
369
370
    /**
371
     * Checks for association with a page, and {@link SiteTree->ProvidePermission}
372
     * flag being set to true.
373
     *
374
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
375
     * @return Boolean
376
     */
377
    public function canView($member = null)
378
    {
379
        $member = $this->getMember($member);
380
381
        $extended = $this->extendedCan('canView', $member);
0 ignored issues
show
$member is of type object<SilverStripe\ORM\DataObject>|null, but the function expects a object<SilverStripe\Security\Member>|integer.

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...
382
        if ($extended !== null) {
383
            return $extended;
384
        }
385
386
        if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
387
            return true;
388
        }
389
390
        if ($parent = $this->Parent()) {
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
391
            return $parent->canView($member)
392
                && $parent->hasExtension(CommentsExtension::class)
393
                && $parent->CommentsEnabled;
394
        }
395
396
        return false;
397
    }
398
399
    /**
400
     * Checks if the comment can be edited.
401
     *
402
     * @param null|int|Member $member
403
     * @return Boolean
404
     */
405
    public function canEdit($member = null)
406
    {
407
        $member = $this->getMember($member);
408
409
        if (!$member) {
410
            return false;
411
        }
412
413
        $extended = $this->extendedCan('canEdit', $member);
0 ignored issues
show
$member is of type object<SilverStripe\ORM\DataObject>, but the function expects a object<SilverStripe\Security\Member>|integer.

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...
414
        if ($extended !== null) {
415
            return $extended;
416
        }
417
418
        if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
419
            return true;
420
        }
421
422
        if ($parent = $this->Parent()) {
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
423
            return $parent->canEdit($member);
424
        }
425
426
        return false;
427
    }
428
429
    /**
430
     * Checks if the comment can be deleted.
431
     *
432
     * @param null|int|Member $member
433
     * @return Boolean
434
     */
435
    public function canDelete($member = null)
436
    {
437
        $member = $this->getMember($member);
438
439
        if (!$member) {
440
            return false;
441
        }
442
443
        $extended = $this->extendedCan('canDelete', $member);
0 ignored issues
show
$member is of type object<SilverStripe\ORM\DataObject>, but the function expects a object<SilverStripe\Security\Member>|integer.

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...
444
        if ($extended !== null) {
445
            return $extended;
446
        }
447
448
        return $this->canEdit($member);
0 ignored issues
show
$member is of type object<SilverStripe\ORM\DataObject>, but the function expects a null|integer|object<SilverStripe\Security\Member>.

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...
449
    }
450
451
    /**
452
     * Resolves Member object.
453
     *
454
     * @param Member|int|null $member
455
     * @return Member|null
0 ignored issues
show
Should the return type not be DataObject|null?

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...
456
     */
457
    protected function getMember($member = null)
458
    {
459
        if (!$member) {
460
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\Security\Member::currentUser() has been deprecated with message: 5.0.0 use Security::getCurrentUser()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
461
        }
462
463
        if (is_numeric($member)) {
464
            $member = DataObject::get_by_id(Member::class, $member, true);
465
        }
466
467
        return $member;
468
    }
469
470
    /**
471
     * Return the authors name for the comment
472
     *
473
     * @return string
0 ignored issues
show
Should the return type not be string|null?

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...
474
     */
475
    public function getAuthorName()
476
    {
477
        if ($this->Name) {
478
            return $this->Name;
479
        } elseif ($author = $this->Author()) {
480
            return $author->getName();
481
        }
482
    }
483
484
    /**
485
     * Generate a secure admin-action link authorised for the specified member
486
     *
487
     * @param string $action An action on CommentingController to link to
488
     * @param Member $member The member authorised to invoke this action
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
489
     *
490
     * @return string
491
     */
492
    protected function actionLink($action, $member = null)
493
    {
494
        if (!$member) {
495
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\Security\Member::currentUser() has been deprecated with message: 5.0.0 use Security::getCurrentUser()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
496
        }
497
        if (!$member) {
498
            return false;
499
        }
500
501
        /**
502
         * @todo: How do we handle "DataObject" instances that don't have a Link to reject/spam/delete?? This may
503
         * we have to make CMS a hard dependency instead.
504
         */
505
        // if (!$this->Parent()->hasMethod('Link')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
506
        //     return false;
507
        // }
508
509
        $url = Controller::join_links(
510
            Director::baseURL(),
511
            'comments',
512
            $action,
513
            $this->ID
514
        );
515
516
        // Limit access for this user
517
        $token = $this->getSecurityToken();
518
        return $token->addToUrl($url, $member);
519
    }
520
521
    /**
522
     * Link to delete this comment
523
     *
524
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
525
     *
526
     * @return string
0 ignored issues
show
Should the return type not be string|null?

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...
527
     */
528
    public function DeleteLink($member = null)
529
    {
530
        if ($this->canDelete($member)) {
531
            return $this->actionLink('delete', $member);
532
        }
533
    }
534
535
    /**
536
     * Link to mark as spam
537
     *
538
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
539
     *
540
     * @return string
0 ignored issues
show
Should the return type not be string|null?

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...
541
     */
542
    public function SpamLink($member = null)
543
    {
544
        if ($this->canEdit($member) && !$this->IsSpam) {
545
            return $this->actionLink('spam', $member);
546
        }
547
    }
548
549
    /**
550
     * Link to mark as not-spam (ham)
551
     *
552
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
553
     *
554
     * @return string
0 ignored issues
show
Should the return type not be string|null?

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...
555
     */
556
    public function HamLink($member = null)
557
    {
558
        if ($this->canEdit($member) && $this->IsSpam) {
559
            return $this->actionLink('ham', $member);
560
        }
561
    }
562
563
    /**
564
     * Link to approve this comment
565
     *
566
     * @param Member $member
0 ignored issues
show
Should the type for parameter $member not be Member|null?

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...
567
     *
568
     * @return string
0 ignored issues
show
Should the return type not be string|null?

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...
569
     */
570
    public function ApproveLink($member = null)
571
    {
572
        if ($this->canEdit($member) && !$this->Moderated) {
573
            return $this->actionLink('approve', $member);
574
        }
575
    }
576
577
    /**
578
     * Mark this comment as spam
579
     */
580
    public function markSpam()
581
    {
582
        $this->IsSpam = true;
583
        $this->Moderated = true;
584
        $this->write();
585
        $this->extend('afterMarkSpam');
586
    }
587
588
    /**
589
     * Mark this comment as approved
590
     */
591
    public function markApproved()
592
    {
593
        $this->IsSpam = false;
594
        $this->Moderated = true;
595
        $this->write();
596
        $this->extend('afterMarkApproved');
597
    }
598
599
    /**
600
     * Mark this comment as unapproved
601
     */
602
    public function markUnapproved()
603
    {
604
        $this->Moderated = false;
605
        $this->write();
606
        $this->extend('afterMarkUnapproved');
607
    }
608
609
    /**
610
     * @return string
611
     */
612
    public function SpamClass()
613
    {
614
        if ($this->IsSpam) {
615
            return 'spam';
616
        } elseif (!$this->Moderated) {
617
            return 'unmoderated';
618
        } else {
619
            return 'notspam';
620
        }
621
    }
622
623
    /**
624
     * @return string
625
     */
626
    public function getTitle()
627
    {
628
        $title = sprintf(_t('SilverStripe\\Comments\\Model\\Comment.COMMENTBY', 'Comment by %s', 'Name'), $this->getAuthorName());
629
630
        if ($parent = $this->Parent()) {
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
631
            if ($parent->Title) {
632
                $title .= sprintf(' %s %s', _t('SilverStripe\\Comments\\Model\\Comment.ON', 'on'), $parent->Title);
633
            }
634
        }
635
636
        return $title;
637
    }
638
639
    /*
640
     * Modify the default fields shown to the user
641
     */
642
    public function getCMSFields()
643
    {
644
        $commentField = $this->AllowHtml ? HTMLEditorField::class : TextareaField::class;
645
        $fields = new FieldList(
646
            $this
647
                ->obj('Created')
648
                ->scaffoldFormField($this->fieldLabel('Created'))
649
                ->performReadonlyTransformation(),
650
            TextField::create('Name', $this->fieldLabel('Name')),
651
            $commentField::create('Comment', $this->fieldLabel('Comment')),
652
            EmailField::create('Email', $this->fieldLabel('Email')),
653
            TextField::create('URL', $this->fieldLabel('URL')),
654
            FieldGroup::create(array(
655
                CheckboxField::create('Moderated', $this->fieldLabel('Moderated')),
656
                CheckboxField::create('IsSpam', $this->fieldLabel('IsSpam')),
657
            ))
658
                ->setTitle(_t('SilverStripe\\Comments\\Model\\Comment.OPTIONS', 'Options'))
659
                ->setDescription(_t(
660
                    'SilverStripe\\Comments\\Model\\Comment.OPTION_DESCRIPTION',
661
                    'Unmoderated and spam comments will not be displayed until approved'
662
                ))
663
        );
664
665
        // Show member name if given
666
        if (($author = $this->Author()) && $author->exists()) {
667
            $fields->insertAfter(
668
                TextField::create('AuthorMember', $this->fieldLabel('Author'), $author->Title)
669
                    ->performReadonlyTransformation(),
670
                'Name'
0 ignored issues
show
'Name' is of type string, but the function expects a object<SilverStripe\Forms\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...
671
            );
672
        }
673
674
        // Show parent comment if given
675
        if (($parent = $this->ParentComment()) && $parent->exists()) {
676
            $fields->push(new HeaderField(
677
                'ParentComment_Title',
678
                _t('SilverStripe\\Comments\\Model\\Comment.ParentComment_Title', 'This comment is a reply to the below')
679
            ));
680
            // Created date
681
            // FIXME - the method setName in DatetimeField is not chainable, hence
682
            // the lack of chaining here
683
            $createdField = $parent
684
                ->obj('Created')
685
                ->scaffoldFormField($parent->fieldLabel('Created'));
686
            $createdField->setName('ParentComment_Created');
687
            $createdField->setValue($parent->Created);
688
            $createdField->performReadonlyTransformation();
689
            $fields->push($createdField);
690
691
            // Name (could be member or string value)
692
            $fields->push(
693
                $parent
694
                    ->obj('AuthorName')
695
                    ->scaffoldFormField($parent->fieldLabel('AuthorName'))
696
                    ->setName('ParentComment_AuthorName')
697
                    ->setValue($parent->getAuthorName())
698
                    ->performReadonlyTransformation()
699
            );
700
701
            // Comment body
702
            $fields->push(
703
                $parent
704
                    ->obj('EscapedComment')
705
                    ->scaffoldFormField($parent->fieldLabel(self::class))
706
                    ->setName('ParentComment_EscapedComment')
707
                    ->setValue($parent->Comment)
708
                    ->performReadonlyTransformation()
709
            );
710
        }
711
712
        $this->extend('updateCMSFields', $fields);
713
        return $fields;
714
    }
715
716
    /**
717
     * @param  string $dirtyHtml
718
     *
719
     * @return string
720
     */
721
    public function purifyHtml($dirtyHtml)
722
    {
723
        if ($service = $this->getHtmlPurifierService()) {
724
            return $service->purify($dirtyHtml);
725
        }
726
727
        return $dirtyHtml;
728
    }
729
730
    /**
731
     * @return HTMLPurifier (or anything with a "purify()" method)
0 ignored issues
show
Should the return type not be HTMLPurifier|null?

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...
732
     */
733
    public function getHtmlPurifierService()
734
    {
735
        if (!class_exists(HTMLPurifier_Config::class)) {
736
            return null;
737
        }
738
739
        $config = HTMLPurifier_Config::createDefault();
740
        $allowedElements = (array) $this->getOption('html_allowed_elements');
741
        if (!empty($allowedElements)) {
742
            $config->set('HTML.AllowedElements', $allowedElements);
743
        }
744
745
        // This injector cannot be set unless the 'p' element is allowed
746
        if (in_array('p', $allowedElements)) {
747
            $config->set('AutoFormat.AutoParagraph', true);
748
        }
749
750
        $config->set('AutoFormat.Linkify', true);
751
        $config->set('URI.DisableExternalResources', true);
752
        $config->set('Cache.SerializerPath', TempFolder::getTempFolder(BASE_PATH));
753
        return new HTMLPurifier($config);
754
    }
755
756
    /**
757
     * Calculate the Gravatar link from the email address
758
     *
759
     * @return string
760
     */
761
    public function Gravatar()
762
    {
763
        $gravatar = '';
764
        $use_gravatar = $this->getOption('use_gravatar');
765
766
        if ($use_gravatar) {
767
            $gravatar = 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->Email)));
768
            $gravatarsize = $this->getOption('gravatar_size');
769
            $gravatardefault = $this->getOption('gravatar_default');
770
            $gravatarrating = $this->getOption('gravatar_rating');
771
            $gravatar .= '?s=' . $gravatarsize . '&d=' . $gravatardefault . '&r=' . $gravatarrating;
772
        }
773
774
        return $gravatar;
775
    }
776
777
    /**
778
     * Determine if replies are enabled for this instance
779
     *
780
     * @return boolean
781
     */
782
    public function getRepliesEnabled()
783
    {
784
        // Check reply option
785
        if (!$this->getOption('nested_comments')) {
786
            return false;
787
        }
788
789
        // Check if depth is limited
790
        $maxLevel = $this->getOption('nested_depth');
791
        $notSpam = ($this->SpamClass() == 'notspam');
792
        return $notSpam && (!$maxLevel || $this->Depth < $maxLevel);
793
    }
794
795
    /**
796
     * Returns the list of all replies
797
     *
798
     * @return SS_List
799
     */
800
    public function AllReplies()
801
    {
802
        // No replies if disabled
803
        if (!$this->getRepliesEnabled()) {
804
            return new ArrayList();
805
        }
806
807
        // Get all non-spam comments
808
        $order = $this->getOption('order_replies_by')
809
            ?: $this->getOption('order_comments_by');
810
        $list = $this
811
            ->ChildComments()
812
            ->sort($order);
813
814
        $this->extend('updateAllReplies', $list);
815
        return $list;
816
    }
817
818
    /**
819
     * Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend
820
     *
821
     * @return SS_List
822
     */
823
    public function Replies()
824
    {
825
        // No replies if disabled
826
        if (!$this->getRepliesEnabled()) {
827
            return new ArrayList();
828
        }
829
        $list = $this->AllReplies();
830
831
        // Filter spam comments for non-administrators if configured
832
        $parent = $this->Parent();
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
833
        $showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments();
834
        if (!$showSpam) {
835
            $list = $list->filter('IsSpam', 0);
836
        }
837
838
        // Filter un-moderated comments for non-administrators if moderation is enabled
839
        $showUnmoderated = $parent && (
840
            ($parent->ModerationRequired === 'None')
841
            || ($this->getOption('frontend_moderation') && $parent->canModerateComments())
842
        );
843
        if (!$showUnmoderated) {
844
            $list = $list->filter('Moderated', 1);
845
        }
846
847
        $this->extend('updateReplies', $list);
848
        return $list;
849
    }
850
851
    /**
852
     * Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend
853
     *
854
     * @return PaginatedList
855
     */
856 View Code Duplication
    public function PagedReplies()
0 ignored issues
show
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...
857
    {
858
        $list = $this->Replies();
859
860
        // Add pagination
861
        $list = new PaginatedList($list, Controller::curr()->getRequest());
0 ignored issues
show
\SilverStripe\Control\Co...r::curr()->getRequest() 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...
862
        $list->setPaginationGetVar('repliesstart' . $this->ID);
863
        $list->setPageLength($this->getOption('comments_per_page'));
864
865
        $this->extend('updatePagedReplies', $list);
866
        return $list;
867
    }
868
869
    /**
870
     * Generate a reply form for this comment
871
     *
872
     * @return Form
0 ignored issues
show
Should the return type not be null|\SilverStripe\Comments\Controllers\Form?

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...
873
     */
874
    public function ReplyForm()
875
    {
876
        // Ensure replies are enabled
877
        if (!$this->getRepliesEnabled()) {
878
            return null;
879
        }
880
881
        // Check parent is available
882
        $parent = $this->Parent();
0 ignored issues
show
The method Parent() does not exist on SilverStripe\Comments\Model\Comment. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
883
        if (!$parent || !$parent->exists()) {
884
            return null;
885
        }
886
887
        // Build reply controller
888
        $controller = CommentingController::create();
889
        $controller->setOwnerRecord($parent);
890
        $controller->setParentClass($parent->ClassName);
891
        $controller->setOwnerController(Controller::curr());
892
893
        return $controller->ReplyForm($this);
894
    }
895
896
    /**
897
     * Refresh of this comment in the hierarchy
898
     */
899
    public function updateDepth()
900
    {
901
        $parent = $this->ParentComment();
902
        if ($parent && $parent->exists()) {
903
            $parent->updateDepth();
904
            $this->Depth = $parent->Depth + 1;
905
        } else {
906
            $this->Depth = 1;
907
        }
908
    }
909
}
910