Completed
Push — master ( b1bf62...c2fee6 )
by Damian
01:51
created

code/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
/**
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 View Code Duplication
		if(isset($defaults['ProvideComments'])) {
0 ignored issues
show
This code seems to be duplicated across 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...
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 View Code Duplication
		if(isset($defaults['CommentsRequireLogin'])) {
0 ignored issues
show
This code seems to be duplicated across 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...
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);
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);
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() {
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::javascript('comments/javascript/CommentsInterface.js');
426
		}
427
428
		$controller = CommentingController::create();
429
		$controller->setOwnerRecord($this->owner);
430
		$controller->setBaseClass($this->ownerBaseClass);
431
		$controller->setOwnerController(Controller::curr());
432
433
		$moderatedSubmitted = Session::get('CommentsModerated');
434
		Session::clear('CommentsModerated');
435
436
		$form = ($enabled) ? $controller->CommentsForm() : false;
437
438
		// a little bit all over the show but to ensure a slightly easier upgrade for users
439
		// return back the same variables as previously done in comments
440
		return $this
441
			->owner
442
			->customise(array(
443
				'AddCommentForm' => $form,
444
				'ModeratedSubmitted' => $moderatedSubmitted,
445
			))
446
			->renderWith('CommentsInterface');
447
	}
448
449
	/**
450
	 * Returns whether this extension instance is attached to a {@link SiteTree} object
451
	 *
452
	 * @return bool
453
	 */
454
	public function attachedToSiteTree() {
455
		$class = $this->ownerBaseClass;
456
457
		return (is_subclass_of($class, 'SiteTree')) || ($class == 'SiteTree');
458
	}
459
460
	/**
461
	 * @deprecated 1.0 Please use {@link CommentsExtension->CommentsForm()}
462
	 */
463
	public function PageComments() {
464
		// This method is very commonly used, don't throw a warning just yet
465
		Deprecation::notice('1.0', '$PageComments is deprecated. Please use $CommentsForm');
466
		return $this->CommentsForm();
467
	}
468
469
	/**
470
	 * Get the commenting option for this object
471
	 *
472
	 * This can be overridden in any instance or extension to customise the option available
473
	 *
474
	 * @param string $key
475
	 *
476
	 * @return mixed Result if the setting is available, or null otherwise
477
	 */
478
	public function getCommentsOption($key) {
479
		$settings = $this->owner // In case singleton is called on the extension directly
480
			? $this->owner->config()->comments
481
			: Config::inst()->get(__CLASS__, 'comments');
482
		$value = null;
483
		if(isset($settings[$key])) $value = $settings[$key];
484
485
		// To allow other extensions to customise this option
486
		if($this->owner) $this->owner->extend('updateCommentsOption', $key, $value);
487
		return $value;
488
	}
489
490
	/**
491
	 * Add moderation functions to the current fieldlist
492
	 *
493
	 * @param FieldList $fields
494
	 */
495
	protected function updateModerationFields(FieldList $fields) {
496
		Requirements::css(COMMENTS_DIR . '/css/cms.css');
497
498
		$newComments = $this->owner->AllComments()->filter('Moderated', 0);
499
500
		$newGrid = new CommentsGridField(
501
			'NewComments',
502
			_t('CommentsAdmin.NewComments', 'New'),
503
			$newComments,
504
			CommentsGridFieldConfig::create()
505
		);
506
507
		$approvedComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 0);
508
509
		$approvedGrid = new CommentsGridField(
510
			'ApprovedComments',
511
			_t('CommentsAdmin.Comments', 'Approved'),
512
			$approvedComments,
513
			CommentsGridFieldConfig::create()
514
		);
515
516
		$spamComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 1);
517
518
		$spamGrid = new CommentsGridField(
519
			'SpamComments',
520
			_t('CommentsAdmin.SpamComments', 'Spam'),
521
			$spamComments,
522
			CommentsGridFieldConfig::create()
523
		);
524
525
		$newCount = '(' . count($newComments) . ')';
526
		$approvedCount = '(' . count($approvedComments) . ')';
527
		$spamCount = '(' . count($spamComments) . ')';
528
529
		if($fields->hasTabSet()) {
530
			$tabs = new TabSet(
531
				'Comments',
532
				new Tab('CommentsNewCommentsTab', _t('CommentAdmin.NewComments', 'New') . ' ' . $newCount,
533
					$newGrid
534
				),
535
				new Tab('CommentsCommentsTab', _t('CommentAdmin.Comments', 'Approved') . ' ' . $approvedCount,
536
					$approvedGrid
537
				),
538
				new Tab('CommentsSpamCommentsTab', _t('CommentAdmin.SpamComments', 'Spam') . ' ' . $spamCount,
539
					$spamGrid
540
				)
541
			);
542
			$fields->addFieldToTab('Root', $tabs);
543
		} else {
544
			$fields->push($newGrid);
545
			$fields->push($approvedGrid);
546
			$fields->push($spamGrid);
547
		}
548
	}
549
550
	public function updateCMSFields(FieldList $fields) {
551
		// Disable moderation if not permitted
552
		if($this->owner->canModerateComments()) {
553
			$this->updateModerationFields($fields);
554
		}
555
556
		// If this isn't a page we should merge the settings into the CMS fields
557
		if(!$this->attachedToSiteTree()) {
558
			$this->updateSettingsFields($fields);
559
		}
560
	}
561
}
562