0; $i--) {
$current = '';
$count = 0;
for ($j = $length; $j >= 0; $j--) {
if ($i & (1 << $j)) {
$count++;
$current .= $parts[$length - $j];
}
else {
$current .= '%';
}
if ($j) {
$current .= '/';
}
}
// If the number was like 10...0 then the next number will be 11...11,
// one bit less wide.
if ($count == 1) {
$length--;
}
$placeholders[] = "'%s'";
$ancestors[] = $current;
}
return array($ancestors, $placeholders);
}
/**
* The menu system uses serialized arrays stored in the database for
* arguments. However, often these need to change according to the
* current path. This function unserializes such an array and does the
* necessary change.
*
* Integer values are mapped according to the $map parameter. For
* example, if unserialize($data) is array('node_load', 1) and $map is
* array('node', '12345') then 'node_load' will not be changed
* because it is not an integer, but 1 will as it is an integer. As
* $map[1] is '12345', 1 will be replaced with '12345'. So the result
* will be array('node_load', '12345').
*
* @param @data
* A serialized array.
* @param @map
* An array of potential replacements.
* @return
* The $data array unserialized and mapped.
*/
function menu_unserialize($data, $map) {
if ($data = unserialize($data)) {
foreach ($data as $k => $v) {
if (is_int($v)) {
$data[$k] = isset($map[$v]) ? $map[$v] : '';
}
}
return $data;
}
else {
return array();
}
}
/**
* Replaces the statically cached item for a given path.
*
* @param $path
* The path
* @param $item
* The menu item. This is a menu entry, an associative array,
* with keys like title, access callback, access arguments etc.
*/
function menu_set_item($path, $item) {
menu_get_item($path, TRUE, $item);
}
function menu_get_item($path = NULL, $execute = TRUE, $item = NULL) {
static $items;
if (!isset($path)) {
$path = $_GET['q'];
}
if (isset($item)) {
$items[$path] = $item;
}
if (!isset($items[$path])) {
$map = arg(NULL, $path);
$parts = array_slice($map, 0, 6);
list($ancestors, $placeholders) = menu_get_ancestors($parts);
if ($item = db_fetch_object(db_query_range('SELECT * FROM {menu} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
$item->access = _menu_access($item, $map);
if ($map === FALSE) {
$items[$path] = FALSE;
return FALSE;
}
if ($execute) {
$item->page_arguments = array_merge(menu_unserialize($item->page_arguments, $map), array_slice($parts, $item->number_parts));
}
}
$items[$path] = $item;
}
return $items[$path];
}
/**
* Execute the handler associated with the active menu item.
*/
function menu_execute_active_handler() {
if ($item = menu_get_item()) {
return $item->access ? call_user_func_array($item->page_callback, $item->page_arguments) : MENU_ACCESS_DENIED;
}
return MENU_NOT_FOUND;
}
function _menu_access($item, &$map) {
if ($item->map_callback) {
$map = call_user_func_array($item->map_callback, array_merge(array($map), unserialize($item->map_arguments)));
if ($map === FALSE) {
return FALSE;
}
}
$callback = $item->access_callback;
if (is_numeric($callback)) {
return $callback;
}
$arguments = menu_unserialize($item->access_arguments, $map);
// As call_user_func_array is quite slow and user_access is a very common
// callback, it is worth making a special case for it.
if ($callback == 'user_access') {
return (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
}
return call_user_func_array($callback, $arguments);
}
/**
* Returns a rendered menu tree.
*/
function menu_tree() {
$item = menu_get_item();
list(, $menu) = _menu_tree(db_query('SELECT * FROM {menu} WHERE pid IN ('. $item->parents .') AND visible = 1 ORDER BY vancode'));
return $menu;
}
function _menu_tree($result = NULL, $depth = 0, $link = array('link' => '', 'has_children' => FALSE)) {
static $original_map;
$remnant = array('link' => '', 'has_children' => FALSE);
$tree = '';
while ($item = db_fetch_object($result)) {
$map = arg(NULL, $item->path);
if (!_menu_access($item, $map)) {
continue;
}
$menu_link = array('link' => $item->menu_link, 'has_children' => $item->has_children);
if ($item->depth > $depth) {
list($remnant, $menu) = _menu_tree($result, $item->depth, $menu_link);
$tree .= theme('menu_tree', $link, $menu);
$link = $remnant;
$remnant = '';
}
elseif ($item->depth == $depth) {
$tree .= theme('menu_link', $link);
$link = $menu_link;
}
else {
$remnant = $menu_link;
break;
}
}
if ($link['link']) {
$tree .= theme('menu_link', $link);
}
return array($remnant, $tree);
}
/**
* Generate the HTML for a menu tree.
*/
function theme_menu_tree($link, $tree) {
$tree = '
';
return $link['link'] ? theme('menu_link', $link, $tree) : $tree;
}
/**
* Generate the HTML for a menu link.
*/
function theme_menu_link($link, $menu = '') {
return ''. $link['link'] . $menu .'' . "\n";
}
/**
* Returns the help associated with the active menu item.
*/
function menu_get_active_help() {
$path = $_GET['q'];
$output = '';
$item = menu_get_item();
if (!$item->access) {
// Don't return help text for areas the user cannot access.
return;
}
foreach (module_list() as $name) {
if (module_hook($name, 'help')) {
if ($temp = module_invoke($name, 'help', $path)) {
$output .= $temp ."\n";
}
if (module_hook('help', 'page')) {
if (arg(0) == "admin") {
if (module_invoke($name, 'help', 'admin/help#'. arg(2)) && !empty($output)) {
$output .= theme("more_help_link", url('admin/help/'. arg(2)));
}
}
}
}
}
return $output;
}
/**
* Populate the database representation of the menu.
*/
function menu_rebuild() {
$next = array();
db_query('DELETE FROM {menu}');
$menu = module_invoke_all('menu');
foreach (module_implements('menu_alter') as $module) {
$function = $module .'_menu_alter';
$function($menu);
}
$mid = 1;
// First pass.
foreach ($menu as $path => $item) {
$item = &$menu[$path];
$parts = explode('/', $path, 6);
$number_parts = count($parts);
// We store the highest index of parts here to save some work in the weight
// calculation loop.
$slashes = $number_parts - 1;
// If there is no %, it fits maximally.
if (strpos($path, '%') === FALSE) {
$fit = (1 << $number_parts) - 1;
}
else {
// We need to calculate the fitness.
$fit = 0;
foreach ($parts as $k => $part) {
// ($part != '%') is the bit we want and we shift it to its place
// by shifting to left by ($slashes - $k) bits.
$fit |= ($part != '%') << ($slashes - $k);
}
}
if (!isset($item['_visible'])) {
$item['_visible'] = (!isset($item['type']) || ($item['type'] & MENU_VISIBLE_IN_TREE)) ? 1 : 0;
}
$depth = 1;
if (!isset($item['_mid'])) {
$item['_mid'] = $mid++;
}
$parents = array($item['_mid']);
for ($i = $slashes; $i; $i--) {
$parent_path = implode('/', array_slice($parts, 0, $i));
// We need to calculate depth to be able to sort. depth needs visibility.
if (isset($menu[$parent_path])) {
$parent = &$menu[$parent_path];
// It's possible that the parent was not processed yet.
if (!isset($parent['_mid'])) {
$parent['_mid'] = $mid++;
}
if (!isset($parent['_visible'])) {
$parent['_visible'] = (!isset($parent['type']) || ($parent['type'] & MENU_VISIBLE_IN_TREE)) ? 1 : 0;
}
if ($item['_visible'] && $parent['_visible']) {
$parent['_has_children'] = 1;
$depth++;
$parents[] = $parent['_mid'];
if (!isset($item['_pid'])) {
$item['_pid'] = $parent['_mid'];
$item['_visible_parent_path'] = $parent_path;
}
}
unset($parent);
}
}
$parents[] = 0;
$parents = implode(',', array_reverse($parents));
// Store variables and set defaults.
$item += array(
'_fit' => $fit,
'_number_parts' => $number_parts,
'_parts' => $parts,
'_pid' => 0,
'_depth' => $depth,
'_parents' => $parents,
'_has_children' => 0,
'title' => '',
'weight' => 0,
);
$sort[$path] = ($item['_visible'] ? $depth : $number_parts) . sprintf('%05d', $item['weight']) . $item['title'];
unset($item);
}
array_multisort($sort, $menu);
// Second pass: calculate ancestors, vancode and store into the database.
foreach ($menu as $path => $item) {
$item = &$menu[$path];
for ($i = $item['_number_parts'] - 1; $i; $i--) {
$parent_path = implode('/', array_slice($item['_parts'], 0, $i));
if (isset($menu[$parent_path])) {
$parent = $menu[$parent_path];
// If a callback is not found, we try to find the first parent that
// has this callback. When found, its callback argument will also be
// copied but only if there is none in the current item.
foreach (array('access', 'map', 'page') as $type) {
if (!isset($item["$type callback"]) && isset($parent["$type callback"])) {
$item["$type callback"] = $parent["$type callback"];
if (!isset($item["$type arguments"]) && isset($parent["$type arguments"])) {
$item["$type arguments"] = $parent["$type arguments"];
}
}
}
}
}
if (!isset($item['access callback'])) {
$menu[$path]['access callback'] = isset($item['access arguments']) ? 'user_access' : 0;
}
if (!isset($item['map callback']) && isset($item['map arguments'])) {
$item['map callback'] = 'menu_map';
}
foreach (array('access', 'map', 'page') as $type) {
if (isset($item["$type callback"]) && !isset($item["$type arguments"])) {
$item["$type arguments"] = array();
}
}
if (is_bool($item['access callback'])) {
$item['access callback'] = intval($item['access callback']);
}
if ($item['_visible']) {
$prefix = isset($item['_visible_parent_path']) ? $menu[$item['_visible_parent_path']]['_prefix'] : '';
if (!isset($next[$prefix])) {
$next[$prefix] = 0;
}
$vancode = $prefix . int2vancode($next[$prefix]++);
$menu[$path]['_prefix'] = $vancode .'.';
$link = l($item['title'], $path, isset($item['attributes']) ? $item['attributes'] : array(), isset($item['query']) ? $item['query'] : NULL, isset($item['fragment']) ? $item['fragment'] : NULL);
}
else {
$vancode = '';
$link = '';
}
db_query("INSERT INTO {menu} (mid, pid, path, access_callback, access_arguments, page_callback, page_arguments, map_callback, map_arguments, fit, number_parts, vancode, menu_link, visible, parents, depth, has_children) VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, '%s', %d, %d)", $item['_mid'], $item['_pid'], $path, $item['access callback'], serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['map callback'], serialize($item['map arguments']), $item['_fit'], $item['_number_parts'], $vancode .'+', $link, $item['_visible'], $item['_parents'], $item['_depth'], $item['_has_children']);
// $item needs to be unset because of the reference above.
unset($item);
}
}
function menu_map($arg, $function, $index, $default = FALSE) {
$arg[$index] = is_numeric($arg[$index]) ? $function($arg[$index]) : $default;
return $arg[$index] ? $arg : FALSE;
}
// Placeholders.
function menu_primary_links() {
}
function menu_secondary_links() {
}
function menu_primary_local_tasks() {
}
function menu_secondary_local_tasks() {
}
function menu_set_active_item() {
}
function menu_set_location() {
}
function menu_get_active_breadcrumb() {
return array(l(t('Home'), ''));
}