Completed
Push — master ( e42a4c...f187a0 )
by Daniel
03:17
created

code/extensions/CommentsExtension.php (1 issue)

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
		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);
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;
0 ignored issues
show
This if statement, and the following return statement can be replaced with return !($requiredPermis... $requiredPermission));.
Loading history...
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
	 * Returns whether this extension instance is attached to a {@link SiteTree} object
452
	 *
453
	 * @return bool
454
	 */
455
	public function attachedToSiteTree() {
456
		$class = $this->ownerBaseClass;
457
458
		return (is_subclass_of($class, 'SiteTree')) || ($class == 'SiteTree');
459
	}
460
461
	/**
462
	 * @deprecated 1.0 Please use {@link CommentsExtension->CommentsForm()}
463
	 */
464
	public function PageComments() {
465
		// This method is very commonly used, don't throw a warning just yet
466
		Deprecation::notice('1.0', '$PageComments is deprecated. Please use $CommentsForm');
467
		return $this->CommentsForm();
468
	}
469
470
	/**
471
	 * Get the commenting option for this object
472
	 *
473
	 * This can be overridden in any instance or extension to customise the option available
474
	 *
475
	 * @param string $key
476
	 *
477
	 * @return mixed Result if the setting is available, or null otherwise
478
	 */
479
	public function getCommentsOption($key) {
480
		$settings = $this->owner // In case singleton is called on the extension directly
481
			? $this->owner->config()->comments
482
			: Config::inst()->get(__CLASS__, 'comments');
483
		$value = null;
484
		if(isset($settings[$key])) $value = $settings[$key];
485
486
		// To allow other extensions to customise this option
487
		if($this->owner) $this->owner->extend('updateCommentsOption', $key, $value);
488
		return $value;
489
	}
490
491
	/**
492
	 * Add moderation functions to the current fieldlist
493
	 *
494
	 * @param FieldList $fields
495
	 */
496
	protected function updateModerationFields(FieldList $fields) {
497
		Requirements::css(COMMENTS_DIR . '/css/cms.css');
498
499
		$newComments = $this->owner->AllComments()->filter('Moderated', 0);
500
501
		$newGrid = new CommentsGridField(
502
			'NewComments',
503
			_t('CommentsAdmin.NewComments', 'New'),
504
			$newComments,
505
			CommentsGridFieldConfig::create()
506
		);
507
508
		$approvedComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 0);
509
510
		$approvedGrid = new CommentsGridField(
511
			'ApprovedComments',
512
			_t('CommentsAdmin.Comments', 'Approved'),
513
			$approvedComments,
514
			CommentsGridFieldConfig::create()
515
		);
516
517
		$spamComments = $this->owner->AllComments()->filter('Moderated', 1)->filter('IsSpam', 1);
518
519
		$spamGrid = new CommentsGridField(
520
			'SpamComments',
521
			_t('CommentsAdmin.SpamComments', 'Spam'),
522
			$spamComments,
523
			CommentsGridFieldConfig::create()
524
		);
525
526
		$newCount = '(' . count($newComments) . ')';
527
		$approvedCount = '(' . count($approvedComments) . ')';
528
		$spamCount = '(' . count($spamComments) . ')';
529
530
		if($fields->hasTabSet()) {
531
			$tabs = new TabSet(
532
				'Comments',
533
				new Tab('CommentsNewCommentsTab', _t('CommentAdmin.NewComments', 'New') . ' ' . $newCount,
534
					$newGrid
535
				),
536
				new Tab('CommentsCommentsTab', _t('CommentAdmin.Comments', 'Approved') . ' ' . $approvedCount,
537
					$approvedGrid
538
				),
539
				new Tab('CommentsSpamCommentsTab', _t('CommentAdmin.SpamComments', 'Spam') . ' ' . $spamCount,
540
					$spamGrid
541
				)
542
			);
543
			$fields->addFieldToTab('Root', $tabs);
544
		} else {
545
			$fields->push($newGrid);
546
			$fields->push($approvedGrid);
547
			$fields->push($spamGrid);
548
		}
549
	}
550
551
	public function updateCMSFields(FieldList $fields) {
552
		// Disable moderation if not permitted
553
		if($this->owner->canModerateComments()) {
554
			$this->updateModerationFields($fields);
555
		}
556
557
		// If this isn't a page we should merge the settings into the CMS fields
558
		if(!$this->attachedToSiteTree()) {
559
			$this->updateSettingsFields($fields);
560
		}
561
	}
562
}
563