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

Comment   F

Complexity

Total Complexity 115

Size/Duplication

Total Lines 859
Duplicated Lines 1.4 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 115
c 1
b 0
f 0
lcom 1
cbo 18
dl 12
loc 859
rs 1.263

40 Methods

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