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