diff --git a/core/core.services.yml b/core/core.services.yml index 0744748..e40ee16 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -947,7 +947,7 @@ services: - { name: event_subscriber } path.alias_storage: class: Drupal\Core\Path\AliasStorage - arguments: ['@database', '@module_handler'] + arguments: ['@database', '@entity_type.manager'] tags: - { name: backend_overridable } path.matcher: diff --git a/core/lib/Drupal/Core/Path/AliasStorage.php b/core/lib/Drupal/Core/Path/AliasStorage.php index b689848..1087630 100644 --- a/core/lib/Drupal/Core/Path/AliasStorage.php +++ b/core/lib/Drupal/Core/Path/AliasStorage.php @@ -2,12 +2,9 @@ namespace Drupal\Core\Path; -use Drupal\Core\Cache\Cache; use Drupal\Core\Database\Connection; -use Drupal\Core\Database\SchemaObjectExistsException; -use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Database\Query\Condition; /** * Provides a class for CRUD operations on path aliases. @@ -21,7 +18,7 @@ class AliasStorage implements AliasStorageInterface { /** * The table for the url_alias storage. */ - const TABLE = 'url_alias'; + const TABLE = 'path_alias'; /** * The database connection. @@ -31,30 +28,36 @@ class AliasStorage implements AliasStorageInterface { protected $connection; /** - * The module handler. + * The entity type manager. * - * @var \Drupal\Core\Extension\ModuleHandlerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $moduleHandler; + protected $entityTypeManager; + + /** + * The storage handler for path_alias entities. + * + * @var \Drupal\Core\Path\PathAliasStorageInterface + */ + protected $pathAliasEntityStorage; /** * Constructs a Path CRUD object. * * @param \Drupal\Core\Database\Connection $connection * 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, EntityTypeManagerInterface $entity_type_manager) { $this->connection = $connection; - $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; } /** * {@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 +66,119 @@ public function save($source, $alias, $langcode = LanguageInterface::LANGCODE_NO throw new \InvalidArgumentException(sprintf('Alias path %s has to start with a slash.', $alias)); } - $fields = [ - 'source' => $source, - 'alias' => $alias, - 'langcode' => $langcode, - ]; - - // 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'; + 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, + ]; + + $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(); 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); + if ($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); + $query = $this->getPathAliasEntityStorage()->getQuery(); 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); + if ($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(); + $entities = $this->getPathAliasEntityStorage()->loadMultiple($result); + $this->getPathAliasEntityStorage()->delete($entities); } /** * {@inheritdoc} */ public function preloadPathAlias($preloaded, $langcode) { - $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED]; - $select = $this->connection->select(static::TABLE) - ->fields(static::TABLE, ['source', 'alias']); - - if (!empty($preloaded)) { - $conditions = new Condition('OR'); - foreach ($preloaded as $preloaded_item) { - $conditions->condition('source', $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'); - } - - $select->orderBy('pid', 'ASC'); - $select->condition('langcode', $langcode_list, 'IN'); - try { - return $select->execute()->fetchAllKeyed(); - } - catch (\Exception $e) { - $this->catchException($e); - return FALSE; - } + return $this->getPathAliasEntityStorage()->preloadPathAlias($preloaded, $langcode); } /** * {@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'); - } - - $select->orderBy('pid', 'DESC'); - $select->condition('langcode', $langcode_list, 'IN'); - try { - return $select->execute()->fetchField(); - } - catch (\Exception $e) { - $this->catchException($e); - return FALSE; - } + return $this->getPathAliasEntityStorage()->lookupPathAlias($path, $langcode); } /** * {@inheritdoc} */ public function lookupPathSource($path, $langcode) { - $alias = $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, ['source']) - ->condition('alias', $alias, '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'); - } - - $select->orderBy('pid', 'DESC'); - $select->condition('langcode', $langcode_list, 'IN'); - try { - return $select->execute()->fetchField(); - } - catch (\Exception $e) { - $this->catchException($e); - return FALSE; - } + return $this->getPathAliasEntityStorage()->lookupPathSource($path, $langcode); } /** @@ -295,7 +190,7 @@ public function aliasExists($alias, $langcode, $source = NULL) { ->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); @@ -313,7 +208,7 @@ public function aliasExists($alias, $langcode, $source = NULL) { */ 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(); + return (bool) $this->connection->queryRange('SELECT 1 FROM {' . static::TABLE . '} WHERE langcode <> :langcode', 0, 1, [':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED])->fetchField(); } catch (\Exception $e) { $this->catchException($e); @@ -350,103 +245,23 @@ public function getAliasesForAdminListing($header, $keys = NULL) { * {@inheritdoc} */ public function pathHasMatchingAlias($initial_substring) { - $query = $this->connection->select(static::TABLE, 'u'); - $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; - } - } - - /** - * Check if the table exists and create it if not. - */ - 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 (SchemaObjectExistsException $e) { - return TRUE; - } - return FALSE; + return $this->getPathAliasEntityStorage()->pathHasMatchingAlias($initial_substring); } /** - * 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. + * Returns the path alias entity storage handler. * - * @param $e - * The exception. + * We can not store it in the constructor because that leads to a circular + * dependency in the service container. * - * @throws \Exception + * @return \Drupal\Core\Path\PathAliasStorageInterface + * The path alias entity storage. */ - protected function catchException(\Exception $e) { - if ($this->connection->schema()->tableExists(static::TABLE)) { - throw $e; + protected function getPathAliasEntityStorage() { + if (!$this->pathAliasEntityStorage) { + $this->pathAliasEntityStorage = $this->entityTypeManager->getStorage('path_alias'); } - } - - /** - * 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'], - ], - ]; + return $this->pathAliasEntityStorage; } } diff --git a/core/lib/Drupal/Core/Path/Entity/PathAlias.php b/core/lib/Drupal/Core/Path/Entity/PathAlias.php new file mode 100644 index 0000000..4b34ae8 --- /dev/null +++ b/core/lib/Drupal/Core/Path/Entity/PathAlias.php @@ -0,0 +1,114 @@ +setLabel(new TranslatableMarkup('Path')) + ->setDescription(new TranslatableMarkup('The path that this alias belongs to.')) + ->setRequired(TRUE) + ->setDefaultValue(''); + + $fields['alias'] = BaseFieldDefinition::create('string') + ->setLabel(new TranslatableMarkup('Alias')) + ->setDescription(new TranslatableMarkup('The alias used with this path.')) + ->setRequired(TRUE) + ->setRevisionable(TRUE) + ->setDefaultValue(''); + + // The language code of a path alias should not be able to change between + // revisions. + $fields['langcode'] + ->setRevisionable(FALSE) + ->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED); + + return $fields; + } + + /** + * {@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']; + } + +} diff --git a/core/lib/Drupal/Core/Path/PathAliasInterface.php b/core/lib/Drupal/Core/Path/PathAliasInterface.php new file mode 100644 index 0000000..73d2b45 --- /dev/null +++ b/core/lib/Drupal/Core/Path/PathAliasInterface.php @@ -0,0 +1,48 @@ + $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()->invokeAll('path_' . $hook, [$values]); + } + } + + /** + * {@inheritdoc} + */ + public function preloadPathAlias($preloaded, $langcode) { + $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED]; + $select = $this->database->select($this->baseTable) + ->fields($this->baseTable, ['path', 'alias']); + + if (!empty($preloaded)) { + $conditions = new Condition('OR'); + foreach ($preloaded as $preloaded_item) { + $conditions->condition('path', $this->database->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 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. + 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'); + } + + $select->orderBy('id', 'ASC'); + $select->condition('langcode', $langcode_list, 'IN'); + + return $select->execute()->fetchAllKeyed(); + } + + /** + * {@inheritdoc} + */ + public function lookupPathAlias($path, $langcode) { + $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED]; + + // See the queries above. Use LIKE for case-insensitive matching. + $select = $this->database->select($this->baseTable) + ->fields($this->baseTable, ['alias']) + ->condition('path', $this->database->escapeLike($path), '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'); + } + + $select->orderBy('id', 'DESC'); + $select->condition('langcode', $langcode_list, 'IN'); + + return $select->execute()->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function lookupPathSource($alias, $langcode) { + $langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED]; + + // See the queries above. Use LIKE for case-insensitive matching. + $select = $this->database->select($this->baseTable, 'pa'); + $select->addField('pa', 'path', 'source'); + $select->condition('alias', $this->database->escapeLike($alias), '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'); + } + + $select->orderBy('id', 'DESC'); + $select->condition('langcode', $langcode_list, 'IN'); + + return $select->execute()->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function pathHasMatchingAlias($initial_substring) { + $query = $this->database->select($this->baseTable); + $query->addExpression(1); + + return (bool) $query + ->condition('path', $this->database->escapeLike($initial_substring) . '%', 'LIKE') + ->range(0, 1) + ->execute() + ->fetchField(); + } + +} diff --git a/core/lib/Drupal/Core/Path/PathAliasStorageInterface.php b/core/lib/Drupal/Core/Path/PathAliasStorageInterface.php new file mode 100644 index 0000000..4109d25 --- /dev/null +++ b/core/lib/Drupal/Core/Path/PathAliasStorageInterface.php @@ -0,0 +1,71 @@ +storage->getBaseTable()]['indexes'] += [ + 'path_alias__alias_langcode_id' => ['alias', 'langcode', 'id'], + 'path_alias__path_langcode_id' => ['path', 'langcode', 'id'], + ]; + + return $schema; + } + +} diff --git a/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php b/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php index 6a7cf4a..028d23a 100644 --- a/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php +++ b/core/modules/menu_link_content/tests/src/Kernel/PathAliasMenuLinkContentTest.php @@ -27,6 +27,7 @@ protected function setUp() { parent::setUp(); $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() diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php index 29219d8..a3f6ac4 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php @@ -84,6 +84,7 @@ protected function getEntityCounts() { 'shortcut_set' => 1, 'action' => 23, 'menu' => 8, + 'path_alias' => 8, 'taxonomy_term' => 8, 'taxonomy_vocabulary' => 7, 'tour' => 5, diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php index b171801..e0e7b19 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php @@ -86,6 +86,7 @@ protected function getEntityCounts() { 'shortcut_set' => 2, 'action' => 17, 'menu' => 6, + 'path_alias' => 6, 'taxonomy_term' => 18, 'taxonomy_vocabulary' => 4, 'tour' => 5, diff --git a/core/modules/path/src/Controller/PathController.php b/core/modules/path/src/Controller/PathController.php index 26fe851..9ca8c9d 100644 --- a/core/modules/path/src/Controller/PathController.php +++ b/core/modules/path/src/Controller/PathController.php @@ -84,12 +84,12 @@ public function adminOverview(Request $request) { $row = []; // @todo Should Path module store leading slashes? See // https://www.drupal.org/node/2430593. - $row['data']['alias'] = $this->l(Unicode::truncate($data->alias, 50, FALSE, TRUE), Url::fromUserInput($data->source, [ + $row['data']['alias'] = $this->l(Unicode::truncate($data->alias, 50, FALSE, TRUE), Url::fromUserInput($data->path, [ 'attributes' => ['title' => $data->alias], ])); - $row['data']['source'] = $this->l(Unicode::truncate($data->source, 50, FALSE, TRUE), Url::fromUserInput($data->source, [ + $row['data']['source'] = $this->l(Unicode::truncate($data->path, 50, FALSE, TRUE), Url::fromUserInput($data->path, [ 'alias' => TRUE, - 'attributes' => ['title' => $data->source], + 'attributes' => ['title' => $data->path], ])); if ($multilanguage) { $row['data']['language_name'] = $this->languageManager()->getLanguageName($data->langcode); @@ -98,11 +98,11 @@ public function adminOverview(Request $request) { $operations = []; $operations['edit'] = [ 'title' => $this->t('Edit'), - 'url' => Url::fromRoute('path.admin_edit', ['pid' => $data->pid], ['query' => $destination]), + 'url' => Url::fromRoute('path.admin_edit', ['pid' => $data->id], ['query' => $destination]), ]; $operations['delete'] = [ 'title' => $this->t('Delete'), - 'url' => Url::fromRoute('path.delete', ['pid' => $data->pid], ['query' => $destination]), + 'url' => Url::fromRoute('path.delete', ['pid' => $data->id], ['query' => $destination]), ]; $row['data']['operations'] = [ 'data' => [ @@ -113,7 +113,7 @@ public function adminOverview(Request $request) { // If the system path maps to a different URL alias, highlight this table // row to let the user know of old aliases. - if ($data->alias != $this->aliasManager->getAliasByPath($data->source, $data->langcode)) { + if ($data->alias != $this->aliasManager->getAliasByPath($data->path, $data->langcode)) { $row['class'] = ['warning']; } diff --git a/core/modules/path/tests/src/Functional/PathAliasTest.php b/core/modules/path/tests/src/Functional/PathAliasTest.php index 19115cd..8e816bc 100644 --- a/core/modules/path/tests/src/Functional/PathAliasTest.php +++ b/core/modules/path/tests/src/Functional/PathAliasTest.php @@ -368,7 +368,11 @@ public function testNodeAlias() { * Integer representing the path ID. */ public function getPID($alias) { - return db_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); } /** diff --git a/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php index 28f22ec..2676844 100644 --- a/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php +++ b/core/modules/path/tests/src/Kernel/Migrate/d6/MigrateUrlAliasTest.php @@ -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); diff --git a/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php index b8d92e0..587709c 100644 --- a/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php +++ b/core/modules/path/tests/src/Kernel/Migrate/d7/MigrateUrlAliasTest.php @@ -32,6 +32,7 @@ protected function setUp() { parent::setUp(); $this->installEntitySchema('node'); + $this->installEntitySchema('path_alias'); $this->installConfig('node'); $this->installSchema('node', ['node_access']); diff --git a/core/modules/path/tests/src/Kernel/PathItemTest.php b/core/modules/path/tests/src/Kernel/PathItemTest.php index 224a842..f001dc6 100644 --- a/core/modules/path/tests/src/Kernel/PathItemTest.php +++ b/core/modules/path/tests/src/Kernel/PathItemTest.php @@ -29,6 +29,7 @@ protected function setUp() { $this->installEntitySchema('node'); $this->installEntitySchema('user'); + $this->installEntitySchema('path_alias'); $this->installSchema('node', ['node_access']); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 17e0c0a..d5119fd 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -874,9 +874,10 @@ public function testPost() { // 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; @@ -961,7 +962,9 @@ public function testPost() { // 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); @@ -972,7 +975,9 @@ public function testPost() { $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); @@ -1103,9 +1108,10 @@ public function testPatch() { // 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; @@ -1472,8 +1478,9 @@ protected function makeNormalizationInvalid(array $normalization, $entity_key) { 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(); diff --git a/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php b/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php index 739089a..205c0d1 100644 --- a/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php +++ b/core/modules/rest/tests/src/Functional/Update/EntityResourcePermissionsUpdateTest.php @@ -33,16 +33,13 @@ public function setDatabaseDumpFiles() { * 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)); + + $rest_permissions_callback = \Drupal::service('controller_resolver')->getControllerFromDefinition('Drupal\rest\RestPermissions::permissions'); + $rest_permissions = array_keys(call_user_func($rest_permissions_callback)); + $this->assertEquals([], $rest_permissions); $this->runUpdates(); @@ -50,8 +47,10 @@ public function testBcEntityResourcePermissionSettingAdded() { $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('Drupal\rest\RestPermissions::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); } } diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index 832cc82..62b0fee 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -384,7 +384,7 @@ public function containerBuild(ContainerBuilder $container) { } if ($container->hasDefinition('path_processor_alias')) { - // Prevent the alias-based path processor, which requires a url_alias db + // 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 within tests. diff --git a/core/modules/system/src/Tests/Path/UrlAliasFixtures.php b/core/modules/system/src/Tests/Path/UrlAliasFixtures.php deleted file mode 100644 index 41fd896..0000000 --- a/core/modules/system/src/Tests/Path/UrlAliasFixtures.php +++ /dev/null @@ -1,96 +0,0 @@ -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; - } - -} diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 2cddcbf..91aa36a 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -11,7 +11,6 @@ use Drupal\Component\Utility\OpCodeCache; use Drupal\Component\Utility\Unicode; use Drupal\Core\Cache\Cache; -use Drupal\Core\Path\AliasStorage; use Drupal\Core\Url; use Drupal\Core\Database\Database; use Drupal\Core\Entity\ContentEntityTypeInterface; @@ -1181,11 +1180,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; } diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index ba8e798..73d1c9d 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -6,10 +6,13 @@ */ use Drupal\Core\Config\Entity\ConfigEntityUpdater; +use Drupal\Core\Entity\ContentEntityType; use Drupal\Core\Entity\Display\EntityDisplayInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Site\Settings; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Re-save all configuration entities to recalculate dependencies. @@ -178,3 +181,76 @@ function system_post_update_extra_fields(&$sandbox = NULL) { function system_post_update_states_clear_cache() { // Empty post-update hook. } + +/** + * Convert path aliases to entities. + */ +function system_post_update_convert_path_aliases_to_entities(&$sandbox = NULL) { + $database = \Drupal::database(); + + if (!isset($sandbox['current_id'])) { + // This must be the first run. Initialize the sandbox. + $sandbox['progress'] = 0; + $sandbox['current_id'] = 0; + + $entity_type = new ContentEntityType([ + 'id' => 'path_alias', + 'class' => '\Drupal\Core\Path\Entity\PathAlias', + 'label' => new TranslatableMarkup('Path alias'), + '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', + ], + ]); + \Drupal::entityDefinitionUpdateManager()->installEntityType($entity_type); + } + + $step_size = Settings::get('entity_update_batch_size', 50); + $url_aliases = $database->select('url_alias', 't') + ->condition('t.pid', $sandbox['current_id'], '>') + ->fields('t') + ->orderBy('pid', 'ASC') + ->range(0, $step_size) + ->execute() + ->fetchAll(); + + $storage = \Drupal::entityTypeManager()->getStorage('path_alias'); + foreach ($url_aliases as $url_alias) { + $path_alias = $storage->create([ + 'id' => $url_alias->pid, + 'revision_id' => $url_alias->pid, + 'path' => $url_alias->source, + 'alias' => $url_alias->alias, + 'langcode' => $url_alias->langcode, + ]); + $path_alias->enforceIsNew(TRUE); + $path_alias->save(); + + $sandbox['progress']++; + $sandbox['current_id'] = $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; + + if ($sandbox['#finished'] >= 1) { + $database->schema()->dropTable('url_alias'); + + return t('Path aliases have been converted to entities.'); + } +} diff --git a/core/modules/system/tests/src/Functional/Path/UrlAlterFunctionalTest.php b/core/modules/system/tests/src/Functional/Path/UrlAlterFunctionalTest.php index afd697a..6e22a4a 100644 --- a/core/modules/system/tests/src/Functional/Path/UrlAlterFunctionalTest.php +++ b/core/modules/system/tests/src/Functional/Path/UrlAlterFunctionalTest.php @@ -25,8 +25,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. diff --git a/core/modules/system/tests/src/Kernel/PathHooksTest.php b/core/modules/system/tests/src/Kernel/PathHooksTest.php index fcbb499..afa6176 100644 --- a/core/modules/system/tests/src/Kernel/PathHooksTest.php +++ b/core/modules/system/tests/src/Kernel/PathHooksTest.php @@ -18,6 +18,15 @@ class PathHooksTest extends KernelTestBase { static public $modules = ['system']; /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installEntitySchema('path_alias'); + } + + /** * Test system_path_*() correctly clears caches. */ public function testPathHooks() { diff --git a/core/tests/Drupal/FunctionalTests/Hal/PathAliasHalJsonAnonTest.php b/core/tests/Drupal/FunctionalTests/Hal/PathAliasHalJsonAnonTest.php new file mode 100644 index 0000000..5c8c16c --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Hal/PathAliasHalJsonAnonTest.php @@ -0,0 +1,29 @@ +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', + ]; + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Rest/PathAliasJsonAnonTest.php b/core/tests/Drupal/FunctionalTests/Rest/PathAliasJsonAnonTest.php new file mode 100644 index 0000000..9febf9e --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Rest/PathAliasJsonAnonTest.php @@ -0,0 +1,26 @@ +grantPermissionsToTestedRole(['administer url aliases']); + break; + case 'POST': + $this->grantPermissionsToTestedRole(['administer url aliases']); + break; + case 'PATCH': + $this->grantPermissionsToTestedRole(['administer url aliases']); + break; + case 'DELETE': + $this->grantPermissionsToTestedRole(['administer url aliases']); + break; + } + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + $path_alias = PathAlias::create([ + 'path' => '/', + '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' => '/', + ], + ], + 'alias' => [ + [ + 'value' => '/frontpage1', + ], + ], + 'uuid' => [ + [ + 'value' => $this->entity->uuid(), + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return [ + 'path' => [ + [ + 'value' => '/', + ], + ], + 'alias' => [ + [ + 'value' => '/frontpage1', + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedUnauthorizedAccessMessage($method) { + if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) { + return parent::getExpectedUnauthorizedAccessMessage($method); + } + + switch ($method) { + case 'GET': + return "The 'administer url aliases' permission is required."; + break; + case 'POST': + return "The 'administer url aliases' permission is required."; + break; + case 'PATCH': + return "The 'administer url aliases' permission is required."; + break; + case 'DELETE': + return "The 'administer url aliases' permission is required."; + break; + } + return parent::getExpectedUnauthorizedAccessMessage($method); + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return ['user.permissions']; + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlAnonTest.php b/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlAnonTest.php new file mode 100644 index 0000000..60c624c --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlAnonTest.php @@ -0,0 +1,36 @@ +markTestSkipped(); + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlBasicAuthTest.php b/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlBasicAuthTest.php new file mode 100644 index 0000000..4eef5af --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlBasicAuthTest.php @@ -0,0 +1,46 @@ +markTestSkipped(); + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlCookieTest.php b/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlCookieTest.php new file mode 100644 index 0000000..514d234 --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Rest/PathAliasXmlCookieTest.php @@ -0,0 +1,41 @@ +markTestSkipped(); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php b/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php index 16070c0..7656702 100644 --- a/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php +++ b/core/tests/Drupal/KernelTests/Core/Command/DbDumpTest.php @@ -93,6 +93,7 @@ protected function setUp() { $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 @@ protected function setUp() { $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'). @@ -132,7 +133,8 @@ protected function setUp() { 'menu_link_content_data', 'sequences', 'sessions', - 'url_alias', + 'path_alias', + 'path_alias_revision', 'user__roles', 'users', 'users_field_data', diff --git a/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php b/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php index d7b24b4..2e986d1 100644 --- a/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php @@ -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'); } diff --git a/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php index 48d6b7d..e341053 100644 --- a/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php @@ -3,39 +3,51 @@ namespace Drupal\KernelTests\Core\Path; 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, format_string('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 @@ -55,7 +67,7 @@ public function testCRUD() { $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'], format_string('Updated entry for pid %pid.', ['%pid' => $pid])); @@ -66,21 +78,47 @@ public function testCRUD() { $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, format_string('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. @@ -156,14 +194,10 @@ public function testLookupPath() { * 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); @@ -220,14 +254,10 @@ public function testWhitelist() { * 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); diff --git a/core/tests/Drupal/KernelTests/Core/Path/PathUnitTestBase.php b/core/tests/Drupal/KernelTests/Core/Path/PathUnitTestBase.php deleted file mode 100644 index 671dbc4..0000000 --- a/core/tests/Drupal/KernelTests/Core/Path/PathUnitTestBase.php +++ /dev/null @@ -1,33 +0,0 @@ -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(); - } - -} diff --git a/core/tests/Drupal/KernelTests/Core/Routing/ContentNegotiationRoutingTest.php b/core/tests/Drupal/KernelTests/Core/Routing/ContentNegotiationRoutingTest.php index 34c6d58..ea23b6f 100644 --- a/core/tests/Drupal/KernelTests/Core/Routing/ContentNegotiationRoutingTest.php +++ b/core/tests/Drupal/KernelTests/Core/Routing/ContentNegotiationRoutingTest.php @@ -22,6 +22,15 @@ class ContentNegotiationRoutingTest extends KernelTestBase { /** * {@inheritdoc} */ + protected function setUp() { + parent::setUp(); + + $this->installEntitySchema('path_alias'); + } + + /** + * {@inheritdoc} + */ public function register(ContainerBuilder $container) { parent::register($container); diff --git a/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php b/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php index 713eb3c..fe21802 100644 --- a/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php +++ b/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php @@ -88,6 +88,7 @@ protected function setUp() { $this->cache = new MemoryBackend(); $this->pathProcessor = \Drupal::service('path_processor_manager'); $this->cacheTagsInvalidator = \Drupal::service('cache_tags.invalidator'); + $this->installEntitySchema('path_alias'); } /** diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index dad4c97..dcca1e5 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -548,7 +548,7 @@ public function register(ContainerBuilder $container) { } if ($container->hasDefinition('path_processor_alias')) { - // Prevent the alias-based path processor, which requires a url_alias db + // 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 within tests.