class AliasManager

Hierarchy

Expanded class hierarchy of AliasManager

1 file declares its use of AliasManager
AliasTest.php in drupal/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php
Definition of Drupal\system\Tests\Path\CrudTest.

File

drupal/core/lib/Drupal/Core/Path/AliasManager.php, line 13
Contains Drupal\Core\Path\AliasManager.

Namespace

Drupal\Core\Path
View source
class AliasManager implements AliasManagerInterface {

  /**
   * The database connectino to use for path lookups.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * The Key/Value Store to use for state
   *
   * @var \Drupal\Core\KeyValueStore\DatabaseStorage
   */
  protected $state;

  /**
   * The default langcode to use when none is specified for path lookups.
   *
   * @var string
   */
  protected $langcode;

  /**
   * Holds the map of path lookups per language.
   *
   * @var array
   */
  protected $lookupMap = array();

  /**
   * Holds an array of path alias for which no source was found.
   *
   * @var array
   */
  protected $noSource = array();

  /**
   * Holds the array of whitelisted path aliases.
   *
   * @var array
   */
  protected $whitelist;

  /**
   * Holds an array of system paths that have no aliases.
   *
   * @var array
   */
  protected $noAliases = array();

  /**
   * Whether lookupPath() has not yet been called.
   *
   * @var boolean
   */
  protected $firstLookup = TRUE;

  /**
   * Holds an array of previously looked up paths for the current request path.
   *
   * This will only ever get populated if the alias manager is being used in
   * the context of a request.
   *
   * @var array
   */
  protected $preloadedPathLookups = array();
  public function __construct(Connection $connection, KeyValueFactory $keyvalue) {
    $this->connection = $connection;
    $this->state = $keyvalue
      ->get('state');
    $this->langcode = language(LANGUAGE_TYPE_URL)->langcode;
    $this->whitelist = $this->state
      ->get('system.path_alias_whitelist', NULL);
    if (!isset($this->whitelist)) {
      $this->whitelist = $this
        ->pathAliasWhitelistRebuild();
    }
  }

  /**
   * Implements \Drupal\Core\Path\AliasManagerInterface::getSystemPath().
   */
  public function getSystemPath($path, $path_language = NULL) {

    // If no language is explicitly specified we default to the current URL
    // language. If we used a language different from the one conveyed by the
    // requested URL, we might end up being unable to check if there is a path
    // alias matching the URL path.
    $path_language = $path_language ?: $this->langcode;
    $original_path = $path;

    // Lookup the path alias first.
    if (!empty($path) && ($source = $this
      ->lookupPathSource($path, $path_language))) {
      $path = $source;
    }
    return $path;
  }

  /**
   * Implements \Drupal\Core\Path\AliasManagerInterface::getPathAlias().
   */
  public function getPathAlias($path, $path_language = NULL) {

    // If no language is explicitly specified we default to the current URL
    // language. If we used a language different from the one conveyed by the
    // requested URL, we might end up being unable to check if there is a path
    // alias matching the URL path.
    $path_language = $path_language ?: $this->langcode;
    $result = $path;
    if (!empty($path) && ($alias = $this
      ->lookupPathAlias($path, $path_language))) {
      $result = $alias;
    }
    return $result;
  }

  /**
   * Implements \Drupal\Core\Path\AliasManagerInterface::cacheClear().
   */
  public function cacheClear($source = NULL) {
    $this->lookupMap = array();
    $this->noSource = array();
    $this->no_aliases = array();
    $this->firstCall = TRUE;
    $this->preloadedPathLookups = array();
    $this->whitelist = $this
      ->pathAliasWhitelistRebuild($source);
  }

  /**
   * Implements \Drupal\Core\Path\AliasManagerInterface::getPathLookups().
   */
  public function getPathLookups() {
    $current = current($this->lookupMap);
    if ($current) {
      return array_keys($current);
    }
    return array();
  }

  /**
   * Implements \Drupal\Core\Path\AliasManagerInterface::preloadPathLookups().
   */
  public function preloadPathLookups(array $path_list) {
    $this->preloadedPathLookups = $path_list;
  }

  /**
   * Given a Drupal system URL return one of its aliases if such a one exists.
   * Otherwise, return FALSE.
   * @param $path
   *   The path to investigate for corresponding aliases.
   * @param $langcode
   *   Optional language code to search the path with. Defaults to the page language.
   *   If there's no path defined for that language it will search paths without
   *   language.
   *
   * @return
   *   An aliased path, or FALSE if no path was found.
   */
  protected function lookupPathAlias($path, $langcode) {

    // During the first call to this method per language, load the expected
    // system paths for the page from cache.
    if (!empty($this->firstLookup)) {
      $this->firstLookup = FALSE;
      $this->lookupMap[$langcode] = array();

      // Load system paths from cache.
      if (!empty($this->preloadedPathLookups)) {

        // Now fetch the aliases corresponding to these system paths.
        $args = array(
          ':system' => $this->preloadedPathLookups,
          ':langcode' => $langcode,
          ':langcode_undetermined' => LANGUAGE_NOT_SPECIFIED,
        );

        // 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.
        // For performance reasons, the query builder is not used here.
        if ($langcode == LANGUAGE_NOT_SPECIFIED) {

          // Prevent PDO from complaining about a token the query doesn't use.
          unset($args[':langcode']);
          $result = $this->connection
            ->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode = :langcode_undetermined ORDER BY pid ASC', $args);
        }
        elseif ($langcode < LANGUAGE_NOT_SPECIFIED) {
          $result = $this->connection
            ->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid ASC', $args);
        }
        else {
          $result = $this->connection
            ->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid ASC', $args);
        }
        $this->lookupMap[$langcode] = $result
          ->fetchAllKeyed();

        // Keep a record of paths with no alias to avoid querying twice.
        $this->noAliases[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups, array_keys($this->lookupMap[$langcode])));
      }
    }

    // If the alias has already been loaded, return it.
    if (isset($this->lookupMap[$langcode][$path])) {
      return $this->lookupMap[$langcode][$path];
    }
    elseif (!isset($this->whitelist[strtok($path, '/')])) {
      return FALSE;
    }
    elseif (!isset($this->noAliases[$langcode][$path])) {
      $args = array(
        ':source' => $path,
        ':langcode' => $langcode,
        ':langcode_undetermined' => LANGUAGE_NOT_SPECIFIED,
      );

      // See the queries above.
      if ($langcode == LANGUAGE_NOT_SPECIFIED) {
        unset($args[':langcode']);
        $alias = $this->connection
          ->query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode = :langcode_undetermined ORDER BY pid DESC", $args)
          ->fetchField();
      }
      elseif ($langcode > LANGUAGE_NOT_SPECIFIED) {
        $alias = $this->connection
          ->query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args)
          ->fetchField();
      }
      else {
        $alias = $this->connection
          ->query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args)
          ->fetchField();
      }
      $this->lookupMap[$langcode][$path] = $alias;
      return $alias;
    }
    return FALSE;
  }

  /**
   * Given an alias, return its Drupal system URL if one exists. Otherwise,
   * return FALSE.
   *
   * @param $path
   *   The path to investigate for corresponding system URLs.
   * @param $langcode
   *   Optional language code to search the path with. Defaults to the page language.
   *   If there's no path defined for that language it will search paths without
   *   language.
   *
   * @return
   *   A Drupal system path, or FALSE if no path was found.
   */
  protected function lookupPathSource($path, $langcode) {
    if ($this->whitelist && !isset($this->noSource[$langcode][$path])) {

      // Look for the value $path within the cached $map
      $source = FALSE;
      if (!isset($this->lookupMap[$langcode]) || !($source = array_search($path, $this->lookupMap[$langcode]))) {
        $args = array(
          ':alias' => $path,
          ':langcode' => $langcode,
          ':langcode_undetermined' => LANGUAGE_NOT_SPECIFIED,
        );

        // See the queries above.
        if ($langcode == LANGUAGE_NOT_SPECIFIED) {
          unset($args[':langcode']);
          $result = $this->connection
            ->query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode = :langcode_undetermined ORDER BY pid DESC", $args);
        }
        elseif ($langcode > LANGUAGE_NOT_SPECIFIED) {
          $result = $this->connection
            ->query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args);
        }
        else {
          $result = $this->connection
            ->query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args);
        }
        if ($source = $result
          ->fetchField()) {
          $this->lookupMap[$langcode][$source] = $path;
        }
        else {

          // We can't record anything into $map because we do not have a valid
          // index and there is no need because we have not learned anything
          // about any Drupal path. Thus cache to $no_source.
          $this->noSource[$langcode][$path] = TRUE;
        }
      }
      return $source;
    }
    return FALSE;
  }

  /**
   * Rebuild the path alias white list.
   *
   * @param $source
   *   An optional system path for which an alias is being inserted.
   *
   * @return
   *   An array containing a white list of path aliases.
   */
  protected function pathAliasWhitelistRebuild($source = NULL) {

    // When paths are inserted, only rebuild the whitelist if the system path
    // has a top level component which is not already in the whitelist.
    if (!empty($source)) {

      // @todo Inject state so we don't have this function call.
      $whitelist = $this->state
        ->get('system.path_alias_whitelist', NULL);
      if (isset($whitelist[strtok($source, '/')])) {
        return $whitelist;
      }
    }

    // For each alias in the database, get the top level component of the system
    // path it corresponds to. This is the portion of the path before the first
    // '/', if present, otherwise the whole path itself.
    $whitelist = array();
    $result = $this->connection
      ->query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}");
    foreach ($result as $row) {
      $whitelist[$row->path] = TRUE;
    }
    $this->state
      ->set('system.path_alias_whitelist', $whitelist);
    return $whitelist;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AliasManager::$connection protected property The database connectino to use for path lookups.
AliasManager::$firstLookup protected property Whether lookupPath() has not yet been called.
AliasManager::$langcode protected property The default langcode to use when none is specified for path lookups.
AliasManager::$lookupMap protected property Holds the map of path lookups per language.
AliasManager::$noAliases protected property Holds an array of system paths that have no aliases.
AliasManager::$noSource protected property Holds an array of path alias for which no source was found.
AliasManager::$preloadedPathLookups protected property Holds an array of previously looked up paths for the current request path.
AliasManager::$state protected property The Key/Value Store to use for state
AliasManager::$whitelist protected property Holds the array of whitelisted path aliases.
AliasManager::cacheClear public function Implements \Drupal\Core\Path\AliasManagerInterface::cacheClear().
AliasManager::getPathAlias public function Implements \Drupal\Core\Path\AliasManagerInterface::getPathAlias(). Overrides AliasManagerInterface::getPathAlias
AliasManager::getPathLookups public function Implements \Drupal\Core\Path\AliasManagerInterface::getPathLookups(). Overrides AliasManagerInterface::getPathLookups
AliasManager::getSystemPath public function Implements \Drupal\Core\Path\AliasManagerInterface::getSystemPath(). Overrides AliasManagerInterface::getSystemPath
AliasManager::lookupPathAlias protected function Given a Drupal system URL return one of its aliases if such a one exists. Otherwise, return FALSE.
AliasManager::lookupPathSource protected function Given an alias, return its Drupal system URL if one exists. Otherwise, return FALSE.
AliasManager::pathAliasWhitelistRebuild protected function Rebuild the path alias white list.
AliasManager::preloadPathLookups public function Implements \Drupal\Core\Path\AliasManagerInterface::preloadPathLookups(). Overrides AliasManagerInterface::preloadPathLookups
AliasManager::__construct public function