Initial Drupal 11 with DDEV setup
This commit is contained in:
7
web/core/modules/path_alias/path_alias.info.yml
Normal file
7
web/core/modules/path_alias/path_alias.info.yml
Normal file
@ -0,0 +1,7 @@
|
||||
name: Path alias
|
||||
type: module
|
||||
description: 'Provides the API allowing to rename URLs.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
required: true
|
||||
hidden: true
|
||||
25
web/core/modules/path_alias/path_alias.post_update.php
Normal file
25
web/core/modules/path_alias/path_alias.post_update.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for Path Alias.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_removed_post_updates().
|
||||
*/
|
||||
function path_alias_removed_post_updates(): array {
|
||||
return [
|
||||
'path_alias_post_update_drop_path_alias_status_index' => '11.0.0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the path_alias_revision indices.
|
||||
*/
|
||||
function path_alias_post_update_update_path_alias_revision_indexes(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $update_manager */
|
||||
$update_manager = \Drupal::service('entity.definition_update_manager');
|
||||
$entity_type = $update_manager->getEntityType('path_alias');
|
||||
$update_manager->updateEntityType($entity_type);
|
||||
}
|
||||
36
web/core/modules/path_alias/path_alias.services.yml
Normal file
36
web/core/modules/path_alias/path_alias.services.yml
Normal file
@ -0,0 +1,36 @@
|
||||
services:
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
path_alias.subscriber:
|
||||
class: Drupal\path_alias\EventSubscriber\PathAliasSubscriber
|
||||
arguments: ['@path_alias.manager', '@path.current']
|
||||
path_alias.path_processor:
|
||||
class: Drupal\path_alias\PathProcessor\AliasPathProcessor
|
||||
tags:
|
||||
- { name: path_processor_inbound, priority: 100 }
|
||||
- { name: path_processor_outbound, priority: 300 }
|
||||
arguments: ['@path_alias.manager']
|
||||
path_alias.manager:
|
||||
class: Drupal\path_alias\AliasManager
|
||||
arguments: ['@path_alias.repository', '@path_alias.prefix_list', '@language_manager', '@cache.data', '@datetime.time']
|
||||
Drupal\path_alias\AliasManagerInterface: '@path_alias.manager'
|
||||
path_alias.repository:
|
||||
class: Drupal\path_alias\AliasRepository
|
||||
arguments: ['@database']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
Drupal\path_alias\AliasRepositoryInterface: '@path_alias.repository'
|
||||
# cspell:ignore whitelist
|
||||
path_alias.whitelist:
|
||||
class: Drupal\path_alias\AliasWhitelist
|
||||
tags:
|
||||
- { name: needs_destruction }
|
||||
arguments: [path_alias_whitelist, '@cache.bootstrap', '@lock', '@state', '@path_alias.repository']
|
||||
deprecated: The "%service_id%" service is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use the 'router.prefix_list' service instead. See https://www.drupal.org/node/3467559
|
||||
Drupal\path_alias\AliasWhitelistInterface: '@path_alias.whitelist'
|
||||
path_alias.prefix_list:
|
||||
class: Drupal\path_alias\AliasPrefixList
|
||||
tags:
|
||||
- { name: needs_destruction }
|
||||
arguments: [path_alias_prefix_list, '@cache.bootstrap', '@lock', '@state', '@path_alias.repository']
|
||||
Drupal\path_alias\AliasPrefixListInterface: '@path_alias.prefix_list'
|
||||
271
web/core/modules/path_alias/src/AliasManager.php
Normal file
271
web/core/modules/path_alias/src/AliasManager.php
Normal file
@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
|
||||
/**
|
||||
* The default alias manager implementation.
|
||||
*/
|
||||
class AliasManager implements AliasManagerInterface {
|
||||
|
||||
/**
|
||||
* The cache key to use when caching paths.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheKey;
|
||||
|
||||
/**
|
||||
* Whether the cache needs to be written.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $cacheNeedsWriting = FALSE;
|
||||
|
||||
/**
|
||||
* Holds the map of path lookups per language.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $lookupMap = [];
|
||||
|
||||
/**
|
||||
* Holds an array of aliases for which no path was found.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $noPath = [];
|
||||
|
||||
/**
|
||||
* Holds an array of paths that have no alias.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $noAlias = [];
|
||||
|
||||
/**
|
||||
* Whether preloaded path lookups has already been loaded.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $langcodePreloaded = [];
|
||||
|
||||
/**
|
||||
* Holds an array of previously looked up paths for the current request path.
|
||||
*
|
||||
* This will only get populated if a cache key has been set, which for example
|
||||
* happens if the alias manager is used in the context of a request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $preloadedPathLookups = FALSE;
|
||||
|
||||
public function __construct(
|
||||
protected AliasRepositoryInterface $pathAliasRepository,
|
||||
protected AliasPrefixListInterface $pathPrefixes,
|
||||
protected LanguageManagerInterface $languageManager,
|
||||
protected CacheBackendInterface $cache,
|
||||
protected TimeInterface $time,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setCacheKey($key) {
|
||||
// Prefix the cache key to avoid clashes with other caches.
|
||||
$this->cacheKey = 'preload-paths:' . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Cache an array of the paths available on each page. We assume that aliases
|
||||
* will be needed for the majority of these paths during subsequent requests,
|
||||
* and load them in a single query during path alias lookup.
|
||||
*/
|
||||
public function writeCache() {
|
||||
// Check if the paths for this page were loaded from cache in this request
|
||||
// to avoid writing to cache on every request.
|
||||
if ($this->cacheNeedsWriting && !empty($this->cacheKey)) {
|
||||
// Start with the preloaded path lookups, so that cached entries for other
|
||||
// languages will not be lost.
|
||||
$path_lookups = $this->preloadedPathLookups ?: [];
|
||||
foreach ($this->lookupMap as $langcode => $lookups) {
|
||||
$path_lookups[$langcode] = array_keys($lookups);
|
||||
if (!empty($this->noAlias[$langcode])) {
|
||||
$path_lookups[$langcode] = array_merge($path_lookups[$langcode], array_keys($this->noAlias[$langcode]));
|
||||
}
|
||||
}
|
||||
|
||||
$twenty_four_hours = 60 * 60 * 24;
|
||||
$this->cache->set($this->cacheKey, $path_lookups, $this->time->getRequestTime() + $twenty_four_hours);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPathByAlias($alias, $langcode = NULL) {
|
||||
// If no language is explicitly specified we default to the current URL
|
||||
// language. If we used a language different from the one conveyed by the
|
||||
// requested URL, we might end up being unable to check if there is a path
|
||||
// alias matching the URL path.
|
||||
$langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
|
||||
|
||||
// If we already know that there are no paths for this alias simply return.
|
||||
if (empty($alias) || !empty($this->noPath[$langcode][$alias])) {
|
||||
return $alias;
|
||||
}
|
||||
|
||||
// Look for the alias within the cached map.
|
||||
if (isset($this->lookupMap[$langcode]) && ($path = array_search($alias, $this->lookupMap[$langcode]))) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Look for path in storage.
|
||||
if ($path_alias = $this->pathAliasRepository->lookupByAlias($alias, $langcode)) {
|
||||
$this->lookupMap[$langcode][$path_alias['path']] = $alias;
|
||||
return $path_alias['path'];
|
||||
}
|
||||
|
||||
// We can't record anything into $this->lookupMap because we didn't find any
|
||||
// paths for this alias. Thus cache to $this->noPath.
|
||||
$this->noPath[$langcode][$alias] = TRUE;
|
||||
|
||||
return $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAliasByPath($path, $langcode = NULL) {
|
||||
if (!str_starts_with($path, '/')) {
|
||||
throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $path));
|
||||
}
|
||||
// If no language is explicitly specified we default to the current URL
|
||||
// language. If we used a language different from the one conveyed by the
|
||||
// requested URL, we might end up being unable to check if there is a path
|
||||
// alias matching the URL path.
|
||||
$langcode = $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId();
|
||||
|
||||
// Check the path prefix, if the top-level part before the first / is not in
|
||||
// the list, then there is no need to do anything further, it is not in the
|
||||
// database.
|
||||
if ($path === '/' || !$this->pathPrefixes->get(strtok(trim($path, '/'), '/'))) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// During the first call to this method per language, load the expected
|
||||
// paths for the page from cache.
|
||||
if (empty($this->langcodePreloaded[$langcode])) {
|
||||
$this->langcodePreloaded[$langcode] = TRUE;
|
||||
$this->lookupMap[$langcode] = [];
|
||||
|
||||
// Load the cached paths that should be used for preloading. This only
|
||||
// happens if a cache key has been set.
|
||||
if ($this->preloadedPathLookups === FALSE) {
|
||||
$this->preloadedPathLookups = [];
|
||||
if ($this->cacheKey) {
|
||||
if ($cached = $this->cache->get($this->cacheKey)) {
|
||||
$this->preloadedPathLookups = $cached->data;
|
||||
}
|
||||
else {
|
||||
$this->cacheNeedsWriting = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load paths from cache.
|
||||
if (!empty($this->preloadedPathLookups[$langcode])) {
|
||||
$this->lookupMap[$langcode] = $this->pathAliasRepository->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode);
|
||||
// Keep a record of paths with no alias to avoid querying twice.
|
||||
$this->noAlias[$langcode] = array_flip(array_diff($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode])));
|
||||
}
|
||||
}
|
||||
|
||||
// If we already know that there are no aliases for this path simply return.
|
||||
if (!empty($this->noAlias[$langcode][$path])) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// If the alias has already been loaded, return it from static cache.
|
||||
if (isset($this->lookupMap[$langcode][$path])) {
|
||||
return $this->lookupMap[$langcode][$path];
|
||||
}
|
||||
|
||||
// Try to load alias from storage.
|
||||
if ($path_alias = $this->pathAliasRepository->lookupBySystemPath($path, $langcode)) {
|
||||
$this->lookupMap[$langcode][$path] = $path_alias['alias'];
|
||||
return $path_alias['alias'];
|
||||
}
|
||||
|
||||
// We can't record anything into $this->lookupMap because we didn't find any
|
||||
// aliases for this path. Thus cache to $this->noAlias.
|
||||
$this->noAlias[$langcode][$path] = TRUE;
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cacheClear($source = NULL) {
|
||||
// Note this method does not flush the preloaded path lookup cache. This is
|
||||
// because if a path is missing from this cache, it still results in the
|
||||
// alias being loaded correctly, only less efficiently.
|
||||
|
||||
if ($source) {
|
||||
foreach (array_keys($this->lookupMap) as $lang) {
|
||||
unset($this->lookupMap[$lang][$source]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->lookupMap = [];
|
||||
}
|
||||
$this->noPath = [];
|
||||
$this->noAlias = [];
|
||||
$this->langcodePreloaded = [];
|
||||
$this->preloadedPathLookups = [];
|
||||
$this->pathAliasPrefixListRebuild($source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the path alias prefix list.
|
||||
*
|
||||
* @param string $path
|
||||
* An optional path for which an alias is being inserted.
|
||||
*/
|
||||
protected function pathAliasPrefixListRebuild($path = NULL) {
|
||||
// When paths are inserted, only rebuild the prefix list if the path has a
|
||||
// top level component which is not already in the prefix list.
|
||||
if (!empty($path)) {
|
||||
if ($this->pathPrefixes->get(strtok($path, '/'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->pathPrefixes->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the path alias prefix list.
|
||||
*
|
||||
* @param string $path
|
||||
* An optional path for which an alias is being inserted.
|
||||
*
|
||||
* @deprecated in drupal:11.1.0 and is removed from drupal:12.0.0.
|
||||
* Use \Drupal\path_alias\AliasManager::pathAliasPrefixListRebuild instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3467559
|
||||
*
|
||||
* cspell:ignore whitelist
|
||||
*/
|
||||
protected function pathAliasWhitelistRebuild($path = NULL) {
|
||||
@trigger_error(__METHOD__ . '() is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use \Drupal\path_alias\AliasManager::pathAliasPrefixListRebuild() instead. See https://www.drupal.org/node/3467559', E_USER_DEPRECATED);
|
||||
$this->pathAliasPrefixListRebuild($path);
|
||||
}
|
||||
|
||||
}
|
||||
54
web/core/modules/path_alias/src/AliasManagerInterface.php
Normal file
54
web/core/modules/path_alias/src/AliasManagerInterface.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
/**
|
||||
* Find an alias for a path and vice versa.
|
||||
*
|
||||
* @see \Drupal\path_alias\AliasStorageInterface
|
||||
*/
|
||||
interface AliasManagerInterface {
|
||||
|
||||
/**
|
||||
* Given the alias, return the path it represents.
|
||||
*
|
||||
* @param string $alias
|
||||
* An alias.
|
||||
* @param string $langcode
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return string
|
||||
* The path represented by alias, or the alias if no path was found.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the path does not start with a slash.
|
||||
*/
|
||||
public function getPathByAlias($alias, $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Given a path, return the alias.
|
||||
*
|
||||
* @param string $path
|
||||
* A path.
|
||||
* @param string $langcode
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return string
|
||||
* An alias that represents the path, or path if no alias was found.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the path does not start with a slash.
|
||||
*/
|
||||
public function getAliasByPath($path, $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Clears the static caches in alias manager and rebuilds the prefix list.
|
||||
*
|
||||
* @param string|null $source
|
||||
* Source path of the alias that is being inserted/updated. If omitted, the
|
||||
* entire lookup static cache will be cleared and the prefix list will be
|
||||
* rebuilt.
|
||||
*/
|
||||
public function cacheClear($source = NULL);
|
||||
|
||||
}
|
||||
120
web/core/modules/path_alias/src/AliasPrefixList.php
Normal file
120
web/core/modules/path_alias/src/AliasPrefixList.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheCollector;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
|
||||
/**
|
||||
* Cache a list of valid alias prefixes.
|
||||
*/
|
||||
class AliasPrefixList extends CacheCollector implements AliasPrefixListInterface {
|
||||
|
||||
/**
|
||||
* The Key/Value Store to use for state.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The path alias repository.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasRepositoryInterface
|
||||
*/
|
||||
protected $pathAliasRepository;
|
||||
|
||||
/**
|
||||
* Constructs an AliasPrefixList object.
|
||||
*
|
||||
* @param string $cid
|
||||
* The cache id to use.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state keyvalue store.
|
||||
* @param \Drupal\path_alias\AliasRepositoryInterface $alias_repository
|
||||
* The path alias repository.
|
||||
*/
|
||||
public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, StateInterface $state, AliasRepositoryInterface $alias_repository) {
|
||||
parent::__construct($cid, $cache, $lock);
|
||||
$this->state = $state;
|
||||
$this->pathAliasRepository = $alias_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function lazyLoadCache() {
|
||||
parent::lazyLoadCache();
|
||||
|
||||
// On a cold start $this->storage will be empty and the prefix list will
|
||||
// need to be rebuilt from scratch. The prefix list is initialized from the
|
||||
// list of all valid path roots stored in the 'router.path_roots' state,
|
||||
// with values initialized to NULL. During the request, each path requested
|
||||
// that matches one of these keys will be looked up and the array value set
|
||||
// to either TRUE or FALSE. This ensures that paths which do not exist in
|
||||
// the router are not looked up, and that paths that do exist in the router
|
||||
// are only looked up once.
|
||||
if (empty($this->storage)) {
|
||||
$this->loadMenuPathRoots();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads menu path roots to prepopulate cache.
|
||||
*/
|
||||
protected function loadMenuPathRoots() {
|
||||
if ($roots = $this->state->get('router.path_roots')) {
|
||||
foreach ($roots as $root) {
|
||||
$this->storage[$root] = NULL;
|
||||
$this->persist($root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($offset) {
|
||||
$this->lazyLoadCache();
|
||||
// This may be called with paths that are not represented by menu router
|
||||
// items such as paths that will be rewritten by hook_url_outbound_alter().
|
||||
// Therefore internally TRUE is used to indicate valid paths. FALSE is
|
||||
// used to indicate paths that have already been checked but are not
|
||||
// valid, and NULL indicates paths that have not been checked yet.
|
||||
if (isset($this->storage[$offset])) {
|
||||
if ($this->storage[$offset]) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
elseif (array_key_exists($offset, $this->storage)) {
|
||||
return $this->resolveCacheMiss($offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolveCacheMiss($root) {
|
||||
$exists = $this->pathAliasRepository->pathHasMatchingAlias('/' . $root);
|
||||
$this->storage[$root] = $exists;
|
||||
$this->persist($root);
|
||||
if ($exists) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear() {
|
||||
parent::clear();
|
||||
$this->loadMenuPathRoots();
|
||||
}
|
||||
|
||||
}
|
||||
15
web/core/modules/path_alias/src/AliasPrefixListInterface.php
Normal file
15
web/core/modules/path_alias/src/AliasPrefixListInterface.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Core\Cache\CacheCollectorInterface;
|
||||
|
||||
/**
|
||||
* Cache a list of valid alias prefixes.
|
||||
*
|
||||
* The list contains the first element of the router paths of all aliases. For
|
||||
* example, if /node/12345 has an alias then "node" is added to the prefix list.
|
||||
* This optimization allows skipping the lookup for every /user/{user} path if
|
||||
* "user" is not in the list.
|
||||
*/
|
||||
interface AliasPrefixListInterface extends CacheCollectorInterface {}
|
||||
151
web/core/modules/path_alias/src/AliasRepository.php
Normal file
151
web/core/modules/path_alias/src/AliasRepository.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Database\Statement\FetchAs;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Provides the default path alias lookup operations.
|
||||
*/
|
||||
class AliasRepository implements AliasRepositoryInterface {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Constructs an AliasRepository object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* A database connection for reading and writing path aliases.
|
||||
*/
|
||||
public function __construct(Connection $connection) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preloadPathAlias($preloaded, $langcode) {
|
||||
$select = $this->getBaseQuery()
|
||||
->fields('base_table', ['path', 'alias']);
|
||||
|
||||
if (!empty($preloaded)) {
|
||||
$conditions = $this->connection->condition('OR');
|
||||
foreach ($preloaded as $preloaded_item) {
|
||||
$conditions->condition('base_table.path', $this->connection->escapeLike($preloaded_item), 'LIKE');
|
||||
}
|
||||
$select->condition($conditions);
|
||||
}
|
||||
|
||||
$this->addLanguageFallback($select, $langcode);
|
||||
|
||||
$select->orderBy('base_table.id', 'DESC');
|
||||
|
||||
// We want the most recently created alias for each source, however that
|
||||
// will be at the start of the result-set, so fetch everything and reverse
|
||||
// it. Note that it would not be sufficient to reverse the ordering of the
|
||||
// 'base_table.id' column, as that would not guarantee other conditions
|
||||
// added to the query, such as those in ::addLanguageFallback, would be
|
||||
// reversed.
|
||||
$results = $select->execute()->fetchAll(FetchAs::Associative);
|
||||
$aliases = [];
|
||||
foreach (array_reverse($results) as $result) {
|
||||
$aliases[$result['path']] = $result['alias'];
|
||||
}
|
||||
|
||||
return $aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupBySystemPath($path, $langcode) {
|
||||
// See the queries above. Use LIKE for case-insensitive matching.
|
||||
$select = $this->getBaseQuery()
|
||||
->fields('base_table', ['id', 'path', 'alias', 'langcode'])
|
||||
->condition('base_table.path', $this->connection->escapeLike($path), 'LIKE');
|
||||
|
||||
$this->addLanguageFallback($select, $langcode);
|
||||
|
||||
$select->orderBy('base_table.id', 'DESC');
|
||||
|
||||
return $select->execute()->fetchAssoc() ?: NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupByAlias($alias, $langcode) {
|
||||
// See the queries above. Use LIKE for case-insensitive matching.
|
||||
$select = $this->getBaseQuery()
|
||||
->fields('base_table', ['id', 'path', 'alias', 'langcode'])
|
||||
->condition('base_table.alias', $this->connection->escapeLike($alias), 'LIKE');
|
||||
|
||||
$this->addLanguageFallback($select, $langcode);
|
||||
|
||||
$select->orderBy('base_table.id', 'DESC');
|
||||
|
||||
return $select->execute()->fetchAssoc() ?: NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pathHasMatchingAlias($initial_substring) {
|
||||
$query = $this->getBaseQuery();
|
||||
$query->addExpression(1);
|
||||
|
||||
return (bool) $query
|
||||
->condition('base_table.path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SELECT query for the path_alias base table.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\SelectInterface
|
||||
* A Select query object.
|
||||
*/
|
||||
protected function getBaseQuery() {
|
||||
$query = $this->connection->select('path_alias', 'base_table');
|
||||
$query->condition('base_table.status', 1);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds path alias language fallback conditions to a select query object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface $query
|
||||
* A Select query object.
|
||||
* @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.
|
||||
*/
|
||||
protected function addLanguageFallback(SelectInterface $query, $langcode) {
|
||||
// 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.
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
if ($langcode === LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
array_pop($langcode_list);
|
||||
}
|
||||
elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$query->orderBy('base_table.langcode', 'DESC');
|
||||
}
|
||||
else {
|
||||
$query->orderBy('base_table.langcode', 'ASC');
|
||||
}
|
||||
$query->condition('base_table.langcode', $langcode_list, 'IN');
|
||||
}
|
||||
|
||||
}
|
||||
81
web/core/modules/path_alias/src/AliasRepositoryInterface.php
Normal file
81
web/core/modules/path_alias/src/AliasRepositoryInterface.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
/**
|
||||
* Provides an interface for path alias lookup operations.
|
||||
*
|
||||
* The path alias repository service is only used internally in order to
|
||||
* optimize alias lookup queries needed in the critical path of each request.
|
||||
* However, it is not marked as an internal service because alternative storage
|
||||
* backends still need to override it if they provide a different storage class
|
||||
* for the PathAlias entity type.
|
||||
*
|
||||
* Whenever you need to determine whether an alias exists for a system path, or
|
||||
* whether a system path has an alias, the 'path_alias.manager' service should
|
||||
* be used instead.
|
||||
*/
|
||||
interface AliasRepositoryInterface {
|
||||
|
||||
/**
|
||||
* Pre-loads path alias information for a given list of system paths.
|
||||
*
|
||||
* @param array $preloaded
|
||||
* System paths that need preloading of aliases.
|
||||
* @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.
|
||||
*
|
||||
* @return string[]
|
||||
* System paths (keys) to alias (values) mapping.
|
||||
*/
|
||||
public function preloadPathAlias($preloaded, $langcode);
|
||||
|
||||
/**
|
||||
* Searches a path alias for a given Drupal system path.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'path' and 'alias' strings.
|
||||
*
|
||||
* @param string $path
|
||||
* The system path to investigate for corresponding path aliases.
|
||||
* @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.
|
||||
*
|
||||
* @return array|null
|
||||
* An array containing the 'id', 'path', 'alias' and 'langcode' properties
|
||||
* of a path alias, or NULL if none was found.
|
||||
*/
|
||||
public function lookupBySystemPath($path, $langcode);
|
||||
|
||||
/**
|
||||
* Searches a path alias for a given alias.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'path' and 'alias' strings.
|
||||
*
|
||||
* @param string $alias
|
||||
* The alias to investigate for corresponding system paths.
|
||||
* @param string $langcode
|
||||
* Language code to search the alias with. If there's no alias defined for
|
||||
* that language it will search aliases without language.
|
||||
*
|
||||
* @return array|null
|
||||
* An array containing the 'id', 'path', 'alias' and 'langcode' properties
|
||||
* of a path alias, or NULL if none was found.
|
||||
*/
|
||||
public function lookupByAlias($alias, $langcode);
|
||||
|
||||
/**
|
||||
* Check if any alias exists starting with $initial_substring.
|
||||
*
|
||||
* @param string $initial_substring
|
||||
* Initial system path substring to test against.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if any alias exists, FALSE otherwise.
|
||||
*/
|
||||
public function pathHasMatchingAlias($initial_substring);
|
||||
|
||||
}
|
||||
15
web/core/modules/path_alias/src/AliasWhitelist.php
Normal file
15
web/core/modules/path_alias/src/AliasWhitelist.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
// cspell:ignore whitelist
|
||||
|
||||
/**
|
||||
* Cache a list of valid alias prefixes.
|
||||
*
|
||||
* @deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use
|
||||
* \Drupal\path_alias\AliasPrefixList instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3467559
|
||||
*/
|
||||
class AliasWhitelist extends AliasPrefixList {}
|
||||
15
web/core/modules/path_alias/src/AliasWhitelistInterface.php
Normal file
15
web/core/modules/path_alias/src/AliasWhitelistInterface.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
// cspell:ignore whitelist
|
||||
|
||||
/**
|
||||
* Cache a list of valid alias prefixes.
|
||||
*
|
||||
* @deprecated in drupal:11.1.0 and is removed from drupal:12.0.0.
|
||||
* Use \Drupal\path_alias\AliasPrefixListInterface instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3467559
|
||||
*/
|
||||
interface AliasWhitelistInterface extends AliasPrefixListInterface {}
|
||||
174
web/core/modules/path_alias/src/Entity/PathAlias.php
Normal file
174
web/core/modules/path_alias/src/Entity/PathAlias.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityPublishedTrait;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\path_alias\PathAliasInterface;
|
||||
use Drupal\path_alias\PathAliasStorage;
|
||||
use Drupal\path_alias\PathAliasStorageSchema;
|
||||
|
||||
/**
|
||||
* Defines the path_alias entity class.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'path_alias',
|
||||
label: new TranslatableMarkup('URL alias'),
|
||||
label_collection: new TranslatableMarkup('URL aliases'),
|
||||
label_singular: new TranslatableMarkup('URL alias'),
|
||||
label_plural: new TranslatableMarkup('URL aliases'),
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'revision' => 'revision_id',
|
||||
'langcode' => 'langcode',
|
||||
'uuid' => 'uuid',
|
||||
'published' => 'status',
|
||||
],
|
||||
handlers: [
|
||||
'storage' => PathAliasStorage::class,
|
||||
'storage_schema' => PathAliasStorageSchema::class,
|
||||
],
|
||||
admin_permission: 'administer url aliases',
|
||||
base_table: 'path_alias',
|
||||
revision_table: 'path_alias_revision',
|
||||
label_count: [
|
||||
'singular' => '@count URL alias',
|
||||
'plural' => '@count URL aliases',
|
||||
],
|
||||
list_cache_tags: ['route_match'],
|
||||
constraints: [
|
||||
'UniquePathAlias' => [],
|
||||
],
|
||||
)]
|
||||
class PathAlias extends ContentEntityBase implements PathAliasInterface {
|
||||
|
||||
use EntityPublishedTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['path'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(new TranslatableMarkup('System path'))
|
||||
->setDescription(new TranslatableMarkup('The path that this alias belongs to.'))
|
||||
->setRequired(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->addPropertyConstraints('value', [
|
||||
'Regex' => [
|
||||
'pattern' => '/^\//i',
|
||||
'message' => new TranslatableMarkup('The source path has to start with a slash.'),
|
||||
],
|
||||
])
|
||||
->addPropertyConstraints('value', ['ValidPath' => []]);
|
||||
|
||||
$fields['alias'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(new TranslatableMarkup('URL alias'))
|
||||
->setDescription(new TranslatableMarkup('An alias used with this path.'))
|
||||
->setRequired(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->addPropertyConstraints('value', [
|
||||
'Regex' => [
|
||||
'pattern' => '/^\//i',
|
||||
'message' => new TranslatableMarkup('The alias path has to start with a slash.'),
|
||||
],
|
||||
]);
|
||||
|
||||
$fields['langcode']->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
|
||||
// Add the published field.
|
||||
$fields += static::publishedBaseFieldDefinitions($entity_type);
|
||||
$fields['status']->setTranslatable(FALSE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
|
||||
// Trim the alias value of whitespace and slashes. Ensure to not trim the
|
||||
// slash on the left side.
|
||||
$alias = rtrim(trim($this->getAlias()), "\\/");
|
||||
$this->setAlias($alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
$alias_manager = \Drupal::service('path_alias.manager');
|
||||
$alias_manager->cacheClear($this->getPath());
|
||||
if ($update) {
|
||||
$alias_manager->cacheClear($this->getOriginal()->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
$alias_manager = \Drupal::service('path_alias.manager');
|
||||
foreach ($entities as $entity) {
|
||||
$alias_manager->cacheClear($entity->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPath() {
|
||||
return $this->get('path')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPath($path) {
|
||||
$this->set('path', $path);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAlias() {
|
||||
return $this->get('alias')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAlias($alias) {
|
||||
$this->set('alias', $alias);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->getAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTagsToInvalidate() {
|
||||
return ['route_match'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Path\CurrentPathStack;
|
||||
use Drupal\path_alias\AliasManagerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
||||
use Symfony\Component\HttpKernel\Event\TerminateEvent;
|
||||
|
||||
/**
|
||||
* Provides a path subscriber that converts path aliases.
|
||||
*/
|
||||
class PathAliasSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The alias manager that caches alias lookups based on the request.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasManagerInterface
|
||||
*/
|
||||
protected $aliasManager;
|
||||
|
||||
/**
|
||||
* The current path.
|
||||
*
|
||||
* @var \Drupal\Core\Path\CurrentPathStack
|
||||
*/
|
||||
protected $currentPath;
|
||||
|
||||
/**
|
||||
* Constructs a new PathSubscriber instance.
|
||||
*
|
||||
* @param \Drupal\path_alias\AliasManagerInterface $alias_manager
|
||||
* The alias manager.
|
||||
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
||||
* The current path.
|
||||
*/
|
||||
public function __construct(AliasManagerInterface $alias_manager, CurrentPathStack $current_path) {
|
||||
$this->aliasManager = $alias_manager;
|
||||
$this->currentPath = $current_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache key on the alias manager cache decorator.
|
||||
*
|
||||
* KernelEvents::CONTROLLER is used in order to be executed after routing.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\ControllerEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onKernelController(ControllerEvent $event) {
|
||||
// Set the cache key on the alias manager cache decorator.
|
||||
if ($event->isMainRequest()) {
|
||||
$this->aliasManager->setCacheKey(rtrim($this->currentPath->getPath($event->getRequest()), '/'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures system paths for the request get cached.
|
||||
*/
|
||||
public function onKernelTerminate(TerminateEvent $event) {
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events[KernelEvents::CONTROLLER][] = ['onKernelController', 200];
|
||||
$events[KernelEvents::TERMINATE][] = ['onKernelTerminate', 200];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
49
web/core/modules/path_alias/src/PathAliasInterface.php
Normal file
49
web/core/modules/path_alias/src/PathAliasInterface.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a path_alias entity.
|
||||
*/
|
||||
interface PathAliasInterface extends ContentEntityInterface, EntityPublishedInterface {
|
||||
|
||||
/**
|
||||
* Gets the source path of the alias.
|
||||
*
|
||||
* @return string
|
||||
* The source path.
|
||||
*/
|
||||
public function getPath();
|
||||
|
||||
/**
|
||||
* Sets the source path of the alias.
|
||||
*
|
||||
* @param string $path
|
||||
* The source path.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPath($path);
|
||||
|
||||
/**
|
||||
* Gets the alias for this path.
|
||||
*
|
||||
* @return string
|
||||
* The alias for this path.
|
||||
*/
|
||||
public function getAlias();
|
||||
|
||||
/**
|
||||
* Sets the alias for this path.
|
||||
*
|
||||
* @param string $alias
|
||||
* The path alias.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias($alias);
|
||||
|
||||
}
|
||||
22
web/core/modules/path_alias/src/PathAliasStorage.php
Normal file
22
web/core/modules/path_alias/src/PathAliasStorage.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Defines the storage handler class for path_alias entities.
|
||||
*/
|
||||
class PathAliasStorage extends SqlContentEntityStorage {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createWithSampleValues($bundle = FALSE, array $values = []) {
|
||||
$entity = parent::createWithSampleValues($bundle, ['path' => '/<front>'] + $values);
|
||||
// Ensure the alias is only 255 characters long.
|
||||
$entity->set('alias', substr('/' . $entity->get('alias')->value, 0, 255));
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
||||
37
web/core/modules/path_alias/src/PathAliasStorageSchema.php
Normal file
37
web/core/modules/path_alias/src/PathAliasStorageSchema.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
|
||||
/**
|
||||
* Defines the path_alias schema handler.
|
||||
*/
|
||||
class PathAliasStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
|
||||
$schema = parent::getEntitySchema($entity_type, $reset);
|
||||
$base_table = $this->storage->getBaseTable();
|
||||
$revision_table = $this->storage->getRevisionTable();
|
||||
|
||||
$schema[$base_table]['indexes'] += [
|
||||
'path_alias__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
|
||||
'path_alias__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
|
||||
];
|
||||
$schema[$revision_table]['indexes'] += [
|
||||
'path_alias_revision__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
|
||||
'path_alias_revision__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
|
||||
];
|
||||
|
||||
// Unset the path_alias__status index as it is slower than the above
|
||||
// indexes and MySQL 5.7 chooses to use it even though it is suboptimal.
|
||||
unset($schema[$base_table]['indexes']['path_alias__status']);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path_alias\PathProcessor;
|
||||
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\path_alias\AliasManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Processes the inbound and outbound path using path alias lookups.
|
||||
*/
|
||||
class AliasPathProcessor implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
|
||||
|
||||
/**
|
||||
* An alias manager for looking up the system path.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasManagerInterface
|
||||
*/
|
||||
protected $aliasManager;
|
||||
|
||||
/**
|
||||
* Constructs a AliasPathProcessor object.
|
||||
*
|
||||
* @param \Drupal\path_alias\AliasManagerInterface $alias_manager
|
||||
* An alias manager for looking up the system path.
|
||||
*/
|
||||
public function __construct(AliasManagerInterface $alias_manager) {
|
||||
$this->aliasManager = $alias_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processInbound($path, Request $request) {
|
||||
$path = $this->aliasManager->getPathByAlias($path);
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($path, &$options = [], ?Request $request = NULL, ?BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
if (empty($options['alias'])) {
|
||||
$langcode = isset($options['language']) ? $options['language']->getId() : NULL;
|
||||
$path = $this->aliasManager->getAliasByPath($path, $langcode);
|
||||
// Ensure the resulting path has at most one leading slash, to prevent it
|
||||
// becoming an external URL without a protocol like //example.com. This
|
||||
// is done in \Drupal\Core\Routing\UrlGenerator::generateFromRoute()
|
||||
// also, to protect against this problem in arbitrary path processors,
|
||||
// but it is duplicated here to protect any other URL generation code
|
||||
// that might call this method separately.
|
||||
if (str_starts_with($path, '//')) {
|
||||
$path = '/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for path_alias.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for unauthenticated JSON requests.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasJsonAnonTest extends PathAliasResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for JSON requests via basic auth.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasJsonBasicAuthTest extends PathAliasResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for JSON requests with cookie authentication.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasJsonCookieTest extends PathAliasResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\path_alias\Entity\PathAlias;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* Base class for path_alias EntityResource tests.
|
||||
*/
|
||||
abstract class PathAliasResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['path', 'path_alias'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'path_alias';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $firstCreatedEntityId = 3;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $secondCreatedEntityId = 4;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer url aliases']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$path_alias = PathAlias::create([
|
||||
'path' => '/<front>',
|
||||
'alias' => '/frontpage1',
|
||||
]);
|
||||
$path_alias->save();
|
||||
return $path_alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'id' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'revision_id' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
],
|
||||
],
|
||||
'path' => [
|
||||
[
|
||||
'value' => '/<front>',
|
||||
],
|
||||
],
|
||||
'alias' => [
|
||||
[
|
||||
'value' => '/frontpage1',
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->entity->uuid(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'path' => [
|
||||
[
|
||||
'value' => '/<front>',
|
||||
],
|
||||
],
|
||||
'alias' => [
|
||||
[
|
||||
'value' => '/frontpage1',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for unauthenticated XML requests.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasXmlAnonTest extends PathAliasResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for XML requests with cookie authentication.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasXmlBasicAuthTest extends PathAliasResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for XML requests.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasXmlCookieTest extends PathAliasResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the update path for the path_alias_revision table indices.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasRevisionIndexesUpdatePathTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles(): void {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the update path for the path_alias_revision table indices.
|
||||
*/
|
||||
public function testRunUpdates(): void {
|
||||
$schema = \Drupal::database()->schema();
|
||||
|
||||
$this->assertFalse($schema->indexExists('path_alias_revision', 'path_alias_revision__alias_langcode_id_status'));
|
||||
$this->assertFalse($schema->indexExists('path_alias_revision', 'path_alias_revision__path_langcode_id_status'));
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
$this->assertTrue($schema->indexExists('path_alias_revision', 'path_alias_revision__alias_langcode_id_status'));
|
||||
$this->assertTrue($schema->indexExists('path_alias_revision', 'path_alias_revision__path_langcode_id_status'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* Tests altering the inbound path and the outbound path.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class UrlAlterFunctionalTest extends BrowserTestBase {
|
||||
|
||||
use PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['path', 'url_alter_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that URL altering works and that it occurs in the correct order.
|
||||
*/
|
||||
public function testUrlAlter(): void {
|
||||
// Ensure that the path_alias table exists after Drupal installation.
|
||||
$this->assertTrue(Database::getConnection()->schema()->tableExists('path_alias'), 'The path_alias table exists after Drupal installation.');
|
||||
|
||||
// User names can have quotes and plus signs so we should ensure that URL
|
||||
// altering works with this.
|
||||
$account = $this->drupalCreateUser(['administer url aliases'], "it's+bar");
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$uid = $account->id();
|
||||
$name = $account->getAccountName();
|
||||
|
||||
// Test a single altered path.
|
||||
$this->drupalGet("user/$name");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertUrlOutboundAlter("/user/$uid", "/user/$name");
|
||||
|
||||
// Test that a path always uses its alias.
|
||||
$this->createPathAlias("/user/$uid/test1", '/alias/test1');
|
||||
$this->rebuildContainer();
|
||||
$this->assertUrlInboundAlter('/alias/test1', "/user/$uid/test1");
|
||||
$this->assertUrlOutboundAlter("/user/$uid/test1", '/alias/test1');
|
||||
|
||||
// Test adding an alias via the UI.
|
||||
$edit = ['path[0][value]' => "/user/$uid/edit", 'alias[0][value]' => '/alias/test2'];
|
||||
$this->drupalGet('admin/config/search/path/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The alias has been saved.');
|
||||
$this->drupalGet('alias/test2');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertUrlOutboundAlter("/user/$uid/edit", '/alias/test2');
|
||||
|
||||
// Test a non-existent user is not altered.
|
||||
$uid++;
|
||||
$this->assertUrlOutboundAlter("/user/$uid", "/user/$uid");
|
||||
|
||||
// Test outbound query string altering.
|
||||
$url = Url::fromRoute('user.login');
|
||||
$this->assertSame(\Drupal::request()->getBaseUrl() . '/user/login?foo=bar', $url->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an outbound path is altered to an expected value.
|
||||
*
|
||||
* @param string $original
|
||||
* A string with the original path that is run through generateFrommPath().
|
||||
* @param string $final
|
||||
* A string with the expected result after generateFrommPath().
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertUrlOutboundAlter(string $original, string $final): void {
|
||||
// Test outbound altering.
|
||||
$result = $this->container->get('path_processor_manager')->processOutbound($original);
|
||||
$this->assertSame($final, $result, "Altered outbound URL $original, expected $final, and got $result.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an inbound path is altered to an expected value.
|
||||
*
|
||||
* @param string $original
|
||||
* The original path before it has been altered by inbound URL processing.
|
||||
* @param string $final
|
||||
* A string with the expected result.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertUrlInboundAlter(string $original, string $final): void {
|
||||
// Test inbound altering.
|
||||
$result = $this->container->get('path_alias.manager')->getPathByAlias($original);
|
||||
$this->assertSame($final, $result, "Altered inbound URL $original, expected $final, and got $result.");
|
||||
}
|
||||
|
||||
}
|
||||
472
web/core/modules/path_alias/tests/src/Kernel/AliasTest.php
Normal file
472
web/core/modules/path_alias/tests/src/Kernel/AliasTest.php
Normal file
@ -0,0 +1,472 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Kernel;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Cache\MemoryCounterBackend;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\path_alias\AliasManager;
|
||||
use Drupal\path_alias\AliasPrefixList;
|
||||
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* Tests path alias CRUD and lookup functionality.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\path_alias\AliasRepository
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class AliasTest extends KernelTestBase {
|
||||
|
||||
use PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['path_alias'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// The alias prefix list expects that the menu path roots are set by a
|
||||
// menu router rebuild.
|
||||
\Drupal::state()->set('router.path_roots', ['user', 'admin']);
|
||||
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::preloadPathAlias
|
||||
*/
|
||||
public function testPreloadPathAlias(): void {
|
||||
$path_alias_repository = $this->container->get('path_alias.repository');
|
||||
|
||||
// Every interesting language combination:
|
||||
// Just unspecified.
|
||||
$this->createPathAlias('/und/src', '/und/alias', LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
// Just a single language.
|
||||
$this->createPathAlias('/en/src', '/en/alias', 'en');
|
||||
// A single language, plus unspecified.
|
||||
$this->createPathAlias('/en-und/src', '/en-und/und', LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$this->createPathAlias('/en-und/src', '/en-und/en', 'en');
|
||||
// Multiple languages.
|
||||
$this->createPathAlias('/en-xx-lolspeak/src', '/en-xx-lolspeak/en', 'en');
|
||||
$this->createPathAlias('/en-xx-lolspeak/src', '/en-xx-lolspeak/xx-lolspeak', 'xx-lolspeak');
|
||||
// A duplicate alias for the same path. This is later, so should be
|
||||
// preferred.
|
||||
$this->createPathAlias('/en-xx-lolspeak/src', '/en-xx-lolspeak/en-dup', 'en');
|
||||
// Multiple languages, plus unspecified.
|
||||
$this->createPathAlias('/en-xx-lolspeak-und/src', '/en-xx-lolspeak-und/und', LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$this->createPathAlias('/en-xx-lolspeak-und/src', '/en-xx-lolspeak-und/en', 'en');
|
||||
$this->createPathAlias('/en-xx-lolspeak-und/src', '/en-xx-lolspeak-und/xx-lolspeak', 'xx-lolspeak');
|
||||
|
||||
// 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-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/und',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias([], LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
);
|
||||
// Ask for nonexistent source.
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$path_alias_repository->preloadPathAlias(['/nonexistent'], LanguageInterface::LANGCODE_NOT_SPECIFIED));
|
||||
// Ask for each saved source, individually.
|
||||
$this->assertEquals(
|
||||
['/und/src' => '/und/alias'],
|
||||
$path_alias_repository->preloadPathAlias(['/und/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
);
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$path_alias_repository->preloadPathAlias(['/en/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-und/src' => '/en-und/und'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-und/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
);
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$path_alias_repository->preloadPathAlias(['/en-xx-lolspeak/src'], LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/und'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-xx-lolspeak-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-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/und',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias(
|
||||
[
|
||||
'/nonexistent',
|
||||
'/und/src',
|
||||
'/en/src',
|
||||
'/en-und/src',
|
||||
'/en-xx-lolspeak/src',
|
||||
'/en-xx-lolspeak-und/src',
|
||||
],
|
||||
LanguageInterface::LANGCODE_NOT_SPECIFIED
|
||||
)
|
||||
);
|
||||
// Ask for multiple sources, just a subset.
|
||||
$this->assertEquals(
|
||||
[
|
||||
'/und/src' => '/und/alias',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/und',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias(
|
||||
[
|
||||
'/und/src',
|
||||
'/en-xx-lolspeak/src',
|
||||
'/en-xx-lolspeak-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-xx-lolspeak/src' => '/en-xx-lolspeak/en-dup',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/en',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias([], 'en')
|
||||
);
|
||||
// Ask for nonexistent source.
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$path_alias_repository->preloadPathAlias(['/nonexistent'], 'en'));
|
||||
// Ask for each saved source, individually.
|
||||
$this->assertEquals(
|
||||
['/und/src' => '/und/alias'],
|
||||
$path_alias_repository->preloadPathAlias(['/und/src'], 'en')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en/src' => '/en/alias'],
|
||||
$path_alias_repository->preloadPathAlias(['/en/src'], 'en')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-und/src' => '/en-und/en'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-und/src'], 'en')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-xx-lolspeak/src' => '/en-xx-lolspeak/en-dup'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-xx-lolspeak/src'], 'en')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/en'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-xx-lolspeak-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-xx-lolspeak/src' => '/en-xx-lolspeak/en-dup',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/en',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias(
|
||||
[
|
||||
'/nonexistent',
|
||||
'/und/src',
|
||||
'/en/src',
|
||||
'/en-und/src',
|
||||
'/en-xx-lolspeak/src',
|
||||
'/en-xx-lolspeak-und/src',
|
||||
],
|
||||
'en'
|
||||
)
|
||||
);
|
||||
// Ask for multiple sources, just a subset.
|
||||
$this->assertEquals(
|
||||
[
|
||||
'/und/src' => '/und/alias',
|
||||
'/en-xx-lolspeak/src' => '/en-xx-lolspeak/en-dup',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/en',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias(
|
||||
[
|
||||
'/und/src',
|
||||
'/en-xx-lolspeak/src',
|
||||
'/en-xx-lolspeak-und/src',
|
||||
],
|
||||
'en'
|
||||
)
|
||||
);
|
||||
|
||||
// Queries for xx-lolspeak aliases.
|
||||
// Ask for an empty array, get all results.
|
||||
$this->assertEquals(
|
||||
[
|
||||
'/und/src' => '/und/alias',
|
||||
'/en-und/src' => '/en-und/und',
|
||||
'/en-xx-lolspeak/src' => '/en-xx-lolspeak/xx-lolspeak',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/xx-lolspeak',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias([], 'xx-lolspeak')
|
||||
);
|
||||
// Ask for nonexistent source.
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$path_alias_repository->preloadPathAlias(['/nonexistent'], 'xx-lolspeak'));
|
||||
// Ask for each saved source, individually.
|
||||
$this->assertEquals(
|
||||
['/und/src' => '/und/alias'],
|
||||
$path_alias_repository->preloadPathAlias(['/und/src'], 'xx-lolspeak')
|
||||
);
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$path_alias_repository->preloadPathAlias(['/en/src'], 'xx-lolspeak')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-und/src' => '/en-und/und'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-und/src'], 'xx-lolspeak')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-xx-lolspeak/src' => '/en-xx-lolspeak/xx-lolspeak'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-xx-lolspeak/src'], 'xx-lolspeak')
|
||||
);
|
||||
$this->assertEquals(
|
||||
['/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/xx-lolspeak'],
|
||||
$path_alias_repository->preloadPathAlias(['/en-xx-lolspeak-und/src'], 'xx-lolspeak')
|
||||
);
|
||||
// Ask for multiple sources, all that are known.
|
||||
$this->assertEquals(
|
||||
[
|
||||
'/und/src' => '/und/alias',
|
||||
'/en-und/src' => '/en-und/und',
|
||||
'/en-xx-lolspeak/src' => '/en-xx-lolspeak/xx-lolspeak',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/xx-lolspeak',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias(
|
||||
[
|
||||
'/nonexistent',
|
||||
'/und/src',
|
||||
'/en/src',
|
||||
'/en-und/src',
|
||||
'/en-xx-lolspeak/src',
|
||||
'/en-xx-lolspeak-und/src',
|
||||
],
|
||||
'xx-lolspeak'
|
||||
)
|
||||
);
|
||||
// Ask for multiple sources, just a subset.
|
||||
$this->assertEquals(
|
||||
[
|
||||
'/und/src' => '/und/alias',
|
||||
'/en-xx-lolspeak/src' => '/en-xx-lolspeak/xx-lolspeak',
|
||||
'/en-xx-lolspeak-und/src' => '/en-xx-lolspeak-und/xx-lolspeak',
|
||||
],
|
||||
$path_alias_repository->preloadPathAlias(
|
||||
[
|
||||
'/und/src',
|
||||
'/en-xx-lolspeak/src',
|
||||
'/en-xx-lolspeak-und/src',
|
||||
],
|
||||
'xx-lolspeak'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::lookupBySystemPath
|
||||
*/
|
||||
public function testLookupBySystemPath(): void {
|
||||
$this->createPathAlias('/test-source-Case', '/test-alias');
|
||||
|
||||
$path_alias_repository = $this->container->get('path_alias.repository');
|
||||
$this->assertEquals('/test-alias', $path_alias_repository->lookupBySystemPath('/test-source-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['alias']);
|
||||
$this->assertEquals('/test-alias', $path_alias_repository->lookupBySystemPath('/test-source-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['alias']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::lookupByAlias
|
||||
*/
|
||||
public function testLookupByAlias(): void {
|
||||
$this->createPathAlias('/test-source', '/test-alias-Case');
|
||||
|
||||
$path_alias_repository = $this->container->get('path_alias.repository');
|
||||
$this->assertEquals('/test-source', $path_alias_repository->lookupByAlias('/test-alias-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['path']);
|
||||
$this->assertEquals('/test-source', $path_alias_repository->lookupByAlias('/test-alias-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\path_alias\AliasManager::getPathByAlias
|
||||
* @covers \Drupal\path_alias\AliasManager::getAliasByPath
|
||||
*/
|
||||
public function testLookupPath(): void {
|
||||
// Create AliasManager and Path object.
|
||||
$aliasManager = $this->container->get('path_alias.manager');
|
||||
|
||||
// Test the situation where the source is the same for multiple aliases.
|
||||
// Start with a language-neutral alias, which we will override.
|
||||
$path_alias = $this->createPathAlias('/user/1', '/foo');
|
||||
$this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'Basic alias lookup works.');
|
||||
$this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'Basic source lookup works.');
|
||||
|
||||
// Create a language specific alias for the default language (English).
|
||||
$path_alias = $this->createPathAlias('/user/1', '/users/Dries', 'en');
|
||||
|
||||
$this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'English alias overrides language-neutral alias.');
|
||||
$this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'English source overrides language-neutral source.');
|
||||
|
||||
// Create a language-neutral alias for the same path, again.
|
||||
$path_alias = $this->createPathAlias('/user/1', '/bar');
|
||||
$this->assertEquals("/users/Dries", $aliasManager->getAliasByPath($path_alias->getPath()), 'English alias still returned after entering a language-neutral alias.');
|
||||
|
||||
// Create a language-specific (xx-lolspeak) alias for the same path.
|
||||
$path_alias = $this->createPathAlias('/user/1', '/LOL', 'xx-lolspeak');
|
||||
$this->assertEquals("/users/Dries", $aliasManager->getAliasByPath($path_alias->getPath()), 'English alias still returned after entering a LOLspeak alias.');
|
||||
// The LOLspeak alias should be returned if we really want LOLspeak.
|
||||
$this->assertEquals('/LOL', $aliasManager->getAliasByPath($path_alias->getPath(), 'xx-lolspeak'), 'LOLspeak alias returned if we specify xx-lolspeak to the alias manager.');
|
||||
|
||||
// Create a new alias for this path in English, which should override the
|
||||
// previous alias for "user/1".
|
||||
$path_alias = $this->createPathAlias('/user/1', '/users/my-new-path', 'en');
|
||||
$this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'Recently created English alias returned.');
|
||||
$this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'Recently created English source returned.');
|
||||
|
||||
// Remove the English aliases, which should cause a fallback to the most
|
||||
// recently created language-neutral alias, 'bar'.
|
||||
$path_alias_storage = $this->container->get('entity_type.manager')->getStorage('path_alias');
|
||||
$entities = $path_alias_storage->loadByProperties(['langcode' => 'en']);
|
||||
$path_alias_storage->delete($entities);
|
||||
$this->assertEquals('/bar', $aliasManager->getAliasByPath($path_alias->getPath()), 'Path lookup falls back to recently created language-neutral alias.');
|
||||
|
||||
// Test the situation where the alias and language are the same, but
|
||||
// the source differs. The newer alias record should be returned.
|
||||
$this->createPathAlias('/user/2', '/bar');
|
||||
$aliasManager->cacheClear();
|
||||
$this->assertEquals('/user/2', $aliasManager->getPathByAlias('/bar'), 'Newer alias record is returned when comparing two LanguageInterface::LANGCODE_NOT_SPECIFIED paths with the same alias.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the alias prefix.
|
||||
*/
|
||||
public function testPrefixList(): void {
|
||||
$memoryCounterBackend = new MemoryCounterBackend(\Drupal::service(TimeInterface::class));
|
||||
|
||||
// Create AliasManager and Path object.
|
||||
$prefix_list = new AliasPrefixList('path_alias_prefix_list', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository'));
|
||||
$aliasManager = new AliasManager($this->container->get('path_alias.repository'), $prefix_list, $this->container->get('language_manager'), $memoryCounterBackend, $this->container->get(TimeInterface::class));
|
||||
|
||||
// No alias for user and admin yet, so should be NULL.
|
||||
$this->assertNull($prefix_list->get('user'));
|
||||
$this->assertNull($prefix_list->get('admin'));
|
||||
|
||||
// Non-existing path roots should be NULL too. Use a length of 7 to avoid
|
||||
// possible conflict with random aliases below.
|
||||
$this->assertNull($prefix_list->get($this->randomMachineName()));
|
||||
|
||||
// Add an alias for user/1, user should get cached now.
|
||||
$this->createPathAlias('/user/1', '/' . $this->randomMachineName());
|
||||
$aliasManager->cacheClear();
|
||||
$this->assertTrue($prefix_list->get('user'));
|
||||
$this->assertNull($prefix_list->get('admin'));
|
||||
$this->assertNull($prefix_list->get($this->randomMachineName()));
|
||||
|
||||
// Add an alias for admin, both should get cached now.
|
||||
$this->createPathAlias('/admin/something', '/' . $this->randomMachineName());
|
||||
$aliasManager->cacheClear();
|
||||
$this->assertTrue($prefix_list->get('user'));
|
||||
$this->assertTrue($prefix_list->get('admin'));
|
||||
$this->assertNull($prefix_list->get($this->randomMachineName()));
|
||||
|
||||
// Remove the user alias again, prefix list entry should be removed.
|
||||
$path_alias_storage = $this->container->get('entity_type.manager')->getStorage('path_alias');
|
||||
$entities = $path_alias_storage->loadByProperties(['path' => '/user/1']);
|
||||
$path_alias_storage->delete($entities);
|
||||
$aliasManager->cacheClear();
|
||||
$this->assertNull($prefix_list->get('user'));
|
||||
$this->assertTrue($prefix_list->get('admin'));
|
||||
$this->assertNull($prefix_list->get($this->randomMachineName()));
|
||||
|
||||
// Destruct the prefix list so that the caches are written.
|
||||
$prefix_list->destruct();
|
||||
$this->assertEquals(1, $memoryCounterBackend->getCounter('set', 'path_alias_prefix_list'));
|
||||
$memoryCounterBackend->resetCounter();
|
||||
|
||||
// Re-initialize the prefix list using the same cache backend, should load
|
||||
// from cache.
|
||||
$prefix_list = new AliasPrefixList('path_alias_prefix_list', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository'));
|
||||
$this->assertNull($prefix_list->get('user'));
|
||||
$this->assertTrue($prefix_list->get('admin'));
|
||||
$this->assertNull($prefix_list->get($this->randomMachineName()));
|
||||
$this->assertEquals(1, $memoryCounterBackend->getCounter('get', 'path_alias_prefix_list'));
|
||||
$this->assertEquals(0, $memoryCounterBackend->getCounter('set', 'path_alias_prefix_list'));
|
||||
|
||||
// Destruct the prefix list, should not attempt to write the cache again.
|
||||
$prefix_list->destruct();
|
||||
$this->assertEquals(1, $memoryCounterBackend->getCounter('get', 'path_alias_prefix_list'));
|
||||
$this->assertEquals(0, $memoryCounterBackend->getCounter('set', 'path_alias_prefix_list'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests situation where the prefix list cache is deleted mid-request.
|
||||
*/
|
||||
public function testPrefixListCacheDeletionMidRequest() {
|
||||
$memoryCounterBackend = new MemoryCounterBackend(\Drupal::service(TimeInterface::class));
|
||||
|
||||
// Create AliasManager and Path object.
|
||||
$prefix_list = new AliasPrefixList('path_alias_prefix_list', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository'));
|
||||
|
||||
// Prefix list cache should not exist at all yet.
|
||||
$this->assertFalse($memoryCounterBackend->get('path_alias_prefix_list'));
|
||||
|
||||
// Add some aliases for both menu routes we have.
|
||||
$this->createPathAlias('/admin/something', '/' . $this->randomMachineName());
|
||||
$this->createPathAlias('/user/something', '/' . $this->randomMachineName());
|
||||
|
||||
// Lookup admin path in prefix list. It will query the DB and figure out
|
||||
// that it indeed has an alias, and add it to the internal prefix list and
|
||||
// flag it to be persisted to cache.
|
||||
$this->assertTrue($prefix_list->get('admin'));
|
||||
|
||||
// Destruct the prefix list so it persists its cache.
|
||||
$prefix_list->destruct();
|
||||
$this->assertEquals(1, $memoryCounterBackend->getCounter('set', 'path_alias_prefix_list'));
|
||||
// Cache data should have data for 'user' and 'admin', even though just
|
||||
// 'admin' was looked up. This is because the cache is primed with all
|
||||
// menu router base paths.
|
||||
$this->assertEquals(['user' => FALSE, 'admin' => TRUE], $memoryCounterBackend->get('path_alias_prefix_list')->data);
|
||||
$memoryCounterBackend->resetCounter();
|
||||
|
||||
// Re-initialize the prefix list and lookup an alias for the 'user' path.
|
||||
// Prefix list should load data from its cache, see that it hasn't done a
|
||||
// check for 'user' yet, perform the check, then mark the result to be
|
||||
// persisted to cache.
|
||||
$prefix_list = new AliasPrefixList('path_alias_prefix_list', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('path_alias.repository'));
|
||||
$this->assertTrue($prefix_list->get('user'));
|
||||
|
||||
// Delete the prefix list cache. This could happen from an outside process,
|
||||
// like a code deployment that performs a cache rebuild.
|
||||
$memoryCounterBackend->delete('path_alias_prefix_list');
|
||||
|
||||
// Destruct prefix list so it attempts to save the prefix list data to
|
||||
// cache. However it should recognize that the previous cache entry was
|
||||
// deleted from underneath it and not save anything to cache, to protect
|
||||
// from cache corruption.
|
||||
$prefix_list->destruct();
|
||||
$this->assertEquals(0, $memoryCounterBackend->getCounter('set', 'path_alias_prefix_list'));
|
||||
$this->assertFalse($memoryCounterBackend->get('path_alias_prefix_list'));
|
||||
$memoryCounterBackend->resetCounter();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* Tests path alias on entities.
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class EntityAliasTest extends KernelTestBase {
|
||||
|
||||
use PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'path_alias',
|
||||
'entity_test',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->installEntitySchema('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests transform.
|
||||
*/
|
||||
public function testEntityAlias(): void {
|
||||
EntityTest::create(['id' => 1])->save();
|
||||
$this->createPathAlias('/entity_test/1', '/entity-alias');
|
||||
$entity = EntityTest::load(1);
|
||||
$this->assertSame('/entity-alias', $entity->toUrl()->toString());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the path_alias storage schema.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\path_alias\PathAliasStorageSchema
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathAliasStorageSchemaTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['path_alias'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the path_alias__status index is removed.
|
||||
*
|
||||
* @covers ::getEntitySchema
|
||||
*/
|
||||
public function testPathAliasStatusIndexRemoved(): void {
|
||||
$schema = \Drupal::database()->schema();
|
||||
$table_name = 'path_alias';
|
||||
$this->assertTrue($schema->indexExists($table_name, 'path_alias__alias_langcode_id_status'));
|
||||
$this->assertTrue($schema->indexExists($table_name, 'path_alias__path_langcode_id_status'));
|
||||
$this->assertFalse($schema->indexExists($table_name, 'path_alias__status'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\path_alias\AliasManagerInterface;
|
||||
use Drupal\path_alias\Entity\PathAlias;
|
||||
use Prophecy\Argument;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\path_alias\Entity\PathAlias
|
||||
*
|
||||
* @group path_alias
|
||||
*/
|
||||
class PathHooksTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['path_alias'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the PathAlias entity clears caches correctly.
|
||||
*
|
||||
* @covers ::postSave
|
||||
* @covers ::postDelete
|
||||
*/
|
||||
public function testPathHooks(): void {
|
||||
$path_alias = PathAlias::create([
|
||||
'path' => '/' . $this->randomMachineName(),
|
||||
'alias' => '/' . $this->randomMachineName(),
|
||||
]);
|
||||
|
||||
// Check \Drupal\path_alias\Entity\PathAlias::postSave() for new path alias
|
||||
// entities.
|
||||
$alias_manager = $this->prophesize(AliasManagerInterface::class);
|
||||
$alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1);
|
||||
$alias_manager->cacheClear($path_alias->getPath())->shouldBeCalledTimes(1);
|
||||
\Drupal::getContainer()->set('path_alias.manager', $alias_manager->reveal());
|
||||
$path_alias->save();
|
||||
|
||||
$new_source = '/' . $this->randomMachineName();
|
||||
|
||||
// Check \Drupal\path_alias\Entity\PathAlias::postSave() for existing path
|
||||
// alias entities.
|
||||
$alias_manager = $this->prophesize(AliasManagerInterface::class);
|
||||
$alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(2);
|
||||
$alias_manager->cacheClear($path_alias->getPath())->shouldBeCalledTimes(1);
|
||||
$alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1);
|
||||
\Drupal::getContainer()->set('path_alias.manager', $alias_manager->reveal());
|
||||
$path_alias->setPath($new_source);
|
||||
$path_alias->save();
|
||||
|
||||
// Check \Drupal\path_alias\Entity\PathAlias::postDelete().
|
||||
$alias_manager = $this->prophesize(AliasManagerInterface::class);
|
||||
$alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1);
|
||||
$alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1);
|
||||
\Drupal::getContainer()->set('path_alias.manager', $alias_manager->reveal());
|
||||
$path_alias->delete();
|
||||
}
|
||||
|
||||
}
|
||||
565
web/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php
Normal file
565
web/core/modules/path_alias/tests/src/Unit/AliasManagerTest.php
Normal file
@ -0,0 +1,565 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Unit;
|
||||
|
||||
use Drupal\Component\Datetime\Time;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\path_alias\AliasRepositoryInterface;
|
||||
use Drupal\path_alias\AliasManager;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\path_alias\AliasManager
|
||||
* @group path_alias
|
||||
*/
|
||||
class AliasManagerTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The alias manager.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasManager
|
||||
*/
|
||||
protected $aliasManager;
|
||||
|
||||
/**
|
||||
* Alias repository.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $aliasRepository;
|
||||
|
||||
/**
|
||||
* Alias prefix list.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasPrefixListInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $aliasPrefixList;
|
||||
|
||||
/**
|
||||
* Language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The internal cache key used by the alias manager.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheKey = 'preload-paths:key';
|
||||
|
||||
/**
|
||||
* The cache key passed to the alias manager.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path = 'key';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->aliasRepository = $this->createMock(AliasRepositoryInterface::class);
|
||||
$this->aliasPrefixList = $this->createMock('Drupal\path_alias\AliasPrefixListInterface');
|
||||
$this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface');
|
||||
$this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface');
|
||||
|
||||
$this->aliasManager = new AliasManager($this->aliasRepository, $this->aliasPrefixList, $this->languageManager, $this->cache, new Time());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getPathByAlias method for an alias that have no matching path.
|
||||
*
|
||||
* @covers ::getPathByAlias
|
||||
*/
|
||||
public function testGetPathByAliasNoMatch(): void {
|
||||
$alias = '/' . $this->randomMachineName();
|
||||
|
||||
$language = new Language(['id' => 'en']);
|
||||
|
||||
$this->languageManager->expects($this->any())
|
||||
->method('getCurrentLanguage')
|
||||
->with(LanguageInterface::TYPE_URL)
|
||||
->willReturn($language);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupByAlias')
|
||||
->with($alias, $language->getId())
|
||||
->willReturn(NULL);
|
||||
|
||||
$this->assertEquals($alias, $this->aliasManager->getPathByAlias($alias));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($alias, $this->aliasManager->getPathByAlias($alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getPathByAlias method for an alias that have a matching path.
|
||||
*
|
||||
* @covers ::getPathByAlias
|
||||
*/
|
||||
public function testGetPathByAliasMatch(): void {
|
||||
$alias = $this->randomMachineName();
|
||||
$path = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupByAlias')
|
||||
->with($alias, $language->getId())
|
||||
->willReturn(['path' => $path]);
|
||||
|
||||
$this->assertEquals($path, $this->aliasManager->getPathByAlias($alias));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($path, $this->aliasManager->getPathByAlias($alias));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getPathByAlias method when a langcode is passed explicitly.
|
||||
*
|
||||
* @covers ::getPathByAlias
|
||||
*/
|
||||
public function testGetPathByAliasLangcode(): void {
|
||||
$alias = $this->randomMachineName();
|
||||
$path = $this->randomMachineName();
|
||||
|
||||
$this->languageManager->expects($this->never())
|
||||
->method('getCurrentLanguage');
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupByAlias')
|
||||
->with($alias, 'de')
|
||||
->willReturn(['path' => $path]);
|
||||
|
||||
$this->assertEquals($path, $this->aliasManager->getPathByAlias($alias, 'de'));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($path, $this->aliasManager->getPathByAlias($alias, 'de'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath method for a path that is not in the prefix list.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
*/
|
||||
public function testGetAliasByPathPrefixList() {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
|
||||
$this->setUpCurrentLanguage();
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(FALSE);
|
||||
|
||||
// The prefix list returns FALSE for that path part, so the storage should
|
||||
// never be called.
|
||||
$this->aliasRepository->expects($this->never())
|
||||
->method('lookupBySystemPath');
|
||||
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath method for a path that has no matching alias.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
*/
|
||||
public function testGetAliasByPathNoMatch(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupBySystemPath')
|
||||
->with($path, $language->getId())
|
||||
->willReturn(NULL);
|
||||
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// This needs to write out the cache.
|
||||
$this->cache->expects($this->once())
|
||||
->method('set')
|
||||
->with($this->cacheKey, [$language->getId() => [$path]], (int) $_SERVER['REQUEST_TIME'] + (60 * 60 * 24));
|
||||
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath method exception.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
*/
|
||||
public function testGetAliasByPathException(): void {
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->aliasManager->getAliasByPath('no-leading-slash-here');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath method for a path that has a matching alias.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
* @covers ::writeCache
|
||||
*/
|
||||
public function testGetAliasByPathMatch(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
$alias = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupBySystemPath')
|
||||
->with($path, $language->getId())
|
||||
->willReturn(['alias' => $alias]);
|
||||
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// This needs to write out the cache.
|
||||
$this->cache->expects($this->once())
|
||||
->method('set')
|
||||
->with($this->cacheKey, [$language->getId() => [$path]], (int) $_SERVER['REQUEST_TIME'] + (60 * 60 * 24));
|
||||
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath method for a path that is preloaded.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
* @covers ::writeCache
|
||||
*/
|
||||
public function testGetAliasByPathCachedMatch(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
$alias = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
// Use a set of cached paths where the tested path is in any position, not
|
||||
// only in the first one.
|
||||
$cached_paths = [
|
||||
$language->getId() => [
|
||||
'/another/path',
|
||||
$path,
|
||||
],
|
||||
];
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->cacheKey)
|
||||
->willReturn((object) ['data' => $cached_paths]);
|
||||
|
||||
// Simulate a request so that the preloaded paths are fetched.
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('preloadPathAlias')
|
||||
->with($cached_paths[$language->getId()], $language->getId())
|
||||
->willReturn([$path => $alias]);
|
||||
|
||||
// LookupPathAlias should not be called.
|
||||
$this->aliasRepository->expects($this->never())
|
||||
->method('lookupBySystemPath');
|
||||
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// This must not write to the cache again.
|
||||
$this->cache->expects($this->never())
|
||||
->method('set');
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath cache when a different language is requested.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
* @covers ::writeCache
|
||||
*/
|
||||
public function testGetAliasByPathCachedMissLanguage(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
$alias = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
$cached_language = new Language(['id' => 'de']);
|
||||
|
||||
$cached_paths = [$cached_language->getId() => [$path]];
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->cacheKey)
|
||||
->willReturn((object) ['data' => $cached_paths]);
|
||||
|
||||
// Simulate a request so that the preloaded paths are fetched.
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
// The requested language is different than the cached, so this will
|
||||
// need to load.
|
||||
$this->aliasRepository->expects($this->never())
|
||||
->method('preloadPathAlias');
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupBySystemPath')
|
||||
->with($path, $language->getId())
|
||||
->willReturn(['alias' => $alias]);
|
||||
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// There is already a cache entry, so this should not write out to the
|
||||
// cache.
|
||||
$this->cache->expects($this->never())
|
||||
->method('set');
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath cache with a preloaded path without alias.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
* @covers ::writeCache
|
||||
*/
|
||||
public function testGetAliasByPathCachedMissNoAlias(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
$cached_path = $this->randomMachineName();
|
||||
$cached_alias = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
$cached_paths = [$language->getId() => [$cached_path, $path]];
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->cacheKey)
|
||||
->willReturn((object) ['data' => $cached_paths]);
|
||||
|
||||
// Simulate a request so that the preloaded paths are fetched.
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('preloadPathAlias')
|
||||
->with($cached_paths[$language->getId()], $language->getId())
|
||||
->willReturn([$cached_path => $cached_alias]);
|
||||
|
||||
// LookupPathAlias() should not be called.
|
||||
$this->aliasRepository->expects($this->never())
|
||||
->method('lookupBySystemPath');
|
||||
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// This must not write to the cache again.
|
||||
$this->cache->expects($this->never())
|
||||
->method('set');
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath cache with an un-preloaded path without alias.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
* @covers ::writeCache
|
||||
*/
|
||||
public function testGetAliasByPathUncachedMissNoAlias(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
$cached_path = $this->randomMachineName();
|
||||
$cached_alias = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
$cached_paths = [$language->getId() => [$cached_path]];
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->cacheKey)
|
||||
->willReturn((object) ['data' => $cached_paths]);
|
||||
|
||||
// Simulate a request so that the preloaded paths are fetched.
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('preloadPathAlias')
|
||||
->with($cached_paths[$language->getId()], $language->getId())
|
||||
->willReturn([$cached_path => $cached_alias]);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupBySystemPath')
|
||||
->with($path, $language->getId())
|
||||
->willReturn(NULL);
|
||||
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($path, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// There is already a cache entry, so this should not write out to the
|
||||
// cache.
|
||||
$this->cache->expects($this->never())
|
||||
->method('set');
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::cacheClear
|
||||
*/
|
||||
public function testCacheClear(): void {
|
||||
$path = '/path';
|
||||
$alias = '/alias';
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
$this->aliasRepository->expects($this->exactly(2))
|
||||
->method('lookupBySystemPath')
|
||||
->with($path, $language->getId())
|
||||
->willReturn(['alias' => $alias]);
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->willReturn(TRUE);
|
||||
|
||||
// Populate the lookup map.
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path, $language->getId()));
|
||||
|
||||
// Check that the cache is populated.
|
||||
$this->aliasRepository->expects($this->never())
|
||||
->method('lookupByAlias');
|
||||
$this->assertEquals($path, $this->aliasManager->getPathByAlias($alias, $language->getId()));
|
||||
|
||||
// Clear specific source.
|
||||
$this->aliasManager->cacheClear($path);
|
||||
|
||||
// Ensure cache has been cleared (this will be the 2nd call to
|
||||
// `lookupPathAlias` if cache is cleared).
|
||||
$this->assertEquals($alias, $this->aliasManager->getAliasByPath($path, $language->getId()));
|
||||
|
||||
// Clear non-existent source.
|
||||
$this->aliasManager->cacheClear('non-existent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getAliasByPath cache with an un-preloaded path with alias.
|
||||
*
|
||||
* @covers ::getAliasByPath
|
||||
* @covers ::writeCache
|
||||
*/
|
||||
public function testGetAliasByPathUncachedMissWithAlias(): void {
|
||||
$path_part1 = $this->randomMachineName();
|
||||
$path_part2 = $this->randomMachineName();
|
||||
$path = '/' . $path_part1 . '/' . $path_part2;
|
||||
$cached_path = $this->randomMachineName();
|
||||
$cached_no_alias_path = $this->randomMachineName();
|
||||
$cached_alias = $this->randomMachineName();
|
||||
$new_alias = $this->randomMachineName();
|
||||
|
||||
$language = $this->setUpCurrentLanguage();
|
||||
|
||||
$cached_paths = [$language->getId() => [$cached_path, $cached_no_alias_path]];
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->cacheKey)
|
||||
->willReturn((object) ['data' => $cached_paths]);
|
||||
|
||||
// Simulate a request so that the preloaded paths are fetched.
|
||||
$this->aliasManager->setCacheKey($this->path);
|
||||
|
||||
$this->aliasPrefixList->expects($this->any())
|
||||
->method('get')
|
||||
->with($path_part1)
|
||||
->willReturn(TRUE);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('preloadPathAlias')
|
||||
->with($cached_paths[$language->getId()], $language->getId())
|
||||
->willReturn([$cached_path => $cached_alias]);
|
||||
|
||||
$this->aliasRepository->expects($this->once())
|
||||
->method('lookupBySystemPath')
|
||||
->with($path, $language->getId())
|
||||
->willReturn(['alias' => $new_alias]);
|
||||
|
||||
$this->assertEquals($new_alias, $this->aliasManager->getAliasByPath($path));
|
||||
// Call it twice to test the static cache.
|
||||
$this->assertEquals($new_alias, $this->aliasManager->getAliasByPath($path));
|
||||
|
||||
// There is already a cache entry, so this should not write out to the
|
||||
// cache.
|
||||
$this->cache->expects($this->never())
|
||||
->method('set');
|
||||
$this->aliasManager->writeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the current language.
|
||||
*
|
||||
* @return \Drupal\Core\Language\LanguageInterface
|
||||
* The current language object.
|
||||
*/
|
||||
protected function setUpCurrentLanguage() {
|
||||
$language = new Language(['id' => 'en']);
|
||||
|
||||
$this->languageManager->expects($this->any())
|
||||
->method('getCurrentLanguage')
|
||||
->with(LanguageInterface::TYPE_URL)
|
||||
->willReturn($language);
|
||||
|
||||
return $language;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\path_alias\Unit\PathProcessor;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\path_alias\PathProcessor\AliasPathProcessor;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\path_alias\PathProcessor\AliasPathProcessor
|
||||
* @group PathProcessor
|
||||
* @group path_alias
|
||||
*/
|
||||
class AliasPathProcessorTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The mocked alias manager.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasManagerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $aliasManager;
|
||||
|
||||
/**
|
||||
* The tested path processor.
|
||||
*
|
||||
* @var \Drupal\path_alias\PathProcessor\AliasPathProcessor
|
||||
*/
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->aliasManager = $this->createMock('Drupal\path_alias\AliasManagerInterface');
|
||||
$this->pathProcessor = new AliasPathProcessor($this->aliasManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the processInbound method.
|
||||
*
|
||||
* @see \Drupal\path_alias\PathProcessor\AliasPathProcessor::processInbound
|
||||
*/
|
||||
public function testProcessInbound(): void {
|
||||
$this->aliasManager->expects($this->exactly(2))
|
||||
->method('getPathByAlias')
|
||||
->willReturnMap([
|
||||
['url-alias', NULL, 'internal-url'],
|
||||
['url', NULL, 'url'],
|
||||
]);
|
||||
|
||||
$request = Request::create('/url-alias');
|
||||
$this->assertEquals('internal-url', $this->pathProcessor->processInbound('url-alias', $request));
|
||||
$request = Request::create('/url');
|
||||
$this->assertEquals('url', $this->pathProcessor->processInbound('url', $request));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::processOutbound
|
||||
*
|
||||
* @dataProvider providerTestProcessOutbound
|
||||
*/
|
||||
public function testProcessOutbound($path, array $options, $expected_path): void {
|
||||
$this->aliasManager->expects($this->any())
|
||||
->method('getAliasByPath')
|
||||
->willReturnMap([
|
||||
['internal-url', NULL, 'url-alias'],
|
||||
['url', NULL, 'url'],
|
||||
]);
|
||||
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$this->assertEquals($expected_path, $this->pathProcessor->processOutbound($path, $options, NULL, $bubbleable_metadata));
|
||||
// Cacheability of paths replaced with path aliases is permanent.
|
||||
// @todo https://www.drupal.org/node/2480077
|
||||
$this->assertEquals((new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT), $bubbleable_metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testing outbound processing.
|
||||
*
|
||||
* @return array
|
||||
* The data provider for testProcessOutbound.
|
||||
*/
|
||||
public static function providerTestProcessOutbound() {
|
||||
return [
|
||||
['internal-url', [], 'url-alias'],
|
||||
['internal-url', ['alias' => TRUE], 'internal-url'],
|
||||
['url', [], 'url'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user