Completed
Push — master ( 9dab44...8bd79e )
by Robbie
02:00
created

Comment   F

Complexity

Total Complexity 110

Size/Duplication

Total Lines 840
Duplicated Lines 1.43 %

Coupling/Cohesion

Components 2
Dependencies 16

Importance

Changes 0
Metric Value
wmc 110
lcom 2
cbo 16
dl 12
loc 840
rs 1.263
c 0
b 0
f 0

40 Methods

Rating   Name   Duplication   Size   Complexity  
A onBeforeWrite() 0 12 2
A onBeforeDelete() 0 9 2
A getSecurityToken() 0 4 1
A Link() 0 6 2
A Permalink() 0 5 1
A fieldLabels() 0 15 1
B getOption() 0 17 6
A getParent() 0 6 3
A getParentTitle() 0 6 3
A getParentClassName() 0 4 1
A castingHelper() 0 8 3
A getEscapedComment() 0 4 1
A isPreview() 0 4 1
A canCreate() 0 4 1
B canView() 0 21 6
B canEdit() 0 23 5
A canDelete() 0 15 3
A getMember() 0 12 3
A getAuthorName() 0 8 3
B actionLink() 0 28 3
A DeleteLink() 0 6 2
A SpamLink() 0 6 3
A HamLink() 0 6 3
A ApproveLink() 0 6 3
A markSpam() 0 7 1
A markApproved() 0 7 1
A markUnapproved() 0 6 1
A SpamClass() 0 10 3
A getTitle() 0 12 3
B getCMSFields() 0 73 6
A purifyHtml() 0 8 2
B getHtmlPurifierService() 0 22 4
A Gravatar() 0 19 2
A getRepliesEnabled() 0 12 4
A AllReplies() 0 17 3
D Replies() 0 27 9
A PagedReplies() 12 12 1
A ReplyForm() 0 21 4
A getDate() 0 4 1
A updateDepth() 0 10 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Comment often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Comment, and based on these observations, apply Extract Interface, too.

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...
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
        '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...
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...
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...
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...
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...
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...
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...
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...
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')) {
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...
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
     * Returns the list of all replies
770
     *
771
     * @return SS_List
772
     */
773
    public function AllReplies()
774
    {
775
        // No replies if disabled
776
        if (!$this->getRepliesEnabled()) {
777
            return new ArrayList();
778
        }
779
780
        // Get all non-spam comments
781
        $order = $this->getOption('order_replies_by')
782
            ?: $this->getOption('order_comments_by');
783
        $list = $this
784
            ->ChildComments()
785
            ->sort($order);
786
787
        $this->extend('updateAllReplies', $list);
788
        return $list;
789
    }
790
791
    /**
792
     * Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend
793
     *
794
     * @return SS_List
795
     */
796
    public function Replies()
797
    {
798
        // No replies if disabled
799
        if (!$this->getRepliesEnabled()) {
800
            return new ArrayList();
801
        }
802
        $list = $this->AllReplies();
803
804
        // Filter spam comments for non-administrators if configured
805
        $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...
806
        $showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments();
807
        if (!$showSpam) {
808
            $list = $list->filter('IsSpam', 0);
809
        }
810
811
        // Filter un-moderated comments for non-administrators if moderation is enabled
812
        $showUnmoderated = $parent && (
813
            ($parent->ModerationRequired === 'None')
814
            || ($this->getOption('frontend_moderation') && $parent->canModerateComments())
815
        );
816
        if (!$showUnmoderated) {
817
            $list = $list->filter('Moderated', 1);
818
        }
819
820
        $this->extend('updateReplies', $list);
821
        return $list;
822
    }
823
824
    /**
825
     * Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend
826
     *
827
     * @return PaginatedList
828
     */
829 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...
830
    {
831
        $list = $this->Replies();
832
833
        // Add pagination
834
        $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...
835
        $list->setPaginationGetVar('repliesstart' . $this->ID);
836
        $list->setPageLength($this->getOption('comments_per_page'));
837
838
        $this->extend('updatePagedReplies', $list);
839
        return $list;
840
    }
841
842
    /**
843
     * Generate a reply form for this comment
844
     *
845
     * @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...
846
     */
847
    public function ReplyForm()
848
    {
849
        // Ensure replies are enabled
850
        if (!$this->getRepliesEnabled()) {
851
            return null;
852
        }
853
854
        // Check parent is available
855
        $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...
856
        if (!$parent || !$parent->exists()) {
857
            return null;
858
        }
859
860
        // Build reply controller
861
        $controller = CommentingController::create();
862
        $controller->setOwnerRecord($parent);
863
        $controller->setParentClass($parent->ClassName);
864
        $controller->setOwnerController(Controller::curr());
865
866
        return $controller->ReplyForm($this);
867
    }
868
869
    /**
870
     * @return string
871
     */
872
    public function getDate()
873
    {
874
        return $this->Created;
875
    }
876
877
    /**
878
     * Refresh of this comment in the hierarchy
879
     */
880
    public function updateDepth()
881
    {
882
        $parent = $this->ParentComment();
883
        if ($parent && $parent->exists()) {
884
            $parent->updateDepth();
885
            $this->Depth = $parent->Depth + 1;
886
        } else {
887
            $this->Depth = 1;
888
        }
889
    }
890
}
891