menu_block.module 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  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. // Retrieve all the menus containing a link to the current page.
  252. $result = db_query("SELECT menu_name FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $_GET['q'] ? $_GET['q'] : '<front>'));
  253. foreach ($result as $item) {
  254. // Check if the menu is in the list of available menus.
  255. if (isset($menu_order[$item->menu_name])) {
  256. // Mark the menu.
  257. $menu_order[$item->menu_name] = MENU_TREE__CURRENT_PAGE_MENU;
  258. }
  259. else {
  260. // Check if the menu matches one of the available patterns.
  261. foreach (array_keys($patterns) as $pattern) {
  262. if (preg_match($pattern, $item->menu_name)) {
  263. // Mark the menu.
  264. $menu_order[$pattern] = MENU_TREE__CURRENT_PAGE_MENU;
  265. // Store the actual menu name.
  266. $patterns[$pattern] = $item->menu_name;
  267. }
  268. }
  269. }
  270. }
  271. // Find the first marked menu.
  272. $config['menu_name'] = array_search(MENU_TREE__CURRENT_PAGE_MENU, $menu_order);
  273. // If a pattern was matched, use the actual menu name instead of the pattern.
  274. if (!empty($patterns[$config['menu_name']])) {
  275. $config['menu_name'] = $patterns[$config['menu_name']];
  276. }
  277. $config['parent_mlid'] = 0;
  278. // If no menu link was found, don't display the block.
  279. if (empty($config['menu_name'])) {
  280. return array();
  281. }
  282. }
  283. // Get the default block name.
  284. $menu_names = menu_block_get_all_menus();
  285. menu_block_set_title(t($menu_names[$config['menu_name']]));
  286. if ($config['expanded'] || $config['parent_mlid']) {
  287. // Get the full, un-pruned tree.
  288. $tree = menu_tree_all_data($config['menu_name']);
  289. // And add the active trail data back to the full tree.
  290. menu_tree_add_active_path($tree);
  291. }
  292. else {
  293. // Get the tree pruned for just the active trail.
  294. $tree = menu_tree_page_data($config['menu_name']);
  295. }
  296. // Allow alteration of the tree and config before we begin operations on it.
  297. drupal_alter('menu_block_tree', $tree, $config);
  298. // Localize the tree.
  299. if (module_exists('i18n_menu')) {
  300. $tree = i18n_menu_localize_tree($tree);
  301. }
  302. // Prune the tree along the active trail to the specified level.
  303. if ($config['level'] > 1 || $config['parent_mlid']) {
  304. if ($config['parent_mlid']) {
  305. $parent_item = menu_link_load($config['parent_mlid']);
  306. menu_tree_prune_tree($tree, $config['level'], $parent_item);
  307. }
  308. else {
  309. menu_tree_prune_tree($tree, $config['level']);
  310. }
  311. }
  312. // Prune the tree to the active menu item.
  313. if ($config['follow']) {
  314. menu_tree_prune_active_tree($tree, $config['follow']);
  315. }
  316. // If the menu-item-based tree is not "expanded", trim the tree to the active path.
  317. if ($config['parent_mlid'] && !$config['expanded']) {
  318. menu_tree_trim_active_path($tree);
  319. }
  320. // Trim the branches that extend beyond the specified depth.
  321. if ($config['depth'] > 0) {
  322. menu_tree_depth_trim($tree, $config['depth']);
  323. }
  324. // Sort the active path to the top of the tree.
  325. if ($config['sort']) {
  326. menu_tree_sort_active_path($tree);
  327. }
  328. // Render the tree.
  329. $data = array();
  330. $title = menu_block_get_title($config['title_link'], $config);
  331. $data['subject_array'] = $title;
  332. $data['subject'] = drupal_render($title);
  333. $data['content']['#content'] = menu_block_tree_output($tree, $config);
  334. if (!empty($data['content']['#content'])) {
  335. $data['content']['#theme'] = array(
  336. 'menu_block_wrapper__' . str_replace('-', '_', $config['delta']),
  337. 'menu_block_wrapper__' . str_replace('-', '_', $config['menu_name']),
  338. 'menu_block_wrapper'
  339. );
  340. $data['content']['#config'] = $config;
  341. $data['content']['#delta'] = $config['delta'];
  342. }
  343. else {
  344. $data['content'] = '';
  345. }
  346. return $data;
  347. }
  348. /**
  349. * Retrieves the menu item to use for the tree's title.
  350. *
  351. * @param $render_title_as_link
  352. * boolean A boolean that says whether to render the title as a link or a
  353. * simple string.
  354. * @return
  355. * array A renderable array containing the tree's title.
  356. */
  357. function menu_block_get_title($render_title_as_link = TRUE) {
  358. $menu_item = menu_block_set_title();
  359. // The tree's title is a menu title, a normal string.
  360. if (is_string($menu_item)) {
  361. $title = array('#markup' => check_plain($menu_item));
  362. }
  363. // The tree's title is a menu item with a link.
  364. elseif ($render_title_as_link) {
  365. if (!empty($menu_item['in_active_trail'])) {
  366. if (!empty($menu_item['localized_options']['attributes']['class'])) {
  367. $menu_item['localized_options']['attributes']['class'][] = 'active-trail';
  368. }
  369. else {
  370. $menu_item['localized_options']['attributes']['class'][] = 'active-trail';
  371. }
  372. }
  373. $title = array(
  374. '#type' => 'link',
  375. '#title' => $menu_item['title'],
  376. '#href' => $menu_item['href'],
  377. '#options' => $menu_item['localized_options'],
  378. );
  379. }
  380. // The tree's title is a menu item.
  381. else {
  382. $title = array('#markup' => check_plain($menu_item['title']));
  383. }
  384. return $title;
  385. }
  386. /**
  387. * Sets the menu item to use for the tree's title.
  388. *
  389. * @param $item
  390. * array The menu item (an array) or the menu item's title as a string.
  391. */
  392. function menu_block_set_title($item = NULL) {
  393. $menu_item = &drupal_static(__FUNCTION__);
  394. // Save the menu item.
  395. if (!is_null($item)) {
  396. $menu_item = $item;
  397. }
  398. return $menu_item;
  399. }
  400. /**
  401. * Add the active trail indicators into the tree.
  402. *
  403. * The data returned by menu_tree_page_data() has link['in_active_trail'] set to
  404. * TRUE for each menu item in the active trail. The data returned from
  405. * menu_tree_all_data() does not contain the active trail indicators. This is a
  406. * helper function that adds it back in.
  407. *
  408. * @param $tree
  409. * array The menu tree.
  410. * @return
  411. * void
  412. */
  413. function menu_tree_add_active_path(&$tree) {
  414. // Grab any menu item to find the menu_name for this tree.
  415. $menu_item = current($tree);
  416. $tree_with_trail = menu_tree_page_data($menu_item['link']['menu_name']);
  417. // To traverse the original tree down the active trail, we use a pointer.
  418. $subtree_pointer =& $tree;
  419. // Find each key in the active trail.
  420. while ($tree_with_trail) {
  421. foreach ($tree_with_trail AS $key => &$value) {
  422. if ($tree_with_trail[$key]['link']['in_active_trail']) {
  423. // Set the active trail info in the original tree.
  424. $subtree_pointer[$key]['link']['in_active_trail'] = TRUE;
  425. // Continue in the subtree, if it exists.
  426. $tree_with_trail =& $tree_with_trail[$key]['below'];
  427. $subtree_pointer =& $subtree_pointer[$key]['below'];
  428. break;
  429. }
  430. else {
  431. unset($tree_with_trail[$key]);
  432. }
  433. }
  434. }
  435. }
  436. /**
  437. * Trim everything but the active trail in the tree.
  438. *
  439. * @param $tree
  440. * array The menu tree to trim.
  441. * @return
  442. * void
  443. */
  444. function menu_tree_trim_active_path(&$tree) {
  445. foreach ($tree AS $key => &$value) {
  446. if (($tree[$key]['link']['in_active_trail'] || $tree[$key]['link']['expanded']) && $tree[$key]['below']) {
  447. // Continue in the subtree, if it exists.
  448. menu_tree_trim_active_path($tree[$key]['below']);
  449. }
  450. else {
  451. // Trim anything not expanded or along the active trail.
  452. $tree[$key]['below'] = FALSE;
  453. }
  454. }
  455. }
  456. /**
  457. * Sort the active trail to the top of the tree.
  458. *
  459. * @param $tree
  460. * array The menu tree to sort.
  461. * @return
  462. * void
  463. */
  464. function menu_tree_sort_active_path(&$tree) {
  465. module_load_include('inc', 'menu_block', 'menu_block.sort');
  466. _menu_tree_sort_active_path($tree);
  467. }
  468. /**
  469. * Prune a tree so that it begins at the specified level.
  470. *
  471. * This function will follow the active menu trail to the specified level.
  472. *
  473. * @param $tree
  474. * array The menu tree to prune.
  475. * @param $level
  476. * int The level of the original tree that will start the pruned tree.
  477. * @param $parent_item
  478. * array The menu item that should be used as the root of the tree.
  479. * @return
  480. * void
  481. */
  482. function menu_tree_prune_tree(&$tree, $level, $parent_item = FALSE) {
  483. if (!empty($parent_item)) {
  484. // Prune the tree along the path to the menu item.
  485. for ($i = 1; $i <= MENU_MAX_DEPTH && $parent_item["p$i"] != '0'; $i++) {
  486. $plid = $parent_item["p$i"];
  487. $found_active_trail = FALSE;
  488. // Examine each element at this level for the ancestor.
  489. foreach ($tree AS $key => &$value) {
  490. if ($tree[$key]['link']['mlid'] == $plid) {
  491. menu_block_set_title($tree[$key]['link']);
  492. // Prune the tree to the children of this ancestor.
  493. $tree = $tree[$key]['below'] ? $tree[$key]['below'] : array();
  494. $found_active_trail = TRUE;
  495. break;
  496. }
  497. }
  498. // If we don't find the ancestor, bail out.
  499. if (!$found_active_trail) {
  500. $tree = array();
  501. break;
  502. }
  503. }
  504. }
  505. // Trim the upper levels down to the one desired.
  506. for ($i = 1; $i < $level; $i++) {
  507. $found_active_trail = FALSE;
  508. // Examine each element at this level for the active trail.
  509. foreach ($tree AS $key => &$value) {
  510. if ($tree[$key]['link']['in_active_trail']) {
  511. // Get the title for the pruned tree.
  512. menu_block_set_title($tree[$key]['link']);
  513. // Prune the tree to the children of the item in the active trail.
  514. $tree = $tree[$key]['below'] ? $tree[$key]['below'] : array();
  515. $found_active_trail = TRUE;
  516. break;
  517. }
  518. }
  519. // If we don't find the active trail, the active item isn't in the tree we want.
  520. if (!$found_active_trail) {
  521. $tree = array();
  522. break;
  523. }
  524. }
  525. }
  526. /**
  527. * Prune a tree so that it begins at the active menu item.
  528. *
  529. * @param $tree
  530. * array The menu tree to prune.
  531. * @param $level
  532. * string The level which the tree will be pruned to: 'active' or 'child'.
  533. * @return
  534. * void
  535. */
  536. function menu_tree_prune_active_tree(&$tree, $level) {
  537. module_load_include('inc', 'menu_block', 'menu_block.follow');
  538. _menu_tree_prune_active_tree($tree, $level);
  539. }
  540. /**
  541. * Prune a tree so it does not extend beyond the specified depth limit.
  542. *
  543. * @param $tree
  544. * array The menu tree to prune.
  545. * @param $depth_limit
  546. * int The maximum depth of the returned tree; must be a positive integer.
  547. * @return
  548. * void
  549. */
  550. function menu_tree_depth_trim(&$tree, $depth_limit) {
  551. // Prevent invalid input from returning a trimmed tree.
  552. if ($depth_limit < 1) {
  553. return;
  554. }
  555. // Examine each element at this level to find any possible children.
  556. foreach ($tree AS $key => &$value) {
  557. if ($tree[$key]['below']) {
  558. if ($depth_limit > 1) {
  559. menu_tree_depth_trim($tree[$key]['below'], $depth_limit-1);
  560. }
  561. else {
  562. // Remove the children items.
  563. $tree[$key]['below'] = FALSE;
  564. }
  565. }
  566. if ($depth_limit == 1 && $tree[$key]['link']['has_children']) {
  567. // Turn off the menu styling that shows there were children.
  568. $tree[$key]['link']['has_children'] = FALSE;
  569. $tree[$key]['link']['leaf_has_children'] = TRUE;
  570. }
  571. }
  572. }
  573. /**
  574. * Returns a rendered menu tree.
  575. *
  576. * This is a copy of menu_tree_output() with additional classes added to the
  577. * output.
  578. *
  579. * @param $tree
  580. * array A data structure representing the tree as returned from menu_tree_data.
  581. * @return
  582. * string The rendered HTML of that data structure.
  583. */
  584. function menu_block_tree_output(&$tree, $config = array()) {
  585. $build = array();
  586. $items = array();
  587. // Create context if no config was provided.
  588. if (empty($config)) {
  589. $config['delta'] = 0;
  590. // Grab any menu item to find the menu_name for this tree.
  591. $menu_item = current($tree);
  592. $config['menu_name'] = $menu_item['link']['menu_name'];
  593. }
  594. $hook_delta = str_replace('-', '_', $config['delta']);
  595. $hook_menu_name = str_replace('-', '_', $config['menu_name']);
  596. // Pull out just the menu items we are going to render so that we
  597. // get an accurate count for the first/last classes.
  598. foreach ($tree as $key => &$value) {
  599. if (!$tree[$key]['link']['hidden']) {
  600. $items[] = $tree[$key];
  601. }
  602. }
  603. $num_items = count($items);
  604. foreach ($items as $i => &$data) {
  605. $class = array();
  606. if ($i == 0) {
  607. $class[] = 'first';
  608. }
  609. if ($i == $num_items - 1) {
  610. $class[] = 'last';
  611. }
  612. // Set a class if the link has children.
  613. if ($data['below']) {
  614. $class[] = 'expanded';
  615. }
  616. elseif ($data['link']['has_children']) {
  617. $class[] = 'collapsed';
  618. }
  619. else {
  620. $class[] = 'leaf';
  621. }
  622. if (!empty($data['link']['leaf_has_children'])) {
  623. $class[] = 'has-children';
  624. }
  625. // Set a class if the link is in the active trail.
  626. if ($data['link']['in_active_trail']) {
  627. $class[] = 'active-trail';
  628. $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
  629. }
  630. if ($data['link']['href'] == $_GET['q'] || ($data['link']['href'] == '<front>' && drupal_is_front_page())) {
  631. $class[] = 'active';
  632. }
  633. // Set a menu link ID class.
  634. $class[] = 'menu-mlid-' . $data['link']['mlid'];
  635. // Allow menu-specific theme overrides.
  636. $element['#theme'] = array(
  637. 'menu_link__menu_block__' . $hook_delta,
  638. 'menu_link__menu_block__' . $hook_menu_name,
  639. 'menu_link__menu_block',
  640. 'menu_link__' . $hook_menu_name,
  641. 'menu_link',
  642. );
  643. $element['#attributes']['class'] = $class;
  644. $element['#title'] = $data['link']['title'];
  645. $element['#href'] = $data['link']['href'];
  646. $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
  647. $element['#below'] = $data['below'] ? menu_block_tree_output($data['below'], $config) : $data['below'];
  648. $element['#original_link'] = $data['link'];
  649. $element['#bid'] = array('module' => 'menu_block', 'delta' => $config['delta']);
  650. // Index using the link's unique mlid.
  651. $build[$data['link']['mlid']] = $element;
  652. }
  653. if ($build) {
  654. // Make sure drupal_render() does not re-order the links.
  655. $build['#sorted'] = TRUE;
  656. // Add the theme wrapper for outer markup.
  657. // Allow menu-specific theme overrides.
  658. $build['#theme_wrappers'][] = array(
  659. 'menu_tree__menu_block__' . $hook_delta,
  660. 'menu_tree__menu_block__' . $hook_menu_name,
  661. 'menu_tree__menu_block',
  662. 'menu_tree__' . $hook_menu_name,
  663. 'menu_tree',
  664. );
  665. }
  666. return $build;
  667. }
  668. /**
  669. * Implements hook_menu_block_get_menus() on behalf of book.module.
  670. */
  671. function book_menu_block_get_menus() {
  672. $menus = array();
  673. foreach (book_get_books() AS $book) {
  674. $menus[$book['menu_name']] = $book['title'];
  675. }
  676. return $menus;
  677. }
  678. /**
  679. * Implements hook_menu_block_get_sort_menus() on behalf of book.module.
  680. */
  681. function book_menu_block_get_sort_menus() {
  682. return array(
  683. '/^book\-toc\-.+/' => t('Book navigation'),
  684. );
  685. }