array('attributes' => array()))); $extended_link['localized_options']['attributes']['id'] = 'dhtml_menu-' . _dhtml_menu_unique_id($link['mlid']); // Each link in series is another level of recursion. Add it to the stack, even if it is disabled. _dhtml_menu_stack($extended_link); // Pass the altered variables to the normal menu themer, but only if DHTML should be used. return $function(!$extended_link['dhtml_disabled'] ? $extended_link : $link); } /** * Preprocessor for menu_item. * Checks whether the current item has children that * were not rendered, and loads and renders them. */ function dhtml_menu_theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) { global $theme; static $cookie = array(), $function; if (empty($function)) { $settings = variable_get('dhtml_menu_settings', array()); if ($settings['effects']['remember'] && $settings['nav'] != 'open' && $settings['effects']['siblings'] != 'close-all') { $cookie = explode(',', @$_COOKIE['dhtml_menu']); } $registry = variable_get('dhtml_menu_theme', array()); if (isset($registry[$theme]) && function_exists($registry[$theme]['menu_item'])) { $function = $registry[$theme]['menu_item']; } else { $function = 'theme_menu_item'; } } /* When theme('menu_item') is called, the menu tree below it has been * rendered already. Since we are done on this recursion level, * one element must be popped off the stack. */ $item = _dhtml_menu_stack(); // If this item should not have DHTML, then return to the "parent" function. if (!$item || !empty($item['dhtml_disabled'])) { return $function($link, $has_children, $menu, $in_active_trail, $extra_class); } $extra_class .= ' dhtml-menu '; // If there are children, but they were not loaded... if ($has_children && !$menu) { // Load the tree below the current position. $tree = _dhtml_menu_subtree($item); // Render it... $menu = menu_tree_output($tree); // Sanitize the tree - uncheck has_children if no children were loaded. if (!$menu) { $has_children = FALSE; } } // If the current item can expand, and is neither saved as open nor in the active trail, close it. if ($menu && !($in_active_trail || in_array($item['localized_options']['attributes']['id'], $cookie))) { $extra_class .= ' collapsed start-collapsed '; } // Cascade up to the original theming function. return $function($link, $has_children, $menu, $in_active_trail, $extra_class); } /** * Helper function for storing recursion levels. * * @param $link * If a menu item link is passed, it will be pushed onto the stack. * Otherwise, one element will be popped off the stack. * * @return * The last element of the stack, if no argument is passed. */ function _dhtml_menu_stack($link = FALSE) { static $stack = array(); if ($link) { $stack[] = $link; } else { return array_pop($stack); } } /** * Traverses the menu tree and returns the sub-tree of the item * indicated by the parameter. * * @param $item * A menu item link that must contain the keys "mlid" and "menu_name". * * @return * The tree below the menu item, or an empty array. */ function _dhtml_menu_subtree($item) { static $index = array(); static $indexed = array(); // This looks expensive, but menu_tree_all_data uses static caching. $tree = menu_tree_all_data($item['menu_name']); // Index the menu tree to find ancestor paths for each item. if (!isset($indexed[$item['menu_name']])) { $index += _dhtml_menu_index($tree); $indexed[$item['menu_name']] = TRUE; } // If the menu tree does not contain this item, stop. if (!isset($index[$item['mlid']])) { return array(); } // Traverse the tree using the ancestor path. foreach ($index[$item['mlid']]['parents'] as $mlid) { $key = $index[$mlid]['key']; if (isset($tree[$key])) { $tree = $tree[$key]['below']; } else { return array(); } } // Go one level further to go below the current item. $key = $index[$item['mlid']]['key']; return isset($tree[$key]) ? $tree[$key]['below'] : array(); } /** * Indexes the menu tree by mlid. This is needed to identify the items * without relying on titles or stacks. This function is recursive. * * @param $tree * A tree of menu items such as the return value of menu_tree_all_data(). * @param $ancestors * Optional, used only by internal recursion. * @param $parent * Optional, used only by internal recursion. * * @return * An array associating mlid values with the internal keys of the menu tree, * and all the mlids of the item's ancestors. */ function _dhtml_menu_index($tree, $ancestors = array(), $parent = NULL) { $index = array(); if ($parent) { $ancestors[] = $parent; } foreach ($tree as $key => $item) { $index[$item['link']['mlid']] = array( 'key' => $key, 'parents' => $ancestors, ); if (!empty($item['below'])) { $index += _dhtml_menu_index($item['below'], $ancestors, $item['link']['mlid']); } } return $index; } /** * Keeps track of ID attributes and adds a suffix to make it unique-when necessary. */ function _dhtml_menu_unique_id($id) { static $ids = array(); if (!isset($ids[$id])) { $ids[$id] = 1; return $id; } else { return $id . '-' . $ids[$id]++; } }