Commit 0a423910 authored by webchick's avatar webchick

Issue #2177031 by dawehner, tim.plunkett, Berdir: Remove menu_get_item() and every use of it. .

parent 5072c181
......@@ -212,7 +212,7 @@ services:
arguments: ['@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager', '@access_manager', '@current_user']
plugin.manager.menu.local_task:
class: Drupal\Core\Menu\LocalTaskManager
arguments: ['@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager', '@access_manager', '@current_user']
arguments: ['@controller_resolver', '@request', '@router.route_provider', '@router.builder', '@module_handler', '@cache.cache', '@language_manager', '@access_manager', '@current_user']
plugin.manager.menu.contextual_link:
class: Drupal\Core\Menu\ContextualLinkManager
arguments: ['@controller_resolver', '@module_handler', '@cache.cache', '@language_manager', '@access_manager', '@current_user']
......
......@@ -431,78 +431,6 @@ function menu_unserialize($data, $map) {
}
}
/**
* Replaces the statically cached item for a given path.
*
* @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 active trail, and the page help to be modified in one
* call.
*/
function menu_set_item($path, $router_item) {
menu_get_item($path, $router_item);
}
/**
* Gets a router item.
*
* @param $path
* The path; for example, 'node/5'. The function will find the corresponding
* node/% item and return that.
* @param $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', and 'access_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__);
if (!isset($path)) {
$path = current_path();
}
if (isset($router_item)) {
$router_items[$path] = $router_item;
}
if (!isset($router_items[$path])) {
\Drupal::service('router.builder')->rebuildIfNeeded();
$original_map = arg(NULL, $path);
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
$ancestors = menu_get_ancestors($parts);
$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
if ($router_item) {
// Allow modules to alter the router item before it is translated and
// checked for access.
\Drupal::moduleHandler()->alter('menu_get_item', $router_item, $path, $original_map);
$map = _menu_translate($router_item, $original_map);
$router_item['original_map'] = $original_map;
if ($map === FALSE) {
$router_items[$path] = FALSE;
return FALSE;
}
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_items[$path] = $router_item;
}
return $router_items[$path];
}
/**
* Loads objects into the map as defined in the $item['load_functions'].
*
......@@ -913,7 +841,7 @@ function _menu_link_translate(&$item) {
* @param \Symfony\Component\Routing\Route $route
* Router for the given menu item.
* @param string $href
* Menu path as returned by $item['href'] of menu_get_item().
* The menu path with '%' replaced by arguments.
* @param array $map
* An array of path arguments; for example, array('node', '5').
* @param \Symfony\Component\HttpFoundation\Request $request
......@@ -1006,8 +934,6 @@ function menu_tree_output($tree) {
}
}
$router_item = menu_get_item();
$num_items = count($items);
foreach ($items as $data) {
$class = array();
// Set a class for the <li>-tag. Since $data['below'] may contain local
......@@ -1201,16 +1127,6 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
$system_path = $request->attributes->get('_system_path');
$page_not_403 = 1;
}
// @todo Remove once the old router system is removed.
elseif ($request->attributes->has('_legacy')) {
// Page is a 404 if no item is loaded.
if ($item = menu_get_item($active_path)) {
$system_path = $item['href'];
// Variable to indicate that we are not serving an access denied response
// for this page. Used to limit the number of links rendered.
$page_not_403 = (int) $item['access'];
}
}
if (isset($system_path)) {
if (isset($max_depth)) {
$max_depth = min($max_depth, MENU_MAX_DEPTH);
......@@ -1778,7 +1694,6 @@ function menu_navigation_links($menu_name, $level = 0) {
}
// Create a single level of links.
$router_item = menu_get_item();
$links = array();
foreach ($tree as $item) {
if (!$item['link']['hidden']) {
......@@ -1794,7 +1709,7 @@ function menu_navigation_links($menu_name, $level = 0) {
// 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 ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != current_path()) {
if ($item['link']['href'] != current_path()) {
$l['attributes']['class'][] = 'active';
}
// Keyed with the unique mlid to generate classes in links.html.twig.
......@@ -1843,22 +1758,6 @@ function menu_local_tasks($level = 0) {
}
}
// @todo Remove when all local tasks/actions are converted to plugins.
$router_item = menu_get_item();
// If this router item points to its parent, start from the parents to
// compute tabs and actions.
if ($router_item && ($router_item['type'] & MENU_LINKS_TO_PARENT)) {
$router_item = menu_get_item($router_item['tab_parent_href']);
}
// If we failed to fetch a router item or the current user doesn't have
// access to it, don't bother computing the tabs.
if ((!$route_name && !$router_item) || ($router_item && !$router_item['access'])) {
return $empty;
}
if ($router_item) {
_menu_get_legacy_tasks($router_item, $data, $root_path);
}
// Allow modules to dynamically add further tasks.
$module_handler = \Drupal::moduleHandler();
foreach ($module_handler->getImplementations('menu_local_tasks') as $module) {
......@@ -1882,170 +1781,6 @@ function menu_local_tasks($level = 0) {
return $empty;
}
/**
* Finds legacy local tasks/actions.
*
* @param array $router_item
* The current router item.
* @param array $data
* An associative array of local tasks/actions.
* @param string $root_path
*
* @deprecated Remove once all local tasks/actions are converted to plugins.
*/
function _menu_get_legacy_tasks($router_item, &$data, &$root_path) {
// Get all tabs (also known as local tasks) and the root page.
$result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC))
->fields('menu_router')
->condition('tab_root', $router_item['tab_root'])
->orderBy('weight')
->orderBy('title')
->execute();
$map = $router_item['original_map'];
$children = array();
$tasks = array();
$root_path = $router_item['path'];
foreach ($result as $item) {
_menu_translate($item, $map, TRUE);
if ($item['tab_parent']) {
// All tabs, but not the root page.
$children[$item['tab_parent']][$item['path']] = $item;
}
// Store the translated item for later use.
$tasks[$item['path']] = $item;
}
// Find all tabs below the current path.
$path = $router_item['path'];
// Tab parenting may skip levels, so the number of parts in the path may not
// equal the depth. Thus we use the $depth counter (offset by 1000 for ksort).
$depth = 1001;
$tabs = array();
$actions = array();
while (isset($children[$path])) {
$tabs_current = array();
$actions_current = array();
$next_path = '';
$tab_count = 0;
$action_count = 0;
foreach ($children[$path] as $item) {
// Local tasks can be normal items too, so bitmask with
// MENU_IS_LOCAL_TASK before checking.
if (!($item['type'] & MENU_IS_LOCAL_TASK)) {
// This item is not a tab, skip it.
continue;
}
if ($item['access']) {
$link = $item;
// The default task is always active. As tabs can be normal items
// too, so bitmask with MENU_LINKS_TO_PARENT before checking.
if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
// Find the first parent which is not a default local task or action.
for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']);
// Use the path of the parent instead.
$link['href'] = $tasks[$p]['href'];
// Mark the link as active, if the current path happens to be the
// path of the default local task itself (i.e., instead of its
// tab_parent_href or tab_root_href). Normally, links for default
// local tasks link to their parent, but the path of default local
// tasks can still be accessed directly, in which case this link
// would not be marked as active, since l() only compares the href
// with current_path().
if ($link['href'] != current_path()) {
$link['localized_options']['attributes']['class'][] = 'active';
}
$tabs_current[$link['href']] = array(
'#theme' => 'menu_local_task',
'#link' => $link,
'#active' => TRUE,
'#weight' => isset($link['weight']) ? $link['weight'] : NULL,
);
$next_path = $item['path'];
$tab_count++;
}
else {
$tabs_current[$link['href']] = array(
'#theme' => 'menu_local_task',
'#link' => $link,
'#weight' => isset($link['weight']) ? $link['weight'] : NULL,
);
$tab_count++;
}
}
}
$path = $next_path;
$tabs[$depth] = $tabs_current;
$actions = array_merge($actions, $actions_current);
$depth++;
}
$data['actions'] = $actions;
// Find all tabs at the same level or above the current one.
$parent = $router_item['tab_parent'];
$path = $router_item['path'];
$current = $router_item;
$depth = 1000;
while (isset($children[$parent])) {
$tabs_current = array();
$next_path = '';
$next_parent = '';
$count = 0;
foreach ($children[$parent] as $item) {
if ($item['access']) {
$count++;
$link = $item;
// Local tasks can be normal items too, so bitmask with
// MENU_LINKS_TO_PARENT before checking.
if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
// Find the first parent which is not a default local task.
for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']);
// Use the path of the parent instead.
$link['href'] = $tasks[$p]['href'];
if ($item['path'] == $router_item['path']) {
$root_path = $tasks[$p]['path'];
}
}
// We check for the active tab.
if ($item['path'] == $path) {
// Mark the link as active, if the current path is a (second-level)
// local task of a default local task. Since this default local task
// links to its parent, l() will not mark it as active, as it only
// compares the link's href to current_path().
if ($link['href'] != current_path()) {
$link['localized_options']['attributes']['class'][] = 'active';
}
$tabs_current[$link['href']] = array(
'#theme' => 'menu_local_task',
'#link' => $link,
'#active' => TRUE,
'#weight' => isset($link['weight']) ? $link['weight'] : NULL,
);
$next_path = $item['tab_parent'];
if (isset($tasks[$next_path])) {
$next_parent = $tasks[$next_path]['tab_parent'];
}
}
else {
$tabs_current[$link['href']] = array(
'#theme' => 'menu_local_task',
'#link' => $link,
'#weight' => isset($link['weight']) ? $link['weight'] : NULL,
);
}
}
}
$path = $next_path;
$parent = $next_parent;
$tabs[$depth] = $tabs_current;
$depth--;
}
// Sort by depth.
ksort($tabs);
// Remove the depth, we are interested only in their relative placement.
$tabs = array_values($tabs);
$data['tabs'] += $tabs;
}
/**
* Returns the rendered local tasks at the top level.
*/
......@@ -2566,6 +2301,10 @@ function menu_link_get_defaults() {
* Builds menu links for the items returned from hook_menu_link_defaults().
*/
function menu_link_rebuild_defaults() {
// Ensure that all configuration used to build the menu items are loaded
// without overrides.
$old_state = \Drupal::configFactory()->getOverrideState();
\Drupal::configFactory()->setOverrideState(FALSE);
$module_handler = \Drupal::moduleHandler();
if (!$module_handler->moduleExists('menu_link')) {
// The Menu link module may not be available during install, so rebuild
......@@ -2672,6 +2411,7 @@ function menu_link_rebuild_defaults() {
if ($result) {
menu_link_delete_multiple($result, TRUE);
}
\Drupal::configFactory()->setOverrideState($old_state);
}
/**
......
......@@ -224,10 +224,6 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) {
$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)) {
$map = array();
......@@ -239,5 +235,5 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) {
$item['access'] = menu_item_route_access($route, $path, $map, $request);
}
$menu_admin = FALSE;
return $item && $item['access'];
return !empty($item['access']);
}
......@@ -18,6 +18,7 @@
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
......@@ -83,6 +84,13 @@ class LocalTaskManager extends DefaultPluginManager {
*/
protected $routeProvider;
/**
* The route builder.
*
* @var \Drupal\Core\Routing\RouteBuilderInterface
*/
protected $routeBuilder;
/**
* The access manager.
*
......@@ -106,6 +114,8 @@ class LocalTaskManager extends DefaultPluginManager {
* The request object to use for building titles and paths for plugin instances.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider to load routes by name.
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* The route builder.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
......@@ -117,13 +127,14 @@ class LocalTaskManager extends DefaultPluginManager {
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*/
public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager, AccessManager $access_manager, AccountInterface $account) {
public function __construct(ControllerResolverInterface $controller_resolver, Request $request, RouteProviderInterface $route_provider, RouteBuilderInterface $route_builder, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManager $language_manager, AccessManager $access_manager, AccountInterface $account) {
$this->discovery = new YamlDiscovery('local_tasks', $module_handler->getModuleDirectories());
$this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
$this->factory = new ContainerFactory($this);
$this->controllerResolver = $controller_resolver;
$this->request = $request;
$this->routeProvider = $route_provider;
$this->routeBuilder = $route_builder;
$this->accessManager = $access_manager;
$this->account = $account;
$this->alterInfo($module_handler, 'local_tasks');
......@@ -177,6 +188,9 @@ public function getLocalTasksForRoute($route_name) {
$children = $cache->data['children'];
}
else {
// Maybe some code asked to rebuild the routes, so rebuild the router
// as we rely on having proper existing routes in dynamic local tasks.
$this->routeBuilder->rebuildIfNeeded();
$definitions = $this->getDefinitions();
// We build the hierarchy by finding all tabs that should
// appear on the current route.
......
......@@ -10,5 +10,5 @@ custom_block_add_action:
appears_on:
- block.admin_display
- block.admin_display_theme
- custom_block.list
class: \Drupal\custom_block\Plugin\Menu\LocalAction\CustomBlockAddLocalAction
......@@ -37,25 +37,6 @@ function custom_block_help($path, $arg) {
}
}
/**
* Implements hook_menu_local_tasks().
*/
function custom_block_menu_local_tasks(&$data, $route_name) {
if ($route_name == 'custom_block.list') {
// @todo Move to a LocalAction plugin when https://drupal.org/node/2045267
// allows local actions to work with query strings.
$item = menu_get_item('block/add');
if ($item['access']) {
// Add a destination parameter.
$item['localized_options']['query']['destination'] = 'admin/structure/block/custom-blocks';
$data['actions']['block/add'] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Implements hook_menu().
*/
......
......@@ -8,6 +8,7 @@
namespace Drupal\custom_block\Plugin\Menu\LocalAction;
use Drupal\Core\Menu\LocalActionDefault;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
/**
......@@ -24,6 +25,10 @@ public function getOptions(Request $request) {
if ($request->attributes->has('theme')) {
$options['query']['theme'] = $request->attributes->get('theme');
}
// Adds a destination on custom block listing.
if ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'custom_block.list') {
$options['query']['destination'] = 'admin/structure/block/custom-blocks';
}
return $options;
}
......
......@@ -614,8 +614,8 @@ public function bookTreeAllData($menu_name, $link = NULL, $max_depth = NULL) {
* @return array
* A structured array to be rendered by drupal_render().
*
* Note: copied from menu_tree_output() but some hacky code using
* menu_get_item() was removed.
* @todo This was copied from menu_tree_output() but with some changes that
* may be obsolete. Attempt to resolve the differences.
*/
public function bookTreeOutput(array $tree) {
$build = array();
......
......@@ -3,3 +3,8 @@ node.type_add:
title: 'Add content type'
appears_on:
- node.overview_types
node.add_page:
route_name: node.add_page
title: 'Add content'
appears_on:
- node.content_overview
......@@ -1008,23 +1008,6 @@ function node_menu_link_defaults() {
return $links;
}
/**
* Implements hook_menu_local_tasks().
*/
function node_menu_local_tasks(&$data, $route_name) {
// Add action link to 'node/add' on 'admin/content' page.
// @todo Switch to inspecting route name in https://drupal.org/node/2021161.
if (current_path() == 'admin/content') {
$item = menu_get_item('node/add');
if ($item['access']) {
$data['actions'][] = array(
'#theme' => 'menu_local_action',
'#link' => $item,
);
}
}
}
/**
* Title callback: Displays the node's title.
*
......
......@@ -329,7 +329,7 @@ function shortcut_valid_link($path) {
}
// An empty path is valid too and will be converted to <front>.
return (!url_is_external($path) && (\Drupal::service('router.route_provider')->getRoutesByPattern('/' . $path)->count() > 0 || menu_get_item($path))) || empty($path) || $path == '<front>';
return (!url_is_external($path) && (\Drupal::service('router.route_provider')->getRoutesByPattern('/' . $path)->count() > 0)) || empty($path) || $path == '<front>';
}
/**
......
......@@ -8,6 +8,9 @@
namespace Drupal\system\Form;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Entity\Query\QueryFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
......@@ -38,6 +41,20 @@ class ModulesListForm extends FormBase {
*/
protected $keyValueExpirable;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The query factory.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $queryFactory;
/**
* {@inheritdoc}
*/
......@@ -45,7 +62,9 @@ public static function create(ContainerInterface $container) {
return new static(
$container->get('module_handler'),
$container->get('keyvalue.expirable')->get('module_list'),
$container->get('access_manager')
$container->get('access_manager'),
$container->get('entity.manager'),
$container->get('entity.query')
);
}
......@@ -58,11 +77,17 @@ public static function create(ContainerInterface $container) {
* The key value expirable factory.
* @param \Drupal\Core\Access\AccessManager $access_manager
* Access manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
* The entity query factory.
*/
public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager) {
public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreExpirableInterface $key_value_expirable, AccessManager $access_manager, EntityManagerInterface $entity_manager, QueryFactory $query_factory) {
$this->moduleHandler = $module_handler;
$this->keyValueExpirable = $key_value_expirable;
$this->accessManager = $access_manager;
$this->entityManager = $entity_manager;
$this->queryFactory = $query_factory;
}
/**
......@@ -199,7 +224,11 @@ protected function buildRow(array $modules, $module, $distribution) {
$row['links']['configure'] = array();
if ($module->status && isset($module->info['configure'])) {
if ($this->accessManager->checkNamedRoute($module->info['configure'], array(), \Drupal::currentUser())) {
$item = menu_get_item(trim($this->url($module->info['configure']), '/'));
$result = $this->queryFactory->get('menu_link')
->condition('route_name', $module->info['configure'])
->execute();
$menu_items = $this->entityManager->getStorageController('menu_link')->loadMultiple($result);
$item = reset($menu_items);
$row['links']['configure'] = array(
'#type' => 'link',
'#title' => $this->t('Configure'),
......
......@@ -8,9 +8,11 @@
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Entity\EntityManagerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* System Manager Service.
......@@ -60,6 +62,13 @@ class SystemManager {
*/
const REQUIREMENT_ERROR = 2;
/**
* The request object.
*