Completed
Push — master ( 82c4a1...4bf0a8 )
by Robbie
10:38
created

src/Model/Comment.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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