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

engine/lib/comments.php (13 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');
0 ignored issues
show
Deprecated Code introduced by
The function elgg_register_page_handler() has been deprecated: 3.0 ( Ignorable by Annotation )

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

31
	/** @scrutinizer ignore-deprecated */ elgg_register_page_handler('comment', '_elgg_comments_page_handler');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
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) {
2 ignored issues
show
The parameter $hook 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

153
function _elgg_comment_url_handler(/** @scrutinizer ignore-unused */ $hook, $type, $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...
The parameter $type 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

153
function _elgg_comment_url_handler($hook, /** @scrutinizer ignore-unused */ $type, $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...
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) {
2 ignored issues
show
The parameter $type 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

187
function _elgg_comments_container_permissions_override($hook, /** @scrutinizer ignore-unused */ $type, $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...
The parameter $hook 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

187
function _elgg_comments_container_permissions_override(/** @scrutinizer ignore-unused */ $hook, $type, $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...
188
189
	// is someone trying to comment, if so override permissions check
190 8
	if ($params['subtype'] === 'comment') {
191
		return true;
192
	}
193
194 8
	return $return;
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) {
2 ignored issues
show
The parameter $hook 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

208
function _elgg_comments_permissions_override(/** @scrutinizer ignore-unused */ $hook, $type, $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...
The parameter $type 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

208
function _elgg_comments_permissions_override($hook, /** @scrutinizer ignore-unused */ $type, $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...
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) {
2 ignored issues
show
The parameter $event 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

271
function _elgg_comments_access_sync(/** @scrutinizer ignore-unused */ $event, $type, $entity) {

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...
The parameter $type 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

271
function _elgg_comments_access_sync($event, /** @scrutinizer ignore-unused */ $type, $entity) {

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...
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) {
3 ignored issues
show
The parameter $type 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

495
function _elgg_comments_test($hook, /** @scrutinizer ignore-unused */ $type, $value, $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...
The parameter $hook 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

495
function _elgg_comments_test(/** @scrutinizer ignore-unused */ $hook, $type, $value, $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...
The parameter $params 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

495
function _elgg_comments_test($hook, $type, $value, /** @scrutinizer ignore-unused */ $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...
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