Completed
Push — master ( 187618...8c43e0 )
by Will
12s
created

src/Extensions/CommentsExtension.php (1 issue)

Severity

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\Comments\Admin\CommentsGridField;
6
use SilverStripe\Comments\Admin\CommentsGridFieldConfig;
7
use SilverStripe\Comments\Controllers\CommentingController;
8
use SilverStripe\Comments\Model\Comment;
9
use SilverStripe\Control\Controller;
10
use SilverStripe\Control\Director;
11
use SilverStripe\Control\Session;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Core\Manifest\ModuleLoader;
14
use SilverStripe\Dev\Deprecation;
15
use SilverStripe\Forms\CheckboxField;
16
use SilverStripe\Forms\DropdownField;
17
use SilverStripe\Forms\FieldGroup;
18
use SilverStripe\Forms\FieldList;
19
use SilverStripe\Forms\Tab;
20
use SilverStripe\Forms\TabSet;
21
use SilverStripe\ORM\DataExtension;
22
use SilverStripe\ORM\PaginatedList;
23
use SilverStripe\Security\Member;
24
use SilverStripe\Security\Permission;
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 = array(
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' => array('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 = array(
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
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...
106
        'Commments' => 'SilverStripe\\Comments\\Model\\Comment.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()->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('CommentsExtension.COMMENTOPTIONS', 'Comments'));
156
157
        // Check if enabled setting should be cms configurable
158
        if ($this->owner->getCommentsOption('enabled_cms')) {
159
            $options->push(new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments')));
160
        }
161
162
        // Check if we should require users to login to comment
163
        if ($this->owner->getCommentsOption('require_login_cms')) {
164
            $options->push(
165
                new CheckboxField(
166
                    'CommentsRequireLogin',
167
                    _t('Comments.COMMENTSREQUIRELOGIN', 'Require login to comment')
168
                )
169
            );
170
        }
171
172
        if ($options->FieldList()->count()) {
173
            if ($fields->hasTabSet()) {
174
                $fields->addFieldsToTab('Root.Settings', $options);
175
            } else {
176
                $fields->push($options);
177
            }
178
        }
179
180
        // Check if moderation should be enabled via cms configurable
181
        if ($this->owner->getCommentsOption('require_moderation_cms')) {
182
            $moderationField = new DropdownField('ModerationRequired', _t('CommentsExtension.COMMENTMODERATION', 'Comment Moderation'), array(
183
                'None' => _t('CommentsExtension.MODERATIONREQUIRED_NONE', 'No moderation required'),
184
                'Required' => _t('CommentsExtension.MODERATIONREQUIRED_REQUIRED', 'Moderate all comments'),
185
                'NonMembersOnly' => _t(
186
                    'CommentsExtension.MODERATIONREQUIRED_NONMEMBERSONLY',
187
                    'Only moderate non-members'
188
                ),
189
            ));
190
            if ($fields->hasTabSet()) {
191
                $fields->addFieldsToTab('Root.Settings', $moderationField);
192
            } else {
193
                $fields->push($moderationField);
194
            }
195
        }
196
    }
197
198
    /**
199
     * Get comment moderation rules for this parent
200
     *
201
     * None:           No moderation required
202
     * Required:       All comments
203
     * NonMembersOnly: Only anonymous users
204
     *
205
     * @return string
206
     */
207
    public function getModerationRequired()
208
    {
209
        if ($this->owner->getCommentsOption('require_moderation_cms')) {
210
            return $this->owner->getField('ModerationRequired');
211
        } elseif ($this->owner->getCommentsOption('require_moderation')) {
212
            return 'Required';
213
        } elseif ($this->owner->getCommentsOption('require_moderation_nonmembers')) {
214
            return 'NonMembersOnly';
215
        } else {
216
            return 'None';
217
        }
218
    }
219
220
    /**
221
     * Determine if users must be logged in to post comments
222
     *
223
     * @return boolean
224
     */
225
    public function getCommentsRequireLogin()
226
    {
227
        if ($this->owner->getCommentsOption('require_login_cms')) {
228
            return (bool) $this->owner->getField('CommentsRequireLogin');
229
        } else {
230
            return (bool) $this->owner->getCommentsOption('require_login');
231
        }
232
    }
233
234
    /**
235
     * Returns the RelationList of all comments against this object. Can be used as a data source
236
     * for a gridfield with write access.
237
     *
238
     * @return DataList
239
     */
240 View Code Duplication
    public function AllComments()
241
    {
242
        $order = $this->owner->getCommentsOption('order_comments_by');
243
        $comments = Comment::get()
244
            ->filter('ParentID', $this->owner->ID)
245
            ->sort($order);
246
        $this->owner->extend('updateAllComments', $comments);
247
        return $comments;
248
    }
249
250
    /**
251
     * Returns all comments against this object, with with spam and unmoderated items excluded, for use in the frontend
252
     *
253
     * @return DataList
254
     */
255
    public function AllVisibleComments()
256
    {
257
        $list = $this->AllComments();
258
259
        // Filter spam comments for non-administrators if configured
260
        $showSpam = $this->owner->getCommentsOption('frontend_spam') && $this->owner->canModerateComments();
261
        if (!$showSpam) {
262
            $list = $list->filter('IsSpam', 0);
263
        }
264
265
        // Filter un-moderated comments for non-administrators if moderation is enabled
266
        $showUnmoderated = ($this->owner->ModerationRequired === 'None')
267
            || ($this->owner->getCommentsOption('frontend_moderation') && $this->owner->canModerateComments());
268
        if (!$showUnmoderated) {
269
            $list = $list->filter('Moderated', 1);
270
        }
271
272
        $this->owner->extend('updateAllVisibleComments', $list);
273
        return $list;
274
    }
275
276
    /**
277
     * Returns the root level comments, with spam and unmoderated items excluded, for use in the frontend
278
     *
279
     * @return DataList
280
     */
281 View Code Duplication
    public function Comments()
282
    {
283
        $list = $this->AllVisibleComments();
284
285
        // If nesting comments, only show root level
286
        if ($this->owner->getCommentsOption('nested_comments')) {
287
            $list = $list->filter('ParentCommentID', 0);
288
        }
289
290
        $this->owner->extend('updateComments', $list);
291
        return $list;
292
    }
293
294
    /**
295
     * Returns a paged list of the root level comments, with spam and unmoderated items excluded,
296
     * for use in the frontend
297
     *
298
     * @return PaginatedList
299
     */
300 View Code Duplication
    public function PagedComments()
301
    {
302
        $list = $this->Comments();
303
304
        // Add pagination
305
        $list = new PaginatedList($list, Controller::curr()->getRequest());
306
        $list->setPaginationGetVar('commentsstart' . $this->owner->ID);
307
        $list->setPageLength($this->owner->getCommentsOption('comments_per_page'));
308
309
        $this->owner->extend('updatePagedComments', $list);
310
        return $list;
311
    }
312
313
    /**
314
     * Determine if comments are enabled for this instance
315
     *
316
     * @return boolean
317
     */
318
    public function getCommentsEnabled()
319
    {
320
        // Don't display comments form for pseudo-pages (such as the login form)
321
        if (!$this->owner->exists()) {
322
            return false;
323
        }
324
325
        // Determine which flag should be used to determine if this is enabled
326
        if ($this->owner->getCommentsOption('enabled_cms')) {
327
            return $this->owner->ProvideComments;
328
        } else {
329
            return $this->owner->getCommentsOption('enabled');
330
        }
331
    }
332
333
    /**
334
     * Get the HTML ID for the comment holder in the template
335
     *
336
     * @return string
337
     */
338
    public function getCommentHolderID()
339
    {
340
        return $this->owner->getCommentsOption('comments_holder_id');
341
    }
342
343
    /**
344
     * Permission codes required in order to post (or empty if none required)
345
     *
346
     * @return string|array Permission or list of permissions, if required
347
     */
348
    public function getPostingRequiredPermission()
349
    {
350
        return $this->owner->getCommentsOption('required_permission');
351
    }
352
353
    /**
354
     * Determine if a user can post comments on this item
355
     *
356
     * @param Member $member Member to check
357
     *
358
     * @return boolean
359
     */
360
    public function canPostComment($member = null)
361
    {
362
        // Deny if not enabled for this object
363
        if (!$this->owner->CommentsEnabled) {
364
            return false;
365
        }
366
        
367
        if (!$this->owner->canView($member)) {
368
            // deny if current user cannot view the underlying record.
369
            return false;
370
        }
371
372
        // Check if member is required
373
        $requireLogin = $this->owner->CommentsRequireLogin;
374
        if (!$requireLogin) {
375
            return true;
376
        }
377
378
        // Check member is logged in
379
        $member = $member ?: Member::currentUser();
380
        if (!$member) {
381
            return false;
382
        }
383
384
        // If member required check permissions
385
        $requiredPermission = $this->owner->PostingRequiredPermission;
386
        if ($requiredPermission && !Permission::checkMember($member, $requiredPermission)) {
387
            return false;
388
        }
389
390
        return true;
391
    }
392
393
    /**
394
     * Determine if this member can moderate comments in the CMS
395
     *
396
     * @param Member $member
397
     *
398
     * @return boolean
399
     */
400
    public function canModerateComments($member = null)
401
    {
402
        // Deny if not enabled for this object
403
        if (!$this->owner->CommentsEnabled) {
404
            return false;
405
        }
406
407
        // Fallback to can-edit
408
        return $this->owner->canEdit($member);
409
    }
410
411
    /**
412
     * Gets the RSS link to all comments
413
     *
414
     * @return string
415
     */
416
    public function getCommentRSSLink()
417
    {
418
        return Director::absoluteURL('comments/rss');
419
    }
420
421
    /**
422
     * Get the RSS link to all comments on this page
423
     *
424
     * @return string
425
     */
426
    public function getCommentRSSLinkPage()
427
    {
428
        return Controller::join_links(
429
            $this->getCommentRSSLink(),
430
            str_replace('\\', '-', $this->ownerBaseClass),
431
            $this->owner->ID
432
        );
433
    }
434
435
    /**
436
     * Comments interface for the front end. Includes the CommentAddForm and the composition
437
     * of the comments display.
438
     *
439
     * To customize the html see templates/CommentInterface.ss or extend this function with
440
     * your own extension.
441
     *
442
     * @todo Cleanup the passing of all this configuration based functionality
443
     *
444
     * @see  docs/en/Extending
445
     */
446
    public function CommentsForm()
447
    {
448
        // Check if enabled
449
        $enabled = $this->getCommentsEnabled();
450
        if ($enabled && $this->owner->getCommentsOption('include_js')) {
451
            $adminThirdPartyDir = ModuleLoader::getModule('silverstripe/admin')->getRelativePath() . '/thirdparty';
452
            Requirements::javascript($adminThirdPartyDir . '/jquery/jquery.js');
453
            Requirements::javascript($adminThirdPartyDir . '/jquery-entwine/dist/jquery.entwine-dist.js');
454
            Requirements::javascript($adminThirdPartyDir . '/jquery-form/jquery.form.js');
455
            Requirements::javascript(COMMENTS_THIRDPARTY . '/jquery-validate/jquery.validate.min.js');
456
            Requirements::add_i18n_javascript('comments/javascript/lang');
457
            Requirements::javascript('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->ownerBaseClass;
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 option available
498
     *
499
     * @param string $key
500
     *
501
     * @return mixed Result if the setting is available, or null otherwise
502
     */
503
    public function getCommentsOption($key)
504
    {
505
        $settings = $this->owner // In case singleton is called on the extension directly
506
            ? $this->owner->config()->comments
507
            : Config::inst()->get(__CLASS__, 'comments');
508
        $value = null;
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
        return $value;
518
    }
519
520
    /**
521
     * Add moderation functions to the current fieldlist
522
     *
523
     * @param FieldList $fields
524
     */
525
    protected function updateModerationFields(FieldList $fields)
526
    {
527
        Requirements::css(COMMENTS_DIR . '/css/cms.css');
528
529
        $newComments = $this->owner->AllComments()->filter('Moderated', 0);
530
531
        $newGrid = new CommentsGridField(
532
            'NewComments',
533
            _t('CommentsAdmin.NewComments', 'New'),
534
            $newComments,
535
            CommentsGridFieldConfig::create()
536
        );
537
538
        $approvedComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 0);
539
540
        $approvedGrid = new CommentsGridField(
541
            'ApprovedComments',
542
            _t('CommentsAdmin.Comments', 'Approved'),
543
            $approvedComments,
544
            CommentsGridFieldConfig::create()
545
        );
546
547
        $spamComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 1);
548
549
        $spamGrid = new CommentsGridField(
550
            'SpamComments',
551
            _t('CommentsAdmin.SpamComments', 'Spam'),
552
            $spamComments,
553
            CommentsGridFieldConfig::create()
554
        );
555
556
        $newCount = '(' . count($newComments) . ')';
557
        $approvedCount = '(' . count($approvedComments) . ')';
558
        $spamCount = '(' . count($spamComments) . ')';
559
560
        if ($fields->hasTabSet()) {
561
            $tabs = new TabSet(
562
                'Comments',
563
                new Tab('CommentsNewCommentsTab', _t('CommentAdmin.NewComments', 'New') . ' ' . $newCount,
564
                    $newGrid
565
                ),
566
                new Tab('CommentsCommentsTab', _t('CommentAdmin.Comments', 'Approved') . ' ' . $approvedCount,
567
                    $approvedGrid
568
                ),
569
                new Tab('CommentsSpamCommentsTab', _t('CommentAdmin.SpamComments', 'Spam') . ' ' . $spamCount,
570
                    $spamGrid
571
                )
572
            );
573
            $fields->addFieldToTab('Root', $tabs);
574
        } else {
575
            $fields->push($newGrid);
576
            $fields->push($approvedGrid);
577
            $fields->push($spamGrid);
578
        }
579
    }
580
581
    public function updateCMSFields(FieldList $fields)
582
    {
583
        // Disable moderation if not permitted
584
        if ($this->owner->canModerateComments()) {
585
            $this->updateModerationFields($fields);
586
        }
587
588
        // If this isn't a page we should merge the settings into the CMS fields
589
        if (!$this->attachedToSiteTree()) {
590
            $this->updateSettingsFields($fields);
591
        }
592
    }
593
}
594