Issues (124)

src/Model/Comment.php (1 issue)

1
<?php
2
3
namespace SilverStripe\Comments\Model;
4
5
use HTMLPurifier;
6
use HTMLPurifier_Config;
7
use SilverStripe\Comments\Controllers\CommentingController;
8
use SilverStripe\Comments\Extensions\CommentsExtension;
9
use SilverStripe\Comments\Model\Comment\SecurityToken;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\Core\TempFolder;
14
use SilverStripe\Forms\CheckboxField;
15
use SilverStripe\Forms\EmailField;
16
use SilverStripe\Forms\FieldGroup;
17
use SilverStripe\Forms\FieldList;
18
use SilverStripe\Forms\Form;
19
use SilverStripe\Forms\HeaderField;
20
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
21
use SilverStripe\Forms\TextareaField;
22
use SilverStripe\Forms\TextField;
23
use SilverStripe\ORM\ArrayList;
24
use SilverStripe\ORM\DataObject;
25
use SilverStripe\ORM\FieldType\DBDatetime;
26
use SilverStripe\ORM\PaginatedList;
27
use SilverStripe\ORM\SS_List;
28
use SilverStripe\Security\Member;
29
use SilverStripe\Security\Permission;
30
31
/**
32
 * Represents a single comment object.
33
 *
34
 * @property string  $Name
35
 * @property string  $Comment
36
 * @property string  $Email
37
 * @property string  $URL
38
 * @property string  $BaseClass
39
 * @property boolean $Moderated
40
 * @property boolean $IsSpam      True if the comment is known as spam
41
 * @property integer $ParentID    ID of the parent page / dataobject
42
 * @property boolean $AllowHtml   If true, treat $Comment as HTML instead of plain text
43
 * @property string  $SecretToken Secret admin token required to provide moderation links between sessions
44
 * @property integer $Depth       Depth of this comment in the nested chain
45
 *
46
 * @method HasManyList ChildComments() List of child comments
47
 * @method Member Author() Member object who created this comment
48
 * @method Comment ParentComment() Parent comment this is a reply to
49
 * @package comments
50
 */
51
class Comment extends DataObject
52
{
53
    /**
54
     * {@inheritDoc}
55
     */
56
    private static $db = array(
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
        '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(
118
        'Name',
119
        'Email',
120
        'Comment',
121
        'Created'
122
    );
123
124
    /**
125
     * {@inheritDoc}
126
     */
127
    private static $summary_fields = array(
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(
140
        'Author' => 'Author Member'
141
    );
142
143
    /**
144
     * {@inheritDoc}
145
     */
146
    private static $table_name = 'Comment';
147
148
    /**
149
     * {@inheritDoc}
150
     */
151
    public function onBeforeWrite()
152
    {
153
        parent::onBeforeWrite();
154
155
        // Sanitize HTML, because its expected to be passed to the template unescaped later
156
        if ($this->AllowHtml) {
157
            $this->Comment = $this->purifyHtml($this->Comment);
158
        }
159
160
        // Check comment depth
161
        $this->updateDepth();
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167
    public function onBeforeDelete()
168
    {
169
        parent::onBeforeDelete();
170
171
        // Delete all children
172
        foreach ($this->ChildComments() as $comment) {
173
            $comment->delete();
174
        }
175
    }
176
177
    /**
178
     * @return Comment_SecurityToken
179
     */
180
    public function getSecurityToken()
181
    {
182
        return Injector::inst()->createWithArgs(SecurityToken::class, array($this));
183
    }
184
185
    /**
186
     * Return a link to this comment
187
     *
188
     * @param string $action
189
     *
190
     * @return string link to this comment.
191
     */
192
    public function Link($action = '')
193
    {
194
        if ($parent = $this->Parent()) {
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) {
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) {
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();
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();
479
        }
480
        if (!$member) {
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'
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