menu_block.module 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. <?php
  2. /**
  3. * @file
  4. * Provides configurable blocks of menu items.
  5. */
  6. /**
  7. * Denotes that the tree should use the menu picked by the curent page.
  8. */
  9. define('MENU_TREE__CURRENT_PAGE_MENU', '_active');
  10. // Off-load the following infrequently called hooks to another file.
  11. function menu_block_theme(&$existing, $type, $theme, $path) {
  12. module_load_include('inc', 'menu_block', 'menu_block.admin');
  13. return _menu_block_theme($existing, $type, $theme, $path);
  14. }
  15. function menu_block_block_info() {
  16. module_load_include('inc', 'menu_block', 'menu_block.admin');
  17. return _menu_block_block_info();
  18. }
  19. function menu_block_block_configure($delta = '') {
  20. module_load_include('inc', 'menu_block', 'menu_block.admin');
  21. return _menu_block_block_configure($delta);
  22. }
  23. function menu_block_block_save($delta = '', $edit = array()) {
  24. module_load_include('inc', 'menu_block', 'menu_block.admin');
  25. return _menu_block_block_save($delta, $edit);
  26. }
  27. function menu_block_form_block_admin_display_form_alter(&$form, $form_state) {
  28. module_load_include('inc', 'menu_block', 'menu_block.admin');
  29. return _menu_block_form_block_admin_display_form_alter($form, $form_state);
  30. }
  31. function menu_block_ctools_plugin_directory($module, $plugin) {
  32. module_load_include('inc', 'menu_block', 'menu_block.admin');
  33. return _menu_block_ctools_plugin_directory($module, $plugin);
  34. }
  35. /**
  36. * Implements hook_menu().
  37. */
  38. function menu_block_menu() {
  39. // @todo Remove this check if block module is re-added as a dependency.
  40. if (module_exists('block')) {
  41. $items['admin/structure/block/add-menu-block'] = array(
  42. 'title' => 'Add menu block',
  43. 'description' => 'Add a new menu block.',
  44. 'page callback' => 'drupal_get_form',
  45. 'page arguments' => array('menu_block_add_block_form'),
  46. 'access arguments' => array('administer blocks'),
  47. 'type' => MENU_LOCAL_ACTION,
  48. 'file' => 'menu_block.admin.inc',
  49. );
  50. $default_theme = variable_get('theme_default', 'bartik');
  51. foreach (list_themes() as $key => $theme) {
  52. if ($key != $default_theme) {
  53. $items['admin/structure/block/list/' . $key . '/add-menu-block'] = array(
  54. 'title' => 'Add menu block',
  55. 'description' => 'Add a new menu block.',
  56. 'page callback' => 'drupal_get_form',
  57. 'page arguments' => array('menu_block_add_block_form'),
  58. 'access arguments' => array('administer blocks'),
  59. 'type' => MENU_LOCAL_ACTION,
  60. 'file' => 'menu_block.admin.inc',
  61. );
  62. }
  63. }
  64. $items['admin/structure/block/delete-menu-block'] = array(
  65. 'title' => 'Delete menu block',
  66. 'page callback' => 'drupal_get_form',
  67. 'page arguments' => array('menu_block_delete'),
  68. 'access arguments' => array('administer blocks'),
  69. 'type' => MENU_CALLBACK,
  70. 'file' => 'menu_block.admin.inc',
  71. );
  72. }
  73. $items['admin/config/user-interface/menu-block'] = array(
  74. 'title' => 'Menu block',
  75. 'description' => 'Configure menu block.',
  76. 'page callback' => 'drupal_get_form',
  77. 'page arguments' => array('menu_block_admin_settings_form'),
  78. 'access arguments' => array('administer blocks'),
  79. 'type' => MENU_NORMAL_ITEM,
  80. 'file' => 'menu_block.admin.inc',
  81. );
  82. return $items;
  83. }
  84. /**
  85. * Implements hook_menu_alter().
  86. */
  87. function menu_block_menu_alter(&$items) {
  88. // Fake the necessary menu attributes necessary for a contextual link.
  89. $items['admin/content/book/%node']['title'] = 'Edit book outline';
  90. $items['admin/content/book/%node']['type'] = MENU_LOCAL_TASK;
  91. $items['admin/content/book/%node']['context'] = (MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE);
  92. $items['admin/content/book/%node']['tab_root'] = 'admin/content/book';
  93. }
  94. /**
  95. * Implements hook_help().
  96. */
  97. function menu_block_help($path, $arg) {
  98. switch ($path) {
  99. case 'admin/structure/block/manage/%/%':
  100. if ($arg[4] != 'menu_block') {
  101. break;
  102. }
  103. case 'admin/help#menu_block':
  104. case 'admin/structure/block/add-menu-block':
  105. module_load_include('inc', 'menu_block', 'menu_block.pages');
  106. return _menu_block_help($path, $arg);
  107. }
  108. }
  109. /**
  110. * Implements hook_block_view().
  111. */
  112. function menu_block_block_view($delta = '') {
  113. $config = menu_block_get_config($delta);
  114. $data = menu_tree_build($config);
  115. // Add contextual links for this block.
  116. if (!empty($data['content'])) {
  117. if (in_array($config['menu_name'], array_keys(menu_get_menus()))) {
  118. $data['content']['#contextual_links']['menu_block'] = array('admin/structure/menu/manage', array($config['menu_name']));
  119. }
  120. elseif (strpos($config['menu_name'], 'book-toc-') === 0) {
  121. $node = str_replace('book-toc-', '', $config['menu_name']);
  122. $data['content']['#contextual_links']['menu_block'] = array('admin/content/book', array($node));
  123. }
  124. }
  125. return $data;
  126. }
  127. /**
  128. * Process variables for menu-block-wrapper.tpl.php.
  129. *
  130. * @see menu-block-wrapper.tpl.php
  131. */
  132. function template_preprocess_menu_block_wrapper(&$variables) {
  133. $variables['classes_array'][] = 'menu-block-' . $variables['delta'];
  134. $variables['classes_array'][] = 'menu-name-' . $variables['config']['menu_name'];
  135. $variables['classes_array'][] = 'parent-mlid-' . $variables['config']['parent_mlid'];
  136. $variables['classes_array'][] = 'menu-level-' . $variables['config']['level'];
  137. }
  138. /**
  139. * Returns a list of menu names implemented by all modules.
  140. *
  141. * @return
  142. * array A list of menu names and titles.
  143. */
  144. function menu_block_get_all_menus() {
  145. $all_menus = &drupal_static(__FUNCTION__);
  146. if (!$all_menus) {
  147. if ($cached = cache_get('menu_block_menus', 'cache_menu')) {
  148. $all_menus = $cached->data;
  149. }
  150. else {
  151. // Retrieve core's menus.
  152. $all_menus = menu_get_menus();
  153. // Retrieve all the menu names provided by hook_menu_block_get_menus().
  154. $all_menus = array_merge($all_menus, module_invoke_all('menu_block_get_menus'));
  155. // Add an option to use the menu for the active menu item.
  156. $all_menus[MENU_TREE__CURRENT_PAGE_MENU] = '<' . t('the menu selected by the page') . '>';
  157. asort($all_menus);
  158. cache_set('menu_block_menus', $all_menus, 'cache_menu');
  159. }
  160. }
  161. return $all_menus;
  162. }
  163. /**
  164. * Returns the configuration for the requested block delta.
  165. *
  166. * @param $delta
  167. * string The delta that uniquely identifies the block in the block system. If
  168. * not specified, the default configuration will be returned.
  169. * @return
  170. * array An associated array of configuration options.
  171. */
  172. function menu_block_get_config($delta = NULL) {
  173. $config = array(
  174. 'delta' => $delta,
  175. 'menu_name' => 'main-menu',
  176. 'parent_mlid' => 0,
  177. 'title_link' => 0,
  178. 'admin_title' => '',
  179. 'level' => 1,
  180. 'follow' => 0,
  181. 'depth' => 0,
  182. 'expanded' => 0,
  183. 'sort' => 0,
  184. );
  185. // Get the block configuration options.
  186. if ($delta) {
  187. static $blocks;
  188. if (!isset($blocks)) {
  189. $blocks = module_invoke_all('menu_block_blocks');
  190. }
  191. if (!empty($blocks[$delta])) {
  192. // Merge the default values.
  193. $config = $blocks[$delta] + $config;
  194. // Set the delta.
  195. $config['delta'] = $delta;
  196. // Flag the block as exported.
  197. $config['exported_to_code'] = TRUE;
  198. }
  199. $config['title_link'] = variable_get("menu_block_{$delta}_title_link", $config['title_link']);
  200. $config['admin_title'] = variable_get("menu_block_{$delta}_admin_title", $config['admin_title']);
  201. $config['level'] = variable_get("menu_block_{$delta}_level", $config['level']);
  202. $config['follow'] = variable_get("menu_block_{$delta}_follow", $config['follow']);
  203. $config['depth'] = variable_get("menu_block_{$delta}_depth", $config['depth']);
  204. $config['expanded'] = variable_get("menu_block_{$delta}_expanded", $config['expanded']);
  205. $config['sort'] = variable_get("menu_block_{$delta}_sort", $config['sort']);
  206. list($config['menu_name'], $config['parent_mlid']) = explode(':', variable_get("menu_block_{$delta}_parent", $config['menu_name'] . ':' . $config['parent_mlid']));
  207. }
  208. return $config;
  209. }
  210. /**
  211. * Build a menu tree based on the provided configuration.
  212. *
  213. * @param $config
  214. * array An array of configuration options that specifies how to build the
  215. * menu tree and its title.
  216. * - delta: (string) The menu_block's block delta.
  217. * - menu_name: (string) The machine name of the requested menu. Can also be
  218. * set to MENU_TREE__CURRENT_PAGE_MENU to use the menu selected by the page.
  219. * - parent_mlid: (int) The mlid of the item that should root the tree. Use 0
  220. * to use the menu's root.
  221. * - title_link: (boolean) Specifies if the title should be rendered as a link
  222. * or a simple string.
  223. * - admin_title: (string) An optional title to uniquely identify the block on
  224. * the administer blocks page.
  225. * - level: (int) The starting level of the tree.
  226. * - follow: (string) Specifies if the starting level should follow the
  227. * active menu item. Should be set to 0, 'active' or 'child'.
  228. * - depth: (int) The maximum depth the tree should contain, relative to the
  229. * starting level.
  230. * - expanded: (boolean) Specifies if the entire tree be expanded or not.
  231. * - sort: (boolean) Specifies if the tree should be sorted with the active
  232. * trail at the top of the tree.
  233. * @return
  234. * array An associative array containing several pieces of data.
  235. * - content: The tree as a renderable array.
  236. * - subject: The title rendered as HTML.
  237. * - subject_array: The title as a renderable array.
  238. */
  239. function menu_tree_build($config) {
  240. // Retrieve the active menu item from the database.
  241. if ($config['menu_name'] == MENU_TREE__CURRENT_PAGE_MENU) {
  242. // Retrieve the list of available menus.
  243. $menu_order = variable_get('menu_block_menu_order', array('main-menu' => '', 'user-menu' => ''));
  244. // Check for regular expressions as menu keys.
  245. $patterns = array();
  246. foreach (array_keys($menu_order) as $pattern) {
  247. if ($pattern[0] == '/') {
  248. $patterns[$pattern] = NULL;
  249. }
  250. }
  251. // Extract the "current" path from the request, or from the active menu
  252. // trail if applicable.
  253. $link_path = $_GET['q'] ? $_GET['q'] : '<front>';
  254. $trail = menu_get_active_trail();
  255. $last_item = end($trail);
  256. if (!empty($last_item['link_path'])) {
  257. $link_path = $last_item['link_path'];
  258. }
  259. // Retrieve all the menus containing a link to the current page.
  260. $result = db_query("SELECT menu_name FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path));
  261. foreach ($result as $item) {
  262. // Check if the menu is in the list of available menus.
  263. if (isset($menu_order[$item->menu_name])) {
  264. // Mark the menu.
  265. $menu_order[$item->menu_name] = MENU_TREE__CURRENT_PAGE_MENU;
  266. }
  267. else {
  268. // Check if the menu matches one of the available patterns.
  269. foreach (array_keys($patterns) as $pattern) {
  270. if (preg_match($pattern, $item->menu_name)) {
  271. // Mark the menu.
  272. $menu_order[$pattern] = MENU_TREE__CURRENT_PAGE_MENU;
  273. // Store the actual menu name.
  274. $patterns[$pattern] = $item->menu_name;
  275. }
  276. }
  277. }
  278. }
  279. // Find the first marked menu.
  280. $config['menu_name'] = array_search(MENU_TREE__CURRENT_PAGE_MENU, $menu_order);
  281. // If a pattern was matched, use the actual menu name instead of the pattern.
  282. if (!empty($patterns[$config['menu_name']])) {
  283. $config['menu_name'] = $patterns[$config['menu_name']];
  284. }
  285. $config['parent_mlid'] = 0;
  286. // If no menu link was found, don't display the block.
  287. if (empty($config['menu_name'])) {
  288. return array(
  289. 'subject' => t('The menu selected by the page'),
  290. 'subject_array' => array(),
  291. 'content' => array(),
  292. );
  293. }
  294. }
  295. // Get the default block name.
  296. $menu_names = menu_block_get_all_menus();
  297. menu_block_set_title(t($menu_names[$config['menu_name']]));
  298. if ($config['expanded'] || $config['parent_mlid']) {
  299. // Get the full, un-pruned tree.
  300. $tree = menu_tree_all_data($config['menu_name']);
  301. // And add the active trail data back to the full tree.
  302. menu_tree_add_active_path($tree);
  303. }
  304. else {
  305. // Get the tree pruned for just the active trail.
  306. $tree = menu_tree_page_data($config['menu_name']);
  307. }
  308. // Allow alteration of the tree and config before we begin operations on it.
  309. drupal_alter('menu_block_tree', $tree, $config);
  310. // Localize the tree.
  311. if (module_exists('i18n_menu')) {
  312. $tree = i18n_menu_localize_tree($tree);
  313. }
  314. // Prune the tree along the active trail to the specified level.
  315. if ($config['level'] > 1 || $config['parent_mlid']) {
  316. if ($config['parent_mlid']) {
  317. $parent_item = menu_link_load($config['parent_mlid']);
  318. menu_tree_prune_tree($tree, $config['level'], $parent_item);
  319. }
  320. else {
  321. menu_tree_prune_tree($tree, $config['level']);
  322. }
  323. }
  324. // Prune the tree to the active menu item.
  325. if ($config['follow']) {
  326. menu_tree_prune_active_tree($tree, $config['follow']);
  327. }
  328. // If the menu-item-based tree is not "expanded", trim the tree to the active path.
  329. if ($config['parent_mlid'] && !$config['expanded']) {
  330. menu_tree_trim_active_path($tree);
  331. }
  332. // Trim the branches that extend beyond the specified depth.
  333. if ($config['depth'] > 0) {
  334. menu_tree_depth_trim($tree, $config['depth']);
  335. }
  336. // Sort the active path to the top of the tree.
  337. if ($config['sort']) {
  338. menu_tree_sort_active_path($tree);
  339. }
  340. // Render the tree.
  341. $data = array();
  342. $title = menu_block_get_title($config['title_link'], $config);
  343. $data['subject_array'] = $title;
  344. $data['subject'] = drupal_render($title);
  345. $data['content']['#content'] = menu_block_tree_output($tree, $config);
  346. if (!empty($data['content']['#content'])) {
  347. $data['content']['#theme'] = array(
  348. 'menu_block_wrapper__' . str_replace('-', '_', $config['delta']),
  349. 'menu_block_wrapper__' . str_replace('-', '_', $config['menu_name']),
  350. 'menu_block_wrapper'
  351. );
  352. $data['content']['#config'] = $config;
  353. $data['content']['#delta'] = $config['delta'];
  354. }
  355. else {
  356. $data['content'] = '';
  357. }
  358. return $data;
  359. }
  360. /**
  361. * Retrieves the menu item to use for the tree's title.
  362. *
  363. * @param $render_title_as_link
  364. * boolean A boolean that says whether to render the title as a link or a
  365. * simple string.
  366. * @return
  367. * array A renderable array containing the tree's title.
  368. */
  369. function menu_block_get_title($render_title_as_link = TRUE) {
  370. $menu_item = menu_block_set_title();
  371. // The tree's title is a menu title, a normal string.
  372. if (is_string($menu_item)) {
  373. $title = array('#markup' => check_plain($menu_item));
  374. }
  375. // The tree's title is a menu item with a link.
  376. elseif ($render_title_as_link) {
  377. if (!empty($menu_item['in_active_trail'])) {
  378. if (!empty($menu_item['localized_options']['attributes']['class'])) {
  379. $menu_item['localized_options']['attributes']['class'][] = 'active-trail';
  380. }
  381. else {
  382. $menu_item['localized_options']['attributes']['class'][] = 'active-trail';
  383. }
  384. }
  385. $title = array(
  386. '#type' => 'link',
  387. '#title' => $menu_item['title'],
  388. '#href' => $menu_item['href'],
  389. '#options' => $menu_item['localized_options'],
  390. );
  391. }
  392. // The tree's title is a menu item.
  393. else {
  394. $title = array('#markup' => check_plain($menu_item['title']));
  395. }
  396. return $title;
  397. }
  398. /**
  399. * Sets the menu item to use for the tree's title.
  400. *
  401. * @param $item
  402. * array The menu item (an array) or the menu item's title as a string.
  403. */
  404. function menu_block_set_title($item = NULL) {
  405. $menu_item = &drupal_static(__FUNCTION__);
  406. // Save the menu item.
  407. if (!is_null($item)) {
  408. $menu_item = $item;
  409. }
  410. return $menu_item;
  411. }
  412. /**
  413. * Add the active trail indicators into the tree.
  414. *
  415. * The data returned by menu_tree_page_data() has link['in_active_trail'] set to
  416. * TRUE for each menu item in the active trail. The data returned from
  417. * menu_tree_all_data() does not contain the active trail indicators. This is a
  418. * helper function that adds it back in.
  419. *
  420. * @param $tree
  421. * array The menu tree.
  422. * @return
  423. * void
  424. */
  425. function menu_tree_add_active_path(&$tree) {
  426. // Grab any menu item to find the menu_name for this tree.
  427. $menu_item = current($tree);
  428. $tree_with_trail = menu_tree_page_data($menu_item['link']['menu_name']);
  429. // To traverse the original tree down the active trail, we use a pointer.
  430. $subtree_pointer =& $tree;
  431. // Find each key in the active trail.
  432. while ($tree_with_trail) {
  433. foreach ($tree_with_trail AS $key => &$value) {
  434. if ($tree_with_trail[$key]['link']['in_active_trail']) {
  435. // Set the active trail info in the original tree.
  436. $subtree_pointer[$key]['link']['in_active_trail'] = TRUE;
  437. // Continue in the subtree, if it exists.
  438. $tree_with_trail =& $tree_with_trail[$key]['below'];
  439. $subtree_pointer =& $subtree_pointer[$key]['below'];
  440. break;
  441. }
  442. else {
  443. unset($tree_with_trail[$key]);
  444. }
  445. }
  446. }
  447. }
  448. /**
  449. * Trim everything but the active trail in the tree.
  450. *
  451. * @param $tree
  452. * array The menu tree to trim.
  453. * @return
  454. * void
  455. */
  456. function menu_tree_trim_active_path(&$tree) {
  457. foreach ($tree AS $key => &$value) {
  458. if (($tree[$key]['link']['in_active_trail'] || $tree[$key]['link']['expanded']) && $tree[$key]['below']) {
  459. // Continue in the subtree, if it exists.
  460. menu_tree_trim_active_path($tree[$key]['below']);
  461. }
  462. else {
  463. // Trim anything not expanded or along the active trail.
  464. $tree[$key]['below'] = FALSE;
  465. }
  466. }
  467. }
  468. /**
  469. * Sort the active trail to the top of the tree.
  470. *
  471. * @param $tree
  472. * array The menu tree to sort.
  473. * @return
  474. * void
  475. */
  476. function menu_tree_sort_active_path(&$tree) {
  477. module_load_include('inc', 'menu_block', 'menu_block.sort');
  478. _menu_tree_sort_active_path($tree);
  479. }
  480. /**
  481. * Prune a tree so that it begins at the specified level.
  482. *
  483. * This function will follow the active menu trail to the specified level.
  484. *
  485. * @param $tree
  486. * array The menu tree to prune.
  487. * @param $level
  488. * int The level of the original tree that will start the pruned tree.
  489. * @param $parent_item
  490. * array The menu item that should be used as the root of the tree.
  491. * @return
  492. * void
  493. */
  494. function menu_tree_prune_tree(&$tree, $level, $parent_item = FALSE) {
  495. if (!empty($parent_item)) {
  496. // Prune the tree along the path to the menu item.
  497. for ($i = 1; $i <= MENU_MAX_DEPTH && $parent_item["p$i"] != '0'; $i++) {
  498. $plid = $parent_item["p$i"];
  499. $found_active_trail = FALSE;
  500. // Examine each element at this level for the ancestor.
  501. foreach ($tree AS $key => &$value) {
  502. if ($tree[$key]['link']['mlid'] == $plid) {
  503. menu_block_set_title($tree[$key]['link']);
  504. // Prune the tree to the children of this ancestor.
  505. $tree = $tree[$key]['below'] ? $tree[$key]['below'] : array();
  506. $found_active_trail = TRUE;
  507. break;
  508. }
  509. }
  510. // If we don't find the ancestor, bail out.
  511. if (!$found_active_trail) {
  512. $tree = array();
  513. break;
  514. }
  515. }
  516. }
  517. // Trim the upper levels down to the one desired.
  518. for ($i = 1; $i < $level; $i++) {
  519. $found_active_trail = FALSE;
  520. // Examine each element at this level for the active trail.
  521. foreach ($tree AS $key => &$value) {
  522. if ($tree[$key]['link']['in_active_trail']) {
  523. // Get the title for the pruned tree.
  524. menu_block_set_title($tree[$key]['link']);
  525. // Prune the tree to the children of the item in the active trail.
  526. $tree = $tree[$key]['below'] ? $tree[$key]['below'] : array();
  527. $found_active_trail = TRUE;
  528. break;
  529. }
  530. }
  531. // If we don't find the active trail, the active item isn't in the tree we want.
  532. if (!$found_active_trail) {
  533. $tree = array();
  534. break;
  535. }
  536. }
  537. }
  538. /**
  539. * Prune a tree so that it begins at the active menu item.
  540. *
  541. * @param $tree
  542. * array The menu tree to prune.
  543. * @param $level
  544. * string The level which the tree will be pruned to: 'active' or 'child'.
  545. * @return
  546. * void
  547. */
  548. function menu_tree_prune_active_tree(&$tree, $level) {
  549. module_load_include('inc', 'menu_block', 'menu_block.follow');
  550. _menu_tree_prune_active_tree($tree, $level);
  551. }
  552. /**
  553. * Prune a tree so it does not extend beyond the specified depth limit.
  554. *
  555. * @param $tree
  556. * array The menu tree to prune.
  557. * @param $depth_limit
  558. * int The maximum depth of the returned tree; must be a positive integer.
  559. * @return
  560. * void
  561. */
  562. function menu_tree_depth_trim(&$tree, $depth_limit) {
  563. // Prevent invalid input from returning a trimmed tree.
  564. if ($depth_limit < 1) {
  565. return;
  566. }
  567. // Examine each element at this level to find any possible children.
  568. foreach ($tree AS $key => &$value) {
  569. if ($tree[$key]['below']) {
  570. if ($depth_limit > 1) {
  571. menu_tree_depth_trim($tree[$key]['below'], $depth_limit-1);
  572. }
  573. else {
  574. // Remove the children items.
  575. $tree[$key]['below'] = FALSE;
  576. }
  577. }
  578. if ($depth_limit == 1 && $tree[$key]['link']['has_children']) {
  579. // Turn off the menu styling that shows there were children.
  580. $tree[$key]['link']['has_children'] = FALSE;
  581. $tree[$key]['link']['leaf_has_children'] = TRUE;
  582. }
  583. }
  584. }
  585. /**
  586. * Returns a rendered menu tree.
  587. *
  588. * This is a copy of menu_tree_output() with additional classes added to the
  589. * output.
  590. *
  591. * @param $tree
  592. * array A data structure representing the tree as returned from menu_tree_data.
  593. * @return
  594. * string The rendered HTML of that data structure.
  595. */
  596. function menu_block_tree_output(&$tree, $config = array()) {
  597. $build = array();
  598. $items = array();
  599. // Create context if no config was provided.
  600. if (empty($config)) {
  601. $config['delta'] = 0;
  602. // Grab any menu item to find the menu_name for this tree.
  603. $menu_item = current($tree);
  604. $config['menu_name'] = $menu_item['link']['menu_name'];
  605. }
  606. $hook_delta = str_replace('-', '_', $config['delta']);
  607. $hook_menu_name = str_replace('-', '_', $config['menu_name']);
  608. // Pull out just the menu items we are going to render so that we
  609. // get an accurate count for the first/last classes.
  610. foreach ($tree as $key => &$value) {
  611. if (!$tree[$key]['link']['hidden']) {
  612. $items[] = $tree[$key];
  613. }
  614. }
  615. $num_items = count($items);
  616. foreach ($items as $i => &$data) {
  617. $class = array();
  618. if ($i == 0) {
  619. $class[] = 'first';
  620. }
  621. if ($i == $num_items - 1) {
  622. $class[] = 'last';
  623. }
  624. // Set a class if the link has children.
  625. if ($data['below']) {
  626. $class[] = 'expanded';
  627. }
  628. elseif ($data['link']['has_children']) {
  629. $class[] = 'collapsed';
  630. }
  631. else {
  632. $class[] = 'leaf';
  633. }
  634. if (!empty($data['link']['leaf_has_children'])) {
  635. $class[] = 'has-children';
  636. }
  637. // Set a class if the link is in the active trail.
  638. if ($data['link']['in_active_trail']) {
  639. $class[] = 'active-trail';
  640. $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
  641. }
  642. if ($data['link']['href'] == $_GET['q'] || ($data['link']['href'] == '<front>' && drupal_is_front_page())) {
  643. $class[] = 'active';
  644. }
  645. // Set a menu link ID class.
  646. $class[] = 'menu-mlid-' . $data['link']['mlid'];
  647. // Allow menu-specific theme overrides.
  648. $element['#theme'] = array(
  649. 'menu_link__menu_block__' . $hook_delta,
  650. 'menu_link__menu_block__' . $hook_menu_name,
  651. 'menu_link__menu_block',
  652. 'menu_link__' . $hook_menu_name,
  653. 'menu_link',
  654. );
  655. $element['#attributes']['class'] = $class;
  656. $element['#title'] = $data['link']['title'];
  657. $element['#href'] = $data['link']['href'];
  658. $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
  659. $element['#below'] = $data['below'] ? menu_block_tree_output($data['below'], $config) : $data['below'];
  660. $element['#original_link'] = $data['link'];
  661. $element['#bid'] = array('module' => 'menu_block', 'delta' => $config['delta']);
  662. // Index using the link's unique mlid.
  663. $build[$data['link']['mlid']] = $element;
  664. }
  665. if ($build) {
  666. // Make sure drupal_render() does not re-order the links.
  667. $build['#sorted'] = TRUE;
  668. // Add the theme wrapper for outer markup.
  669. // Allow menu-specific theme overrides.
  670. $build['#theme_wrappers'][] = array(
  671. 'menu_tree__menu_block__' . $hook_delta,
  672. 'menu_tree__menu_block__' . $hook_menu_name,
  673. 'menu_tree__menu_block',
  674. 'menu_tree__' . $hook_menu_name,
  675. 'menu_tree',
  676. );
  677. }
  678. return $build;
  679. }
  680. /**
  681. * Implements hook_menu_block_get_menus() on behalf of book.module.
  682. */
  683. function book_menu_block_get_menus() {
  684. $menus = array();
  685. foreach (book_get_books() AS $book) {
  686. $menus[$book['menu_name']] = $book['title'];
  687. }
  688. return $menus;
  689. }
  690. /**
  691. * Implements hook_menu_block_get_sort_menus() on behalf of book.module.
  692. */
  693. function book_menu_block_get_sort_menus() {
  694. return array(
  695. '/^book\-toc\-.+/' => t('Book navigation'),
  696. );
  697. }