You are here

AliasStorage.php in Zircon Profile 8

Same filename and directory in other branches
  1. 8.0 core/lib/Drupal/Core/Path/AliasStorage.php

Namespace

Drupal\Core\Path

File

core/lib/Drupal/Core/Path/AliasStorage.php
View source
<?php

/**
 * @file
 * Contains \Drupal\Core\Path\AliasStorage.
 */
namespace Drupal\Core\Path;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Connection;
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.
 *
 * 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.
 */
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();
  }

}

Classes

Namesort descending Description
AliasStorage Provides a class for CRUD operations on path aliases.