CommentsExtension   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 597
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 231
c 3
b 0
f 0
dl 0
loc 597
rs 3.28
wmc 64

21 Methods

Rating   Name   Duplication   Size   Complexity  
B updateSettingsFields() 0 51 7
A getModerationRequired() 0 15 4
B populateDefaults() 0 27 8
A getCommentsRequireLogin() 0 6 2
A getCommentRSSLinkPage() 0 6 1
A updateModerationFields() 0 61 2
A getCommentsOption() 0 15 3
A getCommentsEnabled() 0 13 3
A PagedComments() 0 11 1
A canModerateComments() 0 9 2
A attachedToSiteTree() 0 5 2
A getCommentsOptions() 0 9 2
A getCommentRSSLink() 0 3 1
A Comments() 0 11 2
A updateCMSFields() 0 10 3
A getPostingRequiredPermission() 0 3 1
A getCommentHolderID() 0 3 1
A AllVisibleComments() 0 20 6
A CommentsForm() 0 32 4
B canPostComment() 0 31 8
A AllComments() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like CommentsExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CommentsExtension, and based on these observations, apply Extract Interface, too.

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

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

471
            /** @scrutinizer ignore-deprecated */ Requirements::add_i18n_javascript('silverstripe/comments:javascript/lang');
Loading history...
472
            Requirements::javascript('silverstripe/comments:javascript/CommentsInterface.js');
473
        }
474
475
        $controller = CommentingController::create();
476
        $controller->setOwnerRecord($this->owner);
477
        $controller->setParentClass($this->owner->getClassName());
478
        $controller->setOwnerController(Controller::curr());
479
480
        $session = Controller::curr()->getRequest()->getSession();
481
        $moderatedSubmitted = $session->get('CommentsModerated');
482
        $session->clear('CommentsModerated');
483
484
        $form = ($enabled) ? $controller->CommentsForm() : false;
485
486
        // a little bit all over the show but to ensure a slightly easier upgrade for users
487
        // return back the same variables as previously done in comments
488
        return $this
489
            ->owner
490
            ->customise([
491
                'AddCommentForm' => $form,
492
                'ModeratedSubmitted' => $moderatedSubmitted,
493
            ])
494
            ->renderWith('CommentsInterface');
495
    }
496
497
    /**
498
     * Returns whether this extension instance is attached to a {@link SiteTree} object
499
     *
500
     * @return bool
501
     */
502
    public function attachedToSiteTree()
503
    {
504
        $class = $this->owner->baseClass();
505
506
        return (is_subclass_of($class, SiteTree::class)) || ($class == SiteTree::class);
507
    }
508
509
    /**
510
     * Get the commenting option for this object.
511
     *
512
     * This can be overridden in any instance or extension to customise the
513
     * option available.
514
     *
515
     * @param string $key
516
     *
517
     * @return mixed Result if the setting is available, or null otherwise
518
     */
519
    public function getCommentsOption($key)
520
    {
521
        $settings = $this->getCommentsOptions();
522
        $value = null;
523
524
        if (isset($settings[$key])) {
525
            $value = $settings[$key];
526
        }
527
528
        // To allow other extensions to customise this option
529
        if ($this->owner) {
530
            $this->owner->extend('updateCommentsOption', $key, $value);
531
        }
532
533
        return $value;
534
    }
535
536
    /**
537
     * @return array
538
     */
539
    public function getCommentsOptions()
540
    {
541
        if ($this->owner) {
542
            $settings = $this->owner->config()->get('comments');
543
        } else {
544
            $settings = Config::inst()->get(__CLASS__, 'comments');
545
        }
546
547
        return $settings;
548
    }
549
550
    /**
551
     * Add moderation functions to the current fieldlist
552
     *
553
     * @param FieldList $fields
554
     */
555
    protected function updateModerationFields(FieldList $fields)
556
    {
557
        Requirements::css('silverstripe/comments:css/cms.css');
558
559
        $newComments = $this->owner->AllComments()->filter('Moderated', 0);
560
561
        $newGrid = CommentsGridField::create(
562
            'NewComments',
563
            _t('CommentsAdmin.NewComments', 'New'),
564
            $newComments,
565
            CommentsGridFieldConfig::create()
566
        );
567
568
        $approvedComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 0);
569
570
        $approvedGrid = new CommentsGridField(
571
            'ApprovedComments',
572
            _t('CommentsAdmin.Comments', 'Approved'),
573
            $approvedComments,
574
            CommentsGridFieldConfig::create()
575
        );
576
577
        $spamComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 1);
578
579
        $spamGrid = CommentsGridField::create(
580
            'SpamComments',
581
            _t('CommentsAdmin.SpamComments', 'Spam'),
582
            $spamComments,
583
            CommentsGridFieldConfig::create()
584
        );
585
586
        $newCount = '(' . count($newComments) . ')';
587
        $approvedCount = '(' . count($approvedComments) . ')';
588
        $spamCount = '(' . count($spamComments) . ')';
589
590
        if ($fields->hasTabSet()) {
591
            $tabs = TabSet::create(
592
                'Comments',
593
                Tab::create(
594
                    'CommentsNewCommentsTab',
595
                    _t('SilverStripe\\Comments\\Admin\\CommentAdmin.NewComments', 'New') . ' ' . $newCount,
596
                    $newGrid
597
                ),
598
                Tab::create(
599
                    'CommentsCommentsTab',
600
                    _t('SilverStripe\\Comments\\Admin\\CommentAdmin.Comments', 'Approved') . ' ' . $approvedCount,
601
                    $approvedGrid
602
                ),
603
                Tab::create(
604
                    'CommentsSpamCommentsTab',
605
                    _t('SilverStripe\\Comments\\Admin\\CommentAdmin.SpamComments', 'Spam') . ' ' . $spamCount,
606
                    $spamGrid
607
                )
608
            );
609
            $tabs->setTitle(_t(__CLASS__ . '.COMMENTSTABSET', 'Comments'));
610
611
            $fields->addFieldToTab('Root', $tabs);
612
        } else {
613
            $fields->push($newGrid);
614
            $fields->push($approvedGrid);
615
            $fields->push($spamGrid);
616
        }
617
    }
618
619
    public function updateCMSFields(FieldList $fields)
620
    {
621
        // Disable moderation if not permitted
622
        if ($this->owner->canModerateComments()) {
623
            $this->updateModerationFields($fields);
624
        }
625
626
        // If this isn't a page we should merge the settings into the CMS fields
627
        if (!$this->attachedToSiteTree()) {
628
            $this->updateSettingsFields($fields);
629
        }
630
    }
631
}
632