diff --git a/core/lib/Drupal/Core/Path/AliasStorage.php b/core/lib/Drupal/Core/Path/AliasStorage.php index 4f380125bb..970bbab3c5 100644 --- a/core/lib/Drupal/Core/Path/AliasStorage.php +++ b/core/lib/Drupal/Core/Path/AliasStorage.php @@ -195,16 +195,18 @@ public function preloadPathAlias($preloaded, $langcode) { $select->condition($conditions); } - // Always get the language-specific alias before the language-neutral one. - // For example 'de' is less than 'und' so the order needs to be ASC, while - // 'xx-lolspeak' is more than 'und' so the order needs to be DESC. We also - // order by pid ASC so that fetchAllKeyed() returns the most recently - // created alias for each source. Subsequent queries using fetchField() must - // use pid DESC to have the same effect. + // The language-specific alias should always be preferred over the + // language-neutral one. Since fetchAllKeyed() prefers later values, the + // language-specific alias must therefore come AFTER the language-neutral + // one. For example 'de' is less than 'und' so the order needs to be DESC, + // while 'xx-lolspeak' is more than 'und' so the order needs to be ASC. + // We also order by pid ASC so that fetchAllKeyed() returns the most + // recently created alias for each source. Subsequent queries using + // fetchField() must use pid DESC to have the same effect. if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) { array_pop($langcode_list); } - elseif ($langcode < LanguageInterface::LANGCODE_NOT_SPECIFIED) { + elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) { $select->orderBy('langcode', 'ASC'); } else { diff --git a/core/lib/Drupal/Core/Path/AliasStorageInterface.php b/core/lib/Drupal/Core/Path/AliasStorageInterface.php index 398ce07c65..d20f269dd7 100644 --- a/core/lib/Drupal/Core/Path/AliasStorageInterface.php +++ b/core/lib/Drupal/Core/Path/AliasStorageInterface.php @@ -70,7 +70,8 @@ public function delete($conditions); * Pre-loads path alias information for a given list of source paths. * * @param array $preloaded - * Paths that need preloading of aliases. + * Paths that need preloading of aliases. If empty, will yield every alias + * configured for the given language. * @param string $langcode * Language code to search the path with. If there's no path defined for * that language it will search paths without language. diff --git a/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php b/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php index d7b24b497f..fa0e602a22 100644 --- a/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php @@ -79,4 +79,246 @@ public function testAliasExists() { $this->assertTrue($this->storage->aliasExists('/test-alias-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); } + /** + * @covers ::preloadPathAlias + */ + public function testPreloadPathAlias() { + // Every interesting language combination: + // Just unspecified. + $this->storage->save('/und/src', '/und/alias', LanguageInterface::LANGCODE_NOT_SPECIFIED); + // Just a single language. + $this->storage->save('/en/src', '/en/alias', 'en'); + // A single language, plus unspecified. + $this->storage->save('/en-und/src', '/en-und/und', LanguageInterface::LANGCODE_NOT_SPECIFIED); + $this->storage->save('/en-und/src', '/en-und/en', 'en'); + // Multiple languages. + $this->storage->save('/en-fr/src', '/en-fr/en', 'en'); + $this->storage->save('/en-fr/src', '/en-fr/fr', 'fr'); + // A duplicate alias for the same path. This is later, so should be + // preferred. + $this->storage->save('/en-fr/src', '/en-fr/en-dup', 'en'); + // Multiple languages, plus unspecified. + $this->storage->save('/en-fr-und/src', '/en-fr-und/und', LanguageInterface::LANGCODE_NOT_SPECIFIED); + $this->storage->save('/en-fr-und/src', '/en-fr-und/en', 'en'); + $this->storage->save('/en-fr-und/src', '/en-fr-und/fr', 'fr'); + + // Queries for unspecified language aliases. + // Ask for an empty array, get all results. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-und/src' => '/en-und/und', + '/en-fr-und/src' => '/en-fr-und/und', + ], + $this->storage->preloadPathAlias([], LanguageInterface::LANGCODE_NOT_SPECIFIED) + ); + // Ask for nonexistent source. + $this->assertEquals( + [], + $this->storage->preloadPathAlias(['/nonexistent'], LanguageInterface::LANGCODE_NOT_SPECIFIED)); + // Ask for each saved source, individually. + $this->assertEquals( + ['/und/src' => '/und/alias'], + $this->storage->preloadPathAlias(['/und/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED) + ); + $this->assertEquals( + [], + $this->storage->preloadPathAlias(['/en/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED) + ); + $this->assertEquals( + ['/en-und/src' => '/en-und/und'], + $this->storage->preloadPathAlias(['/en-und/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED) + ); + $this->assertEquals( + [], + $this->storage->preloadPathAlias(['/en-fr/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED) + ); + $this->assertEquals( + ['/en-fr-und/src' => '/en-fr-und/und'], + $this->storage->preloadPathAlias(['/en-fr-und/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED) + ); + // Ask for multiple sources, all that are known. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-und/src' => '/en-und/und', + '/en-fr-und/src' => '/en-fr-und/und', + ], + $this->storage->preloadPathAlias( + [ + '/nonexistent', + '/und/src', + '/en/src', + '/en-und/src', + '/en-fr/src', + '/en-fr-und/src', + ], + LanguageInterface::LANGCODE_NOT_SPECIFIED + ) + ); + // Ask for multiple sources, just a subset. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-fr-und/src' => '/en-fr-und/und', + ], + $this->storage->preloadPathAlias( + [ + '/und/src', + '/en-fr/src', + '/en-fr-und/src', + ], + LanguageInterface::LANGCODE_NOT_SPECIFIED + ) + ); + + // Queries for English aliases. + // Ask for an empty array, get all results. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en/src' => '/en/alias', + '/en-und/src' => '/en-und/en', + '/en-fr/src' => '/en-fr/en-dup', + '/en-fr-und/src' => '/en-fr-und/en', + ], + $this->storage->preloadPathAlias([], 'en') + ); + // Ask for nonexistent source. + $this->assertEquals( + [], + $this->storage->preloadPathAlias(['/nonexistent'], 'en')); + // Ask for each saved source, individually. + $this->assertEquals( + ['/und/src' => '/und/alias'], + $this->storage->preloadPathAlias(['/und/src'], 'en') + ); + $this->assertEquals( + ['/en/src' => '/en/alias'], + $this->storage->preloadPathAlias(['/en/src'], 'en') + ); + $this->assertEquals( + ['/en-und/src' => '/en-und/en'], + $this->storage->preloadPathAlias(['/en-und/src'], 'en') + ); + $this->assertEquals( + ['/en-fr/src' => '/en-fr/en-dup'], + $this->storage->preloadPathAlias(['/en-fr/src'], 'en') + ); + $this->assertEquals( + ['/en-fr-und/src' => '/en-fr-und/en'], + $this->storage->preloadPathAlias(['/en-fr-und/src'], 'en') + ); + // Ask for multiple sources, all that are known. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en/src' => '/en/alias', + '/en-und/src' => '/en-und/en', + '/en-fr/src' => '/en-fr/en-dup', + '/en-fr-und/src' => '/en-fr-und/en', + ], + $this->storage->preloadPathAlias( + [ + '/nonexistent', + '/und/src', + '/en/src', + '/en-und/src', + '/en-fr/src', + '/en-fr-und/src', + ], + 'en' + ) + ); + // Ask for multiple sources, just a subset. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-fr/src' => '/en-fr/en-dup', + '/en-fr-und/src' => '/en-fr-und/en', + ], + $this->storage->preloadPathAlias( + [ + '/und/src', + '/en-fr/src', + '/en-fr-und/src', + ], + 'en' + ) + ); + + // Queries for French aliases. + // Ask for an empty array, get all results. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-und/src' => '/en-und/und', + '/en-fr/src' => '/en-fr/fr', + '/en-fr-und/src' => '/en-fr-und/fr', + ], + $this->storage->preloadPathAlias([], 'fr') + ); + // Ask for nonexistent source. + $this->assertEquals( + [], + $this->storage->preloadPathAlias(['/nonexistent'], 'fr')); + // Ask for each saved source, individually. + $this->assertEquals( + ['/und/src' => '/und/alias'], + $this->storage->preloadPathAlias(['/und/src'], 'fr') + ); + $this->assertEquals( + [], + $this->storage->preloadPathAlias(['/en/src'], 'fr') + ); + $this->assertEquals( + ['/en-und/src' => '/en-und/und'], + $this->storage->preloadPathAlias(['/en-und/src'], 'fr') + ); + $this->assertEquals( + ['/en-fr/src' => '/en-fr/fr'], + $this->storage->preloadPathAlias(['/en-fr/src'], 'fr') + ); + $this->assertEquals( + ['/en-fr-und/src' => '/en-fr-und/fr'], + $this->storage->preloadPathAlias(['/en-fr-und/src'], 'fr') + ); + // Ask for multiple sources, all that are known. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-und/src' => '/en-und/und', + '/en-fr/src' => '/en-fr/fr', + '/en-fr-und/src' => '/en-fr-und/fr', + ], + $this->storage->preloadPathAlias( + [ + '/nonexistent', + '/und/src', + '/en/src', + '/en-und/src', + '/en-fr/src', + '/en-fr-und/src', + ], + 'fr' + ) + ); + // Ask for multiple sources, just a subset. + $this->assertEquals( + [ + '/und/src' => '/und/alias', + '/en-fr/src' => '/en-fr/fr', + '/en-fr-und/src' => '/en-fr-und/fr', + ], + $this->storage->preloadPathAlias( + [ + '/und/src', + '/en-fr/src', + '/en-fr-und/src', + ], + 'fr' + ) + ); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php index 9436115ff4..c1397894ef 100644 --- a/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php @@ -3,6 +3,7 @@ namespace Drupal\KernelTests\Core\Path; use Drupal\Core\Cache\MemoryCounterBackend; +use Drupal\Core\Language\Language; use Drupal\Core\Path\AliasStorage; use Drupal\Core\Database\Database; use Drupal\Core\Path\AliasManager; @@ -216,4 +217,67 @@ public function testWhitelist() { $this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 0); } + /** + * Tests preloadPathAlias(). + */ + public function testPreloadPathAlias() { + // Create some aliases. + $connection = Database::getConnection(); + $this->fixtures->createTables($connection); + $aliasStorage = new AliasStorage($connection, $this->container->get('module_handler')); + foreach ($this->fixtures->sampleUrlAliases() as $alias) { + $aliasStorage->save($alias['source'], $alias['alias'], $alias['langcode']); + } + + // Lookups for aliases with unspecified language. + $this->assertEquals(['/node/1' => '/alias_for_node_1_und'], + $aliasStorage->preloadPathAlias([], Language::LANGCODE_NOT_SPECIFIED), + "Empty und lookup yields all und results", 0, 0, TRUE); + $this->assertEquals([], + $aliasStorage->preloadPathAlias(['/node/2'], Language::LANGCODE_NOT_SPECIFIED), + "Missing und lookup yields empty result", 0, 0, TRUE); + $this->assertEquals(['/node/1' => '/alias_for_node_1_und'], + $aliasStorage->preloadPathAlias(['/node/1'], Language::LANGCODE_NOT_SPECIFIED), + "Single und lookup yields und result", 0, 0, TRUE); + $this->assertEquals(['/node/1' => '/alias_for_node_1_und'], + $aliasStorage->preloadPathAlias(['/node/1', '/node/2'], Language::LANGCODE_NOT_SPECIFIED), + "Multiple und lookup yields only found results", 0, 0, TRUE); + + // Lookups for English aliases. + $this->assertEquals( + [ + '/node/1' => '/alias_for_node_1_en', + '/node/2' => '/alias_for_node_2_en', + ], + $aliasStorage->preloadPathAlias([], 'en'), + "Empty en lookup yields all en results", 0, 0, TRUE); + $this->assertEquals([], + $aliasStorage->preloadPathAlias(['/node/99'], 'en'), + "Missing en lookup yields empty result", 0, 0, TRUE); + $this->assertEquals(['/node/1' => '/alias_for_node_1_en'], + $aliasStorage->preloadPathAlias(['/node/1'], 'en'), + "Single en lookup yields en result", 0, 0, TRUE); + $this->assertEquals( + [ + '/node/1' => '/alias_for_node_1_en', + '/node/2' => '/alias_for_node_2_en', + ], + $aliasStorage->preloadPathAlias(['/node/1', '/node/2'], 'en'), + "Multiple en lookup yields all en results", 0, 0, TRUE); + + // Lookups for French aliases. + $this->assertEquals(['/node/1' => '/alias_for_node_1_fr'], + $aliasStorage->preloadPathAlias([], 'fr'), + "Empty fr lookup yields all fr results", 0, 0, TRUE); + $this->assertEquals([], + $aliasStorage->preloadPathAlias(['/node/2'], 'fr'), + "Missing fr lookup yields empty result", 0, 0, TRUE); + $this->assertEquals(['/node/1' => '/alias_for_node_1_fr'], + $aliasStorage->preloadPathAlias(['/node/1'], 'fr'), + "Single fr lookup yields fr result", 0, 0, TRUE); + $this->assertEquals(['/node/1' => '/alias_for_node_1_fr'], + $aliasStorage->preloadPathAlias(['/node/1', '/node/2'], 'fr'), + "Multiple fr lookup yields only found results", 0, 0, TRUE); + } + }