Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/lib/comments.php (2 issues)

1
<?php
2
/**
3
 * Elgg comments library
4
 *
5
 * @package    Elgg.Core
6
 * @subpackage Comments
7
 * @since 1.9
8
 */
9
10
/**
11
 * Comments initialization function
12
 *
13
 * @return void
14
 * @access private
15
 */
16
function _elgg_comments_init() {
17 31
	elgg_register_entity_type('object', 'comment');
18
	
19 31
	elgg_register_action('comment/save');
20 31
	elgg_register_action('comment/delete');
21
	
22 31
	elgg_register_plugin_hook_handler('entity:url', 'object', '_elgg_comment_url_handler');
23 31
	elgg_register_plugin_hook_handler('container_permissions_check', 'object', '_elgg_comments_container_permissions_override');
24 31
	elgg_register_plugin_hook_handler('permissions_check', 'object', '_elgg_comments_permissions_override');
25 31
	elgg_register_plugin_hook_handler('email', 'system', '_elgg_comments_notification_email_subject');
26
	
27 31
	elgg_register_plugin_hook_handler('register', 'menu:social', '_elgg_comments_social_menu_setup');
28
	
29 31
	elgg_register_event_handler('update:after', 'all', '_elgg_comments_access_sync', 600);
30
31 31
	elgg_register_page_handler('comment', '_elgg_comments_page_handler');
32
33 31
	elgg_register_ajax_view('core/ajax/edit_comment');
34 31
	elgg_register_ajax_view('page/elements/comments');
35 31
	elgg_register_ajax_view('river/elements/responses');
36
37 31
	elgg_register_plugin_hook_handler('likes:is_likable', 'object:comment', 'Elgg\Values::getTrue');
38
	
39 31
	elgg_register_notification_event('object', 'comment', ['create']);
40 31
	elgg_register_plugin_hook_handler('get', 'subscriptions', '_elgg_comments_add_content_owner_to_subscriptions');
41 31
	elgg_register_plugin_hook_handler('prepare', 'notification:create:object:comment', '_elgg_comments_prepare_content_owner_notification');
42 31
	elgg_register_plugin_hook_handler('prepare', 'notification:create:object:comment', '_elgg_comments_prepare_notification');
43 31
}
44
45
/**
46
 * Page handler for generic comments manipulation.
47
 *
48
 * @param array $segments URL segments
49
 *
50
 * @return bool
51
 *
52
 * @access private
53
 */
54
function _elgg_comments_page_handler($segments) {
55
56
	$page = elgg_extract(0, $segments);
57
	switch ($page) {
58
		case 'edit':
59
			echo elgg_view_resource('comments/edit', [
60
				'guid' => elgg_extract(1, $segments),
61
			]);
62
			return true;
63
			break;
64
65
		case 'view':
66
			_elgg_comment_redirect(elgg_extract(1, $segments), elgg_extract(2, $segments));
67
			break;
68
69
		default:
70
			return false;
71
			break;
72
	}
73
}
74
75
/**
76
 * Redirect to the comment in context of the containing page
77
 *
78
 * @param int $comment_guid  GUID of the comment
79
 * @param int $fallback_guid GUID of the containing entity
80
 *
81
 * @return void
82
 * @access private
83
 */
84
function _elgg_comment_redirect($comment_guid, $fallback_guid) {
85
	$fail = function () {
86
		register_error(elgg_echo('generic_comment:notfound'));
87
		forward(REFERER);
88
	};
89
90
	$comment = get_entity($comment_guid);
91
	if (!$comment) {
92
		// try fallback if given
93
		$fallback = get_entity($fallback_guid);
94
		if (!$fallback) {
95
			$fail();
96
		}
97
98
		register_error(elgg_echo('generic_comment:notfound_fallback'));
99
		forward($fallback->getURL());
100
	}
101
102
	if (!$comment instanceof ElggComment) {
103
		$fail();
104
	}
105
106
	$container = $comment->getContainerEntity();
107
	if (!$container) {
108
		$fail();
109
	}
110
111
	// this won't work with threaded comments, but core doesn't support that yet
112
	$count = elgg_get_entities([
113
		'type' => 'object',
114
		'subtype' => 'comment',
115
		'container_guid' => $container->guid,
116
		'count' => true,
117
		'wheres' => ["e.guid < " . (int) $comment->guid],
118
	]);
119
	$limit = (int) get_input('limit');
120
	if (!$limit) {
121
		$limit = elgg_trigger_plugin_hook('config', 'comments_per_page', [], 25);
122
	}
123
	$offset = floor($count / $limit) * $limit;
124
	if (!$offset) {
125
		$offset = null;
126
	}
127
128
	$url = elgg_http_add_url_query_elements($container->getURL(), [
129
		'offset' => $offset,
130
	]);
131
	
132
	// make sure there's only one fragment (#)
133
	$parts = parse_url($url);
134
	$parts['fragment'] = "elgg-object-{$comment->guid}";
135
	$url = elgg_http_build_url($parts, false);
136
	
137
	forward($url);
138
}
139
140
/**
141
 * Format and return the URL for a comment.
142
 *
143
 * This is the container's URL because comments don't have their own page.
144
 *
145
 * @param string $hook   'entity:url'
146
 * @param string $type   'object'
147
 * @param string $return URL for entity
148
 * @param array  $params Array with the elgg entity passed in as 'entity'
149
 *
150
 * @return string
151
 * @access private
152
 */
153
function _elgg_comment_url_handler($hook, $type, $return, $params) {
154 3
	$entity = $params['entity'];
155
	/* @var \ElggObject $entity */
156
157 3
	if (!$entity instanceof ElggComment || !$entity->getOwnerEntity()) {
158
		// not a comment or has no owner
159
160
		// @todo handle anonymous comments
161 3
		return $return;
162
	}
163
164
	$container = $entity->getContainerEntity();
165
	if (!$container) {
166
		return $return;
167
	}
168
169
	return "comment/view/{$entity->guid}/{$container->guid}";
170
}
171
172
/**
173
 * Allow users to comment on entities not owned by them.
174
 *
175
 * Object being commented on is used as the container of the comment so
176
 * permission check must be overridden if user isn't the owner of the object.
177
 *
178
 * @param string  $hook   'container_permissions_check'
179
 * @param string  $type   'object'
180
 * @param boolean $return Can the current user write to this container?
181
 * @param array   $params Array of parameters (container, user, subtype)
182
 *
183
 * @return array
184
 * @access private
185
 * @todo this doesn't seem to make a difference if a user can comment or not
186
 */
187
function _elgg_comments_container_permissions_override($hook, $type, $return, $params) {
188
189
	// is someone trying to comment, if so override permissions check
190 8
	if ($params['subtype'] === 'comment') {
191
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type array.
Loading history...
192
	}
193
194 8
	return $return;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $return returns the type boolean which is incompatible with the documented return type array.
Loading history...
195
}
196
197
/**
198
 * By default, only authors can edit their comments.
199
 *
200
 * @param string  $hook   'permissions_check'
201
 * @param string  $type   'object'
202
 * @param boolean $return Can the given user edit the given entity?
203
 * @param array   $params Array of parameters (entity, user)
204
 *
205
 * @return boolean Whether the given user is allowed to edit the given comment.
206
 * @access private
207
 */
208
function _elgg_comments_permissions_override($hook, $type, $return, $params) {
209 113
	$entity = $params['entity'];
210 113
	$user = $params['user'];
211
	
212 113
	if ($entity instanceof ElggComment && $user) {
213
		return $entity->getOwnerGUID() == $user->getGUID();
214
	}
215
	
216 113
	return $return;
217
}
218
219
/**
220
 * Set subject for email notifications about new ElggComment objects
221
 *
222
 * The "Re: " part is required by some email clients in order to properly
223
 * group the notifications in threads.
224
 *
225
 * Group discussion replies extend ElggComment objects so this takes care
226
 * of their notifications also.
227
 *
228
 * @param string $hook        'email'
229
 * @param string $type        'system'
230
 * @param array  $returnvalue Current mail parameters
231
 * @param array  $params      Original mail parameters
232
 * @return array $returnvalue Modified mail parameters
233
 * @access private
234
 */
235
function _elgg_comments_notification_email_subject($hook, $type, $returnvalue, $params) {
236
	if (!is_array($returnvalue) || !is_array($returnvalue['params'])) {
237
		// another hook handler returned a non-array, let's not override it
238
		return;
239
	}
240
241
	if (empty($returnvalue['params']['notification'])) {
242
		return;
243
	}
244
	
245
	/** @var Elgg\Notifications\Notification */
246
	$notification = $returnvalue['params']['notification'];
247
248
	if ($notification instanceof Elgg\Notifications\Notification) {
249
		$object = elgg_extract('object', $notification->params);
250
251
		if ($object instanceof ElggComment) {
252
			$container = $object->getContainerEntity();
253
254
			$returnvalue['subject'] = 'Re: ' . $container->getDisplayName();
255
		}
256
	}
257
258
	return $returnvalue;
259
}
260
261
/**
262
 * Update comment access to match that of the container
263
 *
264
 * @param string     $event  'update:after'
265
 * @param string     $type   'all'
266
 * @param ElggEntity $entity The updated entity
267
 * @return bool
268
 *
269
 * @access private
270
 */
271
function _elgg_comments_access_sync($event, $type, $entity) {
272 73
	if (!($entity instanceof \ElggEntity)) {
273 63
		return true;
274
	}
275
	
276
	// need to override access in case comments ended up with ACCESS_PRIVATE
277
	// and to ensure write permissions
278 59
	$ia = elgg_set_ignore_access(true);
279
	$options = [
280 59
		'type' => 'object',
281 59
		'subtype' => 'comment',
282 59
		'container_guid' => $entity->getGUID(),
283
		'wheres' => [function(\Elgg\Database\QueryBuilder $qb) use ($entity) {
284 59
			return $qb->compare('e.access_id', '!=', $entity->access_id, 'integer');
285 59
		}],
286 59
		'limit' => 0,
287
	];
288
289 59
	$batch = new \ElggBatch('elgg_get_entities', $options, null, 25, false);
290 59
	foreach ($batch as $comment) {
291
		// Update comment access_id
292 1
		$comment->access_id = $entity->access_id;
293 1
		$comment->save();
294
	}
295
		
296 59
	elgg_set_ignore_access($ia);
297
	
298 59
	return true;
299
}
300
301
/**
302
 * Add the owner of the content being commented on to the subscribers
303
 *
304
 * @param string $hook        'get'
305
 * @param string $type        'subscribers'
306
 * @param array  $returnvalue current subscribers
307
 * @param array  $params      supplied params
308
 *
309
 * @return void|array
310
 *
311
 * @access private
312
 */
313
function _elgg_comments_add_content_owner_to_subscriptions($hook, $type, $returnvalue, $params) {
314
	
315 2
	$event = elgg_extract('event', $params);
316 2
	if (!($event instanceof \Elgg\Notifications\NotificationEvent)) {
317
		return;
318
	}
319
	
320 2
	$object = $event->getObject();
321 2
	if (!$object instanceof ElggComment) {
322 2
		return;
323
	}
324
	
325
	$content_owner = $object->getContainerEntity()->getOwnerEntity();
326
	if (!($content_owner instanceof ElggUser)) {
327
		return;
328
	}
329
	
330
	$notification_settings = $content_owner->getNotificationSettings();
331
	if (empty($notification_settings)) {
332
		return;
333
	}
334
	
335
	$returnvalue[$content_owner->getGUID()] = [];
336
	foreach ($notification_settings as $method => $enabled) {
337
		if (empty($enabled)) {
338
			continue;
339
		}
340
		
341
		$returnvalue[$content_owner->getGUID()][] = $method;
342
	}
343
	
344
	return $returnvalue;
345
}
346
347
/**
348
 * Set the notification message for the owner of the content being commented on
349
 *
350
 * @param string                           $hook        'prepare'
351
 * @param string                           $type        'notification:create:object:comment'
352
 * @param \Elgg\Notifications\Notification $returnvalue current notification message
353
 * @param array                            $params      supplied params
354
 *
355
 * @return void|\Elgg\Notifications\Notification
356
 *
357
 * @access private
358
 */
359
function _elgg_comments_prepare_content_owner_notification($hook, $type, $returnvalue, $params) {
360
	
361
	$comment = elgg_extract('object', $params);
362
	if (!$comment instanceof ElggComment) {
363
		return;
364
	}
365
	
366
	/* @var $content \ElggEntity */
367
	$content = $comment->getContainerEntity();
368
	$recipient = elgg_extract('recipient', $params);
369
	if ($content->owner_guid !== $recipient->guid) {
370
		// not the content owner
371
		return;
372
	}
373
	
374
	$language = elgg_extract('language', $params);
375
	/* @var $commenter \ElggUser */
376
	$commenter = $comment->getOwnerEntity();
377
	
378
	$returnvalue->subject = elgg_echo('generic_comment:notification:owner:subject', [], $language);
379
	$returnvalue->summary = elgg_echo('generic_comment:notification:owner:summary', [], $language);
380
	$returnvalue->body = elgg_echo('generic_comment:notification:owner:body', [
381
		$content->getDisplayName(),
382
		$commenter->getDisplayName(),
383
		$comment->description,
384
		$comment->getURL(),
385
		$commenter->getDisplayName(),
386
		$commenter->getURL(),
387
	], $language);
388
	
389
	return $returnvalue;
390
}
391
392
/**
393
 * Set the notification message for interested users
394
 *
395
 * @param string                           $hook        'prepare'
396
 * @param string                           $type        'notification:create:object:comment'
397
 * @param \Elgg\Notifications\Notification $returnvalue current notification message
398
 * @param array                            $params      supplied params
399
 *
400
 * @return void|\Elgg\Notifications\Notification
401
 *
402
 * @access private
403
 */
404
function _elgg_comments_prepare_notification($hook, $type, $returnvalue, $params) {
405
	
406
	$comment = elgg_extract('object', $params);
407
	if (!$comment instanceof ElggComment) {
408
		return;
409
	}
410
	
411
	/* @var $content \ElggEntity */
412
	$content = $comment->getContainerEntity();
413
	$recipient = elgg_extract('recipient', $params);
414
	if ($content->getOwnerGUID() === $recipient->getGUID()) {
415
		// the content owner, this is handled in other hook
416
		return;
417
	}
418
	
419
	$language = elgg_extract('language', $params);
420
	/* @var $commenter \ElggUser */
421
	$commenter = $comment->getOwnerEntity();
422
	
423
	$returnvalue->subject = elgg_echo('generic_comment:notification:user:subject', [$content->getDisplayName()], $language);
424
	$returnvalue->summary = elgg_echo('generic_comment:notification:user:summary', [$content->getDisplayName()], $language);
425
	$returnvalue->body = elgg_echo('generic_comment:notification:user:body', [
426
		$content->getDisplayName(),
427
		$commenter->getDisplayName(),
428
		$comment->description,
429
		$comment->getURL(),
430
		$commenter->getDisplayName(),
431
		$commenter->getURL(),
432
	], $language);
433
434
	$returnvalue->url = $comment->getURL();
435
	
436
	return $returnvalue;
437
}
438
439
/**
440
 * Adds comment menu items to entity menu
441
 *
442
 * @param \Elgg\Hook $hook Hook information
443
 *
444
 * @return void|\ElggMenuItem[]
445
 *
446
 * @access private
447
 * @since 3.0
448
 */
449
function _elgg_comments_social_menu_setup(\Elgg\Hook $hook) {
450 1
	$entity = $hook->getEntityParam();
451 1
	if (!$entity) {
452
		return;
453
	}
454
	
455 1
	$return = $hook->getValue();
456
	
457 1
	$comment_count = $entity->countComments();
458 1
	$can_comment = $entity->canComment();
459 1
	if ($can_comment || $comment_count) {
460
		$text = $can_comment ? elgg_echo('comment:this') : elgg_echo('comments');
461
		
462
		$options = [
463
			'name' => 'comment',
464
			'icon' => 'speech-bubble',
465
			'badge' => $comment_count ?: null,
466
			'text' => $text,
467
			'title' => $text,
468
			'href' => $entity->getURL() . '#comments',
469
		];
470
				
471
		$item = $hook->getParam('item');
472
		if ($item && $can_comment) {
473
			$options['href'] = "#comments-add-{$entity->guid}-{$item->id}";
474
			$options['rel'] = 'toggle';
475
		}
476
		
477
		$return[] = \ElggMenuItem::factory($options);
478
	}
479
	
480 1
	return $return;
481
}
482
483
/**
484
 * Runs unit tests for \ElggComment
485
 *
486
 * @param string $hook   unit_test
487
 * @param string $type   system
488
 * @param mixed  $value  Array of tests
489
 * @param mixed  $params Params
490
 *
491
 * @return array
492
 * @access private
493
 * @codeCoverageIgnore
494
 */
495
function _elgg_comments_test($hook, $type, $value, $params) {
496
	$value[] = ElggCoreCommentTest::class;
497
	return $value;
498
}
499
500
/**
501
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
502
 */
503
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
504 18
	$events->registerHandler('init', 'system', '_elgg_comments_init');
505 18
	$hooks->registerHandler('unit_test', 'system', '_elgg_comments_test');
506
};
507