comments.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <?php
  2. /**
  3. * Elgg comments library
  4. *
  5. * @package Elgg.Core
  6. * @subpackage Comments
  7. * @since 1.9
  8. */
  9. /**
  10. * Comments initialization function
  11. *
  12. * @return void
  13. * @access private
  14. */
  15. function _elgg_comments_init() {
  16. elgg_register_entity_type('object', 'comment');
  17. elgg_register_plugin_hook_handler('register', 'menu:entity', '_elgg_comment_setup_entity_menu', 900);
  18. elgg_register_plugin_hook_handler('entity:url', 'object', '_elgg_comment_url_handler');
  19. elgg_register_plugin_hook_handler('container_permissions_check', 'object', '_elgg_comments_container_permissions_override');
  20. elgg_register_plugin_hook_handler('permissions_check', 'object', '_elgg_comments_permissions_override');
  21. elgg_register_plugin_hook_handler('email', 'system', '_elgg_comments_notification_email_subject');
  22. elgg_register_event_handler('update:after', 'all', '_elgg_comments_access_sync');
  23. elgg_register_page_handler('comment', '_elgg_comments_page_handler');
  24. elgg_register_ajax_view('core/ajax/edit_comment');
  25. }
  26. /**
  27. * Page handler for generic comments manipulation.
  28. *
  29. * @param array $page
  30. * @return bool
  31. * @access private
  32. */
  33. function _elgg_comments_page_handler($page) {
  34. switch ($page[0]) {
  35. case 'edit':
  36. elgg_gatekeeper();
  37. if (empty($page[1])) {
  38. register_error(elgg_echo('generic_comment:notfound'));
  39. forward(REFERER);
  40. }
  41. $comment = get_entity($page[1]);
  42. if (!($comment instanceof \ElggComment) || !$comment->canEdit()) {
  43. register_error(elgg_echo('generic_comment:notfound'));
  44. forward(REFERER);
  45. }
  46. $target = $comment->getContainerEntity();
  47. if (!($target instanceof \ElggEntity)) {
  48. register_error(elgg_echo('generic_comment:notfound'));
  49. forward(REFERER);
  50. }
  51. $title = elgg_echo('generic_comments:edit');
  52. elgg_push_breadcrumb($target->getDisplayName(), $target->getURL());
  53. elgg_push_breadcrumb($title);
  54. $params = array(
  55. 'entity' => $target,
  56. 'comment' => $comment,
  57. 'is_edit_page' => true,
  58. );
  59. $content = elgg_view_form('comment/save', null, $params);
  60. $params = array(
  61. 'content' => $content,
  62. 'title' => $title,
  63. 'filter' => '',
  64. );
  65. $body = elgg_view_layout('content', $params);
  66. echo elgg_view_page($title, $body);
  67. return true;
  68. break;
  69. case 'view':
  70. _elgg_comment_redirect(elgg_extract(1, $page), elgg_extract(2, $page));
  71. break;
  72. default:
  73. return false;
  74. break;
  75. }
  76. }
  77. /**
  78. * Redirect to the comment in context of the containing page
  79. *
  80. * @param int $comment_guid GUID of the comment
  81. * @param int $fallback_guid GUID of the containing entity
  82. *
  83. * @return void
  84. * @access private
  85. */
  86. function _elgg_comment_redirect($comment_guid, $fallback_guid) {
  87. $fail = function () {
  88. register_error(elgg_echo('generic_comment:notfound'));
  89. forward(REFERER);
  90. };
  91. $comment = get_entity($comment_guid);
  92. if (!$comment) {
  93. // try fallback if given
  94. $fallback = get_entity($fallback_guid);
  95. if (!$fallback) {
  96. $fail();
  97. }
  98. register_error(elgg_echo('generic_comment:notfound_fallback'));
  99. forward($fallback->getURL());
  100. }
  101. if (!elgg_instanceof($comment, 'object', 'comment')) {
  102. $fail();
  103. }
  104. $container = $comment->getContainerEntity();
  105. if (!$container) {
  106. $fail();
  107. }
  108. // this won't work with threaded comments, but core doesn't support that yet
  109. $count = elgg_get_entities([
  110. 'type' => 'object',
  111. 'subtype' => 'comment',
  112. 'container_guid' => $container->guid,
  113. 'count' => true,
  114. 'wheres' => ["e.guid < " . (int)$comment->guid],
  115. ]);
  116. $limit = (int)get_input('limit');
  117. if (!$limit) {
  118. $limit = elgg_trigger_plugin_hook('config', 'comments_per_page', [], 25);
  119. }
  120. $offset = floor($count / $limit) * $limit;
  121. if (!$offset) {
  122. $offset = null;
  123. }
  124. $url = elgg_http_add_url_query_elements($container->getURL(), [
  125. 'offset' => $offset,
  126. ]) . "#elgg-object-{$comment->guid}";
  127. forward($url);
  128. }
  129. /**
  130. * Setup the menu shown with a comment
  131. *
  132. * @param string $hook 'register'
  133. * @param string $type 'menu:entity'
  134. * @param \ElggMenuItem[] $return Array of \ElggMenuItem objects
  135. * @param array $params Array of view vars
  136. *
  137. * @return array
  138. * @access private
  139. */
  140. function _elgg_comment_setup_entity_menu($hook, $type, $return, $params) {
  141. if (elgg_in_context('widgets')) {
  142. return $return;
  143. }
  144. $entity = $params['entity'];
  145. if (!elgg_instanceof($entity, 'object', 'comment')) {
  146. return $return;
  147. }
  148. // Remove edit link and access level from the menu
  149. foreach ($return as $key => $item) {
  150. if ($item->getName() === 'access') {
  151. unset($return[$key]);
  152. }
  153. }
  154. return $return;
  155. }
  156. /**
  157. * Format and return the URL for a comment.
  158. *
  159. * This is the container's URL because comments don't have their own page.
  160. *
  161. * @param string $hook 'entity:url'
  162. * @param string $type 'object'
  163. * @param string $return URL for entity
  164. * @param array $params Array with the elgg entity passed in as 'entity'
  165. *
  166. * @return string
  167. * @access private
  168. */
  169. function _elgg_comment_url_handler($hook, $type, $return, $params) {
  170. $entity = $params['entity'];
  171. /* @var \ElggObject $entity */
  172. if (!elgg_instanceof($entity, 'object', 'comment') || !$entity->getOwnerEntity()) {
  173. // not a comment or has no owner
  174. // @todo handle anonymous comments
  175. return $return;
  176. }
  177. $container = $entity->getContainerEntity();
  178. if (!$container) {
  179. return $return;
  180. }
  181. return "comment/view/{$entity->guid}/{$container->guid}";
  182. }
  183. /**
  184. * Allow users to comment on entities not owned by them.
  185. *
  186. * Object being commented on is used as the container of the comment so
  187. * permission check must be overridden if user isn't the owner of the object.
  188. *
  189. * @param string $hook 'container_permissions_check'
  190. * @param string $type 'object'
  191. * @param boolean $return Can the current user write to this container?
  192. * @param array $params Array of parameters (container, user, subtype)
  193. *
  194. * @return array
  195. * @access private
  196. * @todo this doesn't seem to make a difference if a user can comment or not
  197. */
  198. function _elgg_comments_container_permissions_override($hook, $type, $return, $params) {
  199. // is someone trying to comment, if so override permissions check
  200. if ($params['subtype'] === 'comment') {
  201. return true;
  202. }
  203. return $return;
  204. }
  205. /**
  206. * By default, only authors can edit their comments.
  207. *
  208. * @param string $hook 'permissions_check'
  209. * @param string $type 'object'
  210. * @param boolean $return Can the given user edit the given entity?
  211. * @param array $params Array of parameters (entity, user)
  212. *
  213. * @return boolean Whether the given user is allowed to edit the given comment.
  214. */
  215. function _elgg_comments_permissions_override($hook, $type, $return, $params) {
  216. $entity = $params['entity'];
  217. $user = $params['user'];
  218. if (elgg_instanceof($entity, 'object', 'comment') && $user) {
  219. return $entity->getOwnerGUID() == $user->getGUID();
  220. }
  221. return $return;
  222. }
  223. /**
  224. * Set subject for email notifications about new ElggComment objects
  225. *
  226. * The "Re: " part is required by some email clients in order to properly
  227. * group the notifications in threads.
  228. *
  229. * Group discussion replies extend ElggComment objects so this takes care
  230. * of their notifications also.
  231. *
  232. * @param string $hook 'email'
  233. * @param string $type 'system'
  234. * @param array $returnvalue Current mail parameters
  235. * @param array $params Original mail parameters
  236. * @return array $returnvalue Modified mail parameters
  237. */
  238. function _elgg_comments_notification_email_subject($hook, $type, $returnvalue, $params) {
  239. if (!is_array($returnvalue)) {
  240. // another hook handler returned a non-array, let's not override it
  241. return;
  242. }
  243. /** @var Elgg\Notifications\Notification */
  244. $notification = elgg_extract('notification', $returnvalue['params']);
  245. if ($notification instanceof Elgg\Notifications\Notification) {
  246. $object = elgg_extract('object', $notification->params);
  247. if ($object instanceof ElggComment) {
  248. $container = $object->getContainerEntity();
  249. $returnvalue['subject'] = 'Re: ' . $container->getDisplayName();
  250. }
  251. }
  252. return $returnvalue;
  253. }
  254. /**
  255. * Update comment access to match that of the container
  256. *
  257. * @param string $event 'update:after'
  258. * @param string $type 'all'
  259. * @param ElggEntity $entity The updated entity
  260. * @return bool
  261. *
  262. * @access private
  263. */
  264. function _elgg_comments_access_sync($event, $type, $entity) {
  265. if (!($entity instanceof \ElggEntity)) {
  266. return true;
  267. }
  268. // need to override access in case comments ended up with ACCESS_PRIVATE
  269. // and to ensure write permissions
  270. $ia = elgg_set_ignore_access(true);
  271. $options = array(
  272. 'type' => 'object',
  273. 'subtype' => 'comment',
  274. 'container_guid' => $entity->getGUID(),
  275. 'wheres' => array(
  276. "e.access_id != {$entity->access_id}"
  277. ),
  278. 'limit' => 0,
  279. );
  280. $batch = new \ElggBatch('elgg_get_entities', $options, null, 25, false);
  281. foreach ($batch as $comment) {
  282. // Update comment access_id
  283. $comment->access_id = $entity->access_id;
  284. $comment->save();
  285. }
  286. elgg_set_ignore_access($ia);
  287. return true;
  288. }
  289. /**
  290. * Runs unit tests for \ElggComment
  291. *
  292. * @param string $hook unit_test
  293. * @param string $type system
  294. * @param mixed $value Array of tests
  295. * @param mixed $params Params
  296. *
  297. * @return array
  298. * @access private
  299. */
  300. function _elgg_comments_test($hook, $type, $value, $params) {
  301. global $CONFIG;
  302. $value[] = "{$CONFIG->path}engine/tests/ElggCommentTest.php";
  303. return $value;
  304. }
  305. return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
  306. $events->registerHandler('init', 'system', '_elgg_comments_init');
  307. $hooks->registerHandler('unit_test', 'system', '_elgg_comments_test');
  308. };