navigation.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. <?php
  2. /**
  3. * Elgg navigation library
  4. * Functions for managing menus and other navigational elements
  5. *
  6. * Breadcrumbs
  7. * Elgg uses a breadcrumb stack. The page handlers (controllers in MVC terms)
  8. * push the breadcrumb links onto the stack. @see elgg_push_breadcrumb()
  9. *
  10. *
  11. * Pagination
  12. * Automatically handled by Elgg when using elgg_list_entities* functions.
  13. * @see elgg_list_entities()
  14. *
  15. *
  16. * Tabs
  17. * @see navigation/tabs view
  18. *
  19. *
  20. * Menus
  21. * Elgg uses a single interface to manage its menus. Menu items are added with
  22. * {@link elgg_register_menu_item()}. This is generally used for menus that
  23. * appear only once per page. For dynamic menus (such as the hover
  24. * menu for user's avatar), a plugin hook is emitted when the menu is being
  25. * created. The hook is 'register', 'menu:<menu_name>'. For more details on this,
  26. * @see elgg_view_menu().
  27. *
  28. * Menus supported by the Elgg core
  29. * Standard menus:
  30. * site Site navigation shown on every page.
  31. * page Page menu usually shown in a sidebar. Uses Elgg's context.
  32. * topbar Topbar menu shown on every page. The default has two sections.
  33. * footer Like the topbar but in the footer.
  34. * extras Links about content on the page. The RSS link is added to this.
  35. *
  36. * Dynamic menus (also called just-in-time menus):
  37. * user_hover Avatar hover menu. The user entity is passed as a parameter.
  38. * entity The set of links shown in the summary of an entity.
  39. * river Links shown on river items.
  40. * owner_block Links shown for a user or group in their owner block.
  41. * filter The tab filter for content (all, mine, friends)
  42. * title The buttons shown next to a content title.
  43. * longtext The links shown above the input/longtext view.
  44. * login Menu of links at bottom of login box
  45. *
  46. * @package Elgg.Core
  47. * @subpackage Navigation
  48. */
  49. /**
  50. * Register an item for an Elgg menu
  51. *
  52. * @warning Generally you should not use this in response to the plugin hook:
  53. * 'register', 'menu:<menu_name>'. If you do, you may end up with many incorrect
  54. * links on a dynamic menu.
  55. *
  56. * @warning A menu item's name must be unique per menu. If more than one menu
  57. * item with the same name are registered, the last menu item takes priority.
  58. *
  59. * @see elgg_view_menu() for the plugin hooks available for modifying a menu as
  60. * it is being rendered.
  61. *
  62. * @see ElggMenuItem::factory() is used to turn an array value of $menu_item into an
  63. * ElggMenuItem object.
  64. *
  65. * @param string $menu_name The name of the menu: site, page, userhover,
  66. * userprofile, groupprofile, or any custom menu
  67. * @param mixed $menu_item A \ElggMenuItem object or an array of options in format:
  68. * name => STR Menu item identifier (required)
  69. * text => STR Menu item display text as HTML (required)
  70. * href => STR Menu item URL (required) (false for non-links.
  71. * @warning If you disable the href the <a> tag will
  72. * not appear, so the link_class will not apply. If you
  73. * put <a> tags in manually through the 'text' option
  74. * the default CSS selector .elgg-menu-$menu > li > a
  75. * may affect formatting. Wrap in a <span> if it does.)
  76. * contexts => ARR Page context strings
  77. * section => STR Menu section identifier
  78. * title => STR Menu item tooltip
  79. * selected => BOOL Is this menu item currently selected
  80. * parent_name => STR Identifier of the parent menu item
  81. * link_class => STR A class or classes for the <a> tag
  82. * item_class => STR A class or classes for the <li> tag
  83. *
  84. * Additional options that the view output/url takes can be
  85. * passed in the array. Custom options can be added by using
  86. * the 'data' key with the value being an associative array.
  87. *
  88. * @return bool False if the item could not be added
  89. * @since 1.8.0
  90. */
  91. function elgg_register_menu_item($menu_name, $menu_item) {
  92. global $CONFIG;
  93. if (is_array($menu_item)) {
  94. $options = $menu_item;
  95. $menu_item = \ElggMenuItem::factory($options);
  96. if (!$menu_item) {
  97. $menu_item_name = elgg_extract('name', $options, 'MISSING NAME');
  98. elgg_log("Unable to add menu item '{$menu_item_name}' to '$menu_name' menu", 'WARNING');
  99. return false;
  100. }
  101. }
  102. if (!$menu_item instanceof ElggMenuItem) {
  103. elgg_log('Second argument of elgg_register_menu_item() must be an instance of ElggMenuItem or an array of menu item factory options', 'ERROR');
  104. return false;
  105. }
  106. if (!isset($CONFIG->menus[$menu_name])) {
  107. $CONFIG->menus[$menu_name] = array();
  108. }
  109. $CONFIG->menus[$menu_name][] = $menu_item;
  110. return true;
  111. }
  112. /**
  113. * Remove an item from a menu
  114. *
  115. * @param string $menu_name The name of the menu
  116. * @param string $item_name The unique identifier for this menu item
  117. *
  118. * @return \ElggMenuItem|null
  119. * @since 1.8.0
  120. */
  121. function elgg_unregister_menu_item($menu_name, $item_name) {
  122. global $CONFIG;
  123. if (empty($CONFIG->menus[$menu_name])) {
  124. return null;
  125. }
  126. foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) {
  127. /* @var \ElggMenuItem $menu_object */
  128. if ($menu_object instanceof ElggMenuItem && $menu_object->getName() == $item_name) {
  129. $item = $CONFIG->menus[$menu_name][$index];
  130. unset($CONFIG->menus[$menu_name][$index]);
  131. return $item;
  132. }
  133. }
  134. return null;
  135. }
  136. /**
  137. * Check if a menu item has been registered
  138. *
  139. * @param string $menu_name The name of the menu
  140. * @param string $item_name The unique identifier for this menu item
  141. *
  142. * @return bool
  143. * @since 1.8.0
  144. */
  145. function elgg_is_menu_item_registered($menu_name, $item_name) {
  146. global $CONFIG;
  147. if (!isset($CONFIG->menus[$menu_name])) {
  148. return false;
  149. }
  150. foreach ($CONFIG->menus[$menu_name] as $menu_object) {
  151. /* @var \ElggMenuItem $menu_object */
  152. if ($menu_object->getName() == $item_name) {
  153. return true;
  154. }
  155. }
  156. return false;
  157. }
  158. /**
  159. * Get a menu item registered for a menu
  160. *
  161. * @param string $menu_name The name of the menu
  162. * @param string $item_name The unique identifier for this menu item
  163. *
  164. * @return ElggMenuItem|null
  165. * @since 1.9.0
  166. */
  167. function elgg_get_menu_item($menu_name, $item_name) {
  168. global $CONFIG;
  169. if (!isset($CONFIG->menus[$menu_name])) {
  170. return null;
  171. }
  172. foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) {
  173. /* @var \ElggMenuItem $menu_object */
  174. if ($menu_object->getName() == $item_name) {
  175. return $CONFIG->menus[$menu_name][$index];
  176. }
  177. }
  178. return null;
  179. }
  180. /**
  181. * Convenience function for registering a button to the title menu
  182. *
  183. * The URL must be $handler/$name/$guid where $guid is the guid of the page owner.
  184. * The label of the button is "$handler:$name" so that must be defined in a
  185. * language file.
  186. *
  187. * This is used primarily to support adding an add content button
  188. *
  189. * @param string $handler The handler to use or null to autodetect from context
  190. * @param string $name Name of the button
  191. * @return void
  192. * @since 1.8.0
  193. */
  194. function elgg_register_title_button($handler = null, $name = 'add') {
  195. if (elgg_is_logged_in()) {
  196. if (!$handler) {
  197. $handler = elgg_get_context();
  198. }
  199. $owner = elgg_get_page_owner_entity();
  200. if (!$owner) {
  201. // no owns the page so this is probably an all site list page
  202. $owner = elgg_get_logged_in_user_entity();
  203. }
  204. if ($owner && $owner->canWriteToContainer()) {
  205. $guid = $owner->getGUID();
  206. elgg_register_menu_item('title', array(
  207. 'name' => $name,
  208. 'href' => "$handler/$name/$guid",
  209. 'text' => elgg_echo("$handler:$name"),
  210. 'link_class' => 'elgg-button elgg-button-action',
  211. ));
  212. }
  213. }
  214. }
  215. /**
  216. * Adds a breadcrumb to the breadcrumbs stack.
  217. *
  218. * See elgg_get_breadcrumbs() and the navigation/breadcrumbs view.
  219. *
  220. * @param string $title The title to display. During rendering this is HTML encoded.
  221. * @param string $link Optional. The link for the title. During rendering links are
  222. * normalized via elgg_normalize_url().
  223. *
  224. * @return void
  225. * @since 1.8.0
  226. * @see elgg_get_breadcrumbs
  227. */
  228. function elgg_push_breadcrumb($title, $link = null) {
  229. global $CONFIG;
  230. if (!isset($CONFIG->breadcrumbs)) {
  231. $CONFIG->breadcrumbs = array();
  232. }
  233. $CONFIG->breadcrumbs[] = array('title' => $title, 'link' => $link);
  234. }
  235. /**
  236. * Removes last breadcrumb entry.
  237. *
  238. * @return array popped breadcrumb array or empty array
  239. * @since 1.8.0
  240. */
  241. function elgg_pop_breadcrumb() {
  242. global $CONFIG;
  243. if (empty($CONFIG->breadcrumbs) || !is_array($CONFIG->breadcrumbs)) {
  244. return array();
  245. }
  246. return array_pop($CONFIG->breadcrumbs);
  247. }
  248. /**
  249. * Returns all breadcrumbs as an array of array('title' => 'Title', 'link' => 'URL')
  250. *
  251. * Since 1.11, breadcrumbs are filtered through the plugin hook [prepare, breadcrumbs] before
  252. * being returned.
  253. *
  254. * @return array Breadcrumbs
  255. * @since 1.8.0
  256. * @see elgg_prepare_breadcrumbs
  257. */
  258. function elgg_get_breadcrumbs() {
  259. global $CONFIG;
  260. // if no crumbs set, still allow hook to populate it
  261. if (isset($CONFIG->breadcrumbs) && is_array($CONFIG->breadcrumbs)) {
  262. $breadcrumbs = $CONFIG->breadcrumbs;
  263. } else {
  264. $breadcrumbs = array();
  265. }
  266. $params = array(
  267. 'breadcrumbs' => $breadcrumbs,
  268. );
  269. $breadcrumbs = elgg_trigger_plugin_hook('prepare', 'breadcrumbs', $params, $breadcrumbs);
  270. if (!is_array($breadcrumbs)) {
  271. return array();
  272. }
  273. return $breadcrumbs;
  274. }
  275. /**
  276. * Hook handler to turn titles into 100-character excerpts. To remove this behavior, unregister this
  277. * function from the [prepare, breadcrumbs] hook.
  278. *
  279. * @param string $hook "prepare"
  280. * @param string $type "breadcrumbs"
  281. * @param array $breadcrumbs Breadcrumbs to be altered
  282. * @param array $params Hook parameters
  283. *
  284. * @return array
  285. * @since 1.11
  286. */
  287. function elgg_prepare_breadcrumbs($hook, $type, $breadcrumbs, $params) {
  288. foreach (array_keys($breadcrumbs) as $i) {
  289. $breadcrumbs[$i]['title'] = elgg_get_excerpt($breadcrumbs[$i]['title'], 100);
  290. }
  291. return $breadcrumbs;
  292. }
  293. /**
  294. * Set up the site menu
  295. *
  296. * Handles default, featured, and custom menu items
  297. *
  298. * @access private
  299. */
  300. function _elgg_site_menu_setup($hook, $type, $return, $params) {
  301. $featured_menu_names = elgg_get_config('site_featured_menu_names');
  302. $custom_menu_items = elgg_get_config('site_custom_menu_items');
  303. if ($featured_menu_names || $custom_menu_items) {
  304. // we have featured or custom menu items
  305. $registered = $return['default'];
  306. /* @var \ElggMenuItem[] $registered */
  307. // set up featured menu items
  308. $featured = array();
  309. foreach ($featured_menu_names as $name) {
  310. foreach ($registered as $index => $item) {
  311. if ($item->getName() == $name) {
  312. $featured[] = $item;
  313. unset($registered[$index]);
  314. }
  315. }
  316. }
  317. // add custom menu items
  318. $n = 1;
  319. foreach ($custom_menu_items as $title => $url) {
  320. $item = new \ElggMenuItem("custom$n", $title, $url);
  321. $featured[] = $item;
  322. $n++;
  323. }
  324. $return['default'] = $featured;
  325. if (count($registered) > 0) {
  326. $return['more'] = $registered;
  327. }
  328. } else {
  329. // no featured menu items set
  330. $max_display_items = 5;
  331. // the first n are shown, rest added to more list
  332. // if only one item on more menu, stick it with the rest
  333. $num_menu_items = count($return['default']);
  334. if ($num_menu_items > ($max_display_items + 1)) {
  335. $return['more'] = array_splice($return['default'], $max_display_items);
  336. }
  337. }
  338. // check if we have anything selected
  339. $selected = false;
  340. foreach ($return as $section) {
  341. /* @var \ElggMenuItem[] $section */
  342. foreach ($section as $item) {
  343. if ($item->getSelected()) {
  344. $selected = true;
  345. break 2;
  346. }
  347. }
  348. }
  349. if (!$selected) {
  350. // nothing selected, match name to context or match url
  351. $current_url = current_page_url();
  352. foreach ($return as $section_name => $section) {
  353. foreach ($section as $key => $item) {
  354. // only highlight internal links
  355. if (strpos($item->getHref(), elgg_get_site_url()) === 0) {
  356. if ($item->getName() == elgg_get_context()) {
  357. $return[$section_name][$key]->setSelected(true);
  358. break 2;
  359. }
  360. if ($item->getHref() == $current_url) {
  361. $return[$section_name][$key]->setSelected(true);
  362. break 2;
  363. }
  364. }
  365. }
  366. }
  367. }
  368. return $return;
  369. }
  370. /**
  371. * Add the comment and like links to river actions menu
  372. * @access private
  373. */
  374. function _elgg_river_menu_setup($hook, $type, $return, $params) {
  375. if (elgg_is_logged_in()) {
  376. $item = $params['item'];
  377. /* @var \ElggRiverItem $item */
  378. $object = $item->getObjectEntity();
  379. // add comment link but annotations cannot be commented on
  380. if ($item->annotation_id == 0) {
  381. if ($object->canComment()) {
  382. $options = array(
  383. 'name' => 'comment',
  384. 'href' => "#comments-add-$object->guid",
  385. 'text' => elgg_view_icon('speech-bubble'),
  386. 'title' => elgg_echo('comment:this'),
  387. 'rel' => 'toggle',
  388. 'priority' => 50,
  389. );
  390. $return[] = \ElggMenuItem::factory($options);
  391. }
  392. }
  393. if (elgg_is_admin_logged_in()) {
  394. $options = array(
  395. 'name' => 'delete',
  396. 'href' => elgg_add_action_tokens_to_url("action/river/delete?id=$item->id"),
  397. 'text' => elgg_view_icon('delete'),
  398. 'title' => elgg_echo('river:delete'),
  399. 'confirm' => elgg_echo('deleteconfirm'),
  400. 'priority' => 200,
  401. );
  402. $return[] = \ElggMenuItem::factory($options);
  403. }
  404. }
  405. return $return;
  406. }
  407. /**
  408. * Entity menu is list of links and info on any entity
  409. * @access private
  410. */
  411. function _elgg_entity_menu_setup($hook, $type, $return, $params) {
  412. if (elgg_in_context('widgets')) {
  413. return $return;
  414. }
  415. $entity = $params['entity'];
  416. /* @var \ElggEntity $entity */
  417. $handler = elgg_extract('handler', $params, false);
  418. // access
  419. if (elgg_is_logged_in()) {
  420. $access = elgg_view('output/access', array('entity' => $entity));
  421. $options = array(
  422. 'name' => 'access',
  423. 'text' => $access,
  424. 'href' => false,
  425. 'priority' => 100,
  426. );
  427. $return[] = \ElggMenuItem::factory($options);
  428. }
  429. if ($entity->canEdit() && $handler) {
  430. // edit link
  431. $options = array(
  432. 'name' => 'edit',
  433. 'text' => elgg_echo('edit'),
  434. 'title' => elgg_echo('edit:this'),
  435. 'href' => "$handler/edit/{$entity->getGUID()}",
  436. 'priority' => 200,
  437. );
  438. $return[] = \ElggMenuItem::factory($options);
  439. // delete link
  440. $options = array(
  441. 'name' => 'delete',
  442. 'text' => elgg_view_icon('delete'),
  443. 'title' => elgg_echo('delete:this'),
  444. 'href' => "action/$handler/delete?guid={$entity->getGUID()}",
  445. 'confirm' => elgg_echo('deleteconfirm'),
  446. 'priority' => 300,
  447. );
  448. $return[] = \ElggMenuItem::factory($options);
  449. }
  450. return $return;
  451. }
  452. /**
  453. * Widget menu is a set of widget controls
  454. * @access private
  455. */
  456. function _elgg_widget_menu_setup($hook, $type, $return, $params) {
  457. $widget = $params['entity'];
  458. /* @var \ElggWidget $widget */
  459. $show_edit = elgg_extract('show_edit', $params, true);
  460. $collapse = array(
  461. 'name' => 'collapse',
  462. 'text' => ' ',
  463. 'href' => "#elgg-widget-content-$widget->guid",
  464. 'link_class' => 'elgg-widget-collapse-button',
  465. 'rel' => 'toggle',
  466. 'priority' => 1,
  467. );
  468. $return[] = \ElggMenuItem::factory($collapse);
  469. if ($widget->canEdit()) {
  470. $delete = array(
  471. 'name' => 'delete',
  472. 'text' => elgg_view_icon('delete-alt'),
  473. 'title' => elgg_echo('widget:delete', array($widget->getTitle())),
  474. 'href' => "action/widgets/delete?widget_guid=$widget->guid",
  475. 'is_action' => true,
  476. 'link_class' => 'elgg-widget-delete-button',
  477. 'id' => "elgg-widget-delete-button-$widget->guid",
  478. 'data-elgg-widget-type' => $widget->handler,
  479. 'priority' => 900,
  480. );
  481. $return[] = \ElggMenuItem::factory($delete);
  482. if ($show_edit) {
  483. $edit = array(
  484. 'name' => 'settings',
  485. 'text' => elgg_view_icon('settings-alt'),
  486. 'title' => elgg_echo('widget:edit'),
  487. 'href' => "#widget-edit-$widget->guid",
  488. 'link_class' => "elgg-widget-edit-button",
  489. 'rel' => 'toggle',
  490. 'priority' => 800,
  491. );
  492. $return[] = \ElggMenuItem::factory($edit);
  493. }
  494. }
  495. return $return;
  496. }
  497. /**
  498. * Add the register and forgot password links to login menu
  499. * @access private
  500. */
  501. function _elgg_login_menu_setup($hook, $type, $return, $params) {
  502. if (elgg_get_config('allow_registration')) {
  503. $return[] = \ElggMenuItem::factory(array(
  504. 'name' => 'register',
  505. 'href' => 'register',
  506. 'text' => elgg_echo('register'),
  507. 'link_class' => 'registration_link',
  508. ));
  509. }
  510. $return[] = \ElggMenuItem::factory(array(
  511. 'name' => 'forgotpassword',
  512. 'href' => 'forgotpassword',
  513. 'text' => elgg_echo('user:password:lost'),
  514. 'link_class' => 'forgot_link',
  515. ));
  516. return $return;
  517. }
  518. /**
  519. * Navigation initialization
  520. * @access private
  521. */
  522. function _elgg_nav_init() {
  523. elgg_register_plugin_hook_handler('prepare', 'breadcrumbs', 'elgg_prepare_breadcrumbs');
  524. elgg_register_plugin_hook_handler('prepare', 'menu:site', '_elgg_site_menu_setup');
  525. elgg_register_plugin_hook_handler('register', 'menu:river', '_elgg_river_menu_setup');
  526. elgg_register_plugin_hook_handler('register', 'menu:entity', '_elgg_entity_menu_setup');
  527. elgg_register_plugin_hook_handler('register', 'menu:widget', '_elgg_widget_menu_setup');
  528. elgg_register_plugin_hook_handler('register', 'menu:login', '_elgg_login_menu_setup');
  529. elgg_register_plugin_hook_handler('public_pages', 'walled_garden', '_elgg_nav_public_pages');
  530. elgg_register_menu_item('footer', \ElggMenuItem::factory(array(
  531. 'name' => 'powered',
  532. 'text' => elgg_echo("elgg:powered"),
  533. 'href' => 'http://elgg.org',
  534. 'title' => 'Elgg ' . elgg_get_version(true),
  535. 'section' => 'meta',
  536. )));
  537. elgg_register_ajax_view('navigation/menu/user_hover/contents');
  538. }
  539. /**
  540. * Extend public pages
  541. *
  542. * @param string $hook_name "public_pages"
  543. * @param string $entity_type "walled_garden"
  544. * @param string[] $return_value Paths accessible outside the "walled garden"
  545. * @param mixed $params unused
  546. *
  547. * @return string[]
  548. * @access private
  549. * @since 1.11.0
  550. */
  551. function _elgg_nav_public_pages($hook_name, $entity_type, $return_value, $params) {
  552. if (is_array($return_value)) {
  553. $return_value[] = 'navigation/menu/user_hover/contents';
  554. }
  555. return $return_value;
  556. }
  557. return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
  558. $events->registerHandler('init', 'system', '_elgg_nav_init');
  559. };