diff --git a/includes/menu.inc b/includes/menu.inc index 2be0903..9a416ec 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -391,14 +391,27 @@ function menu_get_ancestors($parts) { * A path argument array, used to replace integer values in $data; an integer * value N in $data will be replaced by value $map[N]. Typically, the $map * array is generated from a call to the arg() function. + * @param $item + * A menu router or menu link item. + * Contains all data to dynamically load a corresponding object (entity). + * $item['access'] may be set to FALSE, when the object cannot be loaded. * * @return - * The unserialized $data array, with path arguments replaced. + * The unserialized $data array, with path arguments replaced, + * or FALSE, if an error occurred loading an object. */ -function menu_unserialize($data, $map) { +function menu_unserialize($data, $map, $item) { if ($data = unserialize($data)) { foreach ($data as $k => $v) { if (is_int($v)) { + // Load the object for the given id. + if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) { + // An error occurred loading an object. + // Do not set access to FALSE: results in unwanted 404-error + // on non-existing page, like content/123. + $item['access'] = FALSE; // This is already done in _menu_load_objects() + return FALSE; + } $data[$k] = isset($map[$v]) ? $map[$v] : ''; } } @@ -417,9 +430,9 @@ function menu_unserialize($data, $map) { * @param $path * The path. * @param $router_item - * The router item. Usually a router entry from menu_get_item() is either - * modified or set to a different path. This allows the navigation block, - * the page title, the breadcrumb, and the page help to be modified in one + * The router item. Usually you take a router entry from menu_get_item and + * set it back either modified or to a different path. This lets you modify the + * navigation block, the page title, the breadcrumb and the page help in one * call. */ function menu_set_item($path, $router_item) { @@ -427,7 +440,7 @@ function menu_set_item($path, $router_item) { } /** - * Gets a router item. + * Get a router item. * * @param $path * The path, for example node/5. The function will find the corresponding @@ -436,13 +449,12 @@ function menu_set_item($path, $router_item) { * Internal use only. * * @return - * The router item or, if an error occurs in _menu_translate(), FALSE. A - * router item is an associative array corresponding to one row in the - * menu_router table. The value corresponding to the key 'map' holds the - * loaded objects. The value corresponding to the key 'access' is TRUE if the - * current user can access this page. The values corresponding to the keys - * 'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will - * be filled in based on the database values and the objects loaded. + * The router item, an associate array corresponding to one row in the + * menu_router table. The value of key map holds the loaded objects. The + * value of key access is TRUE if the current user can access this page. + * The values for key title, page_arguments, access_arguments, and + * theme_arguments will be filled in based on the database values and the + * objects loaded. */ function menu_get_item($path = NULL, $router_item = NULL) { $router_items = &drupal_static(__FUNCTION__); @@ -477,8 +489,8 @@ function menu_get_item($path = NULL, $router_item = NULL) { } if ($router_item['access']) { $router_item['map'] = $map; - $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts'])); - $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts'])); + $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map, $router_item), array_slice($map, $router_item['number_parts'])); + $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map, $router_item), array_slice($map, $router_item['number_parts'])); } } $router_items[$path] = $router_item; @@ -557,6 +569,22 @@ function _menu_load_objects(&$item, &$map) { if (!is_array($load_functions)) { $load_functions = unserialize($load_functions); } + + // If the object is loaded before, don't load it again. + if (isset($item['_object_map'])) { + if ($item['_object_map'] === FALSE) { + $item['access'] = FALSE; + $item['_object_map'] = FALSE; + $map = FALSE; + return FALSE; + } + else { + $map = $item['_object_map']; + return TRUE; + } + } + $item['_object_map'] = array(); + $path_map = $map; foreach ($load_functions as $index => $function) { if ($function) { @@ -595,6 +623,7 @@ function _menu_load_objects(&$item, &$map) { // If callback returned an error or there is no callback, trigger 404. if ($return === FALSE) { $item['access'] = FALSE; + $item['_object_map'] = FALSE; $map = FALSE; return FALSE; } @@ -602,12 +631,13 @@ function _menu_load_objects(&$item, &$map) { } } $item['load_functions'] = $load_functions; + $item['_object_map'] = $map; } return TRUE; } /** - * Checks access to a menu item using the access callback. + * Check access to a menu item using the access callback * * @param $item * A menu router or menu link item @@ -615,10 +645,15 @@ function _menu_load_objects(&$item, &$map) { * An array of path arguments (ex: array('node', '5')) * * @return + * TRUE if the object-at-hand could be loaded, FALSE otherwise. * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. */ function _menu_check_access(&$item, $map) { - $item['access'] = FALSE; + // menu_tree_check_access() may set this ahead of time for links to nodes. + if (isset($item['access'])) { + return TRUE; + } + // Determine access callback, which will decide whether or not the current // user has access to this path. $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); @@ -627,7 +662,12 @@ function _menu_check_access(&$item, $map) { $item['access'] = (bool) $callback; } else { - $arguments = menu_unserialize($item['access_arguments'], $map); + $arguments = menu_unserialize($item['access_arguments'], $map, $item); + if ($arguments === FALSE) { + // An error happened when loading the object. $item['access'] is now set to FALSE. + return FALSE; + } + // 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') { @@ -637,10 +677,11 @@ function _menu_check_access(&$item, $map) { $item['access'] = call_user_func_array($callback, $arguments); } } + return TRUE; } /** - * Localizes the router item title using t() or another callback. + * Localize the router item title using t() or another callback. * * Translate the title and description to allow storage of English title * strings in the database, yet display of them in the language required @@ -683,6 +724,11 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { // If we are translating a router item (tabs, page, breadcrumb), then we // can always use the information from the router item. if (!$link_translate || ($item['title'] == $item['link_title'])) { + $title_arguments = menu_unserialize($item['title_arguments'], $map, $item); + if ($title_arguments === FALSE) { + // An error happened when loading the object. $item['access'] is now set to FALSE. + return; + } // t() is a special case. Since it is used very close to all the time, // we handle it directly instead of using indirect, slower methods. if ($callback == 't') { @@ -690,7 +736,7 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { $item['title'] = t($item['title']); } else { - $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map)); + $item['title'] = t($item['title'], $title_arguments); } } elseif ($callback && function_exists($callback)) { @@ -698,7 +744,7 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { $item['title'] = $callback($item['title']); } else { - $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map)); + $item['title'] = call_user_func_array($callback, $title_arguments); } // Avoid calling check_plain again on l() function. if ($callback == 'check_plain') { @@ -748,7 +794,7 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { * $item['load_functions']. $item['access'] becomes TRUE if the item is * accessible, FALSE otherwise. $item['href'] is set according to the map. * If an error occurs during calling the load_functions (like trying to load - * a non-existent node) then this function returns FALSE. + * a non existing node) then this function return FALSE. */ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { if ($to_arg && !empty($router_item['to_arg_functions'])) { @@ -758,11 +804,6 @@ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { // The $path_map saves the pieces of the path as strings, while elements in // $map may be replaced with loaded objects. $path_map = $map; - if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) { - // An error occurred loading an object. - $router_item['access'] = FALSE; - return FALSE; - } // Generate the link path for the page request or local tasks. $link_map = explode('/', $router_item['path']); @@ -773,6 +814,12 @@ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { $tab_parent_map = explode('/', $router_item['tab_parent']); } for ($i = 0; $i < $router_item['number_parts']; $i++) { + // data may not be aligned, like menu_router item 'node/%/edit/add/%/%'. + if (!isset($path_map[$i])) { +// dpm(debug_backtrace()); +// continue; + } + if ($link_map[$i] == '%') { $link_map[$i] = $path_map[$i]; } @@ -787,7 +834,13 @@ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { $router_item['tab_root_href'] = implode('/', $tab_root_map); $router_item['tab_parent_href'] = implode('/', $tab_parent_map); $router_item['options'] = array(); - _menu_check_access($router_item, $map); + + if (!_menu_check_access($router_item, $map)) { + //if (isset ($router_item['_object_map']) && $router_item['_object_map'] === FALSE) { + // The object could not be loaded. + $router_item['access'] = FALSE; + return FALSE; + } // For performance, don't localize an item the user can't access. if ($router_item['access']) { @@ -798,14 +851,14 @@ function _menu_translate(&$router_item, $map, $to_arg = FALSE) { } /** - * Translates the path elements in the map using any to_arg helper function. + * This function translates the path elements in the map using any to_arg + * helper function. These functions take an argument and return an object. + * See http://drupal.org/node/109153 for more information. * * @param $map * An array of path arguments (ex: array('node', '5')) * @param $to_arg_functions * An array of helper function (ex: array(2 => 'menu_tail_to_arg')) - * - * @see hook_menu() */ function _menu_link_map_translate(&$map, $to_arg_functions) { $to_arg_functions = unserialize($to_arg_functions); @@ -822,14 +875,14 @@ function _menu_link_map_translate(&$map, $to_arg_functions) { } /** - * Returns a string containing the path relative to the current index. + * Returns path as one string from the argument we are currently at. */ function menu_tail_to_arg($arg, $map, $index) { return implode('/', array_slice($map, $index)); } /** - * Loads the path as one string relative to the current index. + * Loads path as one string from the argument we are currently at. * * To use this load function, you must specify the load arguments * in the router item as: @@ -846,10 +899,8 @@ function menu_tail_load($arg, &$map, $index) { } /** - * Provides menu link access control, translation, and argument handling. - * - * This function is similar to _menu_translate(), but it also does - * link-specific preparation (such as always calling to_arg() functions). + * This function is similar to _menu_translate() but does link-specific + * preparation such as always calling to_arg functions * * @param $item * A menu link. @@ -917,15 +968,12 @@ function _menu_link_translate(&$item, $translate = FALSE) { $item['access'] = FALSE; return FALSE; } - // menu_tree_check_access() may set this ahead of time for links to nodes. - if (!isset($item['access'])) { - if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) { - // An error occurred loading an object. - $item['access'] = FALSE; - return FALSE; - } - _menu_check_access($item, $map); + if (!_menu_check_access($item, $map)) { + // The object could not be loaded. + $item['access'] = FALSE; + return FALSE; } + // For performance, don't localize a link the user can't access. if ($item['access']) { _menu_item_localize($item, $map, TRUE); @@ -943,7 +991,7 @@ function _menu_link_translate(&$item, $translate = FALSE) { } /** - * Gets a loaded object from a router item. + * Get a loaded object from a router item. * * menu_get_object() provides access to objects loaded by the current router * item. For example, on the page node/%node, the router loads the %node object, @@ -971,7 +1019,10 @@ function _menu_link_translate(&$item, $translate = FALSE) { function menu_get_object($type = 'node', $position = 1, $path = NULL) { $router_item = menu_get_item($path); if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') { - return $router_item['map'][$position]; + $map = $router_item['map']; + if (_menu_load_objects($router_item, $map)) { + return $map[$position]; + } } }