Completed
Push — master ( 04a525...97461f )
by Robbie
02:19
created

Comment::canPostComment()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
namespace SilverStripe\Comments\Model;
4
5
use HTMLPurifier;
6
use HTMLPurifier_Config;
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\Injector\Injector;
13
use SilverStripe\Core\TempFolder;
14
use SilverStripe\Forms\CheckboxField;
15
use SilverStripe\Forms\EmailField;
16
use SilverStripe\Forms\FieldGroup;
17
use SilverStripe\Forms\FieldList;
18
use SilverStripe\Forms\Form;
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\FieldType\DBDatetime;
26
use SilverStripe\ORM\PaginatedList;
27
use SilverStripe\ORM\SS_List;
28
use SilverStripe\Security\Member;
29
use SilverStripe\Security\Permission;
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...
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...
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...
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...
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...
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...
101
        'Title' => 'Varchar',
102
        'ParentTitle' => 'Varchar',
103
        'ParentClassName' => 'Varchar',
104
        'AuthorName' => 'Varchar',
105
        'RSSName' => 'Varchar',
106
        'DeleteLink' => 'Varchar',
107
        'Date' => 'Datetime',
108
        'SpamLink' => 'Varchar',
109
        'HamLink' => 'Varchar',
110
        'ApproveLink' => 'Varchar',
111
        'Permalink' => 'Varchar'
112
    );
113
114
    /**
115
     * {@inheritDoc}
116
     */
117
    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...
118
        'Name',
119
        'Email',
120
        'Comment',
121
        'Created'
122
    );
123
124
    /**
125
     * {@inheritDoc}
126
     */
127
    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...
128
        'Name' => 'Submitted By',
129
        'Email' => 'Email',
130
        'Comment.LimitWordCount' => 'Comment',
131
        'Created' => 'Date Posted',
132
        'Parent.Title' => 'Post',
133
        'IsSpam' => 'Is Spam'
134
    );
135
136
    /**
137
     * {@inheritDoc}
138
     */
139
    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...
140
        'Author' => 'Author Member'
141
    );
142
143
    /**
144
     * {@inheritDoc}
145
     */
146
    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...
147
148
    /**
149
     * {@inheritDoc}
150
     */
151
    public function onBeforeWrite()
152
    {
153
        parent::onBeforeWrite();
154
155
        // Sanitize HTML, because its expected to be passed to the template unescaped later
156
        if ($this->AllowHtml) {
157
            $this->Comment = $this->purifyHtml($this->Comment);
158
        }
159
160
        // Check comment depth
161
        $this->updateDepth();
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167
    public function onBeforeDelete()
168
    {
169
        parent::onBeforeDelete();
170
171
        // Delete all children
172
        foreach ($this->ChildComments() as $comment) {
173
            $comment->delete();
174
        }
175
    }
176
177
    /**
178
     * @return Comment_SecurityToken
179
     */
180
    public function getSecurityToken()
181
    {
182
        return Injector::inst()->createWithArgs(SecurityToken::class, array($this));
183
    }
184
185
    /**
186
     * Return a link to this comment
187
     *
188
     * @param string $action
189
     *
190
     * @return string link to this comment.
0 ignored issues
show
Documentation introduced by
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...
191
     */
192
    public function Link($action = '')
193
    {
194
        if ($parent = $this->Parent()) {
0 ignored issues
show
Bug introduced by
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...
195
            return $parent->Link($action) . '#' . $this->Permalink();
196
        }
197
    }
198
199
    /**
200
     * Returns the permalink for this {@link Comment}. Inserted into
201
     * the ID tag of the comment
202
     *
203
     * @return string
204
     */
205
    public function Permalink()
206
    {
207
        $prefix = $this->getOption('comment_permalink_prefix');
208
        return $prefix . $this->ID;
209
    }
210
211
    /**
212
     * Translate the form field labels for the CMS administration
213
     *
214
     * @param boolean $includerelations
215
     *
216
     * @return array
0 ignored issues
show
Documentation introduced by
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...
217
     */
218
    public function fieldLabels($includerelations = true)
219
    {
220
        $labels = parent::fieldLabels($includerelations);
221
222
        $labels['Name'] = _t(__CLASS__ . '.NAME', 'Author Name');
223
        $labels['Comment'] = _t(__CLASS__ . '.COMMENT', 'Comment');
224
        $labels['Email'] = _t(__CLASS__ . '.EMAIL', 'Email');
225
        $labels['URL'] = _t(__CLASS__ . '.URL', 'URL');
226
        $labels['IsSpam'] = _t(__CLASS__ . '.ISSPAM', 'Spam?');
227
        $labels['Moderated'] = _t(__CLASS__ . '.MODERATED', 'Moderated?');
228
        $labels['ParentTitle'] = _t(__CLASS__ . '.PARENTTITLE', 'Parent');
229
        $labels['Created'] = _t(__CLASS__ . '.CREATED', 'Date posted');
230
231
        return $labels;
232
    }
233
234
    /**
235
     * Get the commenting option
236
     *
237
     * @param string $key
238
     *
239
     * @return mixed Result if the setting is available, or null otherwise
240
     */
241
    public function getOption($key)
242
    {
243
        // If possible use the current record
244
        $record = $this->Parent();
0 ignored issues
show
Bug introduced by
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...
245
246
        if (!$record && $this->Parent()) {
0 ignored issues
show
Bug introduced by
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...
247
            // Otherwise a singleton of that record
248
            $record = singleton($this->Parent()->dataClass());
0 ignored issues
show
Bug introduced by
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...
249
        } elseif (!$record) {
250
            // Otherwise just use the default options
251
            $record = singleton(CommentsExtension::class);
252
        }
253
254
        return ($record instanceof CommentsExtension || $record->hasExtension(CommentsExtension::class))
255
            ? $record->getCommentsOption($key)
256
            : null;
257
    }
258
259
    /**
260
     * Returns the parent {@link DataObject} this comment is attached too
261
     *
262
     * @deprecated 4.0.0 Use $this->Parent() instead
263
     * @return DataObject
0 ignored issues
show
Documentation introduced by
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...
264
     */
265
    public function getParent()
266
    {
267
        return $this->BaseClass && $this->ParentID
268
            ? DataObject::get_by_id($this->BaseClass, $this->ParentID, true)
269
            : null;
270
    }
271
272
273
    /**
274
     * Returns a string to help identify the parent of the comment
275
     *
276
     * @return string
277
     */
278
    public function getParentTitle()
279
    {
280
        if ($parent = $this->Parent()) {
0 ignored issues
show
Bug introduced by
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...
281
            return $parent->Title ?: ($parent->ClassName . ' #' . $parent->ID);
282
        }
283
    }
284
285
    /**
286
     * Comment-parent classnames obviously vary, return the parent classname
287
     *
288
     * @return string
289
     */
290
    public function getParentClassName()
291
    {
292
        return $this->Parent()->getClassName();
0 ignored issues
show
Bug introduced by
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...
293
    }
294
295
    /**
296
     * {@inheritDoc}
297
     */
298
    public function castingHelper($field)
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...
299
    {
300
        // Safely escape the comment
301
        if (in_array($field, ['EscapedComment', 'Comment'], true)) {
302
            return $this->AllowHtml ? 'HTMLText' : 'Text';
303
        }
304
        return parent::castingHelper($field);
305
    }
306
307
    /**
308
     * Content to be safely escaped on the frontend
309
     *
310
     * @return string
311
     */
312
    public function getEscapedComment()
313
    {
314
        return $this->Comment;
315
    }
316
317
    /**
318
     * Return whether this comment is a preview (has not been written to the db)
319
     *
320
     * @return boolean
321
     */
322
    public function isPreview()
323
    {
324
        return !$this->exists();
325
    }
326
327
    /**
328
     * @todo needs to compare to the new {@link Commenting} configuration API
329
     *
330
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
331
     * @param array  $context
332
     * @return bool
333
     */
334
    public function canCreate($member = null, $context = [])
335
    {
336
        return false;
337
    }
338
339
    /**
340
     * Checks for association with a page, and {@link SiteTree->ProvidePermission}
341
     * flag being set to true.
342
     *
343
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
344
     * @return Boolean
345
     */
346
    public function canView($member = null)
347
    {
348
        $member = $this->getMember($member);
349
350
        $extended = $this->extendedCan('canView', $member);
0 ignored issues
show
Documentation introduced by
$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...
351
        if ($extended !== null) {
352
            return $extended;
353
        }
354
355
        if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
356
            return true;
357
        }
358
359
        if ($parent = $this->Parent()) {
0 ignored issues
show
Bug introduced by
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...
360
            return $parent->canView($member)
361
                && $parent->hasExtension(CommentsExtension::class)
362
                && $parent->CommentsEnabled;
363
        }
364
365
        return false;
366
    }
367
368
    /**
369
     * Checks if the comment can be edited.
370
     *
371
     * @param null|int|Member $member
372
     * @return Boolean
373
     */
374
    public function canEdit($member = null)
375
    {
376
        $member = $this->getMember($member);
377
378
        if (!$member) {
379
            return false;
380
        }
381
382
        $extended = $this->extendedCan('canEdit', $member);
0 ignored issues
show
Documentation introduced by
$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...
383
        if ($extended !== null) {
384
            return $extended;
385
        }
386
387
        if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
388
            return true;
389
        }
390
391
        if ($parent = $this->Parent()) {
0 ignored issues
show
Bug introduced by
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...
392
            return $parent->canEdit($member);
393
        }
394
395
        return false;
396
    }
397
398
    /**
399
     * Checks if the comment can be deleted.
400
     *
401
     * @param null|int|Member $member
402
     * @return Boolean
403
     */
404
    public function canDelete($member = null)
405
    {
406
        $member = $this->getMember($member);
407
408
        if (!$member) {
409
            return false;
410
        }
411
412
        $extended = $this->extendedCan('canDelete', $member);
0 ignored issues
show
Documentation introduced by
$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...
413
        if ($extended !== null) {
414
            return $extended;
415
        }
416
417
        return $this->canEdit($member);
0 ignored issues
show
Documentation introduced by
$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...
418
    }
419
420
    /**
421
     * Resolves Member object.
422
     *
423
     * @param Member|int|null $member
424
     * @return Member|null
0 ignored issues
show
Documentation introduced by
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...
425
     */
426
    protected function getMember($member = null)
427
    {
428
        if (!$member) {
429
            $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...
430
        }
431
432
        if (is_numeric($member)) {
433
            $member = DataObject::get_by_id(Member::class, $member, true);
434
        }
435
436
        return $member;
437
    }
438
439
    /**
440
     * Return the authors name for the comment
441
     *
442
     * @return string
0 ignored issues
show
Documentation introduced by
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...
443
     */
444
    public function getAuthorName()
445
    {
446
        if ($this->Name) {
447
            return $this->Name;
448
        } elseif ($author = $this->Author()) {
449
            return $author->getName();
450
        }
451
    }
452
453
    /**
454
     * Generate a secure admin-action link authorised for the specified member
455
     *
456
     * @param string $action An action on CommentingController to link to
457
     * @param Member $member The member authorised to invoke this action
0 ignored issues
show
Documentation introduced by
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...
458
     *
459
     * @return string
460
     */
461
    protected function actionLink($action, $member = null)
462
    {
463
        if (!$member) {
464
            $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...
465
        }
466
        if (!$member) {
467
            return false;
468
        }
469
470
        /**
471
         * @todo: How do we handle "DataObject" instances that don't have a Link to reject/spam/delete?? This may
472
         * we have to make CMS a hard dependency instead.
473
         */
474
        // if (!$this->Parent()->hasMethod('Link')) {
475
        //     return false;
476
        // }
477
478
        $url = Controller::join_links(
479
            Director::baseURL(),
480
            'comments',
481
            $action,
482
            $this->ID
483
        );
484
485
        // Limit access for this user
486
        $token = $this->getSecurityToken();
487
        return $token->addToUrl($url, $member);
488
    }
489
490
    /**
491
     * Link to delete this comment
492
     *
493
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
494
     *
495
     * @return string
0 ignored issues
show
Documentation introduced by
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...
496
     */
497
    public function DeleteLink($member = null)
498
    {
499
        if ($this->canDelete($member)) {
500
            return $this->actionLink('delete', $member);
501
        }
502
    }
503
504
    /**
505
     * Link to mark as spam
506
     *
507
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
508
     *
509
     * @return string
0 ignored issues
show
Documentation introduced by
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...
510
     */
511
    public function SpamLink($member = null)
512
    {
513
        if ($this->canEdit($member) && !$this->IsSpam) {
514
            return $this->actionLink('spam', $member);
515
        }
516
    }
517
518
    /**
519
     * Link to mark as not-spam (ham)
520
     *
521
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
522
     *
523
     * @return string
0 ignored issues
show
Documentation introduced by
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...
524
     */
525
    public function HamLink($member = null)
526
    {
527
        if ($this->canEdit($member) && $this->IsSpam) {
528
            return $this->actionLink('ham', $member);
529
        }
530
    }
531
532
    /**
533
     * Link to approve this comment
534
     *
535
     * @param Member $member
0 ignored issues
show
Documentation introduced by
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...
536
     *
537
     * @return string
0 ignored issues
show
Documentation introduced by
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...
538
     */
539
    public function ApproveLink($member = null)
540
    {
541
        if ($this->canEdit($member) && !$this->Moderated) {
542
            return $this->actionLink('approve', $member);
543
        }
544
    }
545
546
    /**
547
     * Mark this comment as spam
548
     */
549
    public function markSpam()
550
    {
551
        $this->IsSpam = true;
552
        $this->Moderated = true;
553
        $this->write();
554
        $this->extend('afterMarkSpam');
555
    }
556
557
    /**
558
     * Mark this comment as approved
559
     */
560
    public function markApproved()
561
    {
562
        $this->IsSpam = false;
563
        $this->Moderated = true;
564
        $this->write();
565
        $this->extend('afterMarkApproved');
566
    }
567
568
    /**
569
     * Mark this comment as unapproved
570
     */
571
    public function markUnapproved()
572
    {
573
        $this->Moderated = false;
574
        $this->write();
575
        $this->extend('afterMarkUnapproved');
576
    }
577
578
    /**
579
     * @return string
580
     */
581
    public function SpamClass()
582
    {
583
        if ($this->IsSpam) {
584
            return 'spam';
585
        } elseif (!$this->Moderated) {
586
            return 'unmoderated';
587
        } else {
588
            return 'notspam';
589
        }
590
    }
591
592
    /**
593
     * @return string
594
     */
595
    public function getTitle()
596
    {
597
        $title = sprintf(_t(__CLASS__ . '.COMMENTBY', 'Comment by %s', 'Name'), $this->getAuthorName());
598
599
        if ($parent = $this->Parent()) {
0 ignored issues
show
Bug introduced by
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...
600
            if ($parent->Title) {
601
                $title .= sprintf(' %s %s', _t(__CLASS__ . '.ON', 'on'), $parent->Title);
602
            }
603
        }
604
605
        return $title;
606
    }
607
608
    /*
609
     * Modify the default fields shown to the user
610
     */
611
    public function getCMSFields()
612
    {
613
        $commentField = $this->AllowHtml ? HTMLEditorField::class : TextareaField::class;
614
        $fields = new FieldList(
615
            $this
616
                ->obj('Created')
617
                ->scaffoldFormField($this->fieldLabel('Created'))
618
                ->performReadonlyTransformation(),
619
            TextField::create('Name', $this->fieldLabel('Name')),
620
            $commentField::create('Comment', $this->fieldLabel('Comment')),
621
            EmailField::create('Email', $this->fieldLabel('Email')),
622
            TextField::create('URL', $this->fieldLabel('URL')),
623
            FieldGroup::create(array(
624
                CheckboxField::create('Moderated', $this->fieldLabel('Moderated')),
625
                CheckboxField::create('IsSpam', $this->fieldLabel('IsSpam')),
626
            ))
627
                ->setTitle(_t(__CLASS__ . '.OPTIONS', 'Options'))
628
                ->setDescription(_t(
629
                    __CLASS__ . '.OPTION_DESCRIPTION',
630
                    'Unmoderated and spam comments will not be displayed until approved'
631
                ))
632
        );
633
634
        // Show member name if given
635
        if (($author = $this->Author()) && $author->exists()) {
636
            $fields->insertAfter(
637
                TextField::create('AuthorMember', $this->fieldLabel('Author'), $author->Title)
638
                    ->performReadonlyTransformation(),
639
                'Name'
0 ignored issues
show
Documentation introduced by
'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...
640
            );
641
        }
642
643
        // Show parent comment if given
644
        if (($parent = $this->ParentComment()) && $parent->exists()) {
645
            $fields->push(new HeaderField(
646
                'ParentComment_Title',
647
                _t(__CLASS__ . '.ParentComment_Title', 'This comment is a reply to the below')
648
            ));
649
            // Created date
650
            // FIXME - the method setName in DatetimeField is not chainable, hence
651
            // the lack of chaining here
652
            $createdField = $parent
653
                ->obj('Created')
654
                ->scaffoldFormField($parent->fieldLabel('Created'));
655
            $createdField->setName('ParentComment_Created');
656
            $createdField->setValue($parent->Created);
657
            $createdField->performReadonlyTransformation();
658
            $fields->push($createdField);
659
660
            // Name (could be member or string value)
661
            $fields->push(
662
                $parent
663
                    ->obj('AuthorName')
664
                    ->scaffoldFormField($parent->fieldLabel('AuthorName'))
665
                    ->setName('ParentComment_AuthorName')
666
                    ->setValue($parent->getAuthorName())
667
                    ->performReadonlyTransformation()
668
            );
669
670
            // Comment body
671
            $fields->push(
672
                $parent
673
                    ->obj('EscapedComment')
674
                    ->scaffoldFormField($parent->fieldLabel(self::class))
675
                    ->setName('ParentComment_EscapedComment')
676
                    ->setValue($parent->Comment)
677
                    ->performReadonlyTransformation()
678
            );
679
        }
680
681
        $this->extend('updateCMSFields', $fields);
682
        return $fields;
683
    }
684
685
    /**
686
     * @param  string $dirtyHtml
687
     *
688
     * @return string
689
     */
690
    public function purifyHtml($dirtyHtml)
691
    {
692
        if ($service = $this->getHtmlPurifierService()) {
693
            return $service->purify($dirtyHtml);
694
        }
695
696
        return $dirtyHtml;
697
    }
698
699
    /**
700
     * @return HTMLPurifier (or anything with a "purify()" method)
0 ignored issues
show
Documentation introduced by
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...
701
     */
702
    public function getHtmlPurifierService()
703
    {
704
        if (!class_exists(HTMLPurifier_Config::class)) {
705
            return null;
706
        }
707
708
        $config = HTMLPurifier_Config::createDefault();
709
        $allowedElements = (array) $this->getOption('html_allowed_elements');
710
        if (!empty($allowedElements)) {
711
            $config->set('HTML.AllowedElements', $allowedElements);
712
        }
713
714
        // This injector cannot be set unless the 'p' element is allowed
715
        if (in_array('p', $allowedElements)) {
716
            $config->set('AutoFormat.AutoParagraph', true);
717
        }
718
719
        $config->set('AutoFormat.Linkify', true);
720
        $config->set('URI.DisableExternalResources', true);
721
        $config->set('Cache.SerializerPath', TempFolder::getTempFolder(BASE_PATH));
722
        return new HTMLPurifier($config);
723
    }
724
725
    /**
726
     * Calculate the Gravatar link from the email address
727
     *
728
     * @return string
729
     */
730
    public function Gravatar()
731
    {
732
        $gravatar = '';
733
        $use_gravatar = $this->getOption('use_gravatar');
734
735
        if ($use_gravatar) {
736
            $gravatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($this->Email)));
737
            $gravatarsize = $this->getOption('gravatar_size');
738
            $gravatardefault = $this->getOption('gravatar_default');
739
            $gravatarrating = $this->getOption('gravatar_rating');
740
            $gravatar .= '?' . http_build_query(array(
741
                's' => $gravatarsize,
742
                'd' => $gravatardefault,
743
                'r' => $gravatarrating,
744
            ));
745
        }
746
747
        return $gravatar;
748
    }
749
750
    /**
751
     * Determine if replies are enabled for this instance
752
     *
753
     * @return boolean
754
     */
755
    public function getRepliesEnabled()
756
    {
757
        // Check reply option
758
        if (!$this->getOption('nested_comments')) {
759
            return false;
760
        }
761
762
        // Check if depth is limited
763
        $maxLevel = $this->getOption('nested_depth');
764
        $notSpam = ($this->SpamClass() == 'notspam');
765
        return $notSpam && (!$maxLevel || $this->Depth < $maxLevel);
766
    }
767
768
    /**
769
     * Proxy for checking whether the has permission to comment on the comment parent.
770
     *
771
     * @param Member $member Member to check
0 ignored issues
show
Documentation introduced by
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...
772
     *
773
     * @return boolean
774
     */
775
    public function canPostComment($member = null)
776
    {
777
        return $this->Parent()
0 ignored issues
show
Bug introduced by
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...
778
            && $this->Parent()->exists()
0 ignored issues
show
Bug introduced by
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...
779
            && $this->Parent()->canPostComment($member);
0 ignored issues
show
Bug introduced by
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...
780
    }
781
782
    /**
783
     * Returns the list of all replies
784
     *
785
     * @return SS_List
786
     */
787
    public function AllReplies()
788
    {
789
        // No replies if disabled
790
        if (!$this->getRepliesEnabled()) {
791
            return new ArrayList();
792
        }
793
794
        // Get all non-spam comments
795
        $order = $this->getOption('order_replies_by')
796
            ?: $this->getOption('order_comments_by');
797
        $list = $this
798
            ->ChildComments()
799
            ->sort($order);
800
801
        $this->extend('updateAllReplies', $list);
802
        return $list;
803
    }
804
805
    /**
806
     * Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend
807
     *
808
     * @return SS_List
809
     */
810
    public function Replies()
811
    {
812
        // No replies if disabled
813
        if (!$this->getRepliesEnabled()) {
814
            return new ArrayList();
815
        }
816
        $list = $this->AllReplies();
817
818
        // Filter spam comments for non-administrators if configured
819
        $parent = $this->Parent();
0 ignored issues
show
Bug introduced by
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...
820
        $showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments();
821
        if (!$showSpam) {
822
            $list = $list->filter('IsSpam', 0);
823
        }
824
825
        // Filter un-moderated comments for non-administrators if moderation is enabled
826
        $showUnmoderated = $parent && (
827
            ($parent->ModerationRequired === 'None')
828
            || ($this->getOption('frontend_moderation') && $parent->canModerateComments())
829
        );
830
        if (!$showUnmoderated) {
831
            $list = $list->filter('Moderated', 1);
832
        }
833
834
        $this->extend('updateReplies', $list);
835
        return $list;
836
    }
837
838
    /**
839
     * Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend
840
     *
841
     * @return PaginatedList
842
     */
843
    public function PagedReplies()
844
    {
845
        $list = $this->Replies();
846
847
        // Add pagination
848
        $list = new PaginatedList($list, Controller::curr()->getRequest());
0 ignored issues
show
Documentation introduced by
\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...
849
        $list->setPaginationGetVar('repliesstart' . $this->ID);
850
        $list->setPageLength($this->getOption('comments_per_page'));
851
852
        $this->extend('updatePagedReplies', $list);
853
        return $list;
854
    }
855
856
    /**
857
     * Generate a reply form for this comment
858
     *
859
     * @return Form
0 ignored issues
show
Documentation introduced by
Should the return type not be Form|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...
860
     */
861
    public function ReplyForm()
862
    {
863
        // Ensure replies are enabled
864
        if (!$this->getRepliesEnabled()) {
865
            return null;
866
        }
867
868
        // Check parent is available
869
        $parent = $this->Parent();
0 ignored issues
show
Bug introduced by
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...
870
        if (!$parent || !$parent->exists()) {
871
            return null;
872
        }
873
874
        // Build reply controller
875
        $controller = CommentingController::create();
876
        $controller->setOwnerRecord($parent);
877
        $controller->setParentClass($parent->ClassName);
878
        $controller->setOwnerController(Controller::curr());
879
880
        return $controller->ReplyForm($this);
881
    }
882
883
    /**
884
     * @return string
885
     */
886
    public function getDate()
887
    {
888
        return $this->Created;
889
    }
890
891
    /**
892
     * Refresh of this comment in the hierarchy
893
     */
894
    public function updateDepth()
895
    {
896
        $parent = $this->ParentComment();
897
        if ($parent && $parent->exists()) {
898
            $parent->updateDepth();
899
            $this->Depth = $parent->Depth + 1;
900
        } else {
901
            $this->Depth = 1;
902
        }
903
    }
904
}
905