Issue #2336597 by amateescu, slashrsm, jibran, Wim Leers, saki007ster, Berdir, catch, plach, Charlie ChX Negyesi, dawehner, gabesullice: Convert path aliases to full featured entities
parent
56895fda5c
commit
099ce08ac8
|
@ -955,7 +955,7 @@ services:
|
|||
- { name: event_subscriber }
|
||||
path.alias_storage:
|
||||
class: Drupal\Core\Path\AliasStorage
|
||||
arguments: ['@database', '@module_handler']
|
||||
arguments: ['@database', '@module_handler', '@entity_type.manager']
|
||||
tags:
|
||||
- { name: backend_overridable }
|
||||
path.matcher:
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
namespace Drupal\Core\Path;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\DatabaseException;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
|
||||
/**
|
||||
* Provides a class for CRUD operations on path aliases.
|
||||
|
@ -21,7 +21,7 @@ class AliasStorage implements AliasStorageInterface {
|
|||
/**
|
||||
* The table for the url_alias storage.
|
||||
*/
|
||||
const TABLE = 'url_alias';
|
||||
const TABLE = 'path_alias';
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
|
@ -37,6 +37,13 @@ class AliasStorage implements AliasStorageInterface {
|
|||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a Path CRUD object.
|
||||
*
|
||||
|
@ -44,17 +51,19 @@ class AliasStorage implements AliasStorageInterface {
|
|||
* A database connection for reading and writing path aliases.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(Connection $connection, ModuleHandlerInterface $module_handler) {
|
||||
public function __construct(Connection $connection, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager = NULL) {
|
||||
$this->connection = $connection;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->entityTypeManager = $entity_type_manager ?: \Drupal::entityTypeManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save($source, $alias, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, $pid = NULL) {
|
||||
|
||||
if ($source[0] !== '/') {
|
||||
throw new \InvalidArgumentException(sprintf('Source path %s has to start with a slash.', $source));
|
||||
}
|
||||
|
@ -63,227 +72,186 @@ class AliasStorage implements AliasStorageInterface {
|
|||
throw new \InvalidArgumentException(sprintf('Alias path %s has to start with a slash.', $alias));
|
||||
}
|
||||
|
||||
$fields = [
|
||||
'source' => $source,
|
||||
'alias' => $alias,
|
||||
'langcode' => $langcode,
|
||||
];
|
||||
if ($pid) {
|
||||
/** @var \Drupal\Core\Path\PathAliasInterface $path_alias */
|
||||
$path_alias = $this->getPathAliasEntityStorage()->load($pid);
|
||||
$original_values = [
|
||||
'source' => $path_alias->getPath(),
|
||||
'alias' => $path_alias->getAlias(),
|
||||
'langcode' => $path_alias->get('langcode')->value,
|
||||
];
|
||||
|
||||
// Insert or update the alias.
|
||||
if (empty($pid)) {
|
||||
$try_again = FALSE;
|
||||
try {
|
||||
$query = $this->connection->insert(static::TABLE)
|
||||
->fields($fields);
|
||||
$pid = $query->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// If there was an exception, try to create the table.
|
||||
if (!$try_again = $this->ensureTableExists()) {
|
||||
// If the exception happened for other reason than the missing table,
|
||||
// propagate the exception.
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
// Now that the table has been created, try again if necessary.
|
||||
if ($try_again) {
|
||||
$query = $this->connection->insert(static::TABLE)
|
||||
->fields($fields);
|
||||
$pid = $query->execute();
|
||||
}
|
||||
|
||||
$fields['pid'] = $pid;
|
||||
$operation = 'insert';
|
||||
$path_alias->setPath($source);
|
||||
$path_alias->setAlias($alias);
|
||||
$path_alias->set('langcode', $langcode);
|
||||
}
|
||||
else {
|
||||
// Fetch the current values so that an update hook can identify what
|
||||
// exactly changed.
|
||||
try {
|
||||
$original = $this->connection->query('SELECT source, alias, langcode FROM {url_alias} WHERE pid = :pid', [':pid' => $pid])
|
||||
->fetchAssoc();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
$original = FALSE;
|
||||
}
|
||||
$query = $this->connection->update(static::TABLE)
|
||||
->fields($fields)
|
||||
->condition('pid', $pid);
|
||||
$pid = $query->execute();
|
||||
$fields['pid'] = $pid;
|
||||
$fields['original'] = $original;
|
||||
$operation = 'update';
|
||||
$path_alias = $this->getPathAliasEntityStorage()->create([
|
||||
'path' => $source,
|
||||
'alias' => $alias,
|
||||
'langcode' => $langcode,
|
||||
]);
|
||||
}
|
||||
if ($pid) {
|
||||
// @todo Switch to using an event for this instead of a hook.
|
||||
$this->moduleHandler->invokeAll('path_' . $operation, [$fields]);
|
||||
Cache::invalidateTags(['route_match']);
|
||||
return $fields;
|
||||
|
||||
$path_alias->save();
|
||||
|
||||
$path_alias_values = [
|
||||
'pid' => $path_alias->id(),
|
||||
'source' => $path_alias->getPath(),
|
||||
'alias' => $path_alias->getAlias(),
|
||||
'langcode' => $path_alias->get('langcode')->value,
|
||||
];
|
||||
|
||||
if (isset($original_values)) {
|
||||
$path_alias_values['original'] = $original_values;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
return $path_alias_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($conditions) {
|
||||
$select = $this->connection->select(static::TABLE);
|
||||
$query = $this->getPathAliasEntityStorage()->getQuery();
|
||||
// Ignore access restrictions for this API.
|
||||
$query->accessCheck(FALSE);
|
||||
foreach ($conditions as $field => $value) {
|
||||
if ($field == 'source' || $field == 'alias') {
|
||||
// Use LIKE for case-insensitive matching.
|
||||
$select->condition($field, $this->connection->escapeLike($value), 'LIKE');
|
||||
if ($field === 'source') {
|
||||
$field = 'path';
|
||||
}
|
||||
else {
|
||||
$select->condition($field, $value);
|
||||
elseif ($field === 'pid') {
|
||||
$field = 'id';
|
||||
}
|
||||
|
||||
$query->condition($field, $value, '=');
|
||||
}
|
||||
try {
|
||||
return $select
|
||||
->fields(static::TABLE)
|
||||
->orderBy('pid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
|
||||
$result = $query
|
||||
->sort('id', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
$entities = $this->getPathAliasEntityStorage()->loadMultiple($result);
|
||||
|
||||
/** @var \Drupal\Core\Path\PathAliasInterface $path_alias */
|
||||
$path_alias = reset($entities);
|
||||
if ($path_alias) {
|
||||
return [
|
||||
'pid' => $path_alias->id(),
|
||||
'source' => $path_alias->getPath(),
|
||||
'alias' => $path_alias->getAlias(),
|
||||
'langcode' => $path_alias->get('langcode')->value,
|
||||
];
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($conditions) {
|
||||
$path = $this->load($conditions);
|
||||
$query = $this->connection->delete(static::TABLE);
|
||||
$storage = $this->getPathAliasEntityStorage();
|
||||
$query = $storage->getQuery();
|
||||
// API functions should be able to access all entities regardless of access
|
||||
// restrictions. Those need to happen on a higher level.
|
||||
$query->accessCheck(FALSE);
|
||||
foreach ($conditions as $field => $value) {
|
||||
if ($field == 'source' || $field == 'alias') {
|
||||
// Use LIKE for case-insensitive matching.
|
||||
$query->condition($field, $this->connection->escapeLike($value), 'LIKE');
|
||||
if ($field === 'source') {
|
||||
$field = 'path';
|
||||
}
|
||||
else {
|
||||
$query->condition($field, $value);
|
||||
elseif ($field === 'pid') {
|
||||
$field = 'id';
|
||||
}
|
||||
|
||||
$query->condition($field, $value, '=');
|
||||
}
|
||||
try {
|
||||
$deleted = $query->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
$deleted = FALSE;
|
||||
}
|
||||
// @todo Switch to using an event for this instead of a hook.
|
||||
$this->moduleHandler->invokeAll('path_delete', [$path]);
|
||||
Cache::invalidateTags(['route_match']);
|
||||
return $deleted;
|
||||
|
||||
$result = $query->execute();
|
||||
$storage->delete($storage->loadMultiple($result));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preloadPathAlias($preloaded, $langcode) {
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
$select = $this->connection->select(static::TABLE)
|
||||
->fields(static::TABLE, ['source', 'alias']);
|
||||
->fields(static::TABLE, ['path', 'alias']);
|
||||
|
||||
if (!empty($preloaded)) {
|
||||
$conditions = new Condition('OR');
|
||||
foreach ($preloaded as $preloaded_item) {
|
||||
$conditions->condition('source', $this->connection->escapeLike($preloaded_item), 'LIKE');
|
||||
$conditions->condition('path', $this->connection->escapeLike($preloaded_item), 'LIKE');
|
||||
}
|
||||
$select->condition($conditions);
|
||||
}
|
||||
|
||||
// Always get the language-specific alias before the language-neutral one.
|
||||
// For example 'de' is less than 'und' so the order needs to be ASC, while
|
||||
// 'xx-lolspeak' is more than 'und' so the order needs to be DESC. We also
|
||||
// order by pid ASC so that fetchAllKeyed() returns the most recently
|
||||
// created alias for each source. Subsequent queries using fetchField() must
|
||||
// use pid DESC to have the same effect.
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
array_pop($langcode_list);
|
||||
}
|
||||
elseif ($langcode < LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$select->orderBy('langcode', 'ASC');
|
||||
}
|
||||
else {
|
||||
$select->orderBy('langcode', 'DESC');
|
||||
}
|
||||
$this->addLanguageFallback($select, $langcode);
|
||||
|
||||
$select->orderBy('pid', 'ASC');
|
||||
$select->condition('langcode', $langcode_list, 'IN');
|
||||
try {
|
||||
return $select->execute()->fetchAllKeyed();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
}
|
||||
// We order by ID ASC so that fetchAllKeyed() returns the most recently
|
||||
// created alias for each source. Subsequent queries using fetchField() must
|
||||
// use ID DESC to have the same effect.
|
||||
$select->orderBy('id', 'ASC');
|
||||
|
||||
return $select->execute()->fetchAllKeyed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupPathAlias($path, $langcode) {
|
||||
$source = $this->connection->escapeLike($path);
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
|
||||
// See the queries above. Use LIKE for case-insensitive matching.
|
||||
$select = $this->connection->select(static::TABLE)
|
||||
->fields(static::TABLE, ['alias'])
|
||||
->condition('source', $source, 'LIKE');
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
array_pop($langcode_list);
|
||||
}
|
||||
elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$select->orderBy('langcode', 'DESC');
|
||||
}
|
||||
else {
|
||||
$select->orderBy('langcode', 'ASC');
|
||||
}
|
||||
->condition('path', $this->connection->escapeLike($path), 'LIKE');
|
||||
|
||||
$select->orderBy('pid', 'DESC');
|
||||
$select->condition('langcode', $langcode_list, 'IN');
|
||||
try {
|
||||
return $select->execute()->fetchField();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
}
|
||||
$this->addLanguageFallback($select, $langcode);
|
||||
|
||||
$select->orderBy('id', 'DESC');
|
||||
|
||||
return $select->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupPathSource($path, $langcode) {
|
||||
$alias = $this->connection->escapeLike($path);
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
|
||||
public function lookupPathSource($alias, $langcode) {
|
||||
// See the queries above. Use LIKE for case-insensitive matching.
|
||||
$select = $this->connection->select(static::TABLE)
|
||||
->fields(static::TABLE, ['source'])
|
||||
->condition('alias', $alias, 'LIKE');
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
->fields(static::TABLE, ['path'])
|
||||
->condition('alias', $this->connection->escapeLike($alias), 'LIKE');
|
||||
|
||||
$this->addLanguageFallback($select, $langcode);
|
||||
|
||||
$select->orderBy('id', 'DESC');
|
||||
|
||||
return $select->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$select->orderBy('langcode', 'DESC');
|
||||
$query->orderBy('langcode', 'DESC');
|
||||
}
|
||||
else {
|
||||
$select->orderBy('langcode', 'ASC');
|
||||
}
|
||||
|
||||
$select->orderBy('pid', 'DESC');
|
||||
$select->condition('langcode', $langcode_list, 'IN');
|
||||
try {
|
||||
return $select->execute()->fetchField();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
$query->orderBy('langcode', 'ASC');
|
||||
}
|
||||
$query->condition('langcode', $langcode_list, 'IN');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -295,30 +263,19 @@ class AliasStorage implements AliasStorageInterface {
|
|||
->condition('alias', $this->connection->escapeLike($alias), 'LIKE')
|
||||
->condition('langcode', $langcode);
|
||||
if (!empty($source)) {
|
||||
$query->condition('source', $this->connection->escapeLike($source), 'NOT LIKE');
|
||||
$query->condition('path', $this->connection->escapeLike($source), 'NOT LIKE');
|
||||
}
|
||||
$query->addExpression('1');
|
||||
$query->range(0, 1);
|
||||
try {
|
||||
return (bool) $query->execute()->fetchField();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return (bool) $query->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function languageAliasExists() {
|
||||
try {
|
||||
return (bool) $this->connection->queryRange('SELECT 1 FROM {url_alias} WHERE langcode <> :langcode', 0, 1, [':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED])->fetchField();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
}
|
||||
return (bool) $this->connection->queryRange('SELECT 1 FROM {' . static::TABLE . '} WHERE langcode <> :langcode', 0, 1, [':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED])->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,121 +289,42 @@ class AliasStorage implements AliasStorageInterface {
|
|||
// Replace wildcards with PDO wildcards.
|
||||
$query->condition('alias', '%' . preg_replace('!\*+!', '%', $keys) . '%', 'LIKE');
|
||||
}
|
||||
try {
|
||||
return $query
|
||||
->fields(static::TABLE)
|
||||
->orderByHeader($header)
|
||||
->limit(50)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return [];
|
||||
}
|
||||
|
||||
$query->addField(static::TABLE, 'id', 'pid');
|
||||
$query->addField(static::TABLE, 'path', 'source');
|
||||
return $query
|
||||
->fields(static::TABLE, ['alias', 'langcode'])
|
||||
->orderByHeader($header)
|
||||
->limit(50)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pathHasMatchingAlias($initial_substring) {
|
||||
$query = $this->connection->select(static::TABLE, 'u');
|
||||
$query = $this->connection->select(static::TABLE);
|
||||
$query->addExpression(1);
|
||||
try {
|
||||
return (bool) $query
|
||||
->condition('u.source', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->catchException($e);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return (bool) $query
|
||||
->condition('path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the table exists and create it if not.
|
||||
* Returns the path alias entity storage handler.
|
||||
*
|
||||
* We can not store it in the constructor because that leads to a circular
|
||||
* dependency in the service container.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityStorageInterface
|
||||
* The path alias entity storage.
|
||||
*/
|
||||
protected function ensureTableExists() {
|
||||
try {
|
||||
$database_schema = $this->connection->schema();
|
||||
if (!$database_schema->tableExists(static::TABLE)) {
|
||||
$schema_definition = $this->schemaDefinition();
|
||||
$database_schema->createTable(static::TABLE, $schema_definition);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
// If another process has already created the table, attempting to recreate
|
||||
// it will throw an exception. In this case just catch the exception and do
|
||||
// nothing.
|
||||
catch (DatabaseException $e) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on an exception when url_alias might be stale.
|
||||
*
|
||||
* If the table does not yet exist, that's fine, but if the table exists and
|
||||
* yet the query failed, then the url_alias is stale and the exception needs
|
||||
* to propagate.
|
||||
*
|
||||
* @param $e
|
||||
* The exception.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function catchException(\Exception $e) {
|
||||
if ($this->connection->schema()->tableExists(static::TABLE)) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the schema for the {url_alias} table.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function schemaDefinition() {
|
||||
return [
|
||||
'description' => 'A list of URL aliases for Drupal paths; a user may visit either the source or destination path.',
|
||||
'fields' => [
|
||||
'pid' => [
|
||||
'description' => 'A unique path alias identifier.',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
'source' => [
|
||||
'description' => 'The Drupal path this alias is for; e.g. node/12.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
],
|
||||
'alias' => [
|
||||
'description' => 'The alias for this path; e.g. title-of-the-story.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
],
|
||||
'langcode' => [
|
||||
'description' => "The language code this alias is for; if 'und', the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.",
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 12,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
],
|
||||
],
|
||||
'primary key' => ['pid'],
|
||||
'indexes' => [
|
||||
'alias_langcode_pid' => ['alias', 'langcode', 'pid'],
|
||||
'source_langcode_pid' => ['source', 'langcode', 'pid'],
|
||||
],
|
||||
];
|
||||
protected function getPathAliasEntityStorage() {
|
||||
return $this->entityTypeManager->getStorage('path_alias');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Path\Entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
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\Core\Path\PathAliasInterface;
|
||||
|
||||
/**
|
||||
* Defines the path_alias entity class.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "path_alias",
|
||||
* label = @Translation("Path alias"),
|
||||
* label_collection = @Translation("Path aliases"),
|
||||
* label_singular = @Translation("path alias"),
|
||||
* label_plural = @Translation("path aliases"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count path alias",
|
||||
* plural = "@count path aliases"
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\Core\Path\PathAliasStorage",
|
||||
* "storage_schema" = "Drupal\Core\Path\PathAliasStorageSchema",
|
||||
* },
|
||||
* base_table = "path_alias",
|
||||
* revision_table = "path_alias_revision",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "revision" = "revision_id",
|
||||
* "langcode" = "langcode",
|
||||
* "uuid" = "uuid",
|
||||
* },
|
||||
* admin_permission = "administer url aliases",
|
||||
* list_cache_tags = { "route_match" },
|
||||
* )
|
||||
*/
|
||||
class PathAlias extends ContentEntityBase implements PathAliasInterface {
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
|
||||
$fields['alias'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(new TranslatableMarkup('Path alias'))
|
||||
->setDescription(new TranslatableMarkup('An alias used with this path.'))
|
||||
->setRequired(TRUE)
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
$fields['langcode']->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
|
||||
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->original->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,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Path;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a path_alias entity.
|
||||
*/
|
||||
interface PathAliasInterface extends ContentEntityInterface {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Path;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Defines the storage handler class for path_alias entities.
|
||||
*/
|
||||
class PathAliasStorage extends SqlContentEntityStorage {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function invokeHook($hook, EntityInterface $entity) {
|
||||
parent::invokeHook($hook, $entity);
|
||||
|
||||
// Invoke the deprecated hook_path_OPERATION() hooks.
|
||||
if ($hook === 'insert' || $hook === 'update' || $hook === 'delete') {
|
||||
$values = [
|
||||
'pid' => $entity->id(),
|
||||
'source' => $entity->getPath(),
|
||||
'alias' => $entity->getAlias(),
|
||||
'langcode' => $entity->language()->getId(),
|
||||
];
|
||||
|
||||
if ($hook === 'update') {
|
||||
$values['original'] = [
|
||||
'pid' => $entity->id(),
|
||||
'source' => $entity->original->getPath(),
|
||||
'alias' => $entity->original->getAlias(),
|
||||
'langcode' => $entity->original->language()->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->moduleHandler()->invokeAllDeprecated("It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_{$hook}() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865.", 'path_' . $hook, [$values]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createWithSampleValues($bundle = FALSE, array $values = []) {
|
||||
$entity = parent::createWithSampleValues($bundle, ['path' => '/<front>'] + $values);
|
||||
$entity->set('alias', '/' . $entity->get('alias')->value);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Path;
|
||||
|
||||
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);
|
||||
|
||||
$schema[$this->storage->getBaseTable()]['indexes'] += [
|
||||
'path_alias__alias_langcode_id' => ['alias', 'langcode', 'id'],
|
||||
'path_alias__path_langcode_id' => ['path', 'langcode', 'id'],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
|
@ -36,10 +36,11 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(ContainerBuilder $container) {
|
||||
// Prevent the alias-based path processor, which requires a path_alias db
|
||||
// table, from being registered to the path processor manager. We do this by
|
||||
// removing the tags that the compiler pass looks for. This means the url
|
||||
// generator can safely be used during the database update process.
|
||||
// The alias-based processor requires the path_alias entity schema to be
|
||||
// installed, so we prevent it from being registered to the path processor
|
||||
// manager. We do this by removing the tags that the compiler pass looks
|
||||
// for. This means that the URL generator can safely be used during the
|
||||
// database update process.
|
||||
if ($container->hasDefinition('path_processor_alias')) {
|
||||
$container->getDefinition('path_processor_alias')
|
||||
->clearTag('path_processor_inbound')
|
||||
|
|
|
@ -129,8 +129,11 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
|||
*/
|
||||
public function entityTypeAlter(array &$entity_types) {
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
// The ContentModerationState entity type should never be moderated.
|
||||
if ($entity_type->isRevisionable() && !$entity_type->isInternal()) {
|
||||
// Internal entity types should never be moderated, and the 'path_alias'
|
||||
// entity type needs to be excluded for now.
|
||||
// @todo Enable moderation for path aliases after they become publishable
|
||||
// in https://www.drupal.org/project/drupal/issues/3007669.
|
||||
if ($entity_type->isRevisionable() && !$entity_type->isInternal() && $entity_type_id !== 'path_alias') {
|
||||
$entity_types[$entity_type_id] = $this->addModerationToEntityType($entity_type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\jsonapi\Functional;
|
||||
|
||||
use Drupal\Core\Path\Entity\PathAlias;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* JSON:API integration test for the "PathAlias" content entity type.
|
||||
*
|
||||
* @group jsonapi
|
||||
*/
|
||||
class PathAliasTest extends ResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'path_alias';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $resourceTypeName = 'path_alias--path_alias';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @var \Drupal\user\RoleInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer url aliases']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$path_alias = PathAlias::create([
|
||||
'alias' => '/frontpage1',
|
||||
'path' => '/<front>',
|
||||
'langcode' => 'en',
|
||||
]);
|
||||
$path_alias->save();
|
||||
return $path_alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedDocument() {
|
||||
$self_url = Url::fromUri('base:/jsonapi/path_alias/path_alias/' . $this->entity->uuid())->setAbsolute()->toString(TRUE)->getGeneratedUrl();
|
||||
return [
|
||||
'jsonapi' => [
|
||||
'meta' => [
|
||||
'links' => [
|
||||
'self' => ['href' => 'http://jsonapi.org/format/1.0/'],
|
||||
],
|
||||
],
|
||||
'version' => '1.0',
|
||||
],
|
||||
'links' => [
|
||||
'self' => ['href' => $self_url],
|
||||
],
|
||||
'data' => [
|
||||
'id' => $this->entity->uuid(),
|
||||
'type' => static::$resourceTypeName,
|
||||
'links' => [
|
||||
'self' => ['href' => $self_url],
|
||||
],
|
||||
'attributes' => [
|
||||
'alias' => '/frontpage1',
|
||||
'path' => '/<front>',
|
||||
'langcode' => 'en',
|
||||
'drupal_internal__id' => 1,
|
||||
'drupal_internal__revision_id' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getPostDocument() {
|
||||
return [
|
||||
'data' => [
|
||||
'type' => static::$resourceTypeName,
|
||||
'attributes' => [
|
||||
'alias' => '/frontpage1',
|
||||
'path' => '/<front>',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -2050,7 +2050,9 @@ abstract class ResourceTestBase extends BrowserTestBase {
|
|||
$doc = $this->getModifiedEntityForPostTesting();
|
||||
$doc['data']['id'] = $uuid;
|
||||
$label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
|
||||
$doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]];
|
||||
if (isset($label_field)) {
|
||||
$doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]];
|
||||
}
|
||||
$request_options[RequestOptions::BODY] = Json::encode($doc);
|
||||
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
|
@ -2060,7 +2062,9 @@ abstract class ResourceTestBase extends BrowserTestBase {
|
|||
$doc = $this->getModifiedEntityForPostTesting();
|
||||
$new_uuid = \Drupal::service('uuid')->generate();
|
||||
$doc['data']['id'] = $new_uuid;
|
||||
$doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]];
|
||||
if (isset($label_field)) {
|
||||
$doc['data']['attributes'][$label_field] = [['value' => $this->randomMachineName()]];
|
||||
}
|
||||
$request_options[RequestOptions::BODY] = Json::encode($doc);
|
||||
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
|
@ -2094,7 +2098,9 @@ abstract class ResourceTestBase extends BrowserTestBase {
|
|||
$unparseable_request_body = '!{>}<';
|
||||
$parseable_valid_request_body = Json::encode($this->getPatchDocument());
|
||||
/* $parseable_valid_request_body_2 = Json::encode($this->getNormalizedPatchEntity()); */
|
||||
$parseable_invalid_request_body = Json::encode($this->makeNormalizationInvalid($this->getPatchDocument(), 'label'));
|
||||
if ($this->entity->getEntityType()->hasKey('label')) {
|
||||
$parseable_invalid_request_body = Json::encode($this->makeNormalizationInvalid($this->getPatchDocument(), 'label'));
|
||||
}
|
||||
$parseable_invalid_request_body_2 = Json::encode(NestedArray::mergeDeep(['data' => ['attributes' => ['field_rest_test' => $this->randomString()]]], $this->getPatchDocument()));
|
||||
// The 'field_rest_test' field does not allow 'view' access, so does not end
|
||||
// up in the JSON:API document. Even when we explicitly add it to the JSON
|
||||
|
@ -2340,7 +2346,7 @@ abstract class ResourceTestBase extends BrowserTestBase {
|
|||
|
||||
// Ensure that PATCHing an entity that is not the latest revision is
|
||||
// unsupported.
|
||||
if (!$this->entity->getEntityType()->isRevisionable() || !$this->entity instanceof FieldableEntityInterface) {
|
||||
if (!$this->entity->getEntityType()->isRevisionable() || !$this->entity->getEntityType()->hasHandlerClass('moderation') || !$this->entity instanceof FieldableEntityInterface) {
|
||||
return;
|
||||
}
|
||||
assert($this->entity instanceof RevisionableInterface);
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
* Implements hook_install().
|
||||
*/
|
||||
function menu_link_content_install() {
|
||||
// Add a higher weight so that menu_link_content_path_update() is called after
|
||||
// system_path_update() clears the path alias cache.
|
||||
// Add a higher weight so that menu_link_content_path_alias_update() is called
|
||||
// after system_path_alias_update() clears the path alias cache.
|
||||
// @todo remove this when the cache clearing is moved to path module or if
|
||||
// caching is removed for path aliases due to
|
||||
// https://www.drupal.org/node/1965074
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Path\PathAliasInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\system\MenuInterface;
|
||||
|
||||
|
@ -50,10 +51,10 @@ function menu_link_content_menu_delete(MenuInterface $menu) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_insert().
|
||||
* Implements hook_ENTITY_TYPE_insert() for 'path_alias'.
|
||||
*/
|
||||
function menu_link_content_path_insert($path) {
|
||||
_menu_link_content_update_path_alias($path['alias']);
|
||||
function menu_link_content_path_alias_insert(PathAliasInterface $path_alias) {
|
||||
_menu_link_content_update_path_alias($path_alias->getAlias());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,23 +76,23 @@ function _menu_link_content_update_path_alias($path) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_update().
|
||||
* Implements hook_ENTITY_TYPE_update() for 'path_alias'.
|
||||
*/
|
||||
function menu_link_content_path_update($path) {
|
||||
if ($path['alias'] != $path['original']['alias']) {
|
||||
_menu_link_content_update_path_alias($path['alias']);
|
||||
_menu_link_content_update_path_alias($path['original']['alias']);
|
||||
function menu_link_content_path_alias_update(PathAliasInterface $path_alias) {
|
||||
if ($path_alias->getAlias() != $path_alias->original->getAlias()) {
|
||||
_menu_link_content_update_path_alias($path_alias->getAlias());
|
||||
_menu_link_content_update_path_alias($path_alias->original->getAlias());
|
||||
}
|
||||
elseif ($path['source'] != $path['original']['source']) {
|
||||
_menu_link_content_update_path_alias($path['alias']);
|
||||
elseif ($path_alias->getPath() != $path_alias->original->getPath()) {
|
||||
_menu_link_content_update_path_alias($path_alias->getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_delete().
|
||||
* Implements hook_ENTITY_TYPE_delete() for 'path_alias'.
|
||||
*/
|
||||
function menu_link_content_path_delete($path) {
|
||||
_menu_link_content_update_path_alias($path['alias']);
|
||||
function menu_link_content_path_alias_delete(PathAliasInterface $path_alias) {
|
||||
_menu_link_content_update_path_alias($path_alias->getAlias());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,6 +28,7 @@ class PathAliasMenuLinkContentTest extends KernelTestBase {
|
|||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
$this->installEntitySchema('path_alias');
|
||||
|
||||
// Ensure that the weight of module_link_content is higher than system.
|
||||
// @see menu_link_content_install()
|
||||
|
|
|
@ -42,6 +42,7 @@ trait CreateTestContentEntitiesTrait {
|
|||
$this->installEntitySchema('file');
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installEntitySchema('user');
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ class Upgrade6Test extends MigrateUpgradeExecuteTestBase {
|
|||
'shortcut_set' => 1,
|
||||
'action' => 25,
|
||||
'menu' => 8,
|
||||
'path_alias' => 8,
|
||||
'taxonomy_term' => 15,
|
||||
'taxonomy_vocabulary' => 7,
|
||||
'tour' => 5,
|
||||
|
|
|
@ -90,6 +90,7 @@ class Upgrade7Test extends MigrateUpgradeExecuteTestBase {
|
|||
'menu' => 6,
|
||||
'taxonomy_term' => 24,
|
||||
'taxonomy_vocabulary' => 7,
|
||||
'path_alias' => 8,
|
||||
'tour' => 5,
|
||||
'user' => 4,
|
||||
'user_role' => 3,
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
* The array structure is identical to that of the return value of
|
||||
* \Drupal\Core\Path\AliasStorageInterface::save().
|
||||
*
|
||||
* @see \Drupal\Core\Path\AliasStorageInterface::save()
|
||||
* @deprecated in drupal:8.8.0 and will be removed from drupal:9.0.0. Use
|
||||
* hook_path_alias_insert() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3013865
|
||||
*/
|
||||
function hook_path_insert($path) {
|
||||
\Drupal::database()->insert('mytable')
|
||||
|
@ -35,7 +38,10 @@ function hook_path_insert($path) {
|
|||
* The array structure is identical to that of the return value of
|
||||
* \Drupal\Core\Path\AliasStorageInterface::save().
|
||||
*
|
||||
* @see \Drupal\Core\Path\AliasStorageInterface::save()
|
||||
* @deprecated in drupal:8.8.0 and will be removed from drupal:9.0.0. Use
|
||||
* hook_path_alias_update() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3013865
|
||||
*/
|
||||
function hook_path_update($path) {
|
||||
if ($path['alias'] != $path['original']['alias']) {
|
||||
|
@ -53,7 +59,10 @@ function hook_path_update($path) {
|
|||
* The array structure is identical to that of the return value of
|
||||
* \Drupal\Core\Path\AliasStorageInterface::save().
|
||||
*
|
||||
* @see \Drupal\Core\Path\AliasStorageInterface::delete()
|
||||
* @deprecated in drupal:8.8.0 and will be removed from drupal:9.0.0. Use
|
||||
* hook_path_alias_delete() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3013865
|
||||
*/
|
||||
function hook_path_delete($path) {
|
||||
\Drupal::database()->delete('mytable')
|
||||
|
|
|
@ -368,7 +368,11 @@ class PathAliasTest extends PathTestBase {
|
|||
* Integer representing the path ID.
|
||||
*/
|
||||
public function getPID($alias) {
|
||||
return Database::getConnection()->query("SELECT pid FROM {url_alias} WHERE alias = :alias", [':alias' => $alias])->fetchField();
|
||||
$result = \Drupal::entityTypeManager()->getStorage('path_alias')->getQuery()
|
||||
->condition('alias', $alias, '=')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
return reset($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,6 +31,7 @@ class MigrateUrlAliasTest extends MigrateDrupal6TestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->installConfig(['node']);
|
||||
$this->installSchema('node', ['node_access']);
|
||||
$this->migrateUsers(FALSE);
|
||||
|
|
|
@ -31,6 +31,9 @@ class MigrateUrlAliasTest extends MigrateDrupal7TestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->installConfig('node');
|
||||
$this->installSchema('node', ['node_access']);
|
||||
|
||||
$this->migrateUsers(FALSE);
|
||||
|
|
|
@ -29,6 +29,7 @@ class PathItemTest extends KernelTestBase {
|
|||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('path_alias');
|
||||
|
||||
$this->installSchema('node', ['node_access']);
|
||||
|
||||
|
|
|
@ -901,9 +901,10 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
|
||||
// DX: 422 when invalid entity: multiple values sent for single-value field.
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
$label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
|
||||
$label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel();
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response);
|
||||
if ($label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName) {
|
||||
$label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel();
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response);
|
||||
}
|
||||
|
||||
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2;
|
||||
|
||||
|
@ -988,7 +989,9 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
// 500 when creating an entity with a duplicate UUID.
|
||||
$normalized_entity = $this->getModifiedEntityForPostTesting();
|
||||
$normalized_entity[$created_entity->getEntityType()->getKey('uuid')] = [['value' => $created_entity->uuid()]];
|
||||
$normalized_entity[$label_field] = [['value' => $this->randomMachineName()]];
|
||||
if ($label_field) {
|
||||
$normalized_entity[$label_field] = [['value' => $this->randomMachineName()]];
|
||||
}
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalized_entity, static::$format);
|
||||
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
|
@ -999,7 +1002,9 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
$normalized_entity = $this->getModifiedEntityForPostTesting();
|
||||
$new_uuid = \Drupal::service('uuid')->generate();
|
||||
$normalized_entity[$created_entity->getEntityType()->getKey('uuid')] = [['value' => $new_uuid]];
|
||||
$normalized_entity[$label_field] = [['value' => $this->randomMachineName()]];
|
||||
if ($label_field) {
|
||||
$normalized_entity[$label_field] = [['value' => $this->randomMachineName()]];
|
||||
}
|
||||
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalized_entity, static::$format);
|
||||
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
|
@ -1130,9 +1135,10 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
|
||||
// DX: 422 when invalid entity: multiple values sent for single-value field.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
|
||||
$label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel();
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response);
|
||||
if ($label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName) {
|
||||
$label_field_capitalized = $this->entity->getFieldDefinition($label_field)->getLabel();
|
||||
$this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response);
|
||||
}
|
||||
|
||||
$request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2;
|
||||
|
||||
|
@ -1499,8 +1505,9 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
switch ($entity_key) {
|
||||
case 'label':
|
||||
// Add a second label to this entity to make it invalid.
|
||||
$label_field = $entity_type->hasKey('label') ? $entity_type->getKey('label') : static::$labelFieldName;
|
||||
$normalization[$label_field][1]['value'] = 'Second Title';
|
||||
if ($label_field = $entity_type->hasKey('label') ? $entity_type->getKey('label') : static::$labelFieldName) {
|
||||
$normalization[$label_field][1]['value'] = 'Second Title';
|
||||
}
|
||||
break;
|
||||
case 'id':
|
||||
$normalization[$entity_type->getKey('id')][0]['value'] = $this->anotherEntity->id();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\Tests\rest\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
use Drupal\rest\RestPermissions;
|
||||
|
||||
/**
|
||||
* Tests that existing sites continue to use permissions for EntityResource.
|
||||
|
@ -33,16 +34,17 @@ class EntityResourcePermissionsUpdateTest extends UpdatePathTestBase {
|
|||
* Tests rest_update_8203().
|
||||
*/
|
||||
public function testBcEntityResourcePermissionSettingAdded() {
|
||||
$permission_handler = $this->container->get('user.permissions');
|
||||
|
||||
$is_rest_resource_permission = function ($permission) {
|
||||
return $permission['provider'] === 'rest' && (string) $permission['title'] !== 'Administer REST resource configuration';
|
||||
};
|
||||
|
||||
// Make sure we have the expected values before the update.
|
||||
$rest_settings = $this->config('rest.settings');
|
||||
$this->assertFalse(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
|
||||
$this->assertEqual([], array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
|
||||
|
||||
// We can not use the 'user.permissions' service here because some
|
||||
// permissions include generated URLs inside their description, thus
|
||||
// requiring the path alias system, which is not guaranteed to be working
|
||||
// before running the database updates.
|
||||
$rest_permissions_callback = \Drupal::service('controller_resolver')->getControllerFromDefinition(RestPermissions::class . '::permissions');
|
||||
$rest_permissions = array_keys(call_user_func($rest_permissions_callback));
|
||||
$this->assertEquals([], $rest_permissions);
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
|
@ -50,8 +52,10 @@ class EntityResourcePermissionsUpdateTest extends UpdatePathTestBase {
|
|||
$rest_settings = $this->config('rest.settings');
|
||||
$this->assertTrue(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
|
||||
$this->assertTrue($rest_settings->get('bc_entity_resource_permissions'));
|
||||
$rest_permissions = array_keys(array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
|
||||
$this->assertEqual(['restful delete entity:node', 'restful get entity:node', 'restful patch entity:node', 'restful post entity:node'], $rest_permissions);
|
||||
|
||||
$rest_permissions_callback = \Drupal::service('controller_resolver')->getControllerFromDefinition(RestPermissions::class . '::permissions');
|
||||
$rest_permissions = array_keys(call_user_func($rest_permissions_callback));
|
||||
$this->assertEquals(['restful get entity:node', 'restful post entity:node', 'restful delete entity:node', 'restful patch entity:node'], $rest_permissions);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -386,10 +386,10 @@ EOD;
|
|||
}
|
||||
|
||||
if ($container->hasDefinition('path_processor_alias')) {
|
||||
// Prevent the alias-based path processor, which requires a url_alias db
|
||||
// table, from being registered to the path processor manager. We do this
|
||||
// by removing the tags that the compiler pass looks for. This means the
|
||||
// url generator can safely be used within tests.
|
||||
// The alias-based processor requires the path_alias entity schema to be
|
||||
// installed, so we prevent it from being registered to the path processor
|
||||
// manager. We do this by removing the tags that the compiler pass looks
|
||||
// for. This means that the URL generator can safely be used within tests.
|
||||
$definition = $container->getDefinition('path_processor_alias');
|
||||
$definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound');
|
||||
}
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\system\Tests\Path;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Path\AliasStorage;
|
||||
|
||||
/**
|
||||
* Utility methods to generate sample data, database configuration, etc.
|
||||
*/
|
||||
class UrlAliasFixtures {
|
||||
|
||||
/**
|
||||
* Create the tables required for the sample data.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The connection to use to create the tables.
|
||||
*/
|
||||
public function createTables(Connection $connection) {
|
||||
$tables = $this->tableDefinition();
|
||||
$schema = $connection->schema();
|
||||
|
||||
foreach ($tables as $name => $table) {
|
||||
$schema->dropTable($name);
|
||||
$schema->createTable($name, $table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the tables used for the sample data.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The connection to use to drop the tables.
|
||||
*/
|
||||
public function dropTables(Connection $connection) {
|
||||
$tables = $this->tableDefinition();
|
||||
$schema = $connection->schema();
|
||||
|
||||
foreach ($tables as $name => $table) {
|
||||
$schema->dropTable($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of URL aliases for testing.
|
||||
*
|
||||
* @return array of URL alias definitions.
|
||||
*/
|
||||
public function sampleUrlAliases() {
|
||||
return [
|
||||
[
|
||||
'source' => '/node/1',
|
||||
'alias' => '/alias_for_node_1_en',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
[
|
||||
'source' => '/node/2',
|
||||
'alias' => '/alias_for_node_2_en',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
[
|
||||
'source' => '/node/1',
|
||||
'alias' => '/alias_for_node_1_fr',
|
||||
'langcode' => 'fr',
|
||||
],
|
||||
[
|
||||
'source' => '/node/1',
|
||||
'alias' => '/alias_for_node_1_und',
|
||||
'langcode' => 'und',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table definition for the URL alias fixtures.
|
||||
*
|
||||
* @return array
|
||||
* Table definitions.
|
||||
*/
|
||||
public function tableDefinition() {
|
||||
$tables = [];
|
||||
|
||||
// Prime the drupal_get_filename() cache with the location of the system
|
||||
// module as its location is known and shouldn't change.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
drupal_get_filename('module', 'system', 'core/modules/system/system.info.yml');
|
||||
module_load_install('system');
|
||||
$schema = system_schema();
|
||||
|
||||
$tables['url_alias'] = AliasStorage::schemaDefinition();
|
||||
$tables['key_value'] = $schema['key_value'];
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
}
|
|
@ -13,13 +13,16 @@ use Drupal\Component\Utility\Unicode;
|
|||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\Entity\ContentEntityType;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Path\AliasStorage;
|
||||
use Drupal\Core\Path\Entity\PathAlias;
|
||||
use Drupal\Core\Path\PathAliasStorage;
|
||||
use Drupal\Core\Path\PathAliasStorageSchema;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\StreamWrapper\PrivateStream;
|
||||
use Drupal\Core\StreamWrapper\PublicStream;
|
||||
|
@ -1229,11 +1232,6 @@ function system_schema() {
|
|||
],
|
||||
];
|
||||
|
||||
// Create the url_alias table. The alias_storage service can auto-create its
|
||||
// table, but this relies on exceptions being thrown. These exceptions will be
|
||||
// thrown every request until an alias is created.
|
||||
$schema['url_alias'] = AliasStorage::schemaDefinition();
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
|
@ -2359,3 +2357,118 @@ function system_update_8802() {
|
|||
->save(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the 'path_alias' entity type.
|
||||
*/
|
||||
function system_update_8803() {
|
||||
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
if (!$entity_definition_update_manager->getEntityType('path_alias')) {
|
||||
$entity_type = new ContentEntityType([
|
||||
'id' => 'path_alias',
|
||||
'class' => PathAlias::class,
|
||||
'label' => new TranslatableMarkup('Path alias'),
|
||||
'handlers' => [
|
||||
'storage' => PathAliasStorage::class,
|
||||
'storage_schema' => PathAliasStorageSchema::class,
|
||||
],
|
||||
'base_table' => 'path_alias',
|
||||
'revision_table' => 'path_alias_revision',
|
||||
'entity_keys' => [
|
||||
'id' => 'id',
|
||||
'revision' => 'revision_id',
|
||||
'langcode' => 'langcode',
|
||||
'uuid' => 'uuid',
|
||||
],
|
||||
]);
|
||||
$entity_definition_update_manager->installEntityType($entity_type);
|
||||
|
||||
return t('The "path_alias" entity type has been installed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert path aliases to entities.
|
||||
*/
|
||||
function system_update_8804(&$sandbox = NULL) {
|
||||
// Bail out early if the entity type is not using the default storage class.
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('path_alias');
|
||||
if (!$storage instanceof PathAliasStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($sandbox['current_id'])) {
|
||||
// This must be the first run. Initialize the sandbox.
|
||||
$sandbox['progress'] = 0;
|
||||
$sandbox['current_id'] = 0;
|
||||
}
|
||||
|
||||
$database = \Drupal::database();
|
||||
$step_size = 200;
|
||||
$url_aliases = $database->select('url_alias', 't')
|
||||
->condition('t.pid', $sandbox['current_id'], '>')
|
||||
->fields('t')
|
||||
->orderBy('pid', 'ASC')
|
||||
->range(0, $step_size)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
if ($url_aliases) {
|
||||
/** @var \Drupal\Component\Uuid\UuidInterface $uuid */
|
||||
$uuid = \Drupal::service('uuid');
|
||||
|
||||
$base_table_insert = $database->insert('path_alias');
|
||||
$base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode']);
|
||||
$revision_table_insert = $database->insert('path_alias_revision');
|
||||
$revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'revision_default']);
|
||||
foreach ($url_aliases as $url_alias) {
|
||||
$values = [
|
||||
'id' => $url_alias->pid,
|
||||
'revision_id' => $url_alias->pid,
|
||||
'uuid' => $uuid->generate(),
|
||||
'path' => $url_alias->source,
|
||||
'alias' => $url_alias->alias,
|
||||
'langcode' => $url_alias->langcode,
|
||||
];
|
||||
$base_table_insert->values($values);
|
||||
|
||||
unset($values['uuid']);
|
||||
$values['revision_default'] = 1;
|
||||
$revision_table_insert->values($values);
|
||||
}
|
||||
$base_table_insert->execute();
|
||||
$revision_table_insert->execute();
|
||||
|
||||
$sandbox['progress'] += count($url_aliases);
|
||||
$last_url_alias = end($url_aliases);
|
||||
$sandbox['current_id'] = $last_url_alias->pid;
|
||||
|
||||
// If we're not in maintenance mode, the number of path aliases could change
|
||||
// at any time so make sure that we always use the latest record count.
|
||||
$missing = $database->select('url_alias', 't')
|
||||
->condition('t.pid', $sandbox['current_id'], '>')
|
||||
->orderBy('pid', 'ASC')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$sandbox['#finished'] = $missing ? $sandbox['progress'] / ($sandbox['progress'] + (int) $missing) : 1;
|
||||
}
|
||||
else {
|
||||
$sandbox['#finished'] = 1;
|
||||
}
|
||||
|
||||
if ($sandbox['#finished'] >= 1) {
|
||||
// Keep a backup of the old 'url_alias' table if requested.
|
||||
if (Settings::get('entity_update_backup', TRUE)) {
|
||||
$old_table_name = 'old_' . substr(uniqid(), 0, 6) . '_url_alias';
|
||||
if (!$database->schema()->tableExists($old_table_name)) {
|
||||
$database->schema()->renameTable('url_alias', $old_table_name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$database->schema()->dropTable('url_alias');
|
||||
}
|
||||
|
||||
return t('Path aliases have been converted to entities.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1421,29 +1421,6 @@ function system_block_view_system_main_block_alter(array &$build, BlockPluginInt
|
|||
unset($build['#contextual_links']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_update().
|
||||
*/
|
||||
function system_path_update($path) {
|
||||
$alias_manager = \Drupal::service('path.alias_manager');
|
||||
$alias_manager->cacheClear($path['source']);
|
||||
$alias_manager->cacheClear($path['original']['source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_insert().
|
||||
*/
|
||||
function system_path_insert($path) {
|
||||
\Drupal::service('path.alias_manager')->cacheClear($path['source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_delete().
|
||||
*/
|
||||
function system_path_delete($path) {
|
||||
\Drupal::service('path.alias_manager')->cacheClear($path['source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter() for entity reference selection handlers.
|
||||
*/
|
||||
|
|
46
core/modules/system/tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php
vendored
Normal file
46
core/modules/system/tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
// @codingStandardsIgnoreFile
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains database additions to drupal-8.filled.standard.php.gz for testing
|
||||
* the upgrade path of https://www.drupal.org/node/2336597.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Add a few more url aliases with various language codes.
|
||||
$connection->insert('url_alias')
|
||||
->fields([
|
||||
'pid',
|
||||
'source',
|
||||
'alias',
|
||||
'langcode',
|
||||
])
|
||||
->values([
|
||||
'pid' => '2',
|
||||
'source' => '/node/1',
|
||||
'alias' => '/test-article-new-alias',
|
||||
'langcode' => 'und',
|
||||
])
|
||||
->values([
|
||||
'pid' => '3',
|
||||
'source' => '/node/8',
|
||||
'alias' => '/test-alias-for-any-language',
|
||||
'langcode' => 'und',
|
||||
])
|
||||
->values([
|
||||
'pid' => '4',
|
||||
'source' => '/node/8',
|
||||
'alias' => '/test-alias-in-english',
|
||||
'langcode' => 'en',
|
||||
])
|
||||
->values([
|
||||
'pid' => '5',
|
||||
'source' => '/node/8',
|
||||
'alias' => '/test-alias-in-spanish',
|
||||
'langcode' => 'es',
|
||||
])
|
||||
->execute();
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Path deprecated test'
|
||||
type: module
|
||||
description: 'Support module for testing deprecated functionality for path aliases.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support module for testing deprecated functionality for path aliases.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_path_insert().
|
||||
*/
|
||||
function path_deprecated_test_path_insert($path) {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_update().
|
||||
*/
|
||||
function path_deprecated_test_path_update($path) {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_delete().
|
||||
*/
|
||||
function path_deprecated_test_path_delete($path) {
|
||||
// Nothing to do here.
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
name: 'Hook path tests'
|
||||
type: module
|
||||
description: 'Support module for path hook testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Helper module for the path tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resets the path test results.
|
||||
*/
|
||||
function path_test_reset() {
|
||||
\Drupal::state()->set('path_test.results', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_path_update().
|
||||
*/
|
||||
function path_test_path_update($path) {
|
||||
$results = \Drupal::state()->get('path_test.results') ?: [];
|
||||
$results['hook_path_update'] = $path;
|
||||
\Drupal::state()->set('path_test.results', $results);
|
||||
}
|
|
@ -26,8 +26,8 @@ class UrlAlterFunctionalTest extends BrowserTestBase {
|
|||
* Test that URL altering works and that it occurs in the correct order.
|
||||
*/
|
||||
public function testUrlAlter() {
|
||||
// Ensure that the url_alias table exists after Drupal installation.
|
||||
$this->assertTrue(Database::getConnection()->schema()->tableExists('url_alias'), 'The url_alias table exists after Drupal installation.');
|
||||
// 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.
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the conversion of path aliases to entities.
|
||||
*
|
||||
* @group Update
|
||||
* @group legacy
|
||||
*/
|
||||
class PathAliasToEntityUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.filled.standard.php.gz',
|
||||
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.convert-path-aliases-to-entities-2336597.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the conversion of path aliases to entities.
|
||||
*
|
||||
* @see system_update_8803()
|
||||
* @see system_update_8804()
|
||||
*/
|
||||
public function testConversionToEntities() {
|
||||
$database = \Drupal::database();
|
||||
$schema = $database->schema();
|
||||
$this->assertTrue($schema->tableExists('url_alias'));
|
||||
|
||||
$query = $database->select('url_alias');
|
||||
$query->addField('url_alias', 'pid', 'id');
|
||||
$query->addField('url_alias', 'source', 'path');
|
||||
$query->addField('url_alias', 'alias');
|
||||
$query->addField('url_alias', 'langcode');
|
||||
$original_records = $query->execute()->fetchAllAssoc('id');
|
||||
|
||||
// drupal-8.filled.standard.php.gz contains one URL alias and
|
||||
// drupal-8.convert-path-aliases-to-entities-2336597.php adds another four.
|
||||
$url_alias_count = 5;
|
||||
$this->assertCount($url_alias_count, $original_records);
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// Check that the 'path_alias' entity tables have been created and the
|
||||
// 'url_alias' table has been deleted.
|
||||
$this->assertTrue($schema->tableExists('path_alias'));
|
||||
$this->assertTrue($schema->tableExists('path_alias_revision'));
|
||||
$this->assertFalse($schema->tableExists('url_alias'));
|
||||
|
||||
// Check that we have a backup of the old table.
|
||||
$this->assertCount(1, $schema->findTables('old_%_url_alias'));
|
||||
|
||||
$path_alias_count = \Drupal::entityTypeManager()->getStorage('path_alias')->loadMultiple();
|
||||
$this->assertCount($url_alias_count, $path_alias_count);
|
||||
|
||||
// Make sure that existing aliases still work.
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet('test-article');
|
||||
$assert_session->responseContains('/node/1');
|
||||
$assert_session->pageTextContains('Test Article - New title');
|
||||
|
||||
$this->drupalGet('test-article-new-alias');
|
||||
$assert_session->responseContains('/node/1');
|
||||
$assert_session->pageTextContains('Test Article - New title');
|
||||
|
||||
$this->drupalGet('test-alias-for-any-language');
|
||||
$assert_session->responseContains('/node/8');
|
||||
$assert_session->pageTextContains('Test title');
|
||||
|
||||
$this->drupalGet('test-alias-in-english');
|
||||
$assert_session->responseContains('/node/8');
|
||||
$assert_session->pageTextContains('Test title');
|
||||
|
||||
$spanish = \Drupal::languageManager()->getLanguage('es');
|
||||
|
||||
$this->drupalGet('test-alias-for-any-language', ['language' => $spanish]);
|
||||
$assert_session->responseContains('/es/node/8');
|
||||
$assert_session->pageTextContains('Test title Spanish');
|
||||
|
||||
$this->drupalGet('test-alias-in-spanish', ['language' => $spanish]);
|
||||
$assert_session->responseContains('/es/node/8');
|
||||
$assert_session->pageTextContains('Test title Spanish');
|
||||
|
||||
// Check that correct data was written in both the base and the revision
|
||||
// tables.
|
||||
$base_table_records = $database->select('path_alias')
|
||||
->fields('path_alias', ['id', 'path', 'alias', 'langcode'])
|
||||
->execute()->fetchAllAssoc('id');
|
||||
$this->assertEquals($original_records, $base_table_records);
|
||||
|
||||
$revision_table_records = $database->select('path_alias_revision')
|
||||
->fields('path_alias_revision', ['id', 'path', 'alias', 'langcode'])
|
||||
->execute()->fetchAllAssoc('id');
|
||||
$this->assertEquals($original_records, $revision_table_records);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\system\Kernel;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Core\Path\AliasStorage
|
||||
*
|
||||
* @group path
|
||||
* @group legacy
|
||||
*/
|
||||
class DeprecatedPathHooksTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'path_deprecated_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::save
|
||||
*
|
||||
* @expectedDeprecation The deprecated hook hook_path_insert() is implemented in these functions: path_deprecated_test_path_insert(). It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_insert() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865.
|
||||
*/
|
||||
public function testInsert() {
|
||||
$source = '/' . $this->randomMachineName();
|
||||
$alias = '/' . $this->randomMachineName();
|
||||
|
||||
$alias_storage = \Drupal::service('path.alias_storage');
|
||||
$alias_storage->save($source, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::save
|
||||
*
|
||||
* @expectedDeprecation The deprecated hook hook_path_update() is implemented in these functions: path_deprecated_test_path_update(). It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_update() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865.
|
||||
*/
|
||||
public function testUpdate() {
|
||||
$source = '/' . $this->randomMachineName();
|
||||
$alias = '/' . $this->randomMachineName();
|
||||
|
||||
$alias_storage = \Drupal::service('path.alias_storage');
|
||||
$alias_storage->save($source, $alias);
|
||||
|
||||
$new_source = '/' . $this->randomMachineName();
|
||||
$path = $alias_storage->load(['source' => $source]);
|
||||
$alias_storage->save($new_source, $alias, LanguageInterface::LANGCODE_NOT_SPECIFIED, $path['pid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::delete
|
||||
*
|
||||
* @expectedDeprecation The deprecated hook hook_path_delete() is implemented in these functions: path_deprecated_test_path_delete(). It will be removed before Drupal 9.0.0. Use hook_ENTITY_TYPE_delete() for the 'path_alias' entity type instead. See https://www.drupal.org/node/3013865.
|
||||
*/
|
||||
public function testDelete() {
|
||||
$source = '/' . $this->randomMachineName();
|
||||
$alias = '/' . $this->randomMachineName();
|
||||
|
||||
$alias_storage = \Drupal::service('path.alias_storage');
|
||||
$alias_storage->save($source, $alias);
|
||||
|
||||
$path = $alias_storage->load(['source' => $source]);
|
||||
$alias_storage->delete(['pid' => $path['pid']]);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,13 +18,22 @@ class PathHooksTest extends KernelTestBase {
|
|||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* Test system_path_*() correctly clears caches.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test system_path_alias_*() correctly clears caches.
|
||||
*/
|
||||
public function testPathHooks() {
|
||||
$source = '/' . $this->randomMachineName();
|
||||
$alias = '/' . $this->randomMachineName();
|
||||
|
||||
// Check system_path_insert();
|
||||
// Check system_path_alias_insert();
|
||||
$alias_manager = $this->prophesize(AliasManagerInterface::class);
|
||||
$alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1);
|
||||
$alias_manager->cacheClear($source)->shouldBeCalledTimes(1);
|
||||
|
@ -35,7 +44,7 @@ class PathHooksTest extends KernelTestBase {
|
|||
$new_source = '/' . $this->randomMachineName();
|
||||
$path = $alias_storage->load(['source' => $source]);
|
||||
|
||||
// Check system_path_update();
|
||||
// Check system_path_alias_update();
|
||||
$alias_manager = $this->prophesize(AliasManagerInterface::class);
|
||||
$alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(2);
|
||||
$alias_manager->cacheClear($source)->shouldBeCalledTimes(1);
|
||||
|
@ -43,7 +52,7 @@ class PathHooksTest extends KernelTestBase {
|
|||
\Drupal::getContainer()->set('path.alias_manager', $alias_manager->reveal());
|
||||
$alias_storage->save($new_source, $alias, LanguageInterface::LANGCODE_NOT_SPECIFIED, $path['pid']);
|
||||
|
||||
// Check system_path_delete();
|
||||
// Check system_path_alias_delete();
|
||||
$alias_manager = $this->prophesize(AliasManagerInterface::class);
|
||||
$alias_manager->cacheClear(Argument::any())->shouldBeCalledTimes(1);
|
||||
$alias_manager->cacheClear($new_source)->shouldBeCalledTimes(1);
|
||||
|
|
|
@ -46,7 +46,14 @@ class ExposedFilterBlocksUpdateTest extends UpdatePathTestBase {
|
|||
// the config schema checker ignore the block.
|
||||
static::$configSchemaCheckerExclusions[] = 'block.block.seven_secondary_local_tasks';
|
||||
|
||||
$this->container->get('module_installer')->uninstall(['block']);
|
||||
// We need to uninstall the menu_link_content module because
|
||||
// menu_link_content_entity_predelete() invokes alias processing and we
|
||||
// don't have a working path alias system until system_update_8803() runs.
|
||||
// Note that path alias processing is disabled during the regular database
|
||||
// update process, so this only happens because we uninstall the Block
|
||||
// module before running the updates.
|
||||
// @see \Drupal\Core\Update\UpdateServiceProvider::alter()
|
||||
$this->container->get('module_installer')->uninstall(['menu_link_content', 'block']);
|
||||
$this->runUpdates();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class PathAliasHalJsonAnonTest extends PathAliasHalJsonTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class PathAliasHalJsonBasicAuthTest extends PathAliasHalJsonTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal', 'basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Hal;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group hal
|
||||
*/
|
||||
class PathAliasHalJsonCookieTest extends PathAliasHalJsonTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['hal'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'hal_json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/hal+json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Hal;
|
||||
|
||||
use Drupal\FunctionalTests\Rest\PathAliasResourceTestBase;
|
||||
use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
|
||||
|
||||
/**
|
||||
* Base hal_json test class for the path_alias entity type.
|
||||
*/
|
||||
abstract class PathAliasHalJsonTestBase extends PathAliasResourceTestBase {
|
||||
|
||||
use HalEntityNormalizationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$default_normalization = parent::getExpectedNormalizedEntity();
|
||||
$normalization = $this->applyHalFieldNormalization($default_normalization);
|
||||
return $normalization + [
|
||||
'_links' => [
|
||||
'self' => [
|
||||
'href' => '',
|
||||
],
|
||||
'type' => [
|
||||
'href' => $this->baseUrl . '/rest/type/path_alias/path_alias',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return parent::getNormalizedPostEntity() + [
|
||||
'_links' => [
|
||||
'type' => [
|
||||
'href' => $this->baseUrl . '/rest/type/path_alias/path_alias',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return [
|
||||
'url.site',
|
||||
'user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for unauthenticated JSON requests.
|
||||
*
|
||||
* @group path
|
||||
*/
|
||||
class PathAliasJsonAnonTest extends PathAliasResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for JSON requests via basic auth.
|
||||
*
|
||||
* @group path
|
||||
*/
|
||||
class PathAliasJsonBasicAuthTest extends PathAliasResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for JSON requests with cookie authentication.
|
||||
*
|
||||
* @group path
|
||||
*/
|
||||
class PathAliasJsonCookieTest extends PathAliasResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Rest;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Path\Entity\PathAlias;
|
||||
use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* Base class for path_alias EntityResource tests.
|
||||
*/
|
||||
abstract class PathAliasResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
use BcTimestampNormalizerUnixTestTrait;
|
||||
|
||||
/**
|
||||
* {@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',
|
||||
],
|
||||
],
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->entity->uuid(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'path' => [
|
||||
[
|
||||
'value' => '/<front>',
|
||||
],
|
||||
],
|
||||
'alias' => [
|
||||
[
|
||||
'value' => '/frontpage1',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return ['user.permissions'];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for unauthenticated XML requests.
|
||||
*
|
||||
* @group path
|
||||
*/
|
||||
class PathAliasXmlAnonTest extends PathAliasResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\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
|
||||
*/
|
||||
class PathAliasXmlBasicAuthTest extends PathAliasResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\FunctionalTests\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test path_alias entities for XML requests.
|
||||
*
|
||||
* @group path
|
||||
*/
|
||||
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';
|
||||
|
||||
}
|
|
@ -93,6 +93,7 @@ class DbDumpTest extends KernelTestBase {
|
|||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->installSchema('system', 'sequences');
|
||||
|
||||
// Place some sample config to test for in the export.
|
||||
|
@ -107,7 +108,7 @@ class DbDumpTest extends KernelTestBase {
|
|||
$account = User::create(['mail' => 'q\'uote$dollar@example.com', 'name' => '$dollar']);
|
||||
$account->save();
|
||||
|
||||
// Create url_alias (this will create 'url_alias').
|
||||
// Create a path alias.
|
||||
$this->container->get('path.alias_storage')->save('/user/' . $account->id(), '/user/example');
|
||||
|
||||
// Create a cache table (this will create 'cache_discovery').
|
||||
|
@ -134,7 +135,8 @@ class DbDumpTest extends KernelTestBase {
|
|||
'menu_link_content_field_revision',
|
||||
'sequences',
|
||||
'sessions',
|
||||
'url_alias',
|
||||
'path_alias',
|
||||
'path_alias_revision',
|
||||
'user__roles',
|
||||
'users',
|
||||
'users_field_data',
|
||||
|
|
|
@ -40,6 +40,7 @@ class CreateSampleEntityTest extends KernelTestBase {
|
|||
$this->installEntitySchema('file');
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('comment_type');
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->installEntitySchema('taxonomy_vocabulary');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->entityTypeManager = $this->container->get('entity_type.manager');
|
||||
|
|
|
@ -27,6 +27,7 @@ class AliasStorageTest extends KernelTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('path_alias');
|
||||
$this->storage = $this->container->get('path.alias_storage');
|
||||
}
|
||||
|
||||
|
|
|
@ -4,39 +4,51 @@ namespace Drupal\KernelTests\Core\Path;
|
|||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Cache\MemoryCounterBackend;
|
||||
use Drupal\Core\Path\AliasStorage;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Path\AliasManager;
|
||||
use Drupal\Core\Path\AliasWhitelist;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests path alias CRUD and lookup functionality.
|
||||
*
|
||||
* @group Path
|
||||
*/
|
||||
class AliasTest extends PathUnitTestBase {
|
||||
class AliasTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// The alias whitelist 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');
|
||||
}
|
||||
|
||||
public function testCRUD() {
|
||||
// Prepare database table.
|
||||
$connection = Database::getConnection();
|
||||
$this->fixtures->createTables($connection);
|
||||
|
||||
// Create Path object.
|
||||
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
|
||||
$aliasStorage = $this->container->get('path.alias_storage');
|
||||
|
||||
$aliases = $this->fixtures->sampleUrlAliases();
|
||||
$aliases = $this->sampleUrlAliases();
|
||||
|
||||
// Create a few aliases
|
||||
foreach ($aliases as $idx => $alias) {
|
||||
$aliasStorage->save($alias['source'], $alias['alias'], $alias['langcode']);
|
||||
|
||||
$result = $connection->query('SELECT * FROM {url_alias} WHERE source = :source AND alias= :alias AND langcode = :langcode', [':source' => $alias['source'], ':alias' => $alias['alias'], ':langcode' => $alias['langcode']]);
|
||||
$result = $connection->query('SELECT * FROM {path_alias} WHERE path = :path AND alias= :alias AND langcode = :langcode', [':path' => $alias['source'], ':alias' => $alias['alias'], ':langcode' => $alias['langcode']]);
|
||||
$rows = $result->fetchAll();
|
||||
|
||||
$this->assertEqual(count($rows), 1, new FormattableMarkup('Created an entry for %alias.', ['%alias' => $alias['alias']]));
|
||||
|
||||
// Cache the pid for further tests.
|
||||
$aliases[$idx]['pid'] = $rows[0]->pid;
|
||||
$aliases[$idx]['pid'] = $rows[0]->id;
|
||||
}
|
||||
|
||||
// Load a few aliases
|
||||
|
@ -56,7 +68,7 @@ class AliasTest extends PathUnitTestBase {
|
|||
|
||||
$this->assertEqual($alias['alias'], $fields['original']['alias']);
|
||||
|
||||
$result = $connection->query('SELECT pid FROM {url_alias} WHERE source = :source AND alias= :alias AND langcode = :langcode', [':source' => $alias['source'], ':alias' => $alias['alias'] . '_updated', ':langcode' => $alias['langcode']]);
|
||||
$result = $connection->query('SELECT id FROM {path_alias} WHERE path = :path AND alias= :alias AND langcode = :langcode', [':path' => $alias['source'], ':alias' => $alias['alias'] . '_updated', ':langcode' => $alias['langcode']]);
|
||||
$pid = $result->fetchField();
|
||||
|
||||
$this->assertEqual($pid, $alias['pid'], new FormattableMarkup('Updated entry for pid %pid.', ['%pid' => $pid]));
|
||||
|
@ -67,21 +79,47 @@ class AliasTest extends PathUnitTestBase {
|
|||
$pid = $alias['pid'];
|
||||
$aliasStorage->delete(['pid' => $pid]);
|
||||
|
||||
$result = $connection->query('SELECT * FROM {url_alias} WHERE pid = :pid', [':pid' => $pid]);
|
||||
$result = $connection->query('SELECT * FROM {path_alias} WHERE id = :id', [':id' => $pid]);
|
||||
$rows = $result->fetchAll();
|
||||
|
||||
$this->assertEqual(count($rows), 0, new FormattableMarkup('Deleted entry with pid %pid.', ['%pid' => $pid]));
|
||||
}
|
||||
}
|
||||
|
||||
public function testLookupPath() {
|
||||
// Prepare database table.
|
||||
$connection = Database::getConnection();
|
||||
$this->fixtures->createTables($connection);
|
||||
/**
|
||||
* Returns an array of URL aliases for testing.
|
||||
*
|
||||
* @return array of URL alias definitions.
|
||||
*/
|
||||
protected function sampleUrlAliases() {
|
||||
return [
|
||||
[
|
||||
'source' => '/node/1',
|
||||
'alias' => '/alias_for_node_1_en',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
[
|
||||
'source' => '/node/2',
|
||||
'alias' => '/alias_for_node_2_en',
|
||||
'langcode' => 'en',
|
||||
],
|
||||
[
|
||||
'source' => '/node/1',
|
||||
'alias' => '/alias_for_node_1_fr',
|
||||
'langcode' => 'fr',
|
||||
],
|
||||
[
|
||||
'source' => '/node/1',
|
||||
'alias' => '/alias_for_node_1_und',
|
||||
'langcode' => 'und',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testLookupPath() {
|
||||
// Create AliasManager and Path object.
|
||||
$aliasManager = $this->container->get('path.alias_manager');
|
||||
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
|
||||
$aliasStorage = $this->container->get('path.alias_storage');
|
||||
|
||||
// Test the situation where the source is the same for multiple aliases.
|
||||
// Start with a language-neutral alias, which we will override.
|
||||
|
@ -157,14 +195,10 @@ class AliasTest extends PathUnitTestBase {
|
|||
* Tests the alias whitelist.
|
||||
*/
|
||||
public function testWhitelist() {
|
||||
// Prepare database table.
|
||||
$connection = Database::getConnection();
|
||||
$this->fixtures->createTables($connection);
|
||||
|
||||
$memoryCounterBackend = new MemoryCounterBackend();
|
||||
|
||||
// Create AliasManager and Path object.
|
||||
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
|
||||
$aliasStorage = $this->container->get('path.alias_storage');
|
||||
$whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $aliasStorage);
|
||||
$aliasManager = new AliasManager($aliasStorage, $whitelist, $this->container->get('language_manager'), $memoryCounterBackend);
|
||||
|
||||
|
@ -221,14 +255,10 @@ class AliasTest extends PathUnitTestBase {
|
|||
* Tests situation where the whitelist cache is deleted mid-request.
|
||||
*/
|
||||
public function testWhitelistCacheDeletionMidRequest() {
|
||||
// Prepare database table.
|
||||
$connection = Database::getConnection();
|
||||
$this->fixtures->createTables($connection);
|
||||
|
||||
$memoryCounterBackend = new MemoryCounterBackend();
|
||||
|
||||
// Create AliasManager and Path object.
|
||||
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
|
||||
$aliasStorage = $this->container->get('path.alias_storage');
|
||||
$whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $aliasStorage);
|
||||
$aliasManager = new AliasManager($aliasStorage, $whitelist, $this->container->get('language_manager'), $memoryCounterBackend);
|
||||
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\KernelTests\Core\Path;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\system\Tests\Path\UrlAliasFixtures;
|
||||
|
||||
/**
|
||||
* Base class for Path/URL alias integration tests.
|
||||
*/
|
||||
abstract class PathUnitTestBase extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\system\Tests\Path\UrlAliasFixtures
|
||||
*/
|
||||
protected $fixtures;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->fixtures = new UrlAliasFixtures();
|
||||
// The alias whitelist expects that the menu path roots are set by a
|
||||
// menu router rebuild.
|
||||
\Drupal::state()->set('router.path_roots', ['user', 'admin']);
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
$this->fixtures->dropTables(Database::getConnection());
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,15 @@ class ContentNegotiationRoutingTest extends KernelTestBase {
|
|||
*/
|
||||
public static $modules = ['conneg_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -87,6 +87,7 @@ class RouteProviderTest extends KernelTestBase {
|
|||
$this->cache = new MemoryBackend();
|
||||
$this->pathProcessor = \Drupal::service('path_processor_manager');
|
||||
$this->cacheTagsInvalidator = \Drupal::service('cache_tags.invalidator');
|
||||
$this->installEntitySchema('path_alias');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -539,10 +539,10 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa
|
|||
}
|
||||
|
||||
if ($container->hasDefinition('path_processor_alias')) {
|
||||
// Prevent the alias-based path processor, which requires a url_alias db
|
||||
// table, from being registered to the path processor manager. We do this
|
||||
// by removing the tags that the compiler pass looks for. This means the
|
||||
// url generator can safely be used within tests.
|
||||
// The alias-based processor requires the path_alias entity schema to be
|
||||
// installed, so we prevent it from being registered to the path processor
|
||||
// manager. We do this by removing the tags that the compiler pass looks
|
||||
// for. This means that the URL generator can safely be used within tests.
|
||||
$container->getDefinition('path_processor_alias')
|
||||
->clearTag('path_processor_inbound')
|
||||
->clearTag('path_processor_outbound');
|
||||
|
|
Loading…
Reference in New Issue