Completed
Pull Request — master (#173)
by Gordon
02:02
created

CommentsExtension::populateDefaults()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 28
rs 5.3846
cc 8
eloc 18
nc 36
nop 0
1
<?php
2
3
/**
4
 * Extension to {@link DataObject} to enable tracking comments.
5
 *
6
 * @package comments
7
 */
8
class CommentsExtension extends DataExtension {
9
    /**
10
     * Default configuration values
11
     *
12
     * enabled:                     Allows commenting to be disabled even if the extension is present
13
     * enabled_cms:                 Allows commenting to be enabled or disabled via the CMS
14
     * require_login:               Boolean, whether a user needs to login (required for required_permission)
15
     * require_login_cms:           Allows require_login to be set via the CMS
16
     * required_permission:         Permission (or array of permissions) required to comment
17
     * include_js:                  Enhance operation by ajax behaviour on moderation links (required for use_preview)
18
     * use_gravatar:                Set to true to show gravatar icons
19
     * gravatar_default:            Theme for 'not found' gravatar {@see http://gravatar.com/site/implement/images}
20
     * gravatar_rating:             Gravatar rating (same as the standard default)
21
     * show_comments_when_disabled: Show older comments when commenting has been disabled.
22
     * order_comments_by:           Default sort order.
23
     * order_replies_by:            Sort order for replies.
24
     * comments_holder_id:          ID for the comments holder
25
     * comment_permalink_prefix:    ID prefix for each comment
26
     * require_moderation:          Require moderation for all comments
27
     * require_moderation_cms:      Ignore other comment moderation config settings and set via CMS
28
     * frontend_moderation:         Display unmoderated comments in the frontend, if the user can moderate them.
29
     * frontend_spam:               Display spam comments in the frontend, if the user can moderate them.
30
     * html_allowed:                Allow for sanitized HTML in comments
31
     * use_preview:                 Preview formatted comment (when allowing HTML)
32
     * nested_comments:             Enable nested comments
33
     * nested_depth:                Max depth of nested comments in levels (where root is 1 depth) 0 means no limit.
34
     *
35
     * @var array
36
     *
37
     * @config
38
     */
39
    private static $comments = array(
40
        'enabled' => true,
41
        'enabled_cms' => false,
42
        'require_login' => false,
43
        'require_login_cms' => false,
44
        'required_permission' => false,
45
        'include_js' => true,
46
        'use_gravatar' => false,
47
        'gravatar_size' => 80,
48
        'gravatar_default' => 'identicon',
49
        'gravatar_rating' => 'g',
50
        'show_comments_when_disabled' => false,
51
        'order_comments_by' => '"Created" DESC',
52
        'order_replies_by' => false,
53
        'comments_per_page' => 10,
54
        'comments_holder_id' => 'comments-holder',
55
        'comment_permalink_prefix' => 'comment-',
56
        'require_moderation' => false,
57
        'require_moderation_nonmembers' => false,
58
        'require_moderation_cms' => false,
59
        'frontend_moderation' => false,
60
        'frontend_spam' => false,
61
        'html_allowed' => false,
62
        'html_allowed_elements' => array('a', 'img', 'i', 'b'),
63
        'use_preview' => false,
64
        'nested_comments' => false,
65
        'nested_depth' => 2,
66
    );
67
68
    /**
69
     * @var array
70
     */
71
    private static $db = array(
72
        'ProvideComments' => 'Boolean',
73
        'ModerationRequired' => 'Enum(\'None,Required,NonMembersOnly\',\'None\')',
74
        'CommentsRequireLogin' => 'Boolean',
75
    );
76
77
    /**
78
     * CMS configurable options should default to the config values, but respect
79
     * default values specified by the object
80
     */
81
    public function populateDefaults() {
82
        $defaults = $this->owner->config()->defaults;
83
84
        // Set if comments should be enabled by default
85
        if(isset($defaults['ProvideComments'])) {
86
            $this->owner->ProvideComments = $defaults['ProvideComments'];
87
        } else {
88
            $this->owner->ProvideComments = $this->owner->getCommentsOption('enabled') ? 1 : 0;
89
        }
90
91
        // If moderation options should be configurable via the CMS then
92
        if(isset($defaults['ModerationRequired'])) {
93
            $this->owner->ModerationRequired = $defaults['ModerationRequired'];
94
        } elseif($this->owner->getCommentsOption('require_moderation')) {
95
            $this->owner->ModerationRequired = 'Required';
96
        } elseif($this->owner->getCommentsOption('require_moderation_nonmembers')) {
97
            $this->owner->ModerationRequired = 'NonMembersOnly';
98
        } else {
99
            $this->owner->ModerationRequired = 'None';
100
        }
101
102
        // Set login required
103
        if(isset($defaults['CommentsRequireLogin'])) {
104
            $this->owner->CommentsRequireLogin = $defaults['CommentsRequireLogin'];
105
        } else {
106
            $this->owner->CommentsRequireLogin = $this->owner->getCommentsOption('require_login') ? 1 : 0;
107
        }
108
    }
109
110
111
    /**
112
     * If this extension is applied to a {@link SiteTree} record then
113
     * append a Provide Comments checkbox to allow authors to trigger
114
     * whether or not to display comments
115
     *
116
     * @todo Allow customization of other {@link Commenting} configuration
117
     *
118
     * @param FieldList $fields
119
     */
120
    public function updateSettingsFields(FieldList $fields) {
121
122
        $options = FieldGroup::create()->setTitle(_t('CommentsExtension.COMMENTOPTIONS', 'Comments'));
123
124
        // Check if enabled setting should be cms configurable
125
        if($this->owner->getCommentsOption('enabled_cms')) {
126
            $options->push(new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments')));
127
        }
128
129
        // Check if we should require users to login to comment
130
        if($this->owner->getCommentsOption('require_login_cms')) {
131
            $options->push(
132
                new CheckboxField(
133
                    'CommentsRequireLogin',
134
                    _t('Comments.COMMENTSREQUIRELOGIN', 'Require login to comment')
135
                )
136
            );
137
        }
138
139
        if($options->FieldList()->count()) {
140
            if($fields->hasTabSet()) {
141
                $fields->addFieldsToTab('Root.Settings', $options);
0 ignored issues
show
Documentation introduced by
$options is of type object<FieldGroup>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
142
            } else {
143
                $fields->push($options);
144
            }
145
        }
146
147
        // Check if moderation should be enabled via cms configurable
148
        if($this->owner->getCommentsOption('require_moderation_cms')) {
149
            $moderationField = new DropdownField('ModerationRequired', 'Comment Moderation', array(
150
                'None' => _t('CommentsExtension.MODERATIONREQUIRED_NONE', 'No moderation required'),
151
                'Required' => _t('CommentsExtension.MODERATIONREQUIRED_REQUIRED', 'Moderate all comments'),
152
                'NonMembersOnly' => _t(
153
                    'CommentsExtension.MODERATIONREQUIRED_NONMEMBERSONLY',
154
                    'Only moderate non-members'
155
                ),
156
            ));
157
            if($fields->hasTabSet()) {
158
                $fields->addFieldsToTab('Root.Settings', $moderationField);
0 ignored issues
show
Documentation introduced by
$moderationField is of type object<DropdownField>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
159
            } else {
160
                $fields->push($moderationField);
161
            }
162
        }
163
    }
164
165
    /**
166
     * Get comment moderation rules for this parent
167
     *
168
     * None:           No moderation required
169
     * Required:       All comments
170
     * NonMembersOnly: Only anonymous users
171
     *
172
     * @return string
173
     */
174
    public function getModerationRequired() {
175
        if($this->owner->getCommentsOption('require_moderation_cms')) {
176
            return $this->owner->getField('ModerationRequired');
177
        } elseif($this->owner->getCommentsOption('require_moderation')) {
178
            return 'Required';
179
        } elseif($this->owner->getCommentsOption('require_moderation_nonmembers')) {
180
            return 'NonMembersOnly';
181
        } else {
182
            return 'None';
183
        }
184
    }
185
186
    /**
187
     * Determine if users must be logged in to post comments
188
     *
189
     * @return boolean
190
     */
191
    public function getCommentsRequireLogin() {
192
        if($this->owner->getCommentsOption('require_login_cms')) {
193
            return (bool) $this->owner->getField('CommentsRequireLogin');
194
        } else {
195
            return (bool) $this->owner->getCommentsOption('require_login');
196
        }
197
    }
198
199
    /**
200
     * Returns the RelationList of all comments against this object. Can be used as a data source
201
     * for a gridfield with write access.
202
     *
203
     * @return CommentList
204
     */
205
    public function AllComments() {
206
        $order = $this->owner->getCommentsOption('order_comments_by');
207
        $comments = CommentList::create($this->ownerBaseClass)
208
            ->forForeignID($this->owner->ID)
209
            ->sort($order);
210
        $this->owner->extend('updateAllComments', $comments);
211
        return $comments;
212
    }
213
214
    /**
215
     * Returns all comments against this object, with with spam and unmoderated items excluded, for use in the frontend
216
     *
217
     * @return CommentList
218
     */
219
    public function AllVisibleComments() {
220
        $list = $this->AllComments();
221
222
        // Filter spam comments for non-administrators if configured
223
        $showSpam = $this->owner->getCommentsOption('frontend_spam') && $this->owner->canModerateComments();
224
        if(!$showSpam) {
225
            $list = $list->filter('IsSpam', 0);
226
        }
227
228
        // Filter un-moderated comments for non-administrators if moderation is enabled
229
        $showUnmoderated = ($this->owner->ModerationRequired === 'None')
230
            || ($this->owner->getCommentsOption('frontend_moderation') && $this->owner->canModerateComments());
231
        if(!$showUnmoderated) {
232
            $list = $list->filter('Moderated', 1);
233
        }
234
235
        $this->owner->extend('updateAllVisibleComments', $list);
236
        return $list;
237
    }
238
239
    /**
240
     * Returns the root level comments, with spam and unmoderated items excluded, for use in the frontend
241
     *
242
     * @return CommentList
243
     */
244
    public function Comments() {
245
        $list = $this->AllVisibleComments();
246
247
        // If nesting comments, only show root level
248
        if($this->owner->getCommentsOption('nested_comments')) {
249
            $list = $list->filter('ParentCommentID', 0);
250
        }
251
252
        $this->owner->extend('updateComments', $list);
253
        return $list;
254
    }
255
256
    /**
257
     * Returns a paged list of the root level comments, with spam and unmoderated items excluded,
258
     * for use in the frontend
259
     *
260
     * @return PaginatedList
261
     */
262 View Code Duplication
    public function PagedComments() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
        $list = $this->Comments();
264
265
        // Add pagination
266
        $list = new PaginatedList($list, Controller::curr()->getRequest());
267
        $list->setPaginationGetVar('commentsstart' . $this->owner->ID);
268
        $list->setPageLength($this->owner->getCommentsOption('comments_per_page'));
269
270
        $this->owner->extend('updatePagedComments', $list);
271
        return $list;
272
    }
273
274
    /**
275
     * Check if comments are configured for this page even if they are currently disabled.
276
     * Do not include the comments on pages which don't have id's such as security pages
277
     *
278
     * @deprecated since version 2.0
279
     *
280
     * @return boolean
281
     */
282
    public function getCommentsConfigured() {
283
        Deprecation::notice('2.0', 'getCommentsConfigured is deprecated. Use getCommentsEnabled instead');
284
        return true; // by virtue of all classes with this extension being 'configured'
285
    }
286
287
    /**
288
     * Determine if comments are enabled for this instance
289
     *
290
     * @return boolean
291
     */
292
    public function getCommentsEnabled() {
293
        // Don't display comments form for pseudo-pages (such as the login form)
294
        if(!$this->owner->exists()) return false;
295
296
        // Determine which flag should be used to determine if this is enabled
297
        if($this->owner->getCommentsOption('enabled_cms')) {
298
            return $this->owner->ProvideComments;
299
        } else {
300
            return $this->owner->getCommentsOption('enabled');
301
        }
302
    }
303
304
    /**
305
     * Get the HTML ID for the comment holder in the template
306
     *
307
     * @return string
308
     */
309
    public function getCommentHolderID() {
310
        return $this->owner->getCommentsOption('comments_holder_id');
311
    }
312
313
    /**
314
     * @deprecated since version 2.0
315
     */
316
    public function getPostingRequiresPermission() {
317
        Deprecation::notice('2.0', 'Use getPostingRequiredPermission instead');
318
        return $this->getPostingRequiredPermission();
319
    }
320
321
    /**
322
     * Permission codes required in order to post (or empty if none required)
323
     *
324
     * @return string|array Permission or list of permissions, if required
325
     */
326
    public function getPostingRequiredPermission() {
327
        return $this->owner->getCommentsOption('required_permission');
328
    }
329
330
    public function canPost() {
331
        Deprecation::notice('2.0', 'Use canPostComment instead');
332
        return $this->canPostComment();
333
    }
334
335
    /**
336
     * Determine if a user can post comments on this item
337
     *
338
     * @param Member $member Member to check
339
     *
340
     * @return boolean
341
     */
342
    public function canPostComment($member = null) {
343
        // Deny if not enabled for this object
344
        if(!$this->owner->CommentsEnabled) return false;
345
346
        // Check if member is required
347
        $requireLogin = $this->owner->CommentsRequireLogin;
348
        if(!$requireLogin) return true;
349
350
        // Check member is logged in
351
        $member = $member ?: Member::currentUser();
352
        if(!$member) return false;
353
354
        // If member required check permissions
355
        $requiredPermission = $this->owner->PostingRequiredPermission;
356
        if($requiredPermission && !Permission::checkMember($member, $requiredPermission)) return false;
357
358
        return true;
359
    }
360
361
    /**
362
     * Determine if this member can moderate comments in the CMS
363
     *
364
     * @param Member $member
365
     *
366
     * @return boolean
367
     */
368
    public function canModerateComments($member = null) {
369
        // Deny if not enabled for this object
370
        if(!$this->owner->CommentsEnabled) return false;
371
372
        // Fallback to can-edit
373
        return $this->owner->canEdit($member);
374
    }
375
376
    public function getRssLink() {
377
        Deprecation::notice('2.0', 'Use getCommentRSSLink instead');
378
        return $this->getCommentRSSLink();
379
    }
380
381
    /**
382
     * Gets the RSS link to all comments
383
     *
384
     * @return string
385
     */
386
    public function getCommentRSSLink() {
387
        return Controller::join_links(Director::baseURL(), 'CommentingController/rss');
388
    }
389
390
    public function getRssLinkPage() {
391
        Deprecation::notice('2.0', 'Use getCommentRSSLinkPage instead');
392
        return $this->getCommentRSSLinkPage();
393
    }
394
395
    /**
396
     * Get the RSS link to all comments on this page
397
     *
398
     * @return string
399
     */
400
    public function getCommentRSSLinkPage() {
401
        return Controller::join_links(
402
            $this->getCommentRSSLink(), $this->ownerBaseClass, $this->owner->ID
403
        );
404
    }
405
406
    /**
407
     * Comments interface for the front end. Includes the CommentAddForm and the composition
408
     * of the comments display.
409
     *
410
     * To customize the html see templates/CommentInterface.ss or extend this function with
411
     * your own extension.
412
     *
413
     * @todo Cleanup the passing of all this configuration based functionality
414
     *
415
     * @see  docs/en/Extending
416
     */
417
    public function CommentsForm() {
418
        // Check if enabled
419
        $enabled = $this->getCommentsEnabled();
420
        if($enabled && $this->owner->getCommentsOption('include_js')) {
421
            Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
422
            Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
423
            Requirements::javascript(THIRDPARTY_DIR . '/jquery-validate/lib/jquery.form.js');
424
            Requirements::javascript(COMMENTS_THIRDPARTY . '/jquery-validate/jquery.validate.min.js');
425
            Requirements::add_i18n_javascript('comments/javascript/lang');
426
            Requirements::javascript('comments/javascript/CommentsInterface.js');
427
        }
428
429
        $controller = CommentingController::create();
430
        $controller->setOwnerRecord($this->owner);
431
        $controller->setBaseClass($this->ownerBaseClass);
432
        $controller->setOwnerController(Controller::curr());
433
434
        $moderatedSubmitted = Session::get('CommentsModerated');
435
        Session::clear('CommentsModerated');
436
437
        $form = ($enabled) ? $controller->CommentsForm() : false;
438
439
        // a little bit all over the show but to ensure a slightly easier upgrade for users
440
        // return back the same variables as previously done in comments
441
        return $this
442
            ->owner
443
            ->customise(array(
444
                'AddCommentForm' => $form,
445
                'ModeratedSubmitted' => $moderatedSubmitted,
446
            ))
447
            ->renderWith('CommentsInterface');
448
    }
449
450
451
    /*
452
    JavaScript form reply requires an empty form, when the reply button is
453
    clicked the correct values are inserted, the form moved, and then shown
454
     */
455
    public function getReplyFormForJavaScript() {
456
        // Build reply controller
457
        $controller = CommentingController::create();
458
        $controller->setOwnerRecord($this->owner);
459
        $controller->setBaseClass($this->owner->ClassName);
460
        $controller->setOwnerController(Controller::curr());
461
        $comment = new Comment();
462
        $comment->ID='JSOnly';
463
        $comment->ParentID = $this->owner->ID;
464
        return $controller->ReplyForm($comment);
465
    }
466
467
468
    /**
469
     * Returns whether this extension instance is attached to a {@link SiteTree} object
470
     *
471
     * @return bool
472
     */
473
    public function attachedToSiteTree() {
474
        $class = $this->ownerBaseClass;
475
476
        return (is_subclass_of($class, 'SiteTree')) || ($class == 'SiteTree');
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
477
    }
478
479
    /**
480
     * @deprecated 1.0 Please use {@link CommentsExtension->CommentsForm()}
481
     */
482
    public function PageComments() {
483
        // This method is very commonly used, don't throw a warning just yet
484
        Deprecation::notice('1.0', '$PageComments is deprecated. Please use $CommentsForm');
485
        return $this->CommentsForm();
486
    }
487
488
    /**
489
     * Get the commenting option for this object
490
     *
491
     * This can be overridden in any instance or extension to customise the option available
492
     *
493
     * @param string $key
494
     *
495
     * @return mixed Result if the setting is available, or null otherwise
496
     */
497
    public function getCommentsOption($key) {
498
        $settings = $this->owner // In case singleton is called on the extension directly
499
            ? $this->owner->config()->comments
500
            : Config::inst()->get(__CLASS__, 'comments');
501
        $value = null;
502
        if(isset($settings[$key])) $value = $settings[$key];
503
504
        // To allow other extensions to customise this option
505
        if($this->owner) $this->owner->extend('updateCommentsOption', $key, $value);
506
        return $value;
507
    }
508
509
    /**
510
     * Add moderation functions to the current fieldlist
511
     *
512
     * @param FieldList $fields
513
     */
514
    protected function updateModerationFields(FieldList $fields) {
515
        Requirements::css(COMMENTS_DIR . '/css/cms.css');
516
517
        $newComments = $this->owner->AllComments()->filter('Moderated', 0);
518
519
        $newGrid = new CommentsGridField(
520
            'NewComments',
521
            _t('CommentsAdmin.NewComments', 'New'),
522
            $newComments,
523
            CommentsGridFieldConfig::create()
524
        );
525
526
        $approvedComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 0);
527
528
        $approvedGrid = new CommentsGridField(
529
            'ApprovedComments',
530
            _t('CommentsAdmin.Comments', 'Approved'),
531
            $approvedComments,
532
            CommentsGridFieldConfig::create()
533
        );
534
535
        $spamComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 1);
536
537
        $spamGrid = new CommentsGridField(
538
            'SpamComments',
539
            _t('CommentsAdmin.SpamComments', 'Spam'),
540
            $spamComments,
541
            CommentsGridFieldConfig::create()
542
        );
543
544
        $newCount = '(' . count($newComments) . ')';
545
        $approvedCount = '(' . count($approvedComments) . ')';
546
        $spamCount = '(' . count($spamComments) . ')';
547
548
        if($fields->hasTabSet()) {
549
            $tabs = new TabSet(
550
                'Comments',
551
                new Tab('CommentsNewCommentsTab', _t('CommentAdmin.NewComments', 'New') . ' ' . $newCount,
552
                    $newGrid
553
                ),
554
                new Tab('CommentsCommentsTab', _t('CommentAdmin.Comments', 'Approved') . ' ' . $approvedCount,
555
                    $approvedGrid
556
                ),
557
                new Tab('CommentsSpamCommentsTab', _t('CommentAdmin.SpamComments', 'Spam') . ' ' . $spamCount,
558
                    $spamGrid
559
                )
560
            );
561
            $fields->addFieldToTab('Root', $tabs);
562
        } else {
563
            $fields->push($newGrid);
564
            $fields->push($approvedGrid);
565
            $fields->push($spamGrid);
566
        }
567
    }
568
569
    public function updateCMSFields(FieldList $fields) {
570
        // Disable moderation if not permitted
571
        if($this->owner->canModerateComments()) {
572
            $this->updateModerationFields($fields);
573
        }
574
575
        // If this isn't a page we should merge the settings into the CMS fields
576
        if(!$this->attachedToSiteTree()) {
577
            $this->updateSettingsFields($fields);
578
        }
579
    }
580
}
581