Commit 42e2c369 authored by alexpott's avatar alexpott

Revert "Issue #2047633 by dawehner, pwolanin, Xano, amateescu, tim.plunkett:...

Revert "Issue #2047633 by dawehner, pwolanin, Xano, amateescu, tim.plunkett: Move definition of menu links to hook_menu_link_defaults(), decouple key name from path, and make 'parent' explicit."

This reverts commit 531fd593.
parent ae97f9af
......@@ -1665,7 +1665,14 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
* The current page's title.
*/
function drupal_get_title() {
return drupal_set_title() ?: '';
$title = drupal_set_title();
// During a bootstrap, menu.inc is not included and thus we cannot provide a title.
if (!isset($title) && function_exists('menu_get_active_title')) {
$title = String::checkPlain(menu_get_active_title());
}
return $title;
}
/**
......
......@@ -656,7 +656,7 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
// itself; can't localize.
// If we are translating a router item (tabs, page, breadcrumb), then we
// can always use the information from the router item.
if (!$link_translate || !isset($item['link_title']) || ($item['title'] == $item['link_title'])) {
if (!$link_translate || ($item['title'] == $item['link_title'])) {
// 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 ($title_callback == 't') {
......@@ -760,10 +760,7 @@ function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
$router_item['access'] = FALSE;
return FALSE;
}
// Avoid notices until we remove this function.
// @see https://drupal.org/node/2107533
$tab_root_map = array();
$tab_parent_map = array();
// Generate the link path for the page request or local tasks.
$link_map = explode('/', $router_item['path']);
if (isset($router_item['tab_root'])) {
......@@ -858,51 +855,95 @@ function menu_tail_load($arg, &$map, $index) {
}
/**
* Provides menu link unserializing, access control, and argument handling.
* 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).
*
* @param array $item
* The passed in item has the following keys:
* - access: (optional) Becomes TRUE if the item is accessible, FALSE
* otherwise. If the key is not set, the access manager is used to
* determine the access.
* - options: (required) Is unserialized and copied to $item['localized_options'].
* - link_title: (required) The title of the menu link.
* - route_name: (required) The route name of the menu link.
* - route_parameters: (required) The unserialized route parameters of the menu link.
* The passed in item is changed by the following keys:
* - href: The actual path to the link. This path is generated from the
* link_path of the menu link entity.
* - title: The title of the link. This title is generated from the
* link_title of the menu link entity.
*/
function _menu_link_translate(&$item) {
* @param $item
* A menu link.
* @param $translate
* (optional) Whether to try to translate a link containing dynamic path
* argument placeholders (%) based on the menu router item of the current
* path. Defaults to FALSE. Internally used for breadcrumbs.
*
* @return
* Returns the map of path arguments with objects loaded as defined in the
* $item['load_functions'].
* $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
* $item['href'] is generated from link_path, possibly by to_arg functions.
* $item['title'] is generated from link_title, and may be localized.
* $item['options'] is unserialized; it is also changed within the call here
* to $item['localized_options'] by _menu_item_localize().
*/
function _menu_link_translate(&$item, $translate = FALSE) {
if (!is_array($item['options'])) {
$item['options'] = (array) unserialize($item['options']);
$item['options'] = unserialize($item['options']);
}
$item['localized_options'] = $item['options'];
$item['title'] = $item['link_title'];
if ($item['external'] || empty($item['route_name'])) {
if ($item['external']) {
$item['access'] = 1;
$map = array();
$item['href'] = $item['link_path'];
$item['route_parameters'] = array();
// Set to NULL so that drupal_pre_render_link() is certain to skip it.
$item['route_name'] = NULL;
$item['title'] = $item['link_title'];
$item['localized_options'] = $item['options'];
}
else {
$item['href'] = NULL;
if (!is_array($item['route_parameters'])) {
$item['route_parameters'] = (array) unserialize($item['route_parameters']);
// Complete the path of the menu link with elements from the current path,
// if it contains dynamic placeholders (%).
$map = explode('/', $item['link_path']);
if (strpos($item['link_path'], '%') !== FALSE) {
// Invoke registered to_arg callbacks.
if (!empty($item['to_arg_functions'])) {
_menu_link_map_translate($map, $item['to_arg_functions']);
}
// Or try to derive the path argument map from the current router item,
// if this $item's path is within the router item's path. This means
// that if we are on the current path 'foo/%/bar/%/baz', then
// menu_get_item() will have translated the menu router item for the
// current path, and we can take over the argument map for a link like
// 'foo/%/bar'. This inheritance is only valid for breadcrumb links.
// @see _menu_tree_check_access()
elseif ($translate && ($current_router_item = menu_get_item())) {
// If $translate is TRUE, then this link is in the active trail.
// Only translate paths within the current path.
if (strpos($current_router_item['path'], $item['link_path']) === 0) {
$count = count($map);
$map = array_slice($current_router_item['original_map'], 0, $count);
$item['original_map'] = $map;
if (isset($current_router_item['map'])) {
$item['map'] = array_slice($current_router_item['map'], 0, $count);
}
// Reset access to check it (for the first time).
unset($item['access']);
}
}
}
$item['href'] = implode('/', $map);
// Skip links containing untranslated arguments.
if (strpos($item['href'], '%') !== FALSE) {
$item['access'] = FALSE;
return FALSE;
}
// menu_tree_check_access() may set this ahead of time for links to nodes.
if (!isset($item['access'])) {
$item['access'] = \Drupal::getContainer()->get('access_manager')->checkNamedRoute($item['route_name'], $item['route_parameters'], \Drupal::currentUser());
if ($route = $item->getRoute()) {
$item['access'] = menu_item_route_access($route, $item['href'], $map);
}
elseif (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {
// An error occurred loading an object.
$item['access'] = FALSE;
return FALSE;
}
// Apply the access check defined in hook_menu() if there is not route
// defined.
else {
_menu_check_access($item, $map);
}
}
// For performance, don't localize a link the user can't access.
if ($item['access']) {
_menu_item_localize($item, array(), TRUE);
_menu_item_localize($item, $map, TRUE);
}
}
......@@ -912,6 +953,8 @@ function _menu_link_translate(&$item) {
if (!empty($item['options']['alter'])) {
drupal_alter('translated_menu_link', $item, $map);
}
return $map;
}
/**
......@@ -923,19 +966,17 @@ function _menu_link_translate(&$item) {
* Menu path as returned by $item['href'] of menu_get_item().
* @param array $map
* An array of path arguments; for example, array('node', '5').
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object, used to find the current route.
*
* @return bool
* TRUE if the user has access or FALSE if the user should be presented
* with access denied.
*
* @throws \Symfony\Component\Routing\Exception\ResourceNotFoundException
* If the system path in $href does not match the $route.
*/
function menu_item_route_access(Route $route, $href, &$map, Request $request = NULL) {
if (!isset($request)) {
$request = RequestHelper::duplicate(\Drupal::request(), '/' . $href);
$request->attributes->set('_system_path', $href);
}
function menu_item_route_access(Route $route, $href, &$map) {
$request = RequestHelper::duplicate(\Drupal::request(), '/' . $href);
$request->attributes->set('_system_path', $href);
// Attempt to match this path to provide a fully built request to the
// access checker.
try {
......@@ -1087,14 +1128,19 @@ function menu_tree_output($tree) {
$class[] = 'active-trail';
$data['link']['localized_options']['attributes']['class'][] = 'active-trail';
}
// Normally, l() compares the href of every link with the current path and
// sets the active class accordingly. But local tasks do not appear in menu
// trees, so if the current path is a local task, and this link is its
// tab root, then we have to set the class manually.
if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != current_path()) {
$data['link']['localized_options']['attributes']['class'][] = 'active';
}
// Allow menu-specific theme overrides.
$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
$element['#attributes']['class'] = $class;
$element['#title'] = $data['link']['title'];
// @todo Use route name and parameters to generate the link path, unless
// it is external.
$element['#href'] = $data['link']['link_path'];
$element['#href'] = $data['link']['href'];
$element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
$element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
$element['#original_link'] = $data['link'];
......@@ -1831,7 +1877,7 @@ function menu_navigation_links($menu_name, $level = 0) {
if (!$item['link']['hidden']) {
$class = '';
$l = $item['link']['localized_options'];
$l['href'] = $item['link']['link_path'];
$l['href'] = $item['link']['href'];
$l['title'] = $item['link']['title'];
if ($item['link']['in_active_trail']) {
$class = ' active-trail';
......@@ -2258,10 +2304,10 @@ function menu_set_active_trail($new_trail = NULL) {
// Try to retrieve a menu link corresponding to the current path. If more
// than one exists, the link from the most preferred menu is returned.
$preferred_link = menu_link_get_preferred();
$current_item = menu_get_item();
// There is a link for the current path.
if ($preferred_link) {
_menu_link_translate($preferred_link);
// Pass TRUE for $only_active_trail to make menu_tree_page_data() build
// a stripped down menu tree containing the active trail only, in case
// the given menu has not been built in this request yet.
......@@ -2270,6 +2316,7 @@ function menu_set_active_trail($new_trail = NULL) {
}
// There is no link for the current path.
else {
$preferred_link = $current_item;
$curr = FALSE;
}
......@@ -2287,7 +2334,7 @@ function menu_set_active_trail($new_trail = NULL) {
// @see _menu_tree_check_access()
// @see _menu_link_translate()
if (strpos($link['href'], '%') !== FALSE) {
_menu_link_translate($link);
_menu_link_translate($link, TRUE);
}
if ($link['access']) {
$trail[] = $link;
......@@ -2342,11 +2389,20 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
// which are ordered by priority (translated hrefs are preferred over
// untranslated paths). Afterwards, the most relevant path is picked from
// the menus, ordered by menu preference.
$item = menu_get_item($path);
$path_candidates = array();
// 1. The current item href.
// @todo simplify this code and convert to using route names.
// @see https://drupal.org/node/2154949
$path_candidates[$path] = $path;
$path_candidates[$item['href']] = $item['href'];
// 2. The tab root href of the current item (if any).
if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) {
$path_candidates[$tab_root['href']] = $tab_root['href'];
}
// 3. The current item path (with wildcards).
$path_candidates[$item['path']] = $item['path'];
// 4. The tab root path of the current item (if any).
if (!empty($tab_root)) {
$path_candidates[$tab_root['path']] = $tab_root['path'];
}
// Retrieve a list of menu names, ordered by preference.
$menu_names = menu_get_active_menu_names();
......@@ -2412,6 +2468,19 @@ function menu_get_active_trail() {
return menu_set_active_trail();
}
/**
* Gets the title of the current page, as determined by the active trail.
*/
function menu_get_active_title() {
$active_trail = menu_get_active_trail();
foreach (array_reverse($active_trail) as $item) {
if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) {
return $item['title'];
}
}
}
/**
* Clears the cached cached data for a single named menu.
*/
......@@ -2469,7 +2538,7 @@ function menu_router_rebuild() {
try {
list($menu) = menu_router_build(TRUE);
menu_link_rebuild_defaults();
_menu_navigation_links_rebuild($menu);
// Clear the menu, page and block caches.
menu_cache_clear_all();
_menu_clear_page_cache();
......@@ -2579,147 +2648,110 @@ function menu_get_router() {
}
/**
* Saves menu links recursively for menu_links_rebuild_defaults().
*/
function _menu_link_save_recursive($controller, $machine_name, &$children, &$links) {
$menu_link = $links[$machine_name];
if ($menu_link->isNew() || !$menu_link->customized) {
if (!isset($menu_link->plid) && !empty($menu_link->parent) && !empty($links[$menu_link->parent])) {
$parent = $links[$menu_link->parent];
if (empty($menu_link->menu_name) || $parent->menu_name == $menu_link->menu_name) {
$menu_link->plid = $parent->id();
$menu_link->menu_name = $parent->menu_name;
}
}
$controller->save($menu_link);
}
if (!empty($children[$machine_name])) {
foreach ($children[$machine_name] as $next_name) {
_menu_link_save_recursive($controller, $next_name, $children, $links);
}
}
// Remove processed link names so we can find stragglers.
unset($children[$machine_name]);
}
/**
* Gets all default menu link definitions.
* Builds menu links for the items in the menu router.
*
* @return array
* An array of default menu links.
*/
function menu_link_get_defaults() {
$module_handler = \Drupal::moduleHandler();
$all_links = $module_handler->invokeAll('menu_link_defaults');
// Fill in the machine name from the array key.
foreach ($all_links as $machine_name => &$link) {
$link['machine_name'] = $machine_name;
}
$module_handler->alter('menu_link_defaults', $all_links);
return $all_links;
}
/**
* Builds menu links for the items returned from hook_menu_link_defaults().
* @todo This function should be removed/refactored.
*/
function menu_link_rebuild_defaults() {
$module_handler = \Drupal::moduleHandler();
if (!$module_handler->moduleExists('menu_link')) {
function _menu_navigation_links_rebuild($menu) {
if (!\Drupal::moduleHandler()->moduleExists('menu_link')) {
// The Menu link module may not be available during install, so rebuild
// when possible.
return;
}
/** @var \Drupal\menu_link\MenuLinkStorageControllerInterface $menu_link_storage */
$menu_link_storage = \Drupal::entityManager()
$menu_link_controller = \Drupal::entityManager()
->getStorageController('menu_link');
$links = array();
$children = array();
$top_links = array();
$all_links = menu_link_get_defaults();
if ($all_links) {
foreach ($all_links as $machine_name => $link) {
// Add normal and suggested items as links.
$router_items = array();
foreach ($menu as $path => $router_item) {
if ($router_item['_visible']) {
$router_items[$path] = $router_item;
$sort[$path] = $router_item['_number_parts'];
}
}
if ($router_items) {
// Keep an array of processed menu links, to allow
// Drupal\menu_link\MenuLinkStorageController::save() to check this for
// parents instead of querying the database.
$parent_candidates = array();
// Make sure no child comes before its parent.
array_multisort($sort, SORT_NUMERIC, $router_items);
foreach ($router_items as $key => $router_item) {
// For performance reasons, do a straight query now and convert to a menu
// link entity later.
// @todo revisit before release.
$existing_item = db_select('menu_links')
->fields('menu_links')
->condition('machine_name', $machine_name)
->condition('link_path', $router_item['path'])
->condition('module', 'system')
->execute()->fetchObject();
->execute()->fetchAll();
if ($existing_item) {
$existing_item = reset($existing_item);
$existing_item->options = unserialize($existing_item->options);
$existing_item->route_parameters = unserialize($existing_item->route_parameters);
$link['mlid'] = $existing_item->mlid;
$link['plid'] = $existing_item->plid;
$link['uuid'] = $existing_item->uuid;
$link['customized'] = $existing_item->customized;
$link['updated'] = $existing_item->updated;
$menu_link = $menu_link_storage->createFromDefaultLink($link);
// Convert the existing item to a typed object.
/** @var \Drupal\menu_link\MenuLinkInterface $existing_item */
$existing_item = $menu_link_storage->create(get_object_vars($existing_item));
if (!$existing_item->customized) {
// A change in hook_menu_link_defaults() may move the link to a
// different menu.
if (!empty($link['menu_name']) && ($link['menu_name'] != $existing_item->menu_name)) {
$menu_link->plid = NULL;
$menu_link->menu_name = $link['menu_name'];
}
$menu_link->original = $existing_item;
$router_item['mlid'] = $existing_item->mlid;
$router_item['uuid'] = $existing_item->uuid;
// A change in hook_menu may move the link to a different menu
if (empty($router_item['menu_name']) || ($router_item['menu_name'] == $existing_item->menu_name)) {
$router_item['menu_name'] = $existing_item->menu_name;
$router_item['plid'] = $existing_item->plid;
}
else {
// It moved to a new menu.
// Let Drupal\menu_link\MenuLinkStorageController::save() try to find
// a new parent based on the path.
unset($router_item['plid']);
}
$router_item['has_children'] = $existing_item->has_children;
$router_item['updated'] = $existing_item->updated;
// Convert the existing item to a typed object.
$existing_item = $menu_link_controller->create(get_object_vars($existing_item));
}
else {
if (empty($link['route_name']) && empty($link['link_path'])) {
watchdog('error', 'Menu_link %machine_name does neither provide a route_name nor a lin_path, so it got skipped.');
continue;
}
$menu_link = $menu_link_storage->createFromDefaultLink($link);
$existing_item = NULL;
}
if (!empty($link['parent'])) {
$children[$link['parent']][$machine_name] = $machine_name;
$menu_link->parent = $link['parent'];
if (empty($link['menu_name'])) {
// Unset the default menu name so it's populated from the parent.
unset($menu_link->menu_name);
}
if ($existing_item && $existing_item->customized) {
$parent_candidates[$existing_item->mlid] = $existing_item;
}
else {
// A top level link - we need them to root our tree.
$top_links[$machine_name] = $machine_name;
$menu_link->plid = 0;
$menu_link = MenuLink::buildFromRouterItem($router_item);
$menu_link->original = $existing_item;
$menu_link->parentCandidates = $parent_candidates;
$menu_link_controller->save($menu_link);
$parent_candidates[$menu_link->id()] = $menu_link;
unset($router_items[$key]);
}
$links[$machine_name] = $menu_link;
}
}
foreach ($top_links as $machine_name) {
_menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links);
}
// Handle any children we didn't find starting from top-level links.
foreach ($children as $orphan_links) {
foreach ($orphan_links as $machine_name) {
// Force it to the top level.
$links[$machine_name]->plid = 0;
_menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links);
$paths = array_keys($menu);
// Updated and customized items whose router paths are gone need new ones.
$menu_links = $menu_link_controller->loadUpdatedCustomized($paths);
foreach ($menu_links as $menu_link) {
$router_path = _menu_find_router_path($menu_link->link_path);
if (!empty($router_path) && ($router_path != $menu_link->router_path || $menu_link->updated)) {
// If the router path and the link path matches, it's surely a working
// item, so we clear the updated flag.
$updated = $menu_link->updated && $router_path != $menu_link->link_path;
$menu_link->router_path = $router_path;
$menu_link->updated = (int) $updated;
$menu_link_controller->save($menu_link);
}
}
// Find any item whose router path does not exist any more.
if ($all_links) {
$query = \Drupal::entityQuery('menu_link')
->condition('machine_name', array_keys($all_links), 'NOT IN')
->condition('external', 0)
->condition('updated', 0)
->condition('customized', 0)
->sort('depth', 'DESC');
$result = $query->execute();
}
else {
$result = array();
}
$query = \Drupal::entityQuery('menu_link')
->condition('router_path', $paths, 'NOT IN')
->condition('external', 0)
->condition('updated', 0)
->condition('customized', 0)
->sort('depth', 'DESC');
$result = $query->execute();
// Remove all such items. Starting from those with the greatest depth will
// minimize the amount of re-parenting done by the menu link controller.
......@@ -2951,7 +2983,7 @@ function _menu_router_build($callbacks, $save = FALSE) {
// previous iteration assigned one already), try to find the menu name
// of the parent item in the currently stored menu links.
if (!isset($parent['menu_name'])) {
$menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE link_path = :link_path AND module = 'system'", array(':link_path' => $parent_path))->fetchField();
$menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path AND module = 'system'", array(':router_path' => $parent_path))->fetchField();
if ($menu_name) {
$parent['menu_name'] = $menu_name;
}
......
......@@ -5,7 +5,6 @@
* Functions to handle paths in Drupal.
*/
use Drupal\Core\Routing\RequestHelper;
use Symfony\Component\HttpFoundation\Request;
/**
......@@ -196,23 +195,9 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) {
global $menu_admin;
// We indicate that a menu administrator is running the menu access check.
$menu_admin = TRUE;
/** @var $route_provider \Drupal\Core\Routing\RouteProviderInterface */
$route_provider = \Drupal::service('router.route_provider');
if ($dynamic_allowed && preg_match('/\/\%/', $path)) {
$router_path = '/' . str_replace('%', '{}', $path);
}
else {
$router_path = $path;
}
if ($path == '<front>' || url_is_external($path)) {
$item = array('access' => TRUE);
}
elseif (($collection = $route_provider->getRoutesByPattern('/' . $router_path)) && $collection->count() > 0) {
$routes = $collection->all();
$route_name = key($routes);
}
elseif ($dynamic_allowed && preg_match('/\/\%/', $path)) {
// Path is dynamic (ie 'user/%'), so check directly against menu_router table.
if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) {
......@@ -221,22 +206,16 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) {
$item['external'] = FALSE;
$item['options'] = '';
_menu_link_translate($item);
$route_name = $item['route_name'];
}
}
else {
$item = menu_get_item($path);
$route_name = $item['route_name'];
}
// Check the new routing system.
if (!empty($route_name)) {
if (!empty($item['route_name'])) {
$map = array();
$route = \Drupal::service('router.route_provider')->getRouteByName($route_name);
$request = RequestHelper::duplicate(\Drupal::request(), '/' . $path);
$request->attributes->set('_system_path', $path);
$request->attributes->set('_menu_admin', TRUE);
$item['access'] = menu_item_route_access($route, $path, $map, $request);
$route = \Drupal::service('router.route_provider')->getRouteByName($item['route_name']);
$item['access'] = menu_item_route_access($route, $path, $map);
}
$menu_admin = FALSE;
return $item && $item['access'];
......
......@@ -2137,14 +2137,6 @@ function template_preprocess_html(&$variables) {
'name' => String::checkPlain($site_config->get('name')),
);
}
// @todo Remove once views is not bypassing the view subscriber anymore.
// @see http://drupal.org/node/2068471
elseif (drupal_is_front_page()) {
$head_title = array(
'title' => t('Home'),
'name' => String::checkPlain($site_config->get('name')),
);
}