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

src/Extensions/CommentsExtension.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\Extensions;
4
5
use SilverStripe\CMS\Model\SiteTree;
6
use SilverStripe\Comments\Admin\CommentsGridField;
7
use SilverStripe\Comments\Admin\CommentsGridFieldConfig;
8
use SilverStripe\Comments\Controllers\CommentingController;
9
use SilverStripe\Comments\Model\Comment;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Control\Session;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Core\Manifest\ModuleLoader;
15
use SilverStripe\Dev\Deprecation;
16
use SilverStripe\Forms\CheckboxField;
17
use SilverStripe\Forms\DropdownField;
18
use SilverStripe\Forms\FieldGroup;
19
use SilverStripe\Forms\FieldList;
20
use SilverStripe\Forms\Tab;
21
use SilverStripe\Forms\TabSet;
22
use SilverStripe\ORM\DataExtension;
23
use SilverStripe\ORM\PaginatedList;
24
use SilverStripe\Security\Member;
25
use SilverStripe\Security\Permission;
26
use SilverStripe\View\Requirements;
27
28
/**
29
 * Extension to {@link DataObject} to enable tracking comments.
30
 *
31
 * @package comments
32
 */
33
class CommentsExtension extends DataExtension
34
{
35
    /**
36
     * Default configuration values
37
     *
38
     * enabled:                     Allows commenting to be disabled even if the extension is present
39
     * enabled_cms:                 Allows commenting to be enabled or disabled via the CMS
40
     * require_login:               Boolean, whether a user needs to login (required for required_permission)
41
     * require_login_cms:           Allows require_login to be set via the CMS
42
     * required_permission:         Permission (or array of permissions) required to comment
43
     * include_js:                  Enhance operation by ajax behaviour on moderation links (required for use_preview)
44
     * use_gravatar:                Set to true to show gravatar icons
45
     * gravatar_default:            Theme for 'not found' gravatar {@see http://gravatar.com/site/implement/images}
46
     * gravatar_rating:             Gravatar rating (same as the standard default)
47
     * show_comments_when_disabled: Show older comments when commenting has been disabled.
48
     * order_comments_by:           Default sort order.
49
     * order_replies_by:            Sort order for replies.
50
     * comments_holder_id:          ID for the comments holder
51
     * comment_permalink_prefix:    ID prefix for each comment
52
     * require_moderation:          Require moderation for all comments
53
     * require_moderation_cms:      Ignore other comment moderation config settings and set via CMS
54
     * frontend_moderation:         Display unmoderated comments in the frontend, if the user can moderate them.
55
     * frontend_spam:               Display spam comments in the frontend, if the user can moderate them.
56
     * html_allowed:                Allow for sanitized HTML in comments
57
     * use_preview:                 Preview formatted comment (when allowing HTML)
58
     * nested_comments:             Enable nested comments
59
     * nested_depth:                Max depth of nested comments in levels (where root is 1 depth) 0 means no limit.
60
     *
61
     * @var array
62
     *
63
     * @config
64
     */
65
    private static $comments = [
66
        'enabled' => true,
67
        'enabled_cms' => false,
68
        'require_login' => false,
69
        'require_login_cms' => false,
70
        'required_permission' => false,
71
        'include_js' => true,
72
        'use_gravatar' => false,
73
        'gravatar_size' => 80,
74
        'gravatar_default' => 'identicon',
75
        'gravatar_rating' => 'g',
76
        'show_comments_when_disabled' => false,
77
        'order_comments_by' => '"Created" DESC',
78
        'order_replies_by' => false,
79
        'comments_per_page' => 10,
80
        'comments_holder_id' => 'comments-holder',
81
        'comment_permalink_prefix' => 'comment-',
82
        'require_moderation' => false,
83
        'require_moderation_nonmembers' => false,
84
        'require_moderation_cms' => false,
85
        'frontend_moderation' => false,
86
        'frontend_spam' => false,
87
        'html_allowed' => false,
88
        'html_allowed_elements' => ['a', 'img', 'i', 'b'],
89
        'use_preview' => false,
90
        'nested_comments' => false,
91
        'nested_depth' => 2,
92
    ];
93
94
    /**
95
     * @var array
96
     */
97
    private static $db = [
98
        'ProvideComments' => 'Boolean',
99
        'ModerationRequired' => 'Enum(\'None,Required,NonMembersOnly\',\'None\')',
100
        'CommentsRequireLogin' => 'Boolean',
101
    ];
102
103
    /**
104
     * {@inheritDoc}
105
     */
106
    private static $has_many = [
0 ignored issues
show
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...
107
        'Commments' => Comment::class . '.Parent'
108
    ];
109
110
    /**
111
     * CMS configurable options should default to the config values, but respect
112
     * default values specified by the object
113
     */
114
    public function populateDefaults()
115
    {
116
        $defaults = $this->owner->config()->get('defaults');
117
118
        // Set if comments should be enabled by default
119 View Code Duplication
        if (isset($defaults['ProvideComments'])) {
120
            $this->owner->ProvideComments = $defaults['ProvideComments'];
121
        } else {
122
            $this->owner->ProvideComments = $this->owner->getCommentsOption('enabled') ? 1 : 0;
123
        }
124
125
        // If moderation options should be configurable via the CMS then
126
        if (isset($defaults['ModerationRequired'])) {
127
            $this->owner->ModerationRequired = $defaults['ModerationRequired'];
128
        } elseif ($this->owner->getCommentsOption('require_moderation')) {
129
            $this->owner->ModerationRequired = 'Required';
130
        } elseif ($this->owner->getCommentsOption('require_moderation_nonmembers')) {
131
            $this->owner->ModerationRequired = 'NonMembersOnly';
132
        } else {
133
            $this->owner->ModerationRequired = 'None';
134
        }
135
136
        // Set login required
137 View Code Duplication
        if (isset($defaults['CommentsRequireLogin'])) {
138
            $this->owner->CommentsRequireLogin = $defaults['CommentsRequireLogin'];
139
        } else {
140
            $this->owner->CommentsRequireLogin = $this->owner->getCommentsOption('require_login') ? 1 : 0;
141
        }
142
    }
143
144
145
    /**
146
     * If this extension is applied to a {@link SiteTree} record then
147
     * append a Provide Comments checkbox to allow authors to trigger
148
     * whether or not to display comments
149
     *
150
     * @todo Allow customization of other {@link Commenting} configuration
151
     *
152
     * @param FieldList $fields
153
     */
154
    public function updateSettingsFields(FieldList $fields)
155
    {
156
        $options = FieldGroup::create()->setTitle(_t(__CLASS__ . '.COMMENTOPTIONS', 'Comments'));
157
158
        // Check if enabled setting should be cms configurable
159
        if ($this->owner->getCommentsOption('enabled_cms')) {
160
            $options->push(new CheckboxField('ProvideComments', _t('SilverStripe\\Comments\\Model\\Comment.ALLOWCOMMENTS', 'Allow Comments')));
161
        }
162
163
        // Check if we should require users to login to comment
164
        if ($this->owner->getCommentsOption('require_login_cms')) {
165
            $options->push(
166
                new CheckboxField(
167
                    'CommentsRequireLogin',
168
                    _t('Comments.COMMENTSREQUIRELOGIN', 'Require login to comment')
169
                )
170
            );
171
        }
172
173
        if ($options->FieldList()->count()) {
174
            if ($fields->hasTabSet()) {
175
                $fields->addFieldsToTab('Root.Settings', $options);
176
            } else {
177
                $fields->push($options);
178
            }
179
        }
180
181
        // Check if moderation should be enabled via cms configurable
182
        if ($this->owner->getCommentsOption('require_moderation_cms')) {
183
            $moderationField = new DropdownField('ModerationRequired', _t(__CLASS__ . '.COMMENTMODERATION', 'Comment Moderation'), array(
184
                'None' => _t(__CLASS__ . '.MODERATIONREQUIRED_NONE', 'No moderation required'),
185
                'Required' => _t(__CLASS__ . '.MODERATIONREQUIRED_REQUIRED', 'Moderate all comments'),
186
                'NonMembersOnly' => _t(
187
                    __CLASS__ . '.MODERATIONREQUIRED_NONMEMBERSONLY',
188
                    'Only moderate non-members'
189
                ),
190
            ));
191
            if ($fields->hasTabSet()) {
192
                $fields->addFieldsToTab('Root.Settings', $moderationField);
193
            } else {
194
                $fields->push($moderationField);
195
            }
196
        }
197
    }
198
199
    /**
200
     * Get comment moderation rules for this parent
201
     *
202
     * None:           No moderation required
203
     * Required:       All comments
204
     * NonMembersOnly: Only anonymous users
205
     *
206
     * @return string
207
     */
208
    public function getModerationRequired()
209
    {
210
        if ($this->owner->getCommentsOption('require_moderation_cms')) {
211
            return $this->owner->getField('ModerationRequired');
212
        } elseif ($this->owner->getCommentsOption('require_moderation')) {
213
            return 'Required';
214
        } elseif ($this->owner->getCommentsOption('require_moderation_nonmembers')) {
215
            return 'NonMembersOnly';
216
        } else {
217
            return 'None';
218
        }
219
    }
220
221
    /**
222
     * Determine if users must be logged in to post comments
223
     *
224
     * @return boolean
225
     */
226
    public function getCommentsRequireLogin()
227
    {
228
        if ($this->owner->getCommentsOption('require_login_cms')) {
229
            return (bool) $this->owner->getField('CommentsRequireLogin');
230
        } else {
231
            return (bool) $this->owner->getCommentsOption('require_login');
232
        }
233
    }
234
235
    /**
236
     * Returns the RelationList of all comments against this object. Can be used as a data source
237
     * for a gridfield with write access.
238
     *
239
     * @return DataList
240
     */
241 View Code Duplication
    public function AllComments()
242
    {
243
        $order = $this->owner->getCommentsOption('order_comments_by');
244
        $comments = Comment::get()
245
            ->filter('ParentID', $this->owner->ID)
246
            ->sort($order);
247
        $this->owner->extend('updateAllComments', $comments);
248
        return $comments;
249
    }
250
251
    /**
252
     * Returns all comments against this object, with with spam and unmoderated items excluded, for use in the frontend
253
     *
254
     * @return DataList
255
     */
256
    public function AllVisibleComments()
257
    {
258
        $list = $this->AllComments();
259
260
        // Filter spam comments for non-administrators if configured
261
        $showSpam = $this->owner->getCommentsOption('frontend_spam') && $this->owner->canModerateComments();
262
263
        if (!$showSpam) {
264
            $list = $list->filter('IsSpam', 0);
265
        }
266
267
        // Filter un-moderated comments for non-administrators if moderation is enabled
268
        $showUnmoderated = ($this->owner->ModerationRequired === 'None')
269
            || ($this->owner->getCommentsOption('frontend_moderation') && $this->owner->canModerateComments());
270
        if (!$showUnmoderated) {
271
            $list = $list->filter('Moderated', 1);
272
        }
273
274
        $this->owner->extend('updateAllVisibleComments', $list);
275
        return $list;
276
    }
277
278
    /**
279
     * Returns the root level comments, with spam and unmoderated items excluded, for use in the frontend
280
     *
281
     * @return DataList
282
     */
283 View Code Duplication
    public function Comments()
284
    {
285
        $list = $this->AllVisibleComments();
286
287
        // If nesting comments, only show root level
288
        if ($this->owner->getCommentsOption('nested_comments')) {
289
            $list = $list->filter('ParentCommentID', 0);
290
        }
291
292
        $this->owner->extend('updateComments', $list);
293
        return $list;
294
    }
295
296
    /**
297
     * Returns a paged list of the root level comments, with spam and unmoderated items excluded,
298
     * for use in the frontend
299
     *
300
     * @return PaginatedList
301
     */
302 View Code Duplication
    public function PagedComments()
303
    {
304
        $list = $this->Comments();
305
306
        // Add pagination
307
        $list = new PaginatedList($list, Controller::curr()->getRequest());
308
        $list->setPaginationGetVar('commentsstart' . $this->owner->ID);
309
        $list->setPageLength($this->owner->getCommentsOption('comments_per_page'));
310
311
        $this->owner->extend('updatePagedComments', $list);
312
        return $list;
313
    }
314
315
    /**
316
     * Determine if comments are enabled for this instance
317
     *
318
     * @return boolean
319
     */
320
    public function getCommentsEnabled()
321
    {
322
        // Don't display comments form for pseudo-pages (such as the login form)
323
        if (!$this->owner->exists()) {
324
            return false;
325
        }
326
327
        // Determine which flag should be used to determine if this is enabled
328
        if ($this->owner->getCommentsOption('enabled_cms')) {
329
            return (bool) $this->owner->ProvideComments;
330
        }
331
332
        return (bool) $this->owner->getCommentsOption('enabled');
333
    }
334
335
    /**
336
     * Get the HTML ID for the comment holder in the template
337
     *
338
     * @return string
339
     */
340
    public function getCommentHolderID()
341
    {
342
        return $this->owner->getCommentsOption('comments_holder_id');
343
    }
344
345
    /**
346
     * Permission codes required in order to post (or empty if none required)
347
     *
348
     * @return string|array Permission or list of permissions, if required
349
     */
350
    public function getPostingRequiredPermission()
351
    {
352
        return $this->owner->getCommentsOption('required_permission');
353
    }
354
355
    /**
356
     * Determine if a user can post comments on this item
357
     *
358
     * @param Member $member Member to check
359
     *
360
     * @return boolean
361
     */
362
    public function canPostComment($member = null)
363
    {
364
        // Deny if not enabled for this object
365
        if (!$this->owner->CommentsEnabled) {
366
            return false;
367
        }
368
369
        if (!$this->owner->canView($member)) {
370
            // deny if current user cannot view the underlying record.
371
            return false;
372
        }
373
374
        // Check if member is required
375
        $requireLogin = $this->owner->CommentsRequireLogin;
376
        if (!$requireLogin) {
377
            return true;
378
        }
379
380
        // Check member is logged in
381
        $member = $member ?: Member::currentUser();
382
        if (!$member) {
383
            return false;
384
        }
385
386
        // If member required check permissions
387
        $requiredPermission = $this->owner->PostingRequiredPermission;
388
        if ($requiredPermission && !Permission::checkMember($member, $requiredPermission)) {
389
            return false;
390
        }
391
392
        return true;
393
    }
394
395
    /**
396
     * Determine if this member can moderate comments in the CMS
397
     *
398
     * @param Member $member
399
     *
400
     * @return boolean
401
     */
402
    public function canModerateComments($member = null)
403
    {
404
        // Deny if not enabled for this object
405
        if (!$this->owner->CommentsEnabled) {
406
            return false;
407
        }
408
409
        // Fallback to can-edit
410
        return $this->owner->canEdit($member);
411
    }
412
413
    /**
414
     * Gets the RSS link to all comments
415
     *
416
     * @return string
417
     */
418
    public function getCommentRSSLink()
419
    {
420
        return Director::absoluteURL('comments/rss');
421
    }
422
423
    /**
424
     * Get the RSS link to all comments on this page
425
     *
426
     * @return string
427
     */
428
    public function getCommentRSSLinkPage()
429
    {
430
        return Controller::join_links(
431
            $this->getCommentRSSLink(),
432
            str_replace('\\', '-', $this->owner->baseClass()),
433
            $this->owner->ID
434
        );
435
    }
436
437
    /**
438
     * Comments interface for the front end. Includes the CommentAddForm and the composition
439
     * of the comments display.
440
     *
441
     * To customize the html see templates/CommentInterface.ss or extend this function with
442
     * your own extension.
443
     *
444
     * @todo Cleanup the passing of all this configuration based functionality
445
     *
446
     * @see  docs/en/Extending
447
     */
448
    public function CommentsForm()
449
    {
450
        // Check if enabled
451
        $enabled = $this->getCommentsEnabled();
452
        if ($enabled && $this->owner->getCommentsOption('include_js')) {
453
            Requirements::javascript('//code.jquery.com/jquery-3.3.1.min.js');
454
            Requirements::javascript('silverstripe/comments:thirdparty/jquery-validate/jquery.validate.min.js');
455
            Requirements::javascript('silverstripe/admin:client/dist/js/i18n.js');
456
            Requirements::add_i18n_javascript('silverstripe/comments:javascript/lang');
457
            Requirements::javascript('silverstripe/comments:javascript/CommentsInterface.js');
458
        }
459
460
        $controller = CommentingController::create();
461
        $controller->setOwnerRecord($this->owner);
462
        $controller->setParentClass($this->owner->getClassName());
463
        $controller->setOwnerController(Controller::curr());
464
465
        $session = Controller::curr()->getRequest()->getSession();
466
        $moderatedSubmitted = $session->get('CommentsModerated');
467
        $session->clear('CommentsModerated');
468
469
        $form = ($enabled) ? $controller->CommentsForm() : false;
470
471
        // a little bit all over the show but to ensure a slightly easier upgrade for users
472
        // return back the same variables as previously done in comments
473
        return $this
474
            ->owner
475
            ->customise(array(
476
                'AddCommentForm' => $form,
477
                'ModeratedSubmitted' => $moderatedSubmitted,
478
            ))
479
            ->renderWith('CommentsInterface');
480
    }
481
482
    /**
483
     * Returns whether this extension instance is attached to a {@link SiteTree} object
484
     *
485
     * @return bool
486
     */
487
    public function attachedToSiteTree()
488
    {
489
        $class = $this->owner->baseClass();
490
491
        return (is_subclass_of($class, SiteTree::class)) || ($class == SiteTree::class);
492
    }
493
494
    /**
495
     * Get the commenting option for this object.
496
     *
497
     * This can be overridden in any instance or extension to customise the
498
     * option available.
499
     *
500
     * @param string $key
501
     *
502
     * @return mixed Result if the setting is available, or null otherwise
503
     */
504
    public function getCommentsOption($key)
505
    {
506
        $settings = $this->getCommentsOptions();
507
        $value = null;
508
509
        if (isset($settings[$key])) {
510
            $value = $settings[$key];
511
        }
512
513
        // To allow other extensions to customise this option
514
        if ($this->owner) {
515
            $this->owner->extend('updateCommentsOption', $key, $value);
516
        }
517
518
        return $value;
519
    }
520
521
    /**
522
     * @return array
523
     */
524
    public function getCommentsOptions()
525
    {
526
        $settings = [];
0 ignored issues
show
$settings is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
527
528
        if ($this->owner) {
529
            $settings = $this->owner->config()->get('comments');
530
        } else {
531
            $settings = Config::inst()->get(__CLASS__, 'comments');
532
        }
533
534
        return $settings;
535
    }
536
537
    /**
538
     * Add moderation functions to the current fieldlist
539
     *
540
     * @param FieldList $fields
541
     */
542
    protected function updateModerationFields(FieldList $fields)
543
    {
544
        Requirements::css('silverstripe/comments:css/cms.css');
545
546
        $newComments = $this->owner->AllComments()->filter('Moderated', 0);
547
548
        $newGrid = new CommentsGridField(
549
            'NewComments',
550
            _t('CommentsAdmin.NewComments', 'New'),
551
            $newComments,
552
            CommentsGridFieldConfig::create()
553
        );
554
555
        $approvedComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 0);
556
557
        $approvedGrid = new CommentsGridField(
558
            'ApprovedComments',
559
            _t('CommentsAdmin.Comments', 'Approved'),
560
            $approvedComments,
561
            CommentsGridFieldConfig::create()
562
        );
563
564
        $spamComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 1);
565
566
        $spamGrid = new CommentsGridField(
567
            'SpamComments',
568
            _t('CommentsAdmin.SpamComments', 'Spam'),
569
            $spamComments,
570
            CommentsGridFieldConfig::create()
571
        );
572
573
        $newCount = '(' . count($newComments) . ')';
574
        $approvedCount = '(' . count($approvedComments) . ')';
575
        $spamCount = '(' . count($spamComments) . ')';
576
577
        if ($fields->hasTabSet()) {
578
            $tabs = new TabSet(
579
                'Comments',
580
                new Tab(
581
                    'CommentsNewCommentsTab',
582
                    _t('SilverStripe\\Comments\\Admin\\CommentAdmin.NewComments', 'New') . ' ' . $newCount,
583
                    $newGrid
584
                ),
585
                new Tab(
586
                    'CommentsCommentsTab',
587
                    _t('SilverStripe\\Comments\\Admin\\CommentAdmin.Comments', 'Approved') . ' ' . $approvedCount,
588
                    $approvedGrid
589
                ),
590
                new Tab(
591
                    'CommentsSpamCommentsTab',
592
                    _t('SilverStripe\\Comments\\Admin\\CommentAdmin.SpamComments', 'Spam') . ' ' . $spamCount,
593
                    $spamGrid
594
                )
595
            );
596
            $tabs->setTitle(_t(__CLASS__ . '.COMMENTSTABSET', 'Comments'));
597
598
            $fields->addFieldToTab('Root', $tabs);
599
        } else {
600
            $fields->push($newGrid);
601
            $fields->push($approvedGrid);
602
            $fields->push($spamGrid);
603
        }
604
    }
605
606
    public function updateCMSFields(FieldList $fields)
607
    {
608
        // Disable moderation if not permitted
609
        if ($this->owner->canModerateComments()) {
610
            $this->updateModerationFields($fields);
611
        }
612
613
        // If this isn't a page we should merge the settings into the CMS fields
614
        if (!$this->attachedToSiteTree()) {
615
            $this->updateSettingsFields($fields);
616
        }
617
    }
618
}
619