Comment::getTitle()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Comments\Model;
4
5
use HTMLPurifier;
0 ignored issues
show
Bug introduced by
The type HTMLPurifier was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use HTMLPurifier_Config;
0 ignored issues
show
Bug introduced by
The type HTMLPurifier_Config was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
introduced by
The private property $db is not used, and could 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
introduced by
The private property $has_one is not used, and could 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
introduced by
The private property $has_many is not used, and could 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
introduced by
The private property $default_sort is not used, and could be removed.
Loading history...
88
89
    /**
90
     * {@inheritDoc}
91
     */
92
    private static $defaults = array(
0 ignored issues
show
introduced by
The private property $defaults is not used, and could 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
introduced by
The private property $casting is not used, and could 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
introduced by
The private property $searchable_fields is not used, and could 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
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
128
        'getAuthorName' => 'Submitted By',
129
        'getAuthorEmail' => '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
introduced by
The private property $field_labels is not used, and could be removed.
Loading history...
140
        'Author' => 'Author Member'
141
    );
142
143
    /**
144
     * {@inheritDoc}
145
     */
146
    private static $table_name = 'Comment';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could 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
0 ignored issues
show
Bug introduced by
The type SilverStripe\Comments\Model\Comment_SecurityToken was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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.
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. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

194
        if ($parent = $this->/** @scrutinizer ignore-call */ Parent()) {
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
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();
245
246
        if (!$record && $this->Parent()) {
247
            // Otherwise a singleton of that record
248
            $record = singleton($this->Parent()->dataClass());
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
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()) {
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();
293
    }
294
295
    /**
296
     * {@inheritDoc}
297
     */
298
    public function castingHelper($field)
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
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
344
     * @return Boolean
345
     */
346
    public function canView($member = null)
347
    {
348
        $member = $this->getMember($member);
349
350
        $extended = $this->extendedCan('canView', $member);
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()) {
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) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
379
            return false;
380
        }
381
382
        $extended = $this->extendedCan('canEdit', $member);
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()) {
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) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
409
            return false;
410
        }
411
412
        $extended = $this->extendedCan('canDelete', $member);
413
        if ($extended !== null) {
414
            return $extended;
415
        }
416
417
        return $this->canEdit($member);
418
    }
419
420
    /**
421
     * Resolves Member object.
422
     *
423
     * @param Member|int|null $member
424
     * @return Member|null
425
     */
426
    protected function getMember($member = null)
427
    {
428
        if (!$member) {
429
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Security\Member::currentUser() has been deprecated: 5.0.0 use Security::getCurrentUser() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

429
            $member = /** @scrutinizer ignore-deprecated */ Member::currentUser();

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

The explanatory message should give you some clue as to whether and when the function will be removed and what other function 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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $member also could return the type integer which is incompatible with the documented return type SilverStripe\Security\Member|null.
Loading history...
437
    }
438
439
    /**
440
     * Return the authors name for the comment
441
     *
442
     * @return string
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
     * Return the comment authors email address
455
     *
456
     * @return string
457
     */
458
    public function getAuthorEmail()
459
    {
460
        if ($this->Email) {
461
            return $this->Email;
462
        } elseif ($author = $this->Author()) {
463
            return $author->Email;
464
        }
465
    }
466
467
    /**
468
     * Generate a secure admin-action link authorised for the specified member
469
     *
470
     * @param string $action An action on CommentingController to link to
471
     * @param Member $member The member authorised to invoke this action
472
     *
473
     * @return string
474
     */
475
    protected function actionLink($action, $member = null)
476
    {
477
        if (!$member) {
478
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Security\Member::currentUser() has been deprecated: 5.0.0 use Security::getCurrentUser() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

478
            $member = /** @scrutinizer ignore-deprecated */ Member::currentUser();

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

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

Loading history...
479
        }
480
        if (!$member) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
481
            return false;
482
        }
483
484
        /**
485
         * @todo: How do we handle "DataObject" instances that don't have a Link to reject/spam/delete?? This may
486
         * we have to make CMS a hard dependency instead.
487
         */
488
        // if (!$this->Parent()->hasMethod('Link')) {
489
        //     return false;
490
        // }
491
492
        $url = Controller::join_links(
493
            Director::baseURL(),
494
            'comments',
495
            $action,
496
            $this->ID
497
        );
498
499
        // Limit access for this user
500
        $token = $this->getSecurityToken();
501
        return $token->addToUrl($url, $member);
502
    }
503
504
    /**
505
     * Link to delete this comment
506
     *
507
     * @param Member $member
508
     *
509
     * @return string
510
     */
511
    public function DeleteLink($member = null)
512
    {
513
        if ($this->canDelete($member)) {
514
            return $this->actionLink('delete', $member);
515
        }
516
    }
517
518
    /**
519
     * Link to mark as spam
520
     *
521
     * @param Member $member
522
     *
523
     * @return string
524
     */
525
    public function SpamLink($member = null)
526
    {
527
        if ($this->canEdit($member) && !$this->IsSpam) {
528
            return $this->actionLink('spam', $member);
529
        }
530
    }
531
532
    /**
533
     * Link to mark as not-spam (ham)
534
     *
535
     * @param Member $member
536
     *
537
     * @return string
538
     */
539
    public function HamLink($member = null)
540
    {
541
        if ($this->canEdit($member) && $this->IsSpam) {
542
            return $this->actionLink('ham', $member);
543
        }
544
    }
545
546
    /**
547
     * Link to approve this comment
548
     *
549
     * @param Member $member
550
     *
551
     * @return string
552
     */
553
    public function ApproveLink($member = null)
554
    {
555
        if ($this->canEdit($member) && !$this->Moderated) {
556
            return $this->actionLink('approve', $member);
557
        }
558
    }
559
560
    /**
561
     * Mark this comment as spam
562
     */
563
    public function markSpam()
564
    {
565
        $this->IsSpam = true;
566
        $this->Moderated = true;
567
        $this->write();
568
        $this->extend('afterMarkSpam');
569
    }
570
571
    /**
572
     * Mark this comment as approved
573
     */
574
    public function markApproved()
575
    {
576
        $this->IsSpam = false;
577
        $this->Moderated = true;
578
        $this->write();
579
        $this->extend('afterMarkApproved');
580
    }
581
582
    /**
583
     * Mark this comment as unapproved
584
     */
585
    public function markUnapproved()
586
    {
587
        $this->Moderated = false;
588
        $this->write();
589
        $this->extend('afterMarkUnapproved');
590
    }
591
592
    /**
593
     * @return string
594
     */
595
    public function SpamClass()
596
    {
597
        if ($this->IsSpam) {
598
            return 'spam';
599
        } elseif (!$this->Moderated) {
600
            return 'unmoderated';
601
        } else {
602
            return 'notspam';
603
        }
604
    }
605
606
    /**
607
     * @return string
608
     */
609
    public function getTitle()
610
    {
611
        $title = sprintf(_t(__CLASS__ . '.COMMENTBY', 'Comment by %s', 'Name'), $this->getAuthorName());
612
613
        if ($parent = $this->Parent()) {
614
            if ($parent->Title) {
615
                $title .= sprintf(' %s %s', _t(__CLASS__ . '.ON', 'on'), $parent->Title);
616
            }
617
        }
618
619
        return $title;
620
    }
621
622
    /*
623
     * Modify the default fields shown to the user
624
     */
625
    public function getCMSFields()
626
    {
627
        $commentField = $this->AllowHtml ? HTMLEditorField::class : TextareaField::class;
628
        $fields = new FieldList(
629
            $this
630
                ->obj('Created')
631
                ->scaffoldFormField($this->fieldLabel('Created'))
632
                ->performReadonlyTransformation(),
633
            TextField::create('Name', $this->fieldLabel('Name')),
634
            $commentField::create('Comment', $this->fieldLabel('Comment')),
635
            EmailField::create('Email', $this->fieldLabel('Email')),
636
            TextField::create('URL', $this->fieldLabel('URL')),
637
            FieldGroup::create(array(
638
                CheckboxField::create('Moderated', $this->fieldLabel('Moderated')),
639
                CheckboxField::create('IsSpam', $this->fieldLabel('IsSpam')),
640
            ))
641
                ->setTitle(_t(__CLASS__ . '.OPTIONS', 'Options'))
642
                ->setDescription(_t(
643
                    __CLASS__ . '.OPTION_DESCRIPTION',
644
                    'Unmoderated and spam comments will not be displayed until approved'
645
                ))
646
        );
647
648
        // Show member name if given
649
        if (($author = $this->Author()) && $author->exists()) {
650
            $fields->insertAfter(
651
                TextField::create('AuthorMember', $this->fieldLabel('Author'), $author->Title)
652
                    ->performReadonlyTransformation(),
653
                'Name'
0 ignored issues
show
Bug introduced by
'Name' of type string is incompatible with the type SilverStripe\Forms\FormField expected by parameter $item of SilverStripe\Forms\FieldList::insertAfter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

653
                /** @scrutinizer ignore-type */ 'Name'
Loading history...
654
            );
655
        }
656
657
        // Show parent comment if given
658
        if (($parent = $this->ParentComment()) && $parent->exists()) {
659
            $fields->push(new HeaderField(
660
                'ParentComment_Title',
661
                _t(__CLASS__ . '.ParentComment_Title', 'This comment is a reply to the below')
662
            ));
663
            // Created date
664
            // FIXME - the method setName in DatetimeField is not chainable, hence
665
            // the lack of chaining here
666
            $createdField = $parent
667
                ->obj('Created')
668
                ->scaffoldFormField($parent->fieldLabel('Created'));
669
            $createdField->setName('ParentComment_Created');
670
            $createdField->setValue($parent->Created);
671
            $createdField->performReadonlyTransformation();
672
            $fields->push($createdField);
673
674
            // Name (could be member or string value)
675
            $fields->push(
676
                $parent
677
                    ->obj('AuthorName')
678
                    ->scaffoldFormField($parent->fieldLabel('AuthorName'))
679
                    ->setName('ParentComment_AuthorName')
680
                    ->setValue($parent->getAuthorName())
681
                    ->performReadonlyTransformation()
682
            );
683
684
            // Comment body
685
            $fields->push(
686
                $parent
687
                    ->obj('EscapedComment')
688
                    ->scaffoldFormField($parent->fieldLabel(self::class))
689
                    ->setName('ParentComment_EscapedComment')
690
                    ->setValue($parent->Comment)
691
                    ->performReadonlyTransformation()
692
            );
693
        }
694
695
        $this->extend('updateCMSFields', $fields);
696
        return $fields;
697
    }
698
699
    /**
700
     * @param  string $dirtyHtml
701
     *
702
     * @return string
703
     */
704
    public function purifyHtml($dirtyHtml)
705
    {
706
        if ($service = $this->getHtmlPurifierService()) {
707
            return $service->purify($dirtyHtml);
708
        }
709
710
        return $dirtyHtml;
711
    }
712
713
    /**
714
     * @return HTMLPurifier (or anything with a "purify()" method)
715
     */
716
    public function getHtmlPurifierService()
717
    {
718
        if (!class_exists(HTMLPurifier_Config::class)) {
719
            return null;
720
        }
721
722
        $config = HTMLPurifier_Config::createDefault();
723
        $allowedElements = (array) $this->getOption('html_allowed_elements');
724
        if (!empty($allowedElements)) {
725
            $config->set('HTML.AllowedElements', $allowedElements);
726
        }
727
728
        // This injector cannot be set unless the 'p' element is allowed
729
        if (in_array('p', $allowedElements)) {
730
            $config->set('AutoFormat.AutoParagraph', true);
731
        }
732
733
        $config->set('AutoFormat.Linkify', true);
734
        $config->set('URI.DisableExternalResources', true);
735
        $config->set('Cache.SerializerPath', TempFolder::getTempFolder(BASE_PATH));
736
        return new HTMLPurifier($config);
737
    }
738
739
    /**
740
     * Calculate the Gravatar link from the email address
741
     *
742
     * @return string
743
     */
744
    public function Gravatar()
745
    {
746
        $gravatar = '';
747
        $use_gravatar = $this->getOption('use_gravatar');
748
749
        if ($use_gravatar) {
750
            $gravatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($this->Email)));
751
            $gravatarsize = $this->getOption('gravatar_size');
752
            $gravatardefault = $this->getOption('gravatar_default');
753
            $gravatarrating = $this->getOption('gravatar_rating');
754
            $gravatar .= '?' . http_build_query(array(
755
                's' => $gravatarsize,
756
                'd' => $gravatardefault,
757
                'r' => $gravatarrating,
758
            ));
759
        }
760
761
        return $gravatar;
762
    }
763
764
    /**
765
     * Determine if replies are enabled for this instance
766
     *
767
     * @return boolean
768
     */
769
    public function getRepliesEnabled()
770
    {
771
        // Check reply option
772
        if (!$this->getOption('nested_comments')) {
773
            return false;
774
        }
775
776
        // Check if depth is limited
777
        $maxLevel = $this->getOption('nested_depth');
778
        $notSpam = ($this->SpamClass() == 'notspam');
779
        return $notSpam && (!$maxLevel || $this->Depth < $maxLevel);
780
    }
781
782
    /**
783
     * Proxy for checking whether the has permission to comment on the comment parent.
784
     *
785
     * @param Member $member Member to check
786
     *
787
     * @return boolean
788
     */
789
    public function canPostComment($member = null)
790
    {
791
        return $this->Parent()
792
            && $this->Parent()->exists()
793
            && $this->Parent()->canPostComment($member);
794
    }
795
796
    /**
797
     * Returns the list of all replies
798
     *
799
     * @return SS_List
800
     */
801
    public function AllReplies()
802
    {
803
        // No replies if disabled
804
        if (!$this->getRepliesEnabled()) {
805
            return new ArrayList();
806
        }
807
808
        // Get all non-spam comments
809
        $order = $this->getOption('order_replies_by')
810
            ?: $this->getOption('order_comments_by');
811
        $list = $this
812
            ->ChildComments()
813
            ->sort($order);
814
815
        $this->extend('updateAllReplies', $list);
816
        return $list;
817
    }
818
819
    /**
820
     * Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend
821
     *
822
     * @return SS_List
823
     */
824
    public function Replies()
825
    {
826
        // No replies if disabled
827
        if (!$this->getRepliesEnabled()) {
828
            return new ArrayList();
829
        }
830
        $list = $this->AllReplies();
831
832
        // Filter spam comments for non-administrators if configured
833
        $parent = $this->Parent();
834
        $showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments();
835
        if (!$showSpam) {
836
            $list = $list->filter('IsSpam', 0);
837
        }
838
839
        // Filter un-moderated comments for non-administrators if moderation is enabled
840
        $showUnmoderated = $parent && (
841
            ($parent->ModerationRequired === 'None')
842
            || ($this->getOption('frontend_moderation') && $parent->canModerateComments())
843
        );
844
        if (!$showUnmoderated) {
845
            $list = $list->filter('Moderated', 1);
846
        }
847
848
        $this->extend('updateReplies', $list);
849
        return $list;
850
    }
851
852
    /**
853
     * Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend
854
     *
855
     * @return PaginatedList
856
     */
857
    public function PagedReplies()
858
    {
859
        $list = $this->Replies();
860
861
        // Add pagination
862
        $list = new PaginatedList($list, Controller::curr()->getRequest());
863
        $list->setPaginationGetVar('repliesstart' . $this->ID);
864
        $list->setPageLength($this->getOption('comments_per_page'));
865
866
        $this->extend('updatePagedReplies', $list);
867
        return $list;
868
    }
869
870
    /**
871
     * Generate a reply form for this comment
872
     *
873
     * @return Form
874
     */
875
    public function ReplyForm()
876
    {
877
        // Ensure replies are enabled
878
        if (!$this->getRepliesEnabled()) {
879
            return null;
880
        }
881
882
        // Check parent is available
883
        $parent = $this->Parent();
884
        if (!$parent || !$parent->exists()) {
885
            return null;
886
        }
887
888
        // Build reply controller
889
        $controller = CommentingController::create();
890
        $controller->setOwnerRecord($parent);
891
        $controller->setParentClass($parent->ClassName);
892
        $controller->setOwnerController(Controller::curr());
893
894
        return $controller->ReplyForm($this);
895
    }
896
897
    /**
898
     * @return string
899
     */
900
    public function getDate()
901
    {
902
        return $this->Created;
903
    }
904
905
    /**
906
     * Refresh of this comment in the hierarchy
907
     */
908
    public function updateDepth()
909
    {
910
        $parent = $this->ParentComment();
911
        if ($parent && $parent->exists()) {
912
            $parent->updateDepth();
913
            $this->Depth = $parent->Depth + 1;
914
        } else {
915
            $this->Depth = 1;
916
        }
917
    }
918
}
919