diff --git a/core/core.services.yml b/core/core.services.yml index 51ce4c4..43c6104 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -284,7 +284,7 @@ services: - [setRequest, ['@?request=']] router.route_provider: class: Drupal\Core\Routing\RouteProvider - arguments: ['@database', '@router.builder', '@state'] + arguments: ['@database', '@router.builder'] tags: - { name: event_subscriber } router.route_preloader: @@ -334,7 +334,7 @@ services: arguments: ['@database'] router.dumper: class: Drupal\Core\Routing\MatcherDumper - arguments: ['@database', '@state'] + arguments: ['@database'] router.builder: class: Drupal\Core\Routing\RouteBuilder arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state'] diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 5e11697..f52080f 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Config\Entity\ImportableEntityStorageInterface; +use Drupal\Core\Config\ConfigEvents; use Drupal\Core\DependencyInjection\DependencySerialization; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Lock\LockBackendInterface; @@ -146,7 +147,7 @@ public function reset() { * @return bool * TRUE if there are changes to process and FALSE if not. */ - public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) { + public function hasUnprocessedChanges($ops = array('delete', 'create', 'update', 'rename')) { foreach ($ops as $op) { if (count($this->getUnprocessed($op))) { return TRUE; @@ -212,7 +213,7 @@ public function import() { // to handle dependencies correctly. // @todo Implement proper dependency ordering using // https://drupal.org/node/2080823 - foreach (array('delete', 'create', 'update') as $op) { + foreach (array('delete', 'create', 'update', 'rename') as $op) { foreach ($this->getUnprocessed($op) as $name) { $this->process($op, $name); } @@ -239,6 +240,22 @@ public function validate() { if (!$this->storageComparer->validateSiteUuid()) { throw new ConfigImporterException('Site UUID in source storage does not match the target storage.'); } + // Validate renames. + foreach ($this->getUnprocessed('rename') as $name) { + $names = explode('::', $name); + $old_name = $names[0]; + $new_name = $names[1]; + $old_entity_type_id = $this->configManager->getEntityTypeIdByName($old_name); + $new_entity_type_id = $this->configManager->getEntityTypeIdByName($new_name); + if ($old_entity_type_id != $new_entity_type_id) { + throw new ConfigImporterException(String::format('Entity type mismatch on rename. !old_type not equal to !new_type for existing configuration !old_name and staged configuration !new_name.', array('old_type' => $old_entity_type_id, 'new_type' => $new_entity_type_id, 'old_name' => $old_name, 'new_name' => $new_name))); + } + // Has to be a configuration entity. + if (!$old_entity_type_id) { + throw new ConfigImporterException(String::format('Rename operation for simple configuration. Existing configuration !old_name and staged configuration !new_name.', array('old_name' => $old_name, 'new_name' => $new_name))); + } + } + $this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this)); $this->validated = TRUE; } @@ -303,6 +320,10 @@ protected function importConfig($op, $name) { * otherwise. */ protected function importInvokeOwner($op, $name) { + // Special case renames because they are hard. + if ($op == 'rename') { + return $this->importInvokeRename($name); + } // Validate the configuration object name before importing it. // Config::validateName($name); if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) { @@ -332,6 +353,37 @@ protected function importInvokeOwner($op, $name) { } /** + * @param string $name + * @return bool + */ + protected function importInvokeRename($name) { + $names = explode('::', $name); + $old_name = $names[0]; + $new_name = $names[1]; + $entity_type_id = $this->configManager->getEntityTypeIdByName($old_name); + $old_config = new Config($old_name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager); + if ($old_data = $this->storageComparer->getTargetStorage()->read($old_name)) { + $old_config->initWithData($old_data); + } + + $data = $this->storageComparer->getSourceStorage()->read($new_name); + $new_config = new Config($new_name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager); + if ($data !== FALSE) { + $new_config->setData($data); + } + + $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type_id); + // Call to the configuration entity's storage to handle the configuration + // change. + if (!($entity_storage instanceof ImportableEntityStorageInterface)) { + throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type))); + } + $entity_storage->importRename($old_name, $new_config, $old_config); + $this->setProcessed('rename', $name); + return TRUE; + } + + /** * Determines if a import is already running. * * @return bool diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 4656897..1ac89de 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -417,4 +417,19 @@ public function importDelete($name, Config $new_config, Config $old_config) { return TRUE; } + /** + * {@inheritdoc} + */ + public function importRename($old_name, Config $new_config, Config $old_config) { + $id = static::getIDFromConfigName($old_name, $this->entityType->getConfigPrefix()); + $entity = $this->load($id); + $entity->setSyncing(TRUE); + $data = $new_config->get(); + foreach ($data as $key => $value) { + $entity->set($key, $value); + } + $entity->save(); + return TRUE; + } + } diff --git a/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php b/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php index 6aca4df..cc0ae75 100644 --- a/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ImportableEntityStorageInterface.php @@ -53,4 +53,16 @@ public function importUpdate($name, Config $new_config, Config $old_config); */ public function importDelete($name, Config $new_config, Config $old_config); + /** + * Rename entities upon synchronizing configuration changes. + * + * @param string $old_name + * The original name of the configuration object. + * @param \Drupal\Core\Config\Config $new_config + * A configuration object containing the new configuration data. + * @param \Drupal\Core\Config\Config $old_config + * A configuration object containing the old configuration data. + */ + public function importRename($old_name, Config $new_config, Config $old_config); + } diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php index ac89537..ce6f609 100644 --- a/core/lib/Drupal/Core/Config/StorageComparer.php +++ b/core/lib/Drupal/Core/Config/StorageComparer.php @@ -98,6 +98,7 @@ public function getEmptyChangelist() { 'create' => array(), 'update' => array(), 'delete' => array(), + 'rename' => array() ); } @@ -133,6 +134,7 @@ public function createChangelist() { $this->addChangelistCreate(); $this->addChangelistUpdate(); $this->addChangelistDelete(); + $this->addChangelistRename(); $this->sourceData = NULL; $this->targetData = NULL; return $this; @@ -194,6 +196,53 @@ protected function addChangelistUpdate() { } /** + * Creates the rename changelist. + * + * The list of renames is created from the different source and target names + * with same uuid, these changes will be removed from the create and delete + * lists. For example, renamed content types should be not removed, but + * updated with the related nodes. + */ + protected function addChangelistRename() { + // Renames will be present in both create and delete lists. + if (empty($this->getChangelist('create')) || empty($this->getChangelist('delete'))) { + return; + } + + $create_uuids = array(); + foreach ($this->getSourceStorage()->readMultiple($this->getChangelist('create')) as $id => $data) { + if (isset($data['uuid'])) { + $create_uuids[$data['uuid']] = $id; + } + } + if (empty($create_uuids)) { + return; + } + + $renames = array(); + foreach ($this->getTargetStorage()->readMultiple($this->getChangelist('delete')) as $id => $data) { + if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) { + $renames[] = $id . '::' . $create_uuids[$data['uuid']]; + $this->removeFromChangelistsByUuid($data['uuid']); + } + } + // Reverse the array until we can manage dependencies. + $this->addChangeList('rename', array_reverse($renames)); + } + + /** + * Removes changes from all lists for the given UUID. + */ + protected function removeFromChangelistsByUuid($uuid) { + foreach ($this->getChangelist() as $op => $data) { + $key = array_search($uuid, $data); + if ($key !== FALSE) { + unset($this->changelist[$op][$key]); + } + } + } + + /** * {@inheritdoc} */ public function reset() { @@ -205,7 +254,7 @@ public function reset() { /** * {@inheritdoc} */ - public function hasChanges($ops = array('delete', 'create', 'update')) { + public function hasChanges($ops = array('delete', 'create', 'update', 'rename')) { foreach ($ops as $op) { if (!empty($this->changelist[$op])) { return TRUE; diff --git a/core/lib/Drupal/Core/Routing/MatcherDumper.php b/core/lib/Drupal/Core/Routing/MatcherDumper.php index aeed49c..889b691 100644 --- a/core/lib/Drupal/Core/Routing/MatcherDumper.php +++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Routing; -use Drupal\Core\KeyValueStore\StateInterface; use Symfony\Component\Routing\RouteCollection; use Drupal\Core\Database\Connection; @@ -32,13 +31,6 @@ class MatcherDumper implements MatcherDumperInterface { protected $routes; /** - * The state. - * - * @var \Drupal\Core\KeyValueStore\StateInterface - */ - protected $state; - - /** * The name of the SQL table to which to dump the routes. * * @var string @@ -51,14 +43,11 @@ class MatcherDumper implements MatcherDumperInterface { * @param \Drupal\Core\Database\Connection $connection * The database connection which will be used to store the route * information. - * @param \Drupal\Core\KeyValueStore\StateInterface $state - * The state. * @param string $table * (optional) The table to store the route info in. Defaults to 'router'. */ - public function __construct(Connection $connection, StateInterface $state, $table = 'router') { + public function __construct(Connection $connection, $table = 'router') { $this->connection = $connection; - $this->state = $state; $this->tableName = $table; } @@ -90,7 +79,6 @@ public function dump(array $options = array()) { $options += array( 'provider' => '', ); - // If there are no new routes, just delete any previously existing of this // provider. if (empty($this->routes) || !count($this->routes)) { @@ -100,8 +88,6 @@ public function dump(array $options = array()) { } // Convert all of the routes into database records. else { - // Accumulate the menu masks on top of any we found before. - $masks = array_flip($this->state->get('routing.menu_masks.' . $this->tableName, array())); $insert = $this->connection->insert($this->tableName)->fields(array( 'name', 'provider', @@ -115,11 +101,6 @@ public function dump(array $options = array()) { foreach ($this->routes as $name => $route) { $route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler'); $compiled = $route->compile(); - // The fit value is a binary number which has 1 at every fixed path - // position and 0 where there is a wildcard. We keep track of all such - // patterns that exist so that we can minimize the the number of path - // patterns we need to check in the RouteProvider. - $masks[$compiled->getFit()] = 1; $names[] = $name; $values = array( 'name' => $name, @@ -155,10 +136,6 @@ public function dump(array $options = array()) { watchdog_exception('Routing', $e); throw $e; } - // Sort the masks so they are in order of descending fit. - $masks = array_keys($masks); - rsort($masks); - $this->state->set('routing.menu_masks.' . $this->tableName, $masks); } // The dumper is reused for multiple providers, so reset the queued routes. $this->routes = NULL; diff --git a/core/lib/Drupal/Core/Routing/RouteCompiler.php b/core/lib/Drupal/Core/Routing/RouteCompiler.php index 6ae58e1..966f9d4 100644 --- a/core/lib/Drupal/Core/Routing/RouteCompiler.php +++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php @@ -93,10 +93,7 @@ public static function getFit($path) { // We store the highest index of parts here to save some work in the fit // calculation loop. $slashes = $number_parts - 1; - // The fit value is a binary number which has 1 at every fixed path - // position and 0 where there is a wildcard. We keep track of all such - // patterns that exist so that we can minimize the the number of path - // patterns we need to check in the RouteProvider. + $fit = 0; foreach ($parts as $k => $part) { if (strpos($part, '{') === FALSE) { diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php index 3dfe65e..20b0ec1 100644 --- a/core/lib/Drupal/Core/Routing/RouteProvider.php +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php @@ -8,7 +8,6 @@ namespace Drupal\Core\Routing; use Drupal\Component\Utility\String; -use Drupal\Core\KeyValueStore\StateInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -44,13 +43,6 @@ class RouteProvider implements RouteProviderInterface, EventSubscriberInterface protected $routeBuilder; /** - * The state. - * - * @var \Drupal\Core\KeyValueStore\StateInterface - */ - protected $state; - - /** * A cache of already-loaded routes, keyed by route name. * * @var array @@ -64,15 +56,12 @@ class RouteProvider implements RouteProviderInterface, EventSubscriberInterface * A database connection object. * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder * The route builder. - * @param \Drupal\Core\KeyValueStore\StateInterface $state - * The state. * @param string $table * The table in the database to use for matching. */ - public function __construct(Connection $connection, RouteBuilderInterface $route_builder, StateInterface $state, $table = 'router') { + public function __construct(Connection $connection, RouteBuilderInterface $route_builder, $table = 'router') { $this->connection = $connection; $this->routeBuilder = $route_builder; - $this->state = $state; $this->tableName = $table; } @@ -126,6 +115,10 @@ public function getRouteCollectionForRequest(Request $request) { $collection = $this->getRoutesByPath($path); } + if (!$collection->count()) { + throw new ResourceNotFoundException(String::format("The route for '@path' could not be found", array('@path' => $path))); + } + return $collection; } @@ -209,24 +202,7 @@ public function getCandidateOutlines(array $parts) { // The highest possible mask is a 1 bit for every part of the path. We will // check every value down from there to generate a possible outline. - if ($number_parts == 1) { - $masks = array(1); - } - elseif ($number_parts <= 3) { - // Optimization - don't query the state system for short paths. This also - // insulates against the state entry for masks going missing for common - // user-facing paths since we generate all values without checking state. - $masks = range($end, 1); - } - elseif ($number_parts <= 0) { - // No path can match, short-circuit the process. - $masks = array(); - } - else { - // Get the actual patterns that exist out of state. - $masks = (array) $this->state->get('routing.menu_masks.' . $this->tableName, array()); - } - + $masks = range($end, 0); // Only examine patterns that actually exist as router items (the masks). foreach ($masks as $i) { @@ -284,18 +260,14 @@ protected function getRoutesByPath($path) { return $value !== NULL && $value !== ''; })); - $collection = new RouteCollection(); - $ancestors = $this->getCandidateOutlines($parts); - if (empty($ancestors)) { - return $collection; - } $routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN (:patterns) ORDER BY fit DESC, name ASC", array( ':patterns' => $ancestors, )) ->fetchAllKeyed(); + $collection = new RouteCollection(); foreach ($routes as $name => $route) { $route = unserialize($route); if (preg_match($route->compile()->getRegex(), $path, $matches)) { diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php new file mode 100644 index 0000000..ddc19cc --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameTest.php @@ -0,0 +1,106 @@ + 'Import renamed configuration', + 'description' => 'Tests importing renamed configuration.', + 'group' => 'Configuration', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->installSchema('system', 'config_snapshot'); + $this->installSchema('node', 'node'); + + // Set up the ConfigImporter object for testing. + $storage_comparer = new StorageComparer( + $this->container->get('config.storage.staging'), + $this->container->get('config.storage') + ); + $this->configImporter = new ConfigImporter( + $storage_comparer->createChangelist(), + $this->container->get('event_dispatcher'), + $this->container->get('config.manager'), + $this->container->get('lock'), + $this->container->get('config.typed') + ); + $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); + } + + /** + * Tests configuration renaming. + */ + public function testRenamed() { + $content_type = entity_create('node_type', array( + 'type' => Unicode::strtolower($this->randomName(16)), + 'name' => $this->randomName(), + )); + $content_type->save(); + $active = $this->container->get('config.storage'); + $staging = $this->container->get('config.storage.staging'); + + $config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); + $this->copyConfig($active, $staging); + + // Change the machine name of the content type. This wil rename 5 + // configuration entities: the node type, the body field, the body field + // instance, the entity form display and the entity view display. + $content_type->type = Unicode::strtolower($this->randomName(8)); + $content_type->save(); + $renamed_config_name = $content_type->getEntityType()->getConfigPrefix() . '.' . $content_type->id(); + $this->assertTrue($active->exists($renamed_config_name), 'Content type has new name in active store.'); + $this->assertFalse($active->exists($config_name), 'Content type\'s old name does not exist active store.'); + + $this->configImporter->reset(); + $this->assertEqual(0, count($this->configImporter->getUnprocessed('create')), 'There are no configuration items to create.'); + $this->assertEqual(0, count($this->configImporter->getUnprocessed('delete')), 'There are no configuration items to delete.'); + $this->assertEqual(0, count($this->configImporter->getUnprocessed('update')), 'There are no configuration items to update.'); + debug($this->configImporter->getUnprocessed('rename')); + $this->assertEqual(5, count($this->configImporter->getUnprocessed('rename')), 'There are 5 configuration items to rename.'); + + // If we try to do this then we fail badly because of secondary writes and + // deletes. + //$this->configImporter->import(); + } + +} + diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php index 148ec23..c68789c 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php @@ -153,11 +153,6 @@ public function testRecentNodeBlock() { $this->assertText($label, 'Block was displayed on the node/N when node is of type article.'); $this->drupalGet('node/' . $node5->id()); $this->assertNoText($label, 'Block was not displayed on nodes of type page.'); - - $this->drupalLogin($this->adminUser); - $this->drupalGet('admin/structure/block'); - $this->assertText($label, 'Block was displayed on the admin/structure/block page.'); - $this->assertLinkByHref(url('admin/structure/block/manage/' . $block->id())); } } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 7c429ae..d847788 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -8,6 +8,7 @@ * API pattern. */ +use Drupal\Component\Utility\String; use Drupal\Core\Language\Language; use Symfony\Component\HttpFoundation\Response; use Drupal\Core\Cache\Cache; @@ -22,8 +23,6 @@ use Drupal\file\Entity\File; use Drupal\language\Entity\Language as LanguageEntity; use Symfony\Cmf\Component\Routing\RouteObjectInterface; -use Drupal\block\Entity\Block; -use Drupal\Core\Session\AccountInterface; /** * Denotes that the node is not published. @@ -1076,11 +1075,7 @@ function node_form_block_form_alter(&$form, &$form_state) { * Checks the content type specific visibility settings and removes the block * if the visibility conditions are not met. */ -function node_block_access(Block $block, $operation, AccountInterface $account, $langcode) { - // Only affect access when viewing the block. - if ($operation != 'view') { - return; - } +function node_block_access($block) { $visibility = $block->get('visibility'); if (!empty($visibility)) { if (!empty($visibility['node_type']['types'])) { diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php index 1b2fd8f..8514b3f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php @@ -2,13 +2,11 @@ /** * @file - * Contains \Drupal\system\Tests\Routing\MatcherDumperTest. + * Definition of Drupal\system\Tests\Routing\UrlMatcherDumperTest. */ namespace Drupal\system\Tests\Routing; -use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; -use Drupal\Core\KeyValueStore\State; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -29,13 +27,6 @@ class MatcherDumperTest extends UnitTestBase { */ protected $fixtures; - /** - * The state. - * - * @var \Drupal\Core\KeyValueStore\StateInterface - */ - protected $state; - public static function getInfo() { return array( 'name' => 'Dumper tests', @@ -48,7 +39,6 @@ function __construct($test_id = NULL) { parent::__construct($test_id); $this->fixtures = new RoutingFixtures(); - $this->state = new State(new KeyValueMemoryFactory()); } function setUp() { @@ -60,7 +50,7 @@ function setUp() { */ function testCreate() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection, $this->state); + $dumper= new MatcherDumper($connection); $class_name = 'Drupal\Core\Routing\MatcherDumper'; $this->assertTrue($dumper instanceof $class_name, 'Dumper created successfully'); @@ -71,7 +61,7 @@ function testCreate() { */ function testAddRoutes() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection, $this->state); + $dumper= new MatcherDumper($connection); $route = new Route('test'); $collection = new RouteCollection(); @@ -92,7 +82,7 @@ function testAddRoutes() { */ function testAddAdditionalRoutes() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection, $this->state); + $dumper= new MatcherDumper($connection); $route = new Route('test'); $collection = new RouteCollection(); @@ -128,7 +118,7 @@ function testAddAdditionalRoutes() { */ public function testDump() { $connection = Database::getConnection(); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper= new MatcherDumper($connection, 'test_routes'); $route = new Route('/test/{my}/path'); $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler'); @@ -153,40 +143,11 @@ public function testDump() { } /** - * Tests the determination of the masks generation. - */ - public function testMenuMasksGeneration() { - $connection = Database::getConnection(); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); - - $collection = new RouteCollection(); - $collection->add('test_route_1', new Route('/test-length-3/{my}/path')); - $collection->add('test_route_2', new Route('/test-length-3/hello/path')); - $collection->add('test_route_3', new Route('/test-length-5/{my}/path/marvin/magrathea')); - $collection->add('test_route_4', new Route('/test-length-7/{my}/path/marvin/magrathea/earth/ursa-minor')); - - $dumper->addRoutes($collection); - - $this->fixtures->createTables($connection); - - $dumper->dump(array('provider' => 'test')); - // Using binary for readability, we expect a 0 at any wildcard slug. They - // should be ordered from longest to shortest. - $expected = array( - bindec('1011111'), - bindec('10111'), - bindec('111'), - bindec('101'), - ); - $this->assertEqual($this->state->get('routing.menu_masks.test_routes'), $expected); - } - - /** * Tests that changing the provider of a route updates the dumped value. */ public function testDumpRouteProviderRename() { $connection = Database::getConnection(); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $this->fixtures->createTables($connection); $route = new Route('/test'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php index 05d1131..b143797 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php @@ -7,8 +7,6 @@ namespace Drupal\system\Tests\Routing; -use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; -use Drupal\Core\KeyValueStore\State; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Route; @@ -41,13 +39,6 @@ class RouteProviderTest extends UnitTestBase { */ protected $routeBuilder; - /** - * The state. - * - * @var \Drupal\Core\KeyValueStore\StateInterface - */ - protected $state; - public static function getInfo() { return array( 'name' => 'Route Provider tests', @@ -56,10 +47,11 @@ public static function getInfo() { ); } - public function setUp() { + function __construct($test_id = NULL) { + parent::__construct($test_id); + $this->fixtures = new RoutingFixtures(); $this->routeBuilder = new NullRouteBuilder(); - $this->state = new State(new KeyValueMemoryFactory()); } public function tearDown() { @@ -74,7 +66,7 @@ public function tearDown() { public function testCandidateOutlines() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder); $parts = array('node', '5', 'edit'); @@ -82,7 +74,7 @@ public function testCandidateOutlines() { $candidates = array_flip($candidates); - $this->assertTrue(count($candidates) == 7, 'Correct number of candidates found'); + $this->assertTrue(count($candidates) == 8, 'Correct number of candidates found'); $this->assertTrue(array_key_exists('/node/5/edit', $candidates), 'First candidate found.'); $this->assertTrue(array_key_exists('/node/5/%', $candidates), 'Second candidate found.'); $this->assertTrue(array_key_exists('/node/%/edit', $candidates), 'Third candidate found.'); @@ -90,6 +82,7 @@ public function testCandidateOutlines() { $this->assertTrue(array_key_exists('/node/5', $candidates), 'Fifth candidate found.'); $this->assertTrue(array_key_exists('/node/%', $candidates), 'Sixth candidate found.'); $this->assertTrue(array_key_exists('/node', $candidates), 'Seventh candidate found.'); + $this->assertTrue(array_key_exists('/', $candidates), 'Eighth candidate found.'); } /** @@ -97,11 +90,11 @@ public function testCandidateOutlines() { */ function testExactPathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($this->fixtures->sampleRouteCollection()); $dumper->dump(); @@ -121,11 +114,11 @@ function testExactPathMatch() { */ function testOutlinePathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($this->fixtures->complexRouteCollection()); $dumper->dump(); @@ -150,11 +143,11 @@ function testOutlinePathMatch() { */ function testOutlinePathMatchTrailingSlash() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($this->fixtures->complexRouteCollection()); $dumper->dump(); @@ -179,7 +172,7 @@ function testOutlinePathMatchTrailingSlash() { */ function testOutlinePathMatchDefaults() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); @@ -188,7 +181,7 @@ function testOutlinePathMatchDefaults() { 'value' => 'poink', ))); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -217,7 +210,7 @@ function testOutlinePathMatchDefaults() { */ function testOutlinePathMatchDefaultsCollision() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); @@ -227,7 +220,7 @@ function testOutlinePathMatchDefaultsCollision() { ))); $collection->add('narf', new Route('/some/path/here')); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -256,7 +249,7 @@ function testOutlinePathMatchDefaultsCollision() { */ function testOutlinePathMatchDefaultsCollision2() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); @@ -267,7 +260,7 @@ function testOutlinePathMatchDefaultsCollision2() { $collection->add('narf', new Route('/some/path/here')); $collection->add('eep', new Route('/something/completely/different')); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -295,14 +288,14 @@ function testOutlinePathMatchDefaultsCollision2() { */ public function testOutlinePathMatchZero() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); $collection = new RouteCollection(); $collection->add('poink', new Route('/some/path/{value}')); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -330,11 +323,11 @@ public function testOutlinePathMatchZero() { */ function testOutlinePathNoMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($this->fixtures->complexRouteCollection()); $dumper->dump(); @@ -342,12 +335,16 @@ function testOutlinePathNoMatch() { $request = Request::create($path, 'GET'); + try { + $routes = $provider->getRoutesByPattern($path); + $this->assertFalse(count($routes), 'No path found with this pattern.'); - $routes = $provider->getRoutesByPattern($path); - $this->assertFalse(count($routes), 'No path found with this pattern.'); - - $collection = $provider->getRouteCollectionForRequest($request); - $this->assertTrue(count($collection) == 0, 'Empty route collection found with this pattern.'); + $provider->getRouteCollectionForRequest($request); + $this->fail(t('No exception was thrown.')); + } + catch (\Exception $e) { + $this->assertTrue($e instanceof ResourceNotFoundException, 'The correct exception was thrown.'); + } } /** @@ -355,11 +352,11 @@ function testOutlinePathNoMatch() { */ function testSystemPathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($this->fixtures->sampleRouteCollection()); $dumper->dump(); @@ -380,11 +377,11 @@ function testSystemPathMatch() { */ protected function testRouteByName() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + $dumper = new MatcherDumper($connection, 'test_routes'); $dumper->addRoutes($this->fixtures->sampleRouteCollection()); $dumper->dump(); @@ -415,57 +412,21 @@ protected function testRouteByName() { */ public function testGetRoutesByPatternWithLongPatterns() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); $this->fixtures->createTables($connection); - // This pattern has only 3 parts, so we will get candidates, but no routes, - // even though we have not dumped the routes yet. - $shortest = '/test/1/test2'; - $result = $provider->getRoutesByPattern($shortest); - $this->assertEqual($result->count(), 0); - $candidates = $provider->getCandidateOutlines(explode('/', trim($shortest, '/'))); - $this->assertEqual(count($candidates), 7); - // A longer patten is not found and returns no candidates - $path_to_test = '/test/1/test2/2/test3/3/4/5/6/test4'; - $result = $provider->getRoutesByPattern($path_to_test); - $this->assertEqual($result->count(), 0); - $candidates = $provider->getCandidateOutlines(explode('/', trim($path_to_test, '/'))); - $this->assertEqual(count($candidates), 0); - - // Add a matching route and dump it. - $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + + $dumper = new MatcherDumper($connection, 'test_routes'); $collection = new RouteCollection(); $collection->add('long_pattern', new Route('/test/{v1}/test2/{v2}/test3/{v3}/{v4}/{v5}/{v6}/test4')); $dumper->addRoutes($collection); $dumper->dump(); - $result = $provider->getRoutesByPattern($path_to_test); + $result = $provider->getRoutesByPattern('/test/1/test2/2/test3/3/4/5/6/test4'); $this->assertEqual($result->count(), 1); // We can't compare the values of the routes directly, nor use // spl_object_hash() because they are separate instances. $this->assertEqual(serialize($result->get('long_pattern')), serialize($collection->get('long_pattern')), 'The right route was found.'); - // We now have a single candidate outline. - $candidates = $provider->getCandidateOutlines(explode('/', trim($path_to_test, '/'))); - $this->assertEqual(count($candidates), 1); - // Longer and shorter patterns are not found. Both are longer than 3, so - // we should not have any candidates either. The fact that we do not - // get any candidates for a longer path is a security feature. - $longer = '/test/1/test2/2/test3/3/4/5/6/test4/trailing/more/parts'; - $result = $provider->getRoutesByPattern($longer); - $this->assertEqual($result->count(), 0); - $candidates = $provider->getCandidateOutlines(explode('/', trim($longer, '/'))); - $this->assertEqual(count($candidates), 1); - $shorter = '/test/1/test2/2/test3'; - $result = $provider->getRoutesByPattern($shorter); - $this->assertEqual($result->count(), 0); - $candidates = $provider->getCandidateOutlines(explode('/', trim($shorter, '/'))); - $this->assertEqual(count($candidates), 0); - // This pattern has only 3 parts, so we will get candidates, but no routes. - // This result is unchanged by running the dumper. - $result = $provider->getRoutesByPattern($shortest); - $this->assertEqual($result->count(), 0); - $candidates = $provider->getCandidateOutlines(explode('/', trim($shortest, '/'))); - $this->assertEqual(count($candidates), 7); } } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index ca5d128..21bfb28 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -812,8 +812,7 @@ function system_schema() { ), 'route' => array( 'description' => 'A serialized Route object', - 'type' => 'blob', - 'size' => 'big', + 'type' => 'text', ), 'number_parts' => array( 'description' => 'Number of parts in this router path.', diff --git a/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php b/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php index 500c06f..e3c2585 100644 --- a/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php +++ b/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php @@ -117,10 +117,10 @@ public function testCreateChangelistNoChange() { $this->targetStorage->expects($this->once()) ->method('listAll') ->will($this->returnValue($config_files)); - $this->sourceStorage->expects($this->once()) + $this->sourceStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($config_data)); - $this->targetStorage->expects($this->once()) + $this->targetStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($config_data)); @@ -145,10 +145,10 @@ public function testCreateChangelistCreate() { $this->targetStorage->expects($this->once()) ->method('listAll') ->will($this->returnValue(array_keys($target_data))); - $this->sourceStorage->expects($this->once()) + $this->sourceStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($source_data)); - $this->targetStorage->expects($this->once()) + $this->targetStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($target_data)); @@ -178,10 +178,10 @@ public function testCreateChangelistDelete() { $this->targetStorage->expects($this->once()) ->method('listAll') ->will($this->returnValue(array_keys($target_data))); - $this->sourceStorage->expects($this->once()) + $this->sourceStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($source_data)); - $this->targetStorage->expects($this->once()) + $this->targetStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($target_data)); @@ -211,10 +211,10 @@ public function testCreateChangelistUpdate() { $this->targetStorage->expects($this->once()) ->method('listAll') ->will($this->returnValue(array_keys($target_data))); - $this->sourceStorage->expects($this->once()) + $this->sourceStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($source_data)); - $this->targetStorage->expects($this->once()) + $this->targetStorage->expects($this->atLeastOnce()) ->method('readMultiple') ->will($this->returnValue($target_data));