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