Commit ae57b09a authored by amateescu's avatar amateescu Committed by Berdir
Browse files

Issue #3012050 by amateescu, jibran, pookmish, Berdir: Prepare for the...

Issue #3012050 by amateescu, jibran, pookmish, Berdir: Prepare for the conversion of path aliases to entities
parent d2d66ea5
......@@ -88,7 +88,7 @@ site. If this applies to you, please note that you will need to update any
hard coded links in your content or blocks.
If you use the "system path" (i.e. node/10) for menu items and settings like
that, Drupal will replace it with the url_alias.
that, Drupal will replace it with the url alias.
FAQs
......@@ -123,4 +123,4 @@ Other suggestions and patches contributed by the Drupal community.
Current maintainers:
* Dave Reid - http://www.davereid.net
* Sascha Grossenbacher - https://www.drupal.org/u/berdir
\ No newline at end of file
* Sascha Grossenbacher - https://www.drupal.org/u/berdir
......@@ -16,27 +16,6 @@ use Drupal\pathauto\Entity\PathautoPattern;
function pathauto_install() {
// Set the weight to 1.
module_set_weight('pathauto', 1);
// Ensure the url_alias table exists.
_pathauto_ensure_url_alias_table_exists();
}
/**
* Helper function to ensure the url_alias table exists.
*
* Only necessary on Drupal 8.1.x.
*
* @see https://www.drupal.org/node/2704821
*/
function _pathauto_ensure_url_alias_table_exists() {
$alias_storage = \Drupal::service('path.alias_storage');
if (method_exists($alias_storage, 'schemaDefinition')) {
$database_schema = \Drupal::database()->schema();
if (!$database_schema->tableExists($alias_storage::TABLE)) {
$schema_definition = $alias_storage->schemaDefinition();
$database_schema->createTable($alias_storage::TABLE, $schema_definition);
}
}
}
/**
......@@ -286,7 +265,7 @@ function pathauto_update_8104() {
* Ensure the url_alias table exists.
*/
function pathauto_update_8105() {
_pathauto_ensure_url_alias_table_exists();
// This update function is not needed anymore.
}
/**
......
......@@ -55,7 +55,7 @@ function pathauto_help($route_name, RouteMatchInterface $route_match) {
$output .= '<dd>' . t('The <a href=":pathauto_pattern">"Patterns"</a> page is used to configure automatic path aliasing. New patterns are created here using the <a href=":add_form">Add Pathauto pattern</a> button which presents a form to simplify pattern creation thru the use of <a href="token">available tokens</a>. The patterns page provides a list of all patterns on the site and allows you to edit and reorder them. An alias is generated for the first pattern that applies.', [':pathauto_pattern' => Url::fromRoute('entity.pathauto_pattern.collection')->toString(), ':add_form' => Url::fromRoute('entity.pathauto_pattern.add_form')->toString()]) . '</dd>';
$output .= '<dt>' . t('Pathauto Settings') . '</dt>';
$output .= '<dd>' . t('The <a href=":settings">"Settings"</a> page is used to customize global Pathauto settings for automated pattern creation.', [':settings' => Url::fromRoute('pathauto.settings.form')->toString()]) . '</dd>';
$output .= '<dd>' . t('The <strong>maximum alias length</strong> and <strong>maximum component length</strong> values default to 100 and have a limit of @max from Pathauto. You should enter a value that is the length of the "alias" column of the url_alias database table minus the length of any strings that might get added to the end of the URL. The recommended and default value is 100.', ['@max' => \Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength()]) . '</dd>';
$output .= '<dd>' . t('The <strong>maximum alias length</strong> and <strong>maximum component length</strong> values default to 100 and have a limit of @max from Pathauto. You should enter a value that is the length of the "alias" column of the path_alias database table minus the length of any strings that might get added to the end of the URL. The recommended and default value is 100.', ['@max' => \Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength()]) . '</dd>';
$output .= '<dt>' . t('Bulk Generation') . '</dt>';
$output .= '<dd>' . t('The <a href=":pathauto_bulk">"Bulk Generate"</a> page allows you to create URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.', [':pathauto_bulk' => Url::fromRoute('pathauto.bulk.update.form')->toString()]) . '</dd>';
$output .= '<dt>' . t('Delete Aliases') . '</dt>';
......
......@@ -7,7 +7,7 @@ services:
arguments: ['@config.factory', '@pathauto.alias_storage_helper', '@language_manager', '@cache.discovery', '@transliteration', '@module_handler']
pathauto.alias_storage_helper:
class: Drupal\pathauto\AliasStorageHelper
arguments: ['@config.factory', '@path.alias_storage', '@database','@pathauto.verbose_messenger', '@string_translation']
arguments: ['@config.factory', '@path.alias_storage', '@database','@pathauto.verbose_messenger', '@string_translation', '@entity_type.manager']
tags:
- { name: backend_overridable }
pathauto.alias_uniquifier:
......
......@@ -5,8 +5,10 @@ namespace Drupal\pathauto;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Path\AliasStorageInterface;
use Drupal\Core\Path\Entity\PathAlias;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
......@@ -52,6 +54,13 @@ class AliasStorageHelper implements AliasStorageHelperInterface {
*/
protected $messenger;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The config factory.
*
......@@ -65,13 +74,16 @@ class AliasStorageHelper implements AliasStorageHelperInterface {
* The messenger.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manger.
*/
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageInterface $alias_storage, Connection $database, MessengerInterface $messenger, TranslationInterface $string_translation) {
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageInterface $alias_storage, Connection $database, MessengerInterface $messenger, TranslationInterface $string_translation, EntityTypeManagerInterface $entity_type_manager = NULL) {
$this->configFactory = $config_factory;
$this->aliasStorage = $alias_storage;
$this->database = $database;
$this->messenger = $messenger;
$this->stringTranslation = $string_translation;
$this->entityTypeManager = $entity_type_manager ?: \Drupal::service('entity_type.manager');
}
/**
......@@ -87,63 +99,64 @@ class AliasStorageHelper implements AliasStorageHelperInterface {
public function save(array $path, $existing_alias = NULL, $op = NULL) {
$config = $this->configFactory->get('pathauto.settings');
// Set up all the variables needed to simplify the code below.
$source = $path['source'];
$alias = $path['alias'];
$langcode = $path['language'];
if ($existing_alias) {
/** @var \Drupal\Core\Path\PathAliasInterface $existing_alias */
$existing_alias = $this->entityTypeManager->getStorage('path_alias')->load($existing_alias['pid']);
}
// Alert users if they are trying to create an alias that is the same as the
// internal path.
if ($path['source'] == $path['alias']) {
$this->messenger->addMessage($this->t('Ignoring alias %alias because it is the same as the internal path.', ['%alias' => $path['alias']]));
// internal system path.
if ($source == $alias) {
$this->messenger->addMessage($this->t('Ignoring alias %alias because it is the same as the internal path.', ['%alias' => $alias]));
return NULL;
}
// Skip replacing the current alias with an identical alias.
if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) {
$path += [
'pathauto' => TRUE,
'original' => $existing_alias,
'pid' => NULL,
];
// If there is already an alias, respect some update actions.
if (!empty($existing_alias)) {
switch ($config->get('update_action')) {
case PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW:
// Do not create the alias.
return NULL;
case PathautoGeneratorInterface::UPDATE_ACTION_LEAVE:
// Create a new alias instead of overwriting the existing by leaving
// $path['pid'] empty.
break;
case PathautoGeneratorInterface::UPDATE_ACTION_DELETE:
// The delete actions should overwrite the existing alias.
$path['pid'] = $existing_alias['pid'];
break;
}
// Update the existing alias if there is one and the configuration is set to
// replace it.
if ($existing_alias && $config->get('update_action') == PathautoGeneratorInterface::UPDATE_ACTION_DELETE) {
// Skip replacing the current alias with an identical alias.
if ($existing_alias->getAlias() == $alias) {
return NULL;
}
// Save the path array.
$this->aliasStorage->save($path['source'], $path['alias'], $path['language'], $path['pid']);
if (!empty($existing_alias['pid'])) {
$this->messenger->addMessage($this->t(
'Created new alias %alias for %source, replacing %old_alias.',
[
'%alias' => $path['alias'],
'%source' => $path['source'],
'%old_alias' => $existing_alias['alias'],
]
)
);
}
else {
$this->messenger->addMessage($this->t('Created new alias %alias for %source.', [
'%alias' => $path['alias'],
'%source' => $path['source'],
]));
}
$old_alias = $existing_alias->getAlias();
$existing_alias->setAlias($alias)->save();
$this->messenger->addMessage($this->t('Created new alias %alias for %source, replacing %old_alias.', [
'%alias' => $alias,
'%source' => $source,
'%old_alias' => $old_alias,
]));
return $path;
$return = $existing_alias;
}
else {
// Otherwise, create a new alias.
$path_alias = PathAlias::create([
'path' => $source,
'alias' => $alias,
'langcode' => $langcode,
]);
$path_alias->save();
$this->messenger->addMessage($this->t('Created new alias %alias for %source.', [
'%alias' => $path_alias->getAlias(),
'%source' => $path_alias->getPath(),
]));
$return = $path_alias;
}
return [
'source' => $return->getPath(),
'alias' => $return->getAlias(),
'pid' => $return->id(),
'langcode' => $return->language()->getId(),
];
}
/**
......@@ -179,7 +192,12 @@ class AliasStorageHelper implements AliasStorageHelperInterface {
* {@inheritdoc}
*/
public function deleteAll() {
$this->database->truncate('url_alias')->execute();
/** @var \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping */
$table_mapping = $this->entityTypeManager->getStorage('path_alias')->getTableMapping();
foreach ($table_mapping->getTableNames() as $table_name) {
$this->database->truncate($table_name)->execute();
}
$this->entityTypeManager->getStorage('path_alias')->resetCache();
}
/**
......@@ -196,60 +214,37 @@ class AliasStorageHelper implements AliasStorageHelperInterface {
* {@inheritdoc}
*/
public function loadBySourcePrefix($source) {
$select = $this->database->select('url_alias', 'u')
->fields('u', ['pid']);
$or_group = $select->orConditionGroup()
->condition('source', $source)
->condition('source', rtrim($source, '/') . '/%', 'LIKE');
return $select
->condition($or_group)
->execute()
->fetchCol();
return $this->entityTypeManager->getStorage('path_alias')->getQuery('OR')
->condition('path', $source, '=')
->condition('path', rtrim($source, '/') . '/', 'STARTS_WITH')
->execute();
}
/**
* {@inheritdoc}
*/
public function countBySourcePrefix($source) {
$select = $this->database->select('url_alias', 'u')
->fields('u', ['pid']);
$or_group = $select->orConditionGroup()
->condition('source', $source)
->condition('source', rtrim($source, '/') . '/%', 'LIKE');
return $select
->condition($or_group)
->countQuery()
->execute()
->fetchField();
return $this->entityTypeManager->getStorage('path_alias')->getQuery('OR')
->condition('path', $source, '=')
->condition('path', rtrim($source, '/') . '/', 'STARTS_WITH')
->count()
->execute();
}
/**
* {@inheritdoc}
*/
public function countAll() {
return $this->database->select('url_alias')
->countQuery()
->execute()
->fetchField();
return $this->entityTypeManager->getStorage('path_alias')->getQuery()
->count()
->execute();
}
/**
* Delete multiple URL aliases.
*
* Intent of this is to abstract a potential path_delete_multiple() function
* for Drupal 7 or 8.
*
* @param int[] $pids
* An array of path IDs to delete.
* {@inheritdoc}
*/
public function deleteMultiple($pids) {
foreach ($pids as $pid) {
$this->aliasStorage->delete(['pid' => $pid]);
}
$this->entityTypeManager->getStorage('path_alias')->delete($this->entityTypeManager->getStorage('path_alias')->loadMultiple($pids));
}
}
......@@ -11,7 +11,7 @@ use Drupal\Core\Language\LanguageInterface;
interface AliasStorageHelperInterface {
/**
* Fetch the maximum length of the {url_alias}.alias field from the schema.
* Fetch the maximum length of the {path_alias}.alias field from the schema.
*
* @return int
* An integer of the maximum URL alias length allowed by the database.
......@@ -67,7 +67,7 @@ interface AliasStorageHelperInterface {
public function deleteBySourcePrefix($source);
/**
* Delete all aliases (truncate the url_alias table).
* Delete all aliases (truncate the path alias entity tables).
*/
public function deleteAll();
......@@ -114,4 +114,15 @@ interface AliasStorageHelperInterface {
*/
public function countAll();
/**
* Delete multiple URL aliases.
*
* Intent of this is to abstract a potential path_delete_multiple() function
* for Drupal 7 or 8.
*
* @param int[] $pids
* An array of path IDs to delete.
*/
public function deleteMultiple($pids);
}
......@@ -41,7 +41,7 @@ class PathautoCommands extends DrushCommands {
/**
* The alias storage helper.
*
* @var \Drupal\pathauto\AliasStorageHelper
* @var \Drupal\pathauto\AliasStorageHelperInterface
*/
protected $aliasStorageHelper;
......
......@@ -106,7 +106,7 @@ class PathautoAdminDelete extends FormBase {
];
// Warn them and give a button that shows we mean business.
$form['warning'] = ['#markup' => '<p>' . $this->t('<strong>Note:</strong> there is no confirmation. Be sure of your action before clicking the "Delete aliases now!" button.<br />You may want to make a backup of the database and/or the url_alias table prior to using this feature.') . '</p>'];
$form['warning'] = ['#markup' => '<p>' . $this->t('<strong>Note:</strong> there is no confirmation. Be sure of your action before clicking the "Delete aliases now!" button.<br />You may want to make a backup of the database and/or the path_alias and path_alias_revision tables prior to using this feature.') . '</p>'];
$form['buttons']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Delete aliases now!'),
......
<?php
namespace Drupal\pathauto;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Path\AliasStorageInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Provides helper methods for accessing alias storage.
*/
class LegacyAliasStorageHelper implements AliasStorageHelperInterface {
use StringTranslationTrait;
/**
* Alias schema max length.
*
* @var int
*/
protected $aliasSchemaMaxLength = 255;
/**
* Config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The alias storage.
*
* @var \Drupal\Core\Path\AliasStorageInterface
*/
protected $aliasStorage;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The messenger.
*
* @var \Drupal\pathauto\MessengerInterface
*/
protected $messenger;
/**
* The config factory.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Path\AliasStorageInterface $alias_storage
* The alias storage.
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param MessengerInterface $messenger
* The messenger.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(ConfigFactoryInterface $config_factory, AliasStorageInterface $alias_storage, Connection $database, MessengerInterface $messenger, TranslationInterface $string_translation) {
$this->configFactory = $config_factory;
$this->aliasStorage = $alias_storage;
$this->database = $database;
$this->messenger = $messenger;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public function getAliasSchemaMaxLength() {
return $this->aliasSchemaMaxLength;
}
/**
* {@inheritdoc}
*/
public function save(array $path, $existing_alias = NULL, $op = NULL) {
$config = $this->configFactory->get('pathauto.settings');
// Alert users if they are trying to create an alias that is the same as the
// internal path.
if ($path['source'] == $path['alias']) {
$this->messenger->addMessage($this->t('Ignoring alias %alias because it is the same as the internal path.', ['%alias' => $path['alias']]));
return NULL;
}
// Skip replacing the current alias with an identical alias.
if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) {
$path += [
'pathauto' => TRUE,
'original' => $existing_alias,
'pid' => NULL,
];
// If there is already an alias, respect some update actions.
if (!empty($existing_alias)) {
switch ($config->get('update_action')) {
case PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW:
// Do not create the alias.
return NULL;
case PathautoGeneratorInterface::UPDATE_ACTION_LEAVE:
// Create a new alias instead of overwriting the existing by leaving
// $path['pid'] empty.
break;
case PathautoGeneratorInterface::UPDATE_ACTION_DELETE:
// The delete actions should overwrite the existing alias.
$path['pid'] = $existing_alias['pid'];
break;
}
}
// Save the path array.
$this->aliasStorage->save($path['source'], $path['alias'], $path['language'], $path['pid']);
if (!empty($existing_alias['pid'])) {
$this->messenger->addMessage($this->t(
'Created new alias %alias for %source, replacing %old_alias.',
[
'%alias' => $path['alias'],
'%source' => $path['source'],
'%old_alias' => $existing_alias['alias'],
]
)
);
}
else {
$this->messenger->addMessage($this->t('Created new alias %alias for %source.', [
'%alias' => $path['alias'],
'%source' => $path['source'],
]));
}
return $path;
}
}
/**
* {@inheritdoc}
*/
public function loadBySource($source, $language = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
$alias = $this->aliasStorage->load([
'source' => $source,
'langcode' => $language,
]);
// If no alias was fetched and if a language was specified, fallbacks to
// undefined language.
if (!$alias && ($language !== LanguageInterface::LANGCODE_NOT_SPECIFIED)) {
$alias = $this->aliasStorage->load([
'source' => $source,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
}
return $alias;
}
/**
* {@inheritdoc}
*/
public function deleteBySourcePrefix($source) {
$pids = $this->loadBySourcePrefix($source);
if ($pids) {
$this->deleteMultiple($pids);
}
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
$this->database->truncate('url_alias')->execute();
}
/**
* {@inheritdoc}
*/
public function deleteEntityPathAll(EntityInterface $entity, $default_uri = NULL) {
$this->deleteBySourcePrefix('/' . $entity->toUrl('canonical')->getInternalPath());
if (isset($default_uri) && $entity->toUrl('canonical')->toString() != $default_uri) {
$this->deleteBySourcePrefix($default_uri);
}
}
/**
* {@inheritdoc}
*/
public function loadBySourcePrefix($source) {
$select = $this->database->select('url_alias', 'u')
->fields('u', ['pid']);
$or_group = $select->orConditionGroup()
->condition('source', $source)
->condition('source', rtrim($source, '/') . '/%', 'LIKE');
return $select
->condition($or_group)
->execute()
->fetchCol();
}
/**
* {@inheritdoc}
*/
public function countBySourcePrefix($source) {
$select = $this->database->select('url_alias', 'u')
->fields('u', ['pid']);
$or_group = $select->orConditionGroup()
->condition('source', $source)
->condition('source', rtrim($source, '/') . '/%', 'LIKE');
return $select
->condition($or_group)
->countQuery()
->execute()
->fetchField();
}
/**
* {@inheritdoc}
*/
public function countAll() {
return $this->database->select('url_alias')
->countQuery()
->execute()
->fetchField();
}
/**
* {@inheritdoc}
*/
public function deleteMultiple($pids) {
foreach ($pids as $pid) {
$this->aliasStorage->delete(['pid' => $pid]);
}