Commit 8608fed6 authored by webchick's avatar webchick

Issue #2107533 by tim.plunkett, dawehner, pwolanin, Berdir: Remove {menu_router}.

parent 3ae51ab6
......@@ -326,7 +326,7 @@ services:
arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state']
router.rebuild_subscriber:
class: Drupal\Core\EventSubscriber\RouterRebuildSubscriber
arguments: ['@router.builder']
arguments: ['@router.builder', '@lock']
tags:
- { name: event_subscriber }
path.alias_manager.cached:
......
This diff is collapsed.
......@@ -5,8 +5,9 @@
* Functions to handle paths in Drupal.
*/
use Drupal\Component\Utility\Url;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\RequestHelper;
use Symfony\Component\HttpFoundation\Request;
/**
* Check if the current page is the front page.
......@@ -183,57 +184,42 @@ function path_get_admin_paths() {
/**
* Checks a path exists and the current user has access to it.
*
* @param $path
* @param string $path
* The path to check.
* @param $dynamic_allowed
* Whether paths with menu wildcards (like user/%) should be allowed.
*
* @return
* @return bool
* TRUE if it is a valid path AND the current user has access permission,
* FALSE otherwise.
*/
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;
function drupal_valid_path($path) {
// External URLs and the front page are always valid.
if ($path == '<front>' || Url::isExternal($path)) {
return TRUE;
}
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);
// Check the routing system.
$collection = \Drupal::service('router.route_provider')->getRoutesByPattern('/' . $path);
if ($collection->count() == 0) {
return FALSE;
}
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()) {
$item['link_path'] = $item['path'];
$item['link_title'] = $item['title'];
$item['external'] = FALSE;
$item['options'] = '';
_menu_link_translate($item);
$route_name = $item['route_name'];
}
$request = RequestHelper::duplicate(\Drupal::request(), '/' . $path);
$request->attributes->set('_system_path', $path);
// We indicate that a menu administrator is running the menu access check.
$request->attributes->set('_menu_admin', TRUE);
// Attempt to match this path to provide a fully built request to the
// access checker.
try {
$request->attributes->add(\Drupal::service('router')->matchRequest($request));
}
// Check the new routing system.
if (!empty($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);
catch (ParamNotConvertedException $e) {
return FALSE;
}
$menu_admin = FALSE;
return !empty($item['access']);
// Consult the accsss manager.
$routes = $collection->all();
$route = reset($routes);
return \Drupal::service('access_manager')->check($route, $request, \Drupal::currentUser());
}
......@@ -8,6 +8,7 @@
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\Event;
......@@ -16,7 +17,7 @@
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Rebuilds the router and menu_router if necessary.
* Rebuilds the default menu links and runs menu-specific code if necessary.
*/
class RouterRebuildSubscriber implements EventSubscriberInterface {
......@@ -25,14 +26,22 @@ class RouterRebuildSubscriber implements EventSubscriberInterface {
*/
protected $routeBuilder;
/**
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* Constructs the RouterRebuildSubscriber object.
*
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* The route builder.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
*/
public function __construct(RouteBuilderInterface $route_builder) {
public function __construct(RouteBuilderInterface $route_builder, LockBackendInterface $lock) {
$this->routeBuilder = $route_builder;
$this->lock = $lock;
}
/**
......@@ -46,16 +55,44 @@ public function onKernelTerminate(PostResponseEvent $event) {
}
/**
* Rebuilds the menu_router and deletes the local_task cache tag.
* Rebuilds the menu links and deletes the local_task cache tag.
*
* @param \Symfony\Component\EventDispatcher\Event $event
* The event object.
*/
public function onRouterRebuild(Event $event) {
menu_router_rebuild();
$this->menuLinksRebuild();
Cache::deleteTags(array('local_task' => 1));
}
/**
* Perform menu-specific rebuilding.
*/
protected function menuLinksRebuild() {
if ($this->lock->acquire(__FUNCTION__)) {
$transaction = db_transaction();
try {
// Ensure the menu links are up to date.
menu_link_rebuild_defaults();
// Clear the menu, page and block caches.
menu_cache_clear_all();
_menu_clear_page_cache();
}
catch (\Exception $e) {
$transaction->rollback();
watchdog_exception('menu', $e);
}
$this->lock->release(__FUNCTION__);
}
else {
// Wait for another request that is already doing this work.
// We choose to block here since otherwise the router item may not
// be available during routing resulting in a 404.
$this->lock->wait(__FUNCTION__);
}
}
/**
* Registers the methods in this class that should be listeners.
*
......
......@@ -70,7 +70,7 @@ public function cleanup(Request $request) {}
*/
public function handleException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
if (user_is_anonymous() && $exception instanceof AccessDeniedHttpException) {
if ($GLOBALS['user']->isAnonymous() && $exception instanceof AccessDeniedHttpException) {
if (!$this->applies($event->getRequest())) {
$site_name = $this->configFactory->get('system.site')->get('name');
global $base_url;
......
......@@ -158,7 +158,7 @@ public function testsCustomBlockAddTypes() {
foreach ($themes as $default_theme) {
// Change the default theme.
$theme_settings->set('default', $default_theme)->save();
menu_router_rebuild();
\Drupal::service('router.builder')->rebuild();
// For each enabled theme, go to its block page and test the redirects.
$themes = array('bartik', 'stark', 'seven');
......
......@@ -865,7 +865,7 @@ protected function _menu_tree_check_access(&$tree) {
* 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).
* link-specific preparation.
*
* @param $item
* A menu link.
......
......@@ -228,7 +228,7 @@ function contact_form_user_form_alter(&$form, &$form_state) {
'#weight' => 5,
);
$account = $form_state['controller']->getEntity();
$account_data = !user_is_anonymous() ? \Drupal::service('user.data')->get('contact', $account->id(), 'enabled') : NULL;
$account_data = !\Drupal::currentUser()->isAnonymous() ? \Drupal::service('user.data')->get('contact', $account->id(), 'enabled') : NULL;
$form['contact']['contact'] = array(
'#type' => 'checkbox',
'#title' => t('Personal contact form'),
......
......@@ -181,62 +181,6 @@ function content_translation_entity_operation_alter(array &$operations, \Drupal\
}
}
/**
* Implements hook_menu().
*
* @todo Split this into route definition and menu link definition. See
* https://drupal.org/node/1987882 and https://drupal.org/node/2047633.
*/
function content_translation_menu() {
$items = array();
// Create tabs for all possible entity types.
foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
// Provide the translation UI only for enabled types.
if (content_translation_enabled($entity_type_id)) {
$path = _content_translation_link_to_router_path($entity_type_id, $entity_type->getLinkTemplate('canonical'));
$entity_position = count(explode('/', $path)) - 1;
$keys = array_flip(array('load_arguments'));
$translation = $entity_type->get('translation');
$menu_info = array_intersect_key($translation['content_translation'], $keys) + array('file' => 'content_translation.pages.inc');
$item = array();
// Plugin annotations cannot contain spaces, thus we need to restore them
// from underscores.
foreach ($menu_info as $key => $value) {
$item[str_replace('_', ' ', $key)] = $value;
}
// Add translation callback.
// @todo Add the access callback instead of replacing it as soon as the
// routing system supports multiple callbacks.
$language_position = $entity_position + 3;
$args = array($entity_position, $language_position, $language_position + 1);
$items["$path/translations/add/%language/%language"] = array(
'title' => 'Add',
'route_name' => "content_translation.translation_add_$entity_type_id",
'weight' => 1,
);
// Edit translation callback.
$args = array($entity_position, $language_position);
$items["$path/translations/edit/%language"] = array(
'title' => 'Edit',
'route_name' => "content_translation.translation_edit_$entity_type_id",
'weight' => 1,
);
// Delete translation callback.
$items["$path/translations/delete/%language"] = array(
'title' => 'Delete',
'route_name' => "content_translation.delete_$entity_type_id",
) + $item;
}
}
return $items;
}
/**
* Implements hook_menu_alter().
*
......@@ -973,7 +917,7 @@ function content_translation_language_configuration_element_submit(array $form,
if (content_translation_enabled($context['entity_type'], $context['bundle']) != $enabled) {
content_translation_set_config($context['entity_type'], $context['bundle'], 'enabled', $enabled);
entity_info_cache_clear();
menu_router_rebuild();
\Drupal::service('router.builder')->setRebuildNeeded();
}
}
......@@ -1041,5 +985,5 @@ function content_translation_save_settings($settings) {
// Ensure entity and menu router information are correctly rebuilt.
entity_info_cache_clear();
menu_router_rebuild();
\Drupal::service('router.builder')->setRebuildNeeded();
}
......@@ -166,7 +166,7 @@ protected function enableTranslation() {
content_translation_set_config($this->entityTypeId, $this->bundle, 'enabled', TRUE);
drupal_static_reset();
entity_info_cache_clear();
menu_router_rebuild();
\Drupal::service('router.builder')->rebuild();
}
/**
......
......@@ -104,7 +104,7 @@ public function submit(array $form, array &$form_state) {
return;
}
// Reset all the menu links defined by the system via hook_menu().
// Reset all the menu links defined by the system via hook_menu_link_defaults().
// @todo Convert this to an EFQ.
$result = $this->connection->query("SELECT mlid FROM {menu_links} WHERE menu_name = :menu AND module = 'system' ORDER BY depth ASC", array(':menu' => $this->entity->id()), array('fetch' => \PDO::FETCH_ASSOC))->fetchCol();
$menu_links = $this->storageController->loadMultiple($result);
......
......@@ -235,8 +235,6 @@ public function save(array $form, array &$form_state) {
* their form submit handler.
*/
protected function buildOverviewForm(array &$form, array &$form_state) {
global $menu_admin;
// Ensure that menu_overview_form_submit() knows the parents of this form
// section.
$form['#tree'] = TRUE;
......@@ -261,10 +259,11 @@ protected function buildOverviewForm(array &$form, array &$form_state) {
$tree = menu_tree_data($links);
$node_links = array();
menu_tree_collect_node_links($tree, $node_links);
// We indicate that a menu administrator is running the menu access check.
$menu_admin = TRUE;
$this->getRequest()->attributes->set('_menu_admin', TRUE);
menu_tree_check_access($tree, $node_links);
$menu_admin = FALSE;
$this->getRequest()->attributes->set('_menu_admin', FALSE);
$form = array_merge($form, $this->buildOverviewTreeForm($tree, $delta));
$form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/' . $this->entity->id() .'/add')));
......
......@@ -17,7 +17,6 @@ function menu_install() {
// \Drupal\Core\Extension\ModuleHandler::install().
// @see https://drupal.org/node/2181151
\Drupal::service('router.builder')->rebuild();
menu_router_rebuild();
if (\Drupal::moduleHandler()->moduleExists('node')) {
$node_types = array_keys(node_type_get_names());
foreach ($node_types as $type_id) {
......@@ -33,5 +32,5 @@ function menu_install() {
* Implements hook_uninstall().
*/
function menu_uninstall() {
menu_router_rebuild();
\Drupal::service('router.builder')->setRebuildNeeded();
}
......@@ -7,15 +7,9 @@
namespace Drupal\menu_link;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Entity\DatabaseStorageController;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\field\FieldInfo;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
/**
* Controller class for menu links.
......@@ -25,13 +19,6 @@
*/
class MenuLinkStorageController extends DatabaseStorageController implements MenuLinkStorageControllerInterface {
/**
* Contains all {menu_router} fields without weight.
*
* @var array
*/
protected static $routerItemFields;
/**
* Indicates whether the delete operation should re-parent children items.
*
......@@ -39,35 +26,6 @@ class MenuLinkStorageController extends DatabaseStorageController implements Men
*/
protected $preventReparenting = FALSE;
/**
* The route provider service.
*
* @var \Symfony\Cmf\Component\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* Overrides DatabaseStorageController::__construct().
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Database\Connection $database
* The database connection to be used.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID Service.
* @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
* The route provider service.
*/
public function __construct(EntityTypeInterface $entity_type, Connection $database, UuidInterface $uuid_service, RouteProviderInterface $route_provider) {
parent::__construct($entity_type, $database, $uuid_service);
$this->routeProvider = $route_provider;
if (empty(static::$routerItemFields)) {
static::$routerItemFields = array_diff(drupal_schema_fields_sql('menu_router'), array('weight'));
}
}
/**
* {@inheritdoc}
*/
......@@ -80,29 +38,6 @@ public function create(array $values = array()) {
return parent::create($values);
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('database'),
$container->get('uuid'),
$container->get('router.route_provider')
);
}
/**
* Overrides DatabaseStorageController::buildQuery().
*/
protected function buildQuery($ids, $revision_id = FALSE) {
$query = parent::buildQuery($ids, $revision_id);
// Specify additional fields from the {menu_router} table.
$query->leftJoin('menu_router', 'm', 'base.link_path = m.path');
$query->fields('m', static::$routerItemFields);
return $query;
}
/**
* Overrides DatabaseStorageController::save().
*/
......
......@@ -55,7 +55,7 @@ class OptionsFieldUnitTestBase extends FieldUnitTestBase {
*/
public function setUp() {
parent::setUp();
$this->installSchema('system', array('router', 'menu_router'));
$this->installSchema('system', array('router'));
$this->fieldDefinition = array(
'name' => $this->fieldName,
......
......@@ -324,7 +324,7 @@ protected function drupalCreateContentType(array $values = array()) {
);
$type = entity_create('node_type', $values);
$status = $type->save();
menu_router_rebuild();
\Drupal::service('router.builder')->rebuild();
$this->assertEqual($status, SAVED_NEW, String::format('Created content type %type.', array('%type' => $type->id())));
......
......@@ -49,7 +49,7 @@ function setUp() {
public function testMenuRouterRebuildContext() {
// Enter a language context before rebuilding the menu router tables.
\Drupal::configFactory()->setLanguage(language_load('nl'));
menu_router_rebuild();
\Drupal::service('router.builder')->rebuild();
// Check that the language context was not used for building the menu item.
$menu_items = \Drupal::entityManager()->getStorageController('menu_link')->loadByProperties(array('route_name' => 'menu_test.context'));
......
......@@ -10,7 +10,7 @@
use Drupal\simpletest\WebTestBase;
/**
* Tests menu router and hook_menu() functionality.
* Tests menu router and hook_menu_link_defaults() functionality.
*/
class MenuRouterTest extends WebTestBase {
......@@ -45,7 +45,7 @@ class MenuRouterTest extends WebTestBase {
public static function getInfo() {
return array(
'name' => 'Menu router',
'description' => 'Tests menu router and hook_menu() functionality.',
'description' => 'Tests menu router and hook_menu_link_defaults() functionality.',
'group' => 'Menu',
);
}
......@@ -69,7 +69,6 @@ public function testMenuIntegration() {
$this->doTestMenuLinkMaintain();
$this->doTestMenuLinkOptions();
$this->doTestMenuItemHooks();
$this->doTestDescriptionMenuItems();
$this->doTestHookMenuIntegration();
$this->doTestExoticPath();
}
......@@ -119,7 +118,6 @@ protected function doTestDescriptionMenuItems() {
// Verify that the menu router item title is output as page title.
$this->drupalGet('menu_callback_description');
$this->assertText(t('Menu item description text'));
$this->assertRaw(check_plain('<strong>Menu item description arguments</strong>'));
}
/**
......@@ -169,7 +167,7 @@ protected function doTestMenuLinkMaintain() {
}
/**
* Tests for menu_name parameter for hook_menu().
* Tests for menu_name parameter for hook_menu_link_defaults().
*/
protected function doTestMenuName() {
$admin_user = $this->drupalCreateUser(array('administer site configuration'));
......
<?php
/**
* @file
* Definition of Drupal\system\Tests\Menu\RebuildTest.
*/
namespace Drupal\system\Tests\Menu;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\simpletest\WebTestBase;
/**
* Tests rebuilding the router.
*/
class RebuildTest extends WebTestBase {
public static function getInfo() {
return array(
'name' => 'Menu rebuild test',
'description' => 'Test rebuilding of menu.',
'group' => 'Menu',
);
}
/**
* Tests that set a router rebuild needed works.
*/
function testMenuRebuild() {
// Check if 'admin' path exists.
$admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField();
$this->assertEqual($admin_exists, 'admin', "The path 'admin/' exists prior to deleting.");
// Delete the path item 'admin', and test that the path doesn't exist in the database.
db_delete('menu_router')
->condition('path', 'admin')
->execute();
$admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField();
$this->assertFalse($admin_exists, "The path 'admin/' has been deleted and doesn't exist in the database.");
// Now we set the router to be rebuilt. After the rebuild 'admin' should exist.
\Drupal::service('router.builder')->setRebuildNeeded();
// The request should trigger the rebuild.
$this->drupalGet('<front>');
$admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField();
$this->assertEqual($admin_exists, 'admin', "The menu has been rebuilt, the path 'admin' now exists again.");
}
}
......@@ -14,7 +14,7 @@
use Symfony\Component\Routing\RouteCollection;
/**
* Tests the access check for menu tree using both hook_menu() and route items.
* Tests the access check for menu tree using both menu links and route items.
*/
class TreeAccessTest extends DrupalUnitTestBase {
......@@ -43,7 +43,7 @@ class TreeAccessTest extends DrupalUnitTestBase {
public static function getInfo() {
return array(
'name' => 'Menu tree access',
'description' => 'Tests the access check for menu tree using both hook_menu() and route items.',
'description' => 'Tests the access check for menu tree using both menu links and route items.',
'group' => 'Menu',
);
}
......
......@@ -32,7 +32,7 @@ public static function getInfo() {
function setUp() {
parent::setUp();
$this->installSchema('system', array('router', 'menu_router'));
$this->installSchema('system', array('router'));
}
/**
......
......@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Routing;
use Drupal\Core\Routing\RequestHelper;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
......@@ -37,17 +38,6 @@ public function testPermissionAccess() {
$path = 'router_test/test7';
$this->drupalGet($path);
$this->assertResponse(403, "Access denied for a route where we don't have a permission");
// An invalid path should throw an exception.
$map = array();
$route = \Drupal::service('router.route_provider')->getRouteByName('router_test.7');
try {
menu_item_route_access($route, $path . 'invalid', $map);
$exception = FALSE;
}
catch (ResourceNotFoundException $e) {
$exception = TRUE;
}
$this->assertTrue($exception, 'A ResourceNotFoundException was thrown while checking access for an invalid route.');
$this->drupalGet('router_test/test8');
$this->assertResponse(403, 'Access denied by default if no access specified');
......
......@@ -53,12 +53,10 @@ function testAdminPages() {
// Verify that all visible, top-level administration links are listed on
// the main administration page.
foreach (menu_get_router() as $path => $item) {
if (strpos($path, 'admin/') === 0 && ($item['type'] & MENU_VISIBLE_IN_TREE) && $item['_number_parts'] == 2) {
$this->assertLink($item['title']);
$this->assertLinkByHref($path);
$this->assertText($item['description']);
}
foreach ($this->getTopLevelMenuLinks() as $item) {
$this->assertLink($item['title']);
$this->assertLinkByHref($item['link_path']);
$this->assertText($item['localized_options']['attributes']['title']);
}
// For each administrative listing page on which the Locale module appears,
......@@ -110,6 +108,31 @@ function testAdminPages() {
}
}
/**
* Returns all top level menu links.
*
* @return \Drupal\menu_link\MenuLinkInterface[]
*/
protected function getTopLevelMenuLinks() {
$route_provider = \Drupal::service('router.route_provider');
$routes = array();
foreach ($route_provider->getAllRoutes() as $key => $value) {
$path = $value->getPath();
if (strpos($path, '/admin/') === 0 && count(explode('/', $path)) == 3) {
$routes[$key] = $key;
}
}
$menu_link_ids = \Drupal::entityQuery('menu_link')
->condition('route_name', $routes)
->execute();
$menu_items = \Drupal::entityManager()->getStorageController('menu_link')->loadMultiple($menu_link_ids);
foreach ($menu_items as &$menu_item) {
_menu_link_translate($menu_item);
}
return $menu_items;
}
/**