Completed
Push — 3.0 ( 9dd29c...237018 )
by Jeroen
53:05
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
use Elgg\Database\QueryBuilder;
11
12
/**
13
 * Comments initialization function
14
 *
15
 * @return void
16
 * @access private
17
 */
18
function _elgg_comments_init() {
19 75
	elgg_register_entity_type('object', 'comment');
20
21 75
	elgg_register_plugin_hook_handler('container_permissions_check', 'object', '_elgg_comments_container_permissions_override');
22 75
	elgg_register_plugin_hook_handler('permissions_check', 'object', '_elgg_comments_permissions_override');
23 75
	elgg_register_plugin_hook_handler('email', 'system', '_elgg_comments_notification_email_subject');
24
	
25 75
	elgg_register_plugin_hook_handler('register', 'menu:social', '_elgg_comments_social_menu_setup');
26
	
27 75
	elgg_register_event_handler('update:after', 'all', '_elgg_comments_access_sync', 600);
28
29 75
	elgg_register_ajax_view('core/ajax/edit_comment');
30 75
	elgg_register_ajax_view('page/elements/comments');
31 75
	elgg_register_ajax_view('river/elements/responses');
32
33 75
	elgg_register_plugin_hook_handler('likes:is_likable', 'object:comment', 'Elgg\Values::getTrue');
34
	
35 75
	elgg_register_notification_event('object', 'comment', ['create']);
36 75
	elgg_register_plugin_hook_handler('get', 'subscriptions', '_elgg_comments_add_content_owner_to_subscriptions');
37 75
	elgg_register_plugin_hook_handler('prepare', 'notification:create:object:comment', '_elgg_comments_prepare_content_owner_notification');
38 75
	elgg_register_plugin_hook_handler('prepare', 'notification:create:object:comment', '_elgg_comments_prepare_notification');
39 75
}
40
41
/**
42
 * Are comments displayed with latest first?
43
 *
44
 * @param ElggEntity $container Entity containing comments
45
 * @return bool False means oldest first.
46
 * @since 3.0
47
 */
48
function elgg_comments_are_latest_first(ElggEntity $container = null) {
49
	$params = [
50
		'entity' => $container,
51
	];
52
	return (bool) elgg_trigger_plugin_hook('config', 'comments_latest_first', $params, true);
53
}
54
55
/**
56
 * How many comments appear per page.
57
 *
58
 * @param ElggEntity $container Entity containing comments
59
 * @return int
60
 * @since 3.0
61
 */
62
function elgg_comments_per_page(ElggEntity $container = null) {
63
	$params = [
64
		'entity' => $container,
65
	];
66
	return (int) elgg_trigger_plugin_hook('config', 'comments_per_page', $params, 25);
67
}
68
69
/**
70
 * Redirect to the comment in context of the containing page
71
 *
72
 * @param int $comment_guid  GUID of the comment
73
 * @param int $fallback_guid GUID of the containing entity
74
 *
75
 * @return void
76
 * @access private
77
 */
78
function _elgg_comment_redirect($comment_guid, $fallback_guid) {
79
	$fail = function () {
80
		register_error(elgg_echo('generic_comment:notfound'));
81
		forward(REFERER);
82
	};
83
84
	$comment = get_entity($comment_guid);
85
	if (!$comment) {
86
		// try fallback if given
87
		$fallback = get_entity($fallback_guid);
88
		if (!$fallback) {
89
			$fail();
90
		}
91
92
		register_error(elgg_echo('generic_comment:notfound_fallback'));
93
		forward($fallback->getURL());
94
	}
95
96
	if (!$comment instanceof ElggComment) {
97
		$fail();
98
	}
99
100
	$container = $comment->getContainerEntity();
101
	if (!$container) {
102
		$fail();
103
	}
104
105
	$operator = elgg_comments_are_latest_first($container) ? '>' : '<';
106
107
	// this won't work with threaded comments, but core doesn't support that yet
108
	$condition = function(QueryBuilder $qb, $main_alias) use ($comment, $operator) {
109
		return $qb->compare("{$main_alias}.guid", $operator, $comment->guid, ELGG_VALUE_GUID);
110
	};
111
	$count = elgg_get_entities([
112
		'type' => 'object',
113
		'subtype' => 'comment',
114
		'container_guid' => $container->guid,
115
		'count' => true,
116
		'wheres' => [$condition],
117
	]);
118
	$limit = (int) get_input('limit');
119
	if (!$limit) {
120
		$limit = elgg_comments_per_page($container);
121
	}
122
	$offset = floor($count / $limit) * $limit;
123
	if (!$offset) {
124
		$offset = null;
125
	}
126
127
	$url = elgg_http_add_url_query_elements($container->getURL(), [
128
		'offset' => $offset,
129
	]);
130
	
131
	// make sure there's only one fragment (#)
132
	$parts = parse_url($url);
133
	$parts['fragment'] = "elgg-object-{$comment->guid}";
134
	$url = elgg_http_build_url($parts, false);
135
	
136
	forward($url);
137
}
138
139
/**
140
 * Allow users to comment on entities not owned by them.
141
 *
142
 * Object being commented on is used as the container of the comment so
143
 * permission check must be overridden if user isn't the owner of the object.
144
 *
145
 * @param string  $hook   'container_permissions_check'
146
 * @param string  $type   'object'
147
 * @param boolean $return Can the current user write to this container?
148
 * @param array   $params Array of parameters (container, user, subtype)
149
 *
150
 * @return void|true
151
 * @access private
152
 * @todo this doesn't seem to make a difference if a user can comment or not
153
 */
154
function _elgg_comments_container_permissions_override($hook, $type, $return, $params) {
1 ignored issue
show
The parameter $return is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

154
function _elgg_comments_container_permissions_override($hook, $type, /** @scrutinizer ignore-unused */ $return, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
155
	// is someone trying to comment, if so override permissions check
156 8
	if (elgg_extract('subtype', $params) === 'comment') {
157
		return true;
158
	}
159 8
}
160
161
/**
162
 * By default, only authors can edit their comments.
163
 *
164
 * @param string  $hook   'permissions_check'
165
 * @param string  $type   'object'
166
 * @param boolean $return Can the given user edit the given entity?
167
 * @param array   $params Array of parameters (entity, user)
168
 *
169
 * @return void|boolean Whether the given user is allowed to edit the given comment.
170
 * @access private
171
 */
172
function _elgg_comments_permissions_override($hook, $type, $return, $params) {
1 ignored issue
show
The parameter $return is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

172
function _elgg_comments_permissions_override($hook, $type, /** @scrutinizer ignore-unused */ $return, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
173 126
	$entity = elgg_extract('entity', $params);
174 126
	$user = elgg_extract('user', $params);
175
	
176 126
	if ($entity instanceof ElggComment && $user instanceof ElggUser) {
177
		return $entity->getOwnerGUID() === $user->getGUID();
178
	}
179 126
}
180
181
/**
182
 * Set subject for email notifications about new ElggComment objects
183
 *
184
 * The "Re: " part is required by some email clients in order to properly
185
 * group the notifications in threads.
186
 *
187
 * Group discussion replies extend ElggComment objects so this takes care
188
 * of their notifications also.
189
 *
190
 * @param string $hook        'email'
191
 * @param string $type        'system'
192
 * @param array  $returnvalue Current mail parameters
193
 * @param array  $params      Original mail parameters
194
 * @return array $returnvalue Modified mail parameters
195
 * @access private
196
 */
197
function _elgg_comments_notification_email_subject($hook, $type, $returnvalue, $params) {
198
	if (!is_array($returnvalue) || !is_array($returnvalue['params'])) {
199
		// another hook handler returned a non-array, let's not override it
200
		return;
201
	}
202
203
	if (empty($returnvalue['params']['notification'])) {
204
		return;
205
	}
206
	
207
	/** @var Elgg\Notifications\Notification */
208
	$notification = $returnvalue['params']['notification'];
209
210
	if ($notification instanceof Elgg\Notifications\Notification) {
211
		$object = elgg_extract('object', $notification->params);
212
213
		if ($object instanceof ElggComment) {
214
			$container = $object->getContainerEntity();
215
216
			$returnvalue['subject'] = 'Re: ' . $container->getDisplayName();
217
		}
218
	}
219
220
	return $returnvalue;
221
}
222
223
/**
224
 * Update comment access to match that of the container
225
 *
226
 * @param string     $event  'update:after'
227
 * @param string     $type   'all'
228
 * @param ElggEntity $entity The updated entity
229
 * @return bool
230
 *
231
 * @access private
232
 */
233
function _elgg_comments_access_sync($event, $type, $entity) {
234 196
	if (!($entity instanceof \ElggEntity)) {
235 189
		return true;
236
	}
237
	
238
	// need to override access in case comments ended up with ACCESS_PRIVATE
239
	// and to ensure write permissions
240 136
	$ia = elgg_set_ignore_access(true);
241
	$options = [
242 136
		'type' => 'object',
243 136
		'subtype' => 'comment',
244 136
		'container_guid' => $entity->getGUID(),
245
		'wheres' => [function(\Elgg\Database\QueryBuilder $qb, $main_alias) use ($entity) {
246 136
			return $qb->compare("{$main_alias}.access_id", '!=', $entity->access_id, ELGG_VALUE_INTEGER);
247 136
		}],
248 136
		'limit' => 0,
249
	];
250
251 136
	$batch = new \ElggBatch('elgg_get_entities', $options, null, 25, false);
252 136
	foreach ($batch as $comment) {
253
		// Update comment access_id
254 1
		$comment->access_id = $entity->access_id;
255 1
		$comment->save();
256
	}
257
		
258 136
	elgg_set_ignore_access($ia);
259
	
260 136
	return true;
261
}
262
263
/**
264
 * Add the owner of the content being commented on to the subscribers
265
 *
266
 * @param string $hook        'get'
267
 * @param string $type        'subscribers'
268
 * @param array  $returnvalue current subscribers
269
 * @param array  $params      supplied params
270
 *
271
 * @return void|array
272
 *
273
 * @access private
274
 */
275
function _elgg_comments_add_content_owner_to_subscriptions($hook, $type, $returnvalue, $params) {
276
	
277 2
	$event = elgg_extract('event', $params);
278 2
	if (!$event instanceof \Elgg\Notifications\SubscriptionNotificationEvent) {
279 2
		return;
280
	}
281
	
282
	if ($event->getAction() !== 'create') {
283
		return;
284
	}
285
	
286
	$object = $event->getObject();
287
	if (!$object instanceof ElggComment) {
288
		return;
289
	}
290
	
291
	$content_owner = $object->getContainerEntity()->getOwnerEntity();
292
	if (!$content_owner instanceof ElggUser) {
293
		return;
294
	}
295
	
296
	$notification_settings = $content_owner->getNotificationSettings();
297
	if (empty($notification_settings)) {
298
		return;
299
	}
300
	
301
	$returnvalue[$content_owner->guid] = [];
302
	foreach ($notification_settings as $method => $enabled) {
303
		if (empty($enabled)) {
304
			continue;
305
		}
306
		
307
		$returnvalue[$content_owner->guid][] = $method;
308
	}
309
	
310
	return $returnvalue;
311
}
312
313
/**
314
 * Set the notification message for the owner of the content being commented on
315
 *
316
 * @param string                           $hook        'prepare'
317
 * @param string                           $type        'notification:create:object:comment'
318
 * @param \Elgg\Notifications\Notification $returnvalue current notification message
319
 * @param array                            $params      supplied params
320
 *
321
 * @return void|\Elgg\Notifications\Notification
322
 *
323
 * @access private
324
 */
325
function _elgg_comments_prepare_content_owner_notification($hook, $type, $returnvalue, $params) {
326
	
327
	$comment = elgg_extract('object', $params);
328
	if (!$comment instanceof ElggComment) {
329
		return;
330
	}
331
	
332
	/* @var $content \ElggEntity */
333
	$content = $comment->getContainerEntity();
334
	$recipient = elgg_extract('recipient', $params);
335
	if ($content->owner_guid !== $recipient->guid) {
336
		// not the content owner
337
		return;
338
	}
339
	
340
	$language = elgg_extract('language', $params);
341
	/* @var $commenter \ElggUser */
342
	$commenter = $comment->getOwnerEntity();
343
	
344
	$returnvalue->subject = elgg_echo('generic_comment:notification:owner:subject', [], $language);
345
	$returnvalue->summary = elgg_echo('generic_comment:notification:owner:summary', [], $language);
346
	$returnvalue->body = elgg_echo('generic_comment:notification:owner:body', [
347
		$content->getDisplayName(),
348
		$commenter->getDisplayName(),
349
		$comment->description,
350
		$comment->getURL(),
351
		$commenter->getDisplayName(),
352
		$commenter->getURL(),
353
	], $language);
354
	
355
	return $returnvalue;
356
}
357
358
/**
359
 * Set the notification message for interested users
360
 *
361
 * @param string                           $hook        'prepare'
362
 * @param string                           $type        'notification:create:object:comment'
363
 * @param \Elgg\Notifications\Notification $returnvalue current notification message
364
 * @param array                            $params      supplied params
365
 *
366
 * @return void|\Elgg\Notifications\Notification
367
 *
368
 * @access private
369
 */
370
function _elgg_comments_prepare_notification($hook, $type, $returnvalue, $params) {
371
	
372
	$comment = elgg_extract('object', $params);
373
	if (!$comment instanceof ElggComment) {
374
		return;
375
	}
376
	
377
	/* @var $content \ElggEntity */
378
	$content = $comment->getContainerEntity();
379
	$recipient = elgg_extract('recipient', $params);
380
	if ($content->getOwnerGUID() === $recipient->getGUID()) {
381
		// the content owner, this is handled in other hook
382
		return;
383
	}
384
	
385
	$language = elgg_extract('language', $params);
386
	/* @var $commenter \ElggUser */
387
	$commenter = $comment->getOwnerEntity();
388
	
389
	$returnvalue->subject = elgg_echo('generic_comment:notification:user:subject', [$content->getDisplayName()], $language);
390
	$returnvalue->summary = elgg_echo('generic_comment:notification:user:summary', [$content->getDisplayName()], $language);
391
	$returnvalue->body = elgg_echo('generic_comment:notification:user:body', [
392
		$content->getDisplayName(),
393
		$commenter->getDisplayName(),
394
		$comment->description,
395
		$comment->getURL(),
396
		$commenter->getDisplayName(),
397
		$commenter->getURL(),
398
	], $language);
399
400
	$returnvalue->url = $comment->getURL();
401
	
402
	return $returnvalue;
403
}
404
405
/**
406
 * Adds comment menu items to entity menu
407
 *
408
 * @param \Elgg\Hook $hook Hook information
409
 *
410
 * @return void|\ElggMenuItem[]
411
 *
412
 * @access private
413
 * @since 3.0
414
 */
415
function _elgg_comments_social_menu_setup(\Elgg\Hook $hook) {
416 1
	$entity = $hook->getEntityParam();
417 1
	if (!$entity) {
418
		return;
419
	}
420
	
421 1
	$return = $hook->getValue();
422
	
423 1
	$comment_count = $entity->countComments();
424 1
	$can_comment = $entity->canComment();
425 1
	if ($can_comment || $comment_count) {
426
		$text = $can_comment ? elgg_echo('comment:this') : elgg_echo('comments');
427
		
428
		$options = [
429
			'name' => 'comment',
430
			'icon' => 'speech-bubble',
431
			'badge' => $comment_count ?: null,
432
			'text' => $text,
433
			'title' => $text,
434
			'href' => $entity->getURL() . '#comments',
435
		];
436
				
437
		$item = $hook->getParam('item');
438
		if ($item && $can_comment) {
439
			$options['href'] = "#comments-add-{$entity->guid}-{$item->id}";
440
			$options['rel'] = 'toggle';
441
		}
442
		
443
		$return[] = \ElggMenuItem::factory($options);
444
	}
445
	
446 1
	return $return;
447
}
448
449
/**
450
 * Runs unit tests for \ElggComment
451
 *
452
 * @param string $hook   unit_test
453
 * @param string $type   system
454
 * @param mixed  $value  Array of tests
455
 * @param mixed  $params Params
456
 *
457
 * @return array
458
 * @access private
459
 * @codeCoverageIgnore
460
 */
461
function _elgg_comments_test($hook, $type, $value, $params) {
462
	$value[] = ElggCoreCommentTest::class;
463
	return $value;
464
}
465
466
/**
467
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
468
 */
469
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
470 64
	$events->registerHandler('init', 'system', '_elgg_comments_init');
471 64
	$hooks->registerHandler('unit_test', 'system', '_elgg_comments_test');
472
};
473