You are here

class AliasStorage in Zircon Profile 8.0

Same name and namespace in other branches
  1. 8 core/lib/Drupal/Core/Path/AliasStorage.php \Drupal\Core\Path\AliasStorage

Provides a class for CRUD operations on path aliases.

All queries perform case-insensitive matching on the 'source' and 'alias' fields, so the aliases '/test-alias' and '/test-Alias' are considered to be the same, and will both refer to the same internal system path.

Hierarchy

Expanded class hierarchy of AliasStorage

2 files declare their use of AliasStorage
AliasTest.php in core/modules/system/src/Tests/Path/AliasTest.php
Contains \Drupal\system\Tests\Path\AliasTest.
UrlAlias.php in core/modules/path/src/Plugin/migrate/destination/UrlAlias.php
Contains \Drupal\path\Plugin\migrate\destination\UrlAlias.
1 string reference to 'AliasStorage'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses AliasStorage
path.alias_storage in core/core.services.yml
Drupal\Core\Path\AliasStorage

File

core/lib/Drupal/Core/Path/AliasStorage.php, line 23
Contains \Drupal\Core\Path\AliasStorage.

Namespace

Drupal\Core\Path
View source
class AliasStorage implements AliasStorageInterface {

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * 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.
   */
  public function __construct(Connection $connection, ModuleHandlerInterface $module_handler) {
    $this->connection = $connection;
    $this->moduleHandler = $module_handler;
  }

  /**
   * {@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));
    }
    if ($alias[0] !== '/') {
      throw new \InvalidArgumentException(sprintf('Alias path %s has to start with a slash.', $alias));
    }
    $fields = array(
      'source' => $source,
      'alias' => $alias,
      'langcode' => $langcode,
    );

    // Insert or update the alias.
    if (empty($pid)) {
      $query = $this->connection
        ->insert('url_alias')
        ->fields($fields);
      $pid = $query
        ->execute();
      $fields['pid'] = $pid;
      $operation = 'insert';
    }
    else {

      // Fetch the current values so that an update hook can identify what
      // exactly changed.
      $original = $this->connection
        ->query('SELECT source, alias, langcode FROM {url_alias} WHERE pid = :pid', array(
        ':pid' => $pid,
      ))
        ->fetchAssoc();
      $fields['pid'] = $pid;
      $query = $this->connection
        ->update('url_alias')
        ->fields($fields)
        ->condition('pid', $pid);
      $pid = $query
        ->execute();
      $fields['original'] = $original;
      $operation = 'update';
    }
    if ($pid) {

      // @todo Switch to using an event for this instead of a hook.
      $this->moduleHandler
        ->invokeAll('path_' . $operation, array(
        $fields,
      ));
      Cache::invalidateTags([
        'route_match',
      ]);
      return $fields;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function load($conditions) {
    $select = $this->connection
      ->select('url_alias');
    foreach ($conditions as $field => $value) {
      if ($field == 'source' || $field == 'alias') {

        // Use LIKE for case-insensitive matching.
        $select
          ->condition($field, $this->connection
          ->escapeLike($value), 'LIKE');
      }
      else {
        $select
          ->condition($field, $value);
      }
    }
    return $select
      ->fields('url_alias')
      ->orderBy('pid', 'DESC')
      ->range(0, 1)
      ->execute()
      ->fetchAssoc();
  }

  /**
   * {@inheritdoc}
   */
  public function delete($conditions) {
    $path = $this
      ->load($conditions);
    $query = $this->connection
      ->delete('url_alias');
    foreach ($conditions as $field => $value) {
      if ($field == 'source' || $field == 'alias') {

        // Use LIKE for case-insensitive matching.
        $query
          ->condition($field, $this->connection
          ->escapeLike($value), 'LIKE');
      }
      else {
        $query
          ->condition($field, $value);
      }
    }
    $deleted = $query
      ->execute();

    // @todo Switch to using an event for this instead of a hook.
    $this->moduleHandler
      ->invokeAll('path_delete', array(
      $path,
    ));
    Cache::invalidateTags([
      'route_match',
    ]);
    return $deleted;
  }

  /**
   * {@inheritdoc}
   */
  public function preloadPathAlias($preloaded, $langcode) {
    $langcode_list = [
      $langcode,
      LanguageInterface::LANGCODE_NOT_SPECIFIED,
    ];
    $select = $this->connection
      ->select('url_alias')
      ->fields('url_alias', [
      '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');
    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('url_alias')
      ->fields('url_alias', [
      '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');
    return $select
      ->execute()
      ->fetchField();
  }

  /**
   * {@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('url_alias')
      ->fields('url_alias', [
      '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');
    return $select
      ->execute()
      ->fetchField();
  }

  /**
   * {@inheritdoc}
   */
  public function aliasExists($alias, $langcode, $source = NULL) {

    // Use LIKE and NOT LIKE for case-insensitive matching.
    $query = $this->connection
      ->select('url_alias')
      ->condition('alias', $this->connection
      ->escapeLike($alias), 'LIKE')
      ->condition('langcode', $langcode);
    if (!empty($source)) {
      $query
        ->condition('source', $this->connection
        ->escapeLike($source), 'NOT LIKE');
    }
    $query
      ->addExpression('1');
    $query
      ->range(0, 1);
    return (bool) $query
      ->execute()
      ->fetchField();
  }

  /**
   * {@inheritdoc}
   */
  public function languageAliasExists() {
    return (bool) $this->connection
      ->queryRange('SELECT 1 FROM {url_alias} WHERE langcode <> :langcode', 0, 1, array(
      ':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
    ))
      ->fetchField();
  }

  /**
   * {@inheritdoc}
   */
  public function getAliasesForAdminListing($header, $keys = NULL) {
    $query = $this->connection
      ->select('url_alias')
      ->extend('Drupal\\Core\\Database\\Query\\PagerSelectExtender')
      ->extend('Drupal\\Core\\Database\\Query\\TableSortExtender');
    if ($keys) {

      // Replace wildcards with PDO wildcards.
      $query
        ->condition('alias', '%' . preg_replace('!\\*+!', '%', $keys) . '%', 'LIKE');
    }
    return $query
      ->fields('url_alias')
      ->orderByHeader($header)
      ->limit(50)
      ->execute()
      ->fetchAll();
  }

  /**
   * {@inheritdoc}
   */
  public function pathHasMatchingAlias($initial_substring) {
    $query = $this->connection
      ->select('url_alias', 'u');
    $query
      ->addExpression(1);
    return (bool) $query
      ->condition('u.source', $this->connection
      ->escapeLike($initial_substring) . '%', 'LIKE')
      ->range(0, 1)
      ->execute()
      ->fetchField();
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AliasStorage::$connection protected property The database connection.
AliasStorage::$moduleHandler protected property The module handler.
AliasStorage::aliasExists public function Checks if alias already exists. Overrides AliasStorageInterface::aliasExists
AliasStorage::delete public function Deletes a URL alias. Overrides AliasStorageInterface::delete
AliasStorage::getAliasesForAdminListing public function Loads aliases for admin listing. Overrides AliasStorageInterface::getAliasesForAdminListing
AliasStorage::languageAliasExists public function Checks if there are any aliases with language defined. Overrides AliasStorageInterface::languageAliasExists
AliasStorage::load public function Fetches a specific URL alias from the database. Overrides AliasStorageInterface::load
AliasStorage::lookupPathAlias public function Returns an alias of Drupal system URL. Overrides AliasStorageInterface::lookupPathAlias
AliasStorage::lookupPathSource public function Returns Drupal system URL of an alias. Overrides AliasStorageInterface::lookupPathSource
AliasStorage::pathHasMatchingAlias public function Check if any alias exists starting with $initial_substring. Overrides AliasStorageInterface::pathHasMatchingAlias
AliasStorage::preloadPathAlias public function Pre-loads path alias information for a given list of source paths. Overrides AliasStorageInterface::preloadPathAlias
AliasStorage::save public function Saves a path alias to the database. Overrides AliasStorageInterface::save
AliasStorage::__construct public function Constructs a Path CRUD object.