Completed
Push — master ( 4bf0a8...17714f )
by Robbie
10s
created

Comment::requireDefaultRecords()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 28
rs 8.439
c 1
b 0
f 0
cc 6
eloc 15
nc 3
nop 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A Comment::Link() 0 6 2
A Comment::Permalink() 0 5 1
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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...
Unused Code introduced by
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
     * Return a link to this comment
186
     *
187
     * @param string $action
188
     *
189
     * @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...
190
     */
191
    public function Link($action = '')
192
    {
193
        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...
194
            return $parent->Link($action) . '#' . $this->Permalink();
195
        }
196
    }
197
198
    /**
199
     * Returns the permalink for this {@link Comment}. Inserted into
200
     * the ID tag of the comment
201
     *
202
     * @return string
203
     */
204
    public function Permalink()
205
    {
206
        $prefix = $this->getOption('comment_permalink_prefix');
207
        return $prefix . $this->ID;
208
    }
209
210
    /**
211
     * Translate the form field labels for the CMS administration
212
     *
213
     * @param boolean $includerelations
214
     *
215
     * @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...
216
     */
217
    public function fieldLabels($includerelations = true)
218
    {
219
        $labels = parent::fieldLabels($includerelations);
220
221
        $labels['Name'] = _t('SilverStripe\\Comments\\Model\\Comment.NAME', 'Author Name');
222
        $labels['Comment'] = _t('SilverStripe\\Comments\\Model\\Comment.COMMENT', 'Comment');
223
        $labels['Email'] = _t('SilverStripe\\Comments\\Model\\Comment.EMAIL', 'Email');
224
        $labels['URL'] = _t('SilverStripe\\Comments\\Model\\Comment.URL', 'URL');
225
        $labels['IsSpam'] = _t('SilverStripe\\Comments\\Model\\Comment.ISSPAM', 'Spam?');
226
        $labels['Moderated'] = _t('SilverStripe\\Comments\\Model\\Comment.MODERATED', 'Moderated?');
227
        $labels['ParentTitle'] = _t('SilverStripe\\Comments\\Model\\Comment.PARENTTITLE', 'Parent');
228
        $labels['Created'] = _t('SilverStripe\\Comments\\Model\\Comment.CREATED', 'Date posted');
229
230
        return $labels;
231
    }
232
233
    /**
234
     * Get the commenting option
235
     *
236
     * @param string $key
237
     *
238
     * @return mixed Result if the setting is available, or null otherwise
239
     */
240
    public function getOption($key)
241
    {
242
        // If possible use the current record
243
        $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...
244
245
        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...
246
            // Otherwise a singleton of that record
247
            $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...
248
        } elseif (!$record) {
249
            // Otherwise just use the default options
250
            $record = singleton(CommentsExtension::class);
251
        }
252
253
        return ($record instanceof CommentsExtension || $record->hasExtension(CommentsExtension::class))
254
            ? $record->getCommentsOption($key)
255
            : null;
256
    }
257
258
    /**
259
     * Returns the parent {@link DataObject} this comment is attached too
260
     *
261
     * @deprecated 4.0.0 Use $this->Parent() instead
262
     * @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...
263
     */
264
    public function getParent()
265
    {
266
        return $this->BaseClass && $this->ParentID
267
            ? DataObject::get_by_id($this->BaseClass, $this->ParentID, true)
268
            : null;
269
    }
270
271
272
    /**
273
     * Returns a string to help identify the parent of the comment
274
     *
275
     * @return string
276
     */
277
    public function getParentTitle()
278
    {
279
        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...
280
            return $parent->Title ?: ($parent->ClassName . ' #' . $parent->ID);
281
        }
282
    }
283
284
    /**
285
     * Comment-parent classnames obviously vary, return the parent classname
286
     *
287
     * @return string
288
     */
289
    public function getParentClassName()
290
    {
291
        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...
292
    }
293
294
    /**
295
     * {@inheritDoc}
296
     */
297
    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...
298
    {
299
        // Safely escape the comment
300
        if (in_array($field, ['EscapedComment', 'Comment'], true)) {
301
            return $this->AllowHtml ? 'HTMLText' : 'Text';
302
        }
303
        return parent::castingHelper($field);
304
    }
305
306
    /**
307
     * Content to be safely escaped on the frontend
308
     *
309
     * @return string
310
     */
311
    public function getEscapedComment()
312
    {
313
        return $this->Comment;
314
    }
315
316
    /**
317
     * Return whether this comment is a preview (has not been written to the db)
318
     *
319
     * @return boolean
320
     */
321
    public function isPreview()
322
    {
323
        return !$this->exists();
324
    }
325
326
    /**
327
     * @todo needs to compare to the new {@link Commenting} configuration API
328
     *
329
     * @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...
330
     * @param array  $context
331
     * @return bool
332
     */
333
    public function canCreate($member = null, $context = [])
334
    {
335
        return false;
336
    }
337
338
    /**
339
     * Checks for association with a page, and {@link SiteTree->ProvidePermission}
340
     * flag being set to true.
341
     *
342
     * @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...
343
     * @return Boolean
344
     */
345
    public function canView($member = null)
346
    {
347
        $member = $this->getMember($member);
348
349
        $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...
350
        if ($extended !== null) {
351
            return $extended;
352
        }
353
354
        if (Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
355
            return true;
356
        }
357
358
        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...
359
            return $parent->canView($member)
360
                && $parent->hasExtension(CommentsExtension::class)
361
                && $parent->CommentsEnabled;
362
        }
363
364
        return false;
365
    }
366
367
    /**
368
     * Checks if the comment can be edited.
369
     *
370
     * @param null|int|Member $member
371
     * @return Boolean
372
     */
373
    public function canEdit($member = null)
374
    {
375
        $member = $this->getMember($member);
376
377
        if (!$member) {
378
            return false;
379
        }
380
381
        $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...
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
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...
391
            return $parent->canEdit($member);
392
        }
393
394
        return false;
395
    }
396
397
    /**
398
     * Checks if the comment can be deleted.
399
     *
400
     * @param null|int|Member $member
401
     * @return Boolean
402
     */
403
    public function canDelete($member = null)
404
    {
405
        $member = $this->getMember($member);
406
407
        if (!$member) {
408
            return false;
409
        }
410
411
        $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...
412
        if ($extended !== null) {
413
            return $extended;
414
        }
415
416
        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...
417
    }
418
419
    /**
420
     * Resolves Member object.
421
     *
422
     * @param Member|int|null $member
423
     * @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...
424
     */
425
    protected function getMember($member = null)
426
    {
427
        if (!$member) {
428
            $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...
429
        }
430
431
        if (is_numeric($member)) {
432
            $member = DataObject::get_by_id(Member::class, $member, true);
433
        }
434
435
        return $member;
436
    }
437
438
    /**
439
     * Return the authors name for the comment
440
     *
441
     * @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...
442
     */
443
    public function getAuthorName()
444
    {
445
        if ($this->Name) {
446
            return $this->Name;
447
        } elseif ($author = $this->Author()) {
448
            return $author->getName();
449
        }
450
    }
451
452
    /**
453
     * Generate a secure admin-action link authorised for the specified member
454
     *
455
     * @param string $action An action on CommentingController to link to
456
     * @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...
457
     *
458
     * @return string
459
     */
460
    protected function actionLink($action, $member = null)
461
    {
462
        if (!$member) {
463
            $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...
464
        }
465
        if (!$member) {
466
            return false;
467
        }
468
469
        /**
470
         * @todo: How do we handle "DataObject" instances that don't have a Link to reject/spam/delete?? This may
471
         * we have to make CMS a hard dependency instead.
472
         */
473
        // 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...
474
        //     return false;
475
        // }
476
477
        $url = Controller::join_links(
478
            Director::baseURL(),
479
            'comments',
480
            $action,
481
            $this->ID
482
        );
483
484
        // Limit access for this user
485
        $token = $this->getSecurityToken();
486
        return $token->addToUrl($url, $member);
487
    }
488
489
    /**
490
     * Link to delete this comment
491
     *
492
     * @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...
493
     *
494
     * @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...
495
     */
496
    public function DeleteLink($member = null)
497
    {
498
        if ($this->canDelete($member)) {
499
            return $this->actionLink('delete', $member);
500
        }
501
    }
502
503
    /**
504
     * Link to mark as spam
505
     *
506
     * @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...
507
     *
508
     * @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...
509
     */
510
    public function SpamLink($member = null)
511
    {
512
        if ($this->canEdit($member) && !$this->IsSpam) {
513
            return $this->actionLink('spam', $member);
514
        }
515
    }
516
517
    /**
518
     * Link to mark as not-spam (ham)
519
     *
520
     * @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...
521
     *
522
     * @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...
523
     */
524
    public function HamLink($member = null)
525
    {
526
        if ($this->canEdit($member) && $this->IsSpam) {
527
            return $this->actionLink('ham', $member);
528
        }
529
    }
530
531
    /**
532
     * Link to approve this comment
533
     *
534
     * @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...
535
     *
536
     * @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...
537
     */
538
    public function ApproveLink($member = null)
539
    {
540
        if ($this->canEdit($member) && !$this->Moderated) {
541
            return $this->actionLink('approve', $member);
542
        }
543
    }
544
545
    /**
546
     * Mark this comment as spam
547
     */
548
    public function markSpam()
549
    {
550
        $this->IsSpam = true;
551
        $this->Moderated = true;
552
        $this->write();
553
        $this->extend('afterMarkSpam');
554
    }
555
556
    /**
557
     * Mark this comment as approved
558
     */
559
    public function markApproved()
560
    {
561
        $this->IsSpam = false;
562
        $this->Moderated = true;
563
        $this->write();
564
        $this->extend('afterMarkApproved');
565
    }
566
567
    /**
568
     * Mark this comment as unapproved
569
     */
570
    public function markUnapproved()
571
    {
572
        $this->Moderated = false;
573
        $this->write();
574
        $this->extend('afterMarkUnapproved');
575
    }
576
577
    /**
578
     * @return string
579
     */
580
    public function SpamClass()
581
    {
582
        if ($this->IsSpam) {
583
            return 'spam';
584
        } elseif (!$this->Moderated) {
585
            return 'unmoderated';
586
        } else {
587
            return 'notspam';
588
        }
589
    }
590
591
    /**
592
     * @return string
593
     */
594
    public function getTitle()
595
    {
596
        $title = sprintf(_t('SilverStripe\\Comments\\Model\\Comment.COMMENTBY', 'Comment by %s', 'Name'), $this->getAuthorName());
597
598
        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...
599
            if ($parent->Title) {
600
                $title .= sprintf(' %s %s', _t('SilverStripe\\Comments\\Model\\Comment.ON', 'on'), $parent->Title);
601
            }
602
        }
603
604
        return $title;
605
    }
606
607
    /*
608
     * Modify the default fields shown to the user
609
     */
610
    public function getCMSFields()
611
    {
612
        $commentField = $this->AllowHtml ? HTMLEditorField::class : TextareaField::class;
613
        $fields = new FieldList(
614
            $this
615
                ->obj('Created')
616
                ->scaffoldFormField($this->fieldLabel('Created'))
617
                ->performReadonlyTransformation(),
618
            TextField::create('Name', $this->fieldLabel('Name')),
619
            $commentField::create('Comment', $this->fieldLabel('Comment')),
620
            EmailField::create('Email', $this->fieldLabel('Email')),
621
            TextField::create('URL', $this->fieldLabel('URL')),
622
            FieldGroup::create(array(
623
                CheckboxField::create('Moderated', $this->fieldLabel('Moderated')),
624
                CheckboxField::create('IsSpam', $this->fieldLabel('IsSpam')),
625
            ))
626
                ->setTitle(_t('SilverStripe\\Comments\\Model\\Comment.OPTIONS', 'Options'))
627
                ->setDescription(_t(
628
                    'SilverStripe\\Comments\\Model\\Comment.OPTION_DESCRIPTION',
629
                    'Unmoderated and spam comments will not be displayed until approved'
630
                ))
631
        );
632
633
        // Show member name if given
634
        if (($author = $this->Author()) && $author->exists()) {
635
            $fields->insertAfter(
636
                TextField::create('AuthorMember', $this->fieldLabel('Author'), $author->Title)
637
                    ->performReadonlyTransformation(),
638
                '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...
639
            );
640
        }
641
642
        // Show parent comment if given
643
        if (($parent = $this->ParentComment()) && $parent->exists()) {
644
            $fields->push(new HeaderField(
645
                'ParentComment_Title',
646
                _t('SilverStripe\\Comments\\Model\\Comment.ParentComment_Title', 'This comment is a reply to the below')
647
            ));
648
            // Created date
649
            // FIXME - the method setName in DatetimeField is not chainable, hence
650
            // the lack of chaining here
651
            $createdField = $parent
652
                ->obj('Created')
653
                ->scaffoldFormField($parent->fieldLabel('Created'));
654
            $createdField->setName('ParentComment_Created');
655
            $createdField->setValue($parent->Created);
656
            $createdField->performReadonlyTransformation();
657
            $fields->push($createdField);
658
659
            // Name (could be member or string value)
660
            $fields->push(
661
                $parent
662
                    ->obj('AuthorName')
663
                    ->scaffoldFormField($parent->fieldLabel('AuthorName'))
664
                    ->setName('ParentComment_AuthorName')
665
                    ->setValue($parent->getAuthorName())
666
                    ->performReadonlyTransformation()
667
            );
668
669
            // Comment body
670
            $fields->push(
671
                $parent
672
                    ->obj('EscapedComment')
673
                    ->scaffoldFormField($parent->fieldLabel(self::class))
674
                    ->setName('ParentComment_EscapedComment')
675
                    ->setValue($parent->Comment)
676
                    ->performReadonlyTransformation()
677
            );
678
        }
679
680
        $this->extend('updateCMSFields', $fields);
681
        return $fields;
682
    }
683
684
    /**
685
     * @param  string $dirtyHtml
686
     *
687
     * @return string
688
     */
689
    public function purifyHtml($dirtyHtml)
690
    {
691
        if ($service = $this->getHtmlPurifierService()) {
692
            return $service->purify($dirtyHtml);
693
        }
694
695
        return $dirtyHtml;
696
    }
697
698
    /**
699
     * @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...
700
     */
701
    public function getHtmlPurifierService()
702
    {
703
        if (!class_exists(HTMLPurifier_Config::class)) {
704
            return null;
705
        }
706
707
        $config = HTMLPurifier_Config::createDefault();
708
        $allowedElements = (array) $this->getOption('html_allowed_elements');
709
        if (!empty($allowedElements)) {
710
            $config->set('HTML.AllowedElements', $allowedElements);
711
        }
712
713
        // This injector cannot be set unless the 'p' element is allowed
714
        if (in_array('p', $allowedElements)) {
715
            $config->set('AutoFormat.AutoParagraph', true);
716
        }
717
718
        $config->set('AutoFormat.Linkify', true);
719
        $config->set('URI.DisableExternalResources', true);
720
        $config->set('Cache.SerializerPath', TempFolder::getTempFolder(BASE_PATH));
721
        return new HTMLPurifier($config);
722
    }
723
724
    /**
725
     * Calculate the Gravatar link from the email address
726
     *
727
     * @return string
728
     */
729
    public function Gravatar()
730
    {
731
        $gravatar = '';
732
        $use_gravatar = $this->getOption('use_gravatar');
733
734
        if ($use_gravatar) {
735
            $gravatar = 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->Email)));
736
            $gravatarsize = $this->getOption('gravatar_size');
737
            $gravatardefault = $this->getOption('gravatar_default');
738
            $gravatarrating = $this->getOption('gravatar_rating');
739
            $gravatar .= '?s=' . $gravatarsize . '&d=' . $gravatardefault . '&r=' . $gravatarrating;
740
        }
741
742
        return $gravatar;
743
    }
744
745
    /**
746
     * Determine if replies are enabled for this instance
747
     *
748
     * @return boolean
749
     */
750
    public function getRepliesEnabled()
751
    {
752
        // Check reply option
753
        if (!$this->getOption('nested_comments')) {
754
            return false;
755
        }
756
757
        // Check if depth is limited
758
        $maxLevel = $this->getOption('nested_depth');
759
        $notSpam = ($this->SpamClass() == 'notspam');
760
        return $notSpam && (!$maxLevel || $this->Depth < $maxLevel);
761
    }
762
763
    /**
764
     * Returns the list of all replies
765
     *
766
     * @return SS_List
767
     */
768
    public function AllReplies()
769
    {
770
        // No replies if disabled
771
        if (!$this->getRepliesEnabled()) {
772
            return new ArrayList();
773
        }
774
775
        // Get all non-spam comments
776
        $order = $this->getOption('order_replies_by')
777
            ?: $this->getOption('order_comments_by');
778
        $list = $this
779
            ->ChildComments()
780
            ->sort($order);
781
782
        $this->extend('updateAllReplies', $list);
783
        return $list;
784
    }
785
786
    /**
787
     * Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend
788
     *
789
     * @return SS_List
790
     */
791
    public function Replies()
792
    {
793
        // No replies if disabled
794
        if (!$this->getRepliesEnabled()) {
795
            return new ArrayList();
796
        }
797
        $list = $this->AllReplies();
798
799
        // Filter spam comments for non-administrators if configured
800
        $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...
801
        $showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments();
802
        if (!$showSpam) {
803
            $list = $list->filter('IsSpam', 0);
804
        }
805
806
        // Filter un-moderated comments for non-administrators if moderation is enabled
807
        $showUnmoderated = $parent && (
808
            ($parent->ModerationRequired === 'None')
809
            || ($this->getOption('frontend_moderation') && $parent->canModerateComments())
810
        );
811
        if (!$showUnmoderated) {
812
            $list = $list->filter('Moderated', 1);
813
        }
814
815
        $this->extend('updateReplies', $list);
816
        return $list;
817
    }
818
819
    /**
820
     * Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend
821
     *
822
     * @return PaginatedList
823
     */
824 View Code Duplication
    public function PagedReplies()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
825
    {
826
        $list = $this->Replies();
827
828
        // Add pagination
829
        $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...
830
        $list->setPaginationGetVar('repliesstart' . $this->ID);
831
        $list->setPageLength($this->getOption('comments_per_page'));
832
833
        $this->extend('updatePagedReplies', $list);
834
        return $list;
835
    }
836
837
    /**
838
     * Generate a reply form for this comment
839
     *
840
     * @return Form
0 ignored issues
show
Documentation introduced by
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...
841
     */
842
    public function ReplyForm()
843
    {
844
        // Ensure replies are enabled
845
        if (!$this->getRepliesEnabled()) {
846
            return null;
847
        }
848
849
        // Check parent is available
850
        $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...
851
        if (!$parent || !$parent->exists()) {
852
            return null;
853
        }
854
855
        // Build reply controller
856
        $controller = CommentingController::create();
857
        $controller->setOwnerRecord($parent);
858
        $controller->setParentClass($parent->ClassName);
859
        $controller->setOwnerController(Controller::curr());
860
861
        return $controller->ReplyForm($this);
862
    }
863
864
    /**
865
     * Refresh of this comment in the hierarchy
866
     */
867
    public function updateDepth()
868
    {
869
        $parent = $this->ParentComment();
870
        if ($parent && $parent->exists()) {
871
            $parent->updateDepth();
872
            $this->Depth = $parent->Depth + 1;
873
        } else {
874
            $this->Depth = 1;
875
        }
876
    }
877
}
878