hooks.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <?php
  2. /**
  3. * All plugin hooks are bundled here
  4. */
  5. /**
  6. * Add menu items to the owner_block menu
  7. *
  8. * @param string $hook the name of the hook
  9. * @param string $type the type of the hook
  10. * @param ElggMenuItem[] $items current return value
  11. * @param array $params supplied params
  12. *
  13. * @return void|ElggMenuItem[]
  14. */
  15. function questions_owner_block_menu_handler($hook, $type, $items, $params) {
  16. if (empty($params) || !is_array($params)) {
  17. return;
  18. }
  19. $entity = elgg_extract('entity', $params);
  20. if (($entity instanceof ElggGroup) && ($entity->questions_enable === 'yes')) {
  21. $items[] = ElggMenuItem::factory([
  22. 'name' => 'questions',
  23. 'href' => "questions/group/{$entity->getGUID()}/all",
  24. 'text' => elgg_echo('questions:group'),
  25. ]);
  26. } elseif ($entity instanceof ElggUser) {
  27. $items[] = ElggMenuItem::factory([
  28. 'name' => 'questions',
  29. 'href' => "questions/owner/{$entity->username}",
  30. 'text' => elgg_echo('questions'),
  31. ]);
  32. }
  33. return $items;
  34. }
  35. /**
  36. * Add menu items to the entity menu
  37. *
  38. * @param string $hook the name of the hook
  39. * @param string $type the type of the hook
  40. * @param ElggMenuItem[] $items current return value
  41. * @param array $params supplied params
  42. *
  43. * @return void|ElggMenuItem[]
  44. */
  45. function questions_entity_menu_handler($hook, $type, $items, $params) {
  46. if (empty($params) || !is_array($params)) {
  47. return;
  48. }
  49. $entity = elgg_extract('entity', $params);
  50. if (empty($entity) || (!($entity instanceof ElggQuestion) && !($entity instanceof ElggAnswer))) {
  51. return;
  52. }
  53. if ($entity->canComment()) {
  54. if (elgg_extract('full_view', $params, false) || ($entity instanceof ElggAnswer)) {
  55. $items[] = ElggMenuItem::factory([
  56. 'name' => 'comments',
  57. 'rel' => 'toggle',
  58. 'link_class' => 'elgg-toggler',
  59. 'href' => "#comments-add-{$entity->getGUID()}",
  60. 'text' => elgg_view_icon('speech-bubble'),
  61. 'priority' => 600,
  62. ]);
  63. }
  64. }
  65. if (elgg_in_context('questions') && ($entity instanceof ElggAnswer) && questions_can_mark_answer($entity)) {
  66. $question = $entity->getContainerEntity();
  67. $answer = $question->getMarkedAnswer();
  68. if (empty($answer)) {
  69. $items[] = ElggMenuItem::factory([
  70. 'name' => 'questions_mark',
  71. 'text' => elgg_echo('questions:menu:entity:answer:mark'),
  72. 'href' => "action/answers/toggle_mark?guid={$entity->getGUID()}",
  73. 'is_action' => true,
  74. ]);
  75. } elseif ($entity->getGUID() === $answer->getGUID()) {
  76. // there is an anwser and it's this entity
  77. $items[] = ElggMenuItem::factory([
  78. 'name' => 'questions_mark',
  79. 'text' => elgg_echo('questions:menu:entity:answer:unmark'),
  80. 'href' => "action/answers/toggle_mark?guid={$entity->getGUID()}",
  81. 'is_action' => true,
  82. ]);
  83. }
  84. }
  85. return $items;
  86. }
  87. /**
  88. * Add menu items to the filter menu
  89. *
  90. * @param string $hook the name of the hook
  91. * @param string $type the type of the hook
  92. * @param ElggMenuItem[] $items current return value
  93. * @param array $params supplied params
  94. *
  95. * @return void|ElggMenuItem[]
  96. */
  97. function questions_filter_menu_handler($hook, $type, $items, $params) {
  98. if (empty($items) || !is_array($items) || !elgg_in_context('questions')) {
  99. return;
  100. }
  101. $page_owner = elgg_get_page_owner_entity();
  102. // change some menu items
  103. foreach ($items as $key => $item) {
  104. // remove friends
  105. if ($item->getName() == 'friend') {
  106. unset($items[$key]);
  107. }
  108. // in group context
  109. if ($page_owner instanceof ElggGroup) {
  110. // remove mine
  111. if ($item->getName() == 'mine') {
  112. unset($items[$key]);
  113. }
  114. // check if all is correct
  115. if ($item->getName() === 'all') {
  116. // set correct url
  117. $item->setHref("questions/group/{$page_owner->getGUID()}/all");
  118. // highlight all
  119. $current_page = current_page_url();
  120. if (stristr($current_page, "questions/group/{$page_owner->getGUID()}/all")) {
  121. $item->setSelected(true);
  122. }
  123. }
  124. }
  125. }
  126. if (questions_is_expert()) {
  127. $items[] = ElggMenuItem::factory([
  128. 'name' => 'todo',
  129. 'text' => elgg_echo('questions:menu:filter:todo'),
  130. 'href' => 'questions/todo',
  131. 'priority' => 700,
  132. ]);
  133. if ($page_owner instanceof ElggGroup) {
  134. $items[] = ElggMenuItem::factory([
  135. 'name' => 'todo_group',
  136. 'text' => elgg_echo('questions:menu:filter:todo_group'),
  137. 'href' => "questions/todo/{$page_owner->getGUID()}",
  138. 'priority' => 710,
  139. ]);
  140. }
  141. }
  142. if (questions_experts_enabled()) {
  143. $experts_href = 'questions/experts';
  144. if ($page_owner instanceof ElggGroup) {
  145. $experts_href .= "/{$page_owner->getGUID()}";
  146. }
  147. $items[] = ElggMenuItem::factory([
  148. 'name' => 'experts',
  149. 'text' => elgg_echo('questions:menu:filter:experts'),
  150. 'href' => $experts_href,
  151. 'priority' => 800,
  152. ]);
  153. }
  154. return $items;
  155. }
  156. /**
  157. * Add menu items to the user_hover menu
  158. *
  159. * @param string $hook the name of the hook
  160. * @param string $type the type of the hook
  161. * @param ElggMenuItem[] $items current return value
  162. * @param array $params supplied params
  163. *
  164. * @return void|ElggMenuItem[]
  165. */
  166. function questions_user_hover_menu_handler($hook, $type, $items, $params) {
  167. if (empty($params) || !is_array($params)) {
  168. return;
  169. }
  170. // are experts enabled
  171. if (!questions_experts_enabled()) {
  172. return;
  173. }
  174. // get the user for this menu
  175. $user = elgg_extract('entity', $params);
  176. if (empty($user) || !($user instanceof ElggUser)) {
  177. return;
  178. }
  179. // get page owner
  180. $page_owner = elgg_get_page_owner_entity();
  181. if (!($page_owner instanceof ElggGroup)) {
  182. $page_owner = elgg_get_site_entity();
  183. }
  184. // can the current person edit the page owner, to assign the role
  185. // and is the current user not the owner of this page owner
  186. if (!$page_owner->canEdit()) {
  187. return;
  188. }
  189. $text = elgg_echo('questions:menu:user_hover:make_expert');
  190. $confirm_text = elgg_echo('questions:menu:user_hover:make_expert:confirm', [$page_owner->name]);
  191. if (check_entity_relationship($user->getGUID(), QUESTIONS_EXPERT_ROLE, $page_owner->getGUID())) {
  192. $text = elgg_echo('questions:menu:user_hover:remove_expert');
  193. $confirm_text = elgg_echo('questions:menu:user_hover:remove_expert:confirm', [$page_owner->name]);
  194. }
  195. $items[] = ElggMenuItem::factory([
  196. 'name' => 'questions_expert',
  197. 'text' => $text,
  198. 'href' => "action/questions/toggle_expert?user_guid={$user->getGUID()}&guid={$page_owner->getGUID()}",
  199. 'confirm' => $confirm_text,
  200. ]);
  201. return $items;
  202. }
  203. /**
  204. * Check if a user can write an answer
  205. *
  206. * @param string $hook the name of the hook
  207. * @param string $type the type of the hook
  208. * @param bool $returnvalue current return value
  209. * @param array $params supplied params
  210. *
  211. * @return void|bool
  212. */
  213. function questions_container_permissions_handler($hook, $type, $returnvalue, $params) {
  214. static $experts_only;
  215. if ($returnvalue || empty($params) || !is_array($params)) {
  216. return;
  217. }
  218. $question = elgg_extract('container', $params);
  219. $user = elgg_extract('user', $params);
  220. $subtype = elgg_extract('subtype', $params);
  221. if (($subtype !== 'answer') || !($user instanceof ElggUser) || !($question instanceof ElggQuestion)) {
  222. return;
  223. }
  224. return questions_can_answer_question($question, $user);
  225. }
  226. /**
  227. * Check if a user has permissions
  228. *
  229. * @param string $hook the name of the hook
  230. * @param string $type the type of the hook
  231. * @param bool $returnvalue current return value
  232. * @param array $params supplied params
  233. *
  234. * @return void|bool
  235. */
  236. function questions_permissions_handler($hook, $type, $returnvalue, $params) {
  237. if (empty($params) || !is_array($params)) {
  238. return;
  239. }
  240. // get the provided data
  241. $entity = elgg_extract('entity', $params);
  242. $user = elgg_extract('user', $params);
  243. if (!($user instanceof ElggUser)) {
  244. return;
  245. }
  246. if (!($entity instanceof ElggQuestion) && !($entity instanceof ElggAnswer)) {
  247. return;
  248. }
  249. // expert only changes
  250. if (questions_experts_enabled()) {
  251. // check if an expert can edit a question
  252. if (!$returnvalue && ($entity instanceof ElggQuestion)) {
  253. $container = $entity->getContainerEntity();
  254. if (!($container instanceof ElggGroup)) {
  255. $container = elgg_get_site_entity();
  256. }
  257. if (questions_is_expert($container, $user)) {
  258. $returnvalue = true;
  259. }
  260. }
  261. // an expert should be able to edit an answer, so fix this
  262. if (!$returnvalue && ($entity instanceof ElggAnswer)) {
  263. // user is not the owner
  264. if ($entity->getOwnerGUID() !== $user->getGUID()) {
  265. $question = $entity->getContainerEntity();
  266. if ($question instanceof ElggQuestion) {
  267. $container = $question->getContainerEntity();
  268. if (!($container instanceof ElggGroup)) {
  269. $container = elgg_get_site_entity();
  270. }
  271. // if the user is an expert
  272. if (questions_is_expert($container, $user)) {
  273. $returnvalue = true;
  274. }
  275. }
  276. }
  277. }
  278. }
  279. // questions can't be editted by owner if it is closed
  280. if ($returnvalue && ($entity instanceof ElggQuestion)) {
  281. // is the question closed
  282. if ($entity->getStatus() === 'closed') {
  283. // are you the owner
  284. if ($user->getGUID() === $entity->getOwnerGUID()) {
  285. $returnvalue = false;
  286. }
  287. }
  288. }
  289. return $returnvalue;
  290. }
  291. /**
  292. * Return the widget title url
  293. *
  294. * @param string $hook the name of the hook
  295. * @param string $type the type of the hook
  296. * @param string $returnvalue current return value
  297. * @param array $params supplied params
  298. *
  299. * @return void|string
  300. */
  301. function questions_widget_url_handler($hook, $type, $returnvalue, $params) {
  302. if (!empty($returnvalue)) {
  303. // someone already set an url
  304. return;
  305. }
  306. if (empty($params) || !is_array($params)) {
  307. return;
  308. }
  309. $widget = elgg_extract('entity', $params);
  310. if (!($widget instanceof ElggWidget)) {
  311. return;
  312. }
  313. // only handle questions widget
  314. if ($widget->handler !== 'questions') {
  315. return;
  316. }
  317. // who owns the widget
  318. $owner = $widget->getOwnerEntity();
  319. if ($owner instanceof ElggUser) {
  320. // user
  321. $returnvalue = "questions/owner/{$owner->username}";
  322. if ($widget->context === 'dashboard') {
  323. switch ($widget->content_type) {
  324. case 'all':
  325. $returnvalue = 'questions/all';
  326. break;
  327. case 'todo':
  328. if (questions_is_expert()) {
  329. $returnvalue = 'questions/todo';
  330. }
  331. break;
  332. }
  333. }
  334. } elseif ($owner instanceof ElggGroup) {
  335. // group
  336. $returnvalue = "questions/group/{$owner->getGUID()}/all";
  337. } elseif ($owner instanceof ElggSite) {
  338. // site
  339. $returnvalue = 'questions/all';
  340. }
  341. return $returnvalue;
  342. }
  343. /**
  344. * A plugin hook for the CRON, so we can send out notifications to the experts about there workload
  345. *
  346. * @param string $hook the name of the hook
  347. * @param string $type the type of the hook
  348. * @param string $returnvalue current return value
  349. * @param array $params supplied params
  350. *
  351. * @return void
  352. */
  353. function questions_daily_cron_handler($hook, $type, $returnvalue, $params) {
  354. if (empty($params) || !is_array($params)) {
  355. return;
  356. }
  357. // are experts enabled
  358. if (!questions_experts_enabled()) {
  359. return;
  360. }
  361. $time = (int) elgg_extract('time', $params, time());
  362. $dbprefix = elgg_get_config('dbprefix');
  363. $site = elgg_get_site_entity();
  364. // get all experts
  365. $expert_options = [
  366. 'type' => 'user',
  367. 'site_guids' => false,
  368. 'limit' => false,
  369. 'joins' => ["JOIN {$dbprefix}entity_relationships re2 ON e.guid = re2.guid_one"],
  370. 'wheres' =>["(re2.guid_two = {$site->getGUID()} AND re2.relationship = 'member_of_site')"],
  371. 'relationship' => QUESTIONS_EXPERT_ROLE,
  372. 'inverse_relationship' => true,
  373. ];
  374. $experts = new ElggBatch('elgg_get_entities_from_relationship', $expert_options);
  375. // sending could take a while
  376. set_time_limit(0);
  377. $status_id = elgg_get_metastring_id('status');
  378. $closed_id = elgg_get_metastring_id('closed');
  379. $status_where = "NOT EXISTS (
  380. SELECT 1
  381. FROM {$dbprefix}metadata md
  382. WHERE md.entity_guid = e.guid
  383. AND md.name_id = {$status_id}
  384. AND md.value_id = {$closed_id})";
  385. $question_options = [
  386. 'type' => 'object',
  387. 'subtype' => 'question',
  388. 'limit' => 3,
  389. ];
  390. // loop through all experts
  391. foreach ($experts as $expert) {
  392. // fake a logged in user
  393. $backup_user = elgg_extract('user', $_SESSION);
  394. $_SESSION['user'] = $expert;
  395. $subject = elgg_echo('questions:daily:notification:subject', [], get_current_language());
  396. $message = '';
  397. $container_where = [];
  398. if (check_entity_relationship($expert->getGUID(), QUESTIONS_EXPERT_ROLE, $site->getGUID())) {
  399. $container_where[] = "(e.container_guid NOT IN (
  400. SELECT ge.guid
  401. FROM {$dbprefix}entities ge
  402. WHERE ge.type = 'group'
  403. AND ge.site_guid = {$site->getGUID()}
  404. AND ge.enabled = 'yes'
  405. ))";
  406. }
  407. $group_options = [
  408. 'type' => 'group',
  409. 'limit' => false,
  410. 'relationship' => QUESTIONS_EXPERT_ROLE,
  411. 'relationship_guid' => $expert->getGUID(),
  412. 'callback' => 'questions_row_to_guid',
  413. ];
  414. $groups = elgg_get_entities_from_relationship($group_options);
  415. if (!empty($groups)) {
  416. $container_where[] = '(e.container_guid IN (' . implode(',', $groups) . '))';
  417. }
  418. if (empty($container_where)) {
  419. // no groups or site? then skip to next expert
  420. continue;
  421. }
  422. $container_where = '(' . implode(' OR ', $container_where) . ')';
  423. // get overdue questions
  424. // eg: solution_time < $time && status != closed
  425. $question_options['metadata_name_value_pairs'] = [
  426. 'name' => 'solution_time',
  427. 'value' => $time,
  428. 'operand' => '<',
  429. ];
  430. $question_options['wheres'] = [
  431. $status_where,
  432. $container_where
  433. ];
  434. $question_options['order_by_metadata'] = [
  435. 'name' => 'solution_time',
  436. 'direction' => 'ASC',
  437. 'as' => 'integer'
  438. ];
  439. $questions = elgg_get_entities_from_metadata($question_options);
  440. if (!empty($questions)) {
  441. $message .= elgg_echo('questions:daily:notification:message:overdue', [], get_current_language()) . PHP_EOL;
  442. foreach ($questions as $question) {
  443. $message .= " - {$question->title} ({$question->getURL()})" . PHP_EOL;
  444. }
  445. $message .= elgg_echo('questions:daily:notification:message:more', [], get_current_language());
  446. $message .= " {$site->url}questions/todo" . PHP_EOL . PHP_EOL;
  447. }
  448. // get due questions
  449. // eg: solution_time >= $time && solution_time < ($time + 1 day) && status != closed
  450. $question_options['metadata_name_value_pairs'] = [
  451. [
  452. 'name' => 'solution_time',
  453. 'value' => $time,
  454. 'operand' => '>=',
  455. ],
  456. [
  457. 'name' => 'solution_time',
  458. 'value' => $time + (24 * 60 * 60),
  459. 'operand' => '<',
  460. ],
  461. ];
  462. $questions = elgg_get_entities_from_metadata($question_options);
  463. if (!empty($questions)) {
  464. $message .= elgg_echo('questions:daily:notification:message:due', [], get_current_language()) . PHP_EOL;
  465. foreach ($questions as $question) {
  466. $message .= " - {$question->title} ({$question->getURL()})" . PHP_EOL;
  467. }
  468. $message .= elgg_echo('questions:daily:notification:message:more', [], get_current_language());
  469. $message .= " {$site->url}questions/todo" . PHP_EOL . PHP_EOL;
  470. }
  471. // get new questions
  472. // eg: time_created >= ($time - 1 day)
  473. unset($question_options['metadata_name_value_pairs']);
  474. unset($question_options['order_by_metadata']);
  475. $question_options['wheres'] = [
  476. $container_where,
  477. '(e.time_created > ' . ($time - (24 * 60 *60)) . ')'
  478. ];
  479. $questions = elgg_get_entities_from_metadata($question_options);
  480. if (!empty($questions)) {
  481. $message .= elgg_echo('questions:daily:notification:message:new', [], get_current_language()) . PHP_EOL;
  482. foreach ($questions as $question) {
  483. $message .= " - {$question->title} ({$question->getURL()})" . PHP_EOL;
  484. }
  485. $message .= elgg_echo('questions:daily:notification:message:more', array(), get_current_language());
  486. $message .= " {$site->url}questions/all" . PHP_EOL . PHP_EOL;
  487. }
  488. // is there content in the message
  489. if (!empty($message)) {
  490. // force to email
  491. notify_user($expert->getGUID(), $site->getGUID(), $subject, $message, null, 'email');
  492. }
  493. // restore user
  494. $_SESSION['user'] = $backup_user;
  495. }
  496. }