AliasStorage.php

Namespace

Drupal\Core\Path

File

core/lib/Drupal/Core/Path/AliasStorage.php

View source
<?php

namespace Drupal\Core\Path;

use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageInterface;
@trigger_error('\\Drupal\\Core\\Path\\AliasStorage is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the "path_alias.repository" service instead, or the entity storage handler for the "path_alias" entity type for CRUD methods. See https://www.drupal.org/node/3013865.', E_USER_DEPRECATED);

/**
 * 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.
 *
 * @deprecated \Drupal\Core\Path\AliasStorage is deprecated in drupal:8.8.0 and
 *   is removed from drupal:9.0.0. Use the "path_alias.repository" service
 *   instead, or the entity storage handler for the "path_alias" entity type
 *   for CRUD methods.
 *
 * @see https://www.drupal.org/node/3013865
 */
class AliasStorage implements AliasStorageInterface {
    
    /**
     * The table for the url_alias storage.
     */
    const TABLE = 'path_alias';
    
    /**
     * The database connection.
     *
     * @var \Drupal\Core\Database\Connection
     */
    protected $connection;
    
    /**
     * The module handler.
     *
     * @var \Drupal\Core\Extension\ModuleHandlerInterface
     */
    protected $moduleHandler;
    
    /**
     * The entity type manager.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     */
    protected $entityTypeManager;
    
    /**
     * 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, EntityTypeManagerInterface $entity_type_manager = NULL) {
        $this->connection = $connection;
        $this->moduleHandler = $module_handler;
        $this->entityTypeManager = $entity_type_manager ?: \Drupal::entityTypeManager();
    }
    
    /**
     * {@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));
        }
        if ($pid) {
            
            /** @var \Drupal\path_alias\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 {
            $path_alias = $this->getPathAliasEntityStorage()
                ->create([
                'path' => $source,
                'alias' => $alias,
                'langcode' => $langcode,
            ]);
        }
        $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 $path_alias_values;
    }
    
    /**
     * {@inheritdoc}
     */
    public function load($conditions) {
        $query = $this->getPathAliasEntityStorage()
            ->getQuery();
        // Ignore access restrictions for this API.
        $query->accessCheck(FALSE);
        foreach ($conditions as $field => $value) {
            if ($field === 'source') {
                $field = 'path';
            }
            elseif ($field === 'pid') {
                $field = 'id';
            }
            $query->condition($field, $value, '=');
        }
        $result = $query->sort('id', 'DESC')
            ->range(0, 1)
            ->execute();
        $entities = $this->getPathAliasEntityStorage()
            ->loadMultiple($result);
        
        /** @var \Drupal\path_alias\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) {
        $storage = $this->getPathAliasEntityStorage();
        $query = $storage->getQuery();
        // API functions should be able to access all entities regardless of access
        // restrictions. Those need to happen on a higher level.
        $query->accessCheck(FALSE);
        foreach ($conditions as $field => $value) {
            if ($field === 'source') {
                $field = 'path';
            }
            elseif ($field === 'pid') {
                $field = 'id';
            }
            $query->condition($field, $value, '=');
        }
        $result = $query->execute();
        $storage->delete($storage->loadMultiple($result));
    }
    
    /**
     * Returns a SELECT query for the path_alias base table.
     *
     * @return \Drupal\Core\Database\Query\SelectInterface
     *   A Select query object.
     */
    protected function getBaseQuery() {
        $query = $this->connection
            ->select(static::TABLE, 'base_table');
        $query->condition('base_table.status', 1);
        return $query;
    }
    
    /**
     * {@inheritdoc}
     */
    public function preloadPathAlias($preloaded, $langcode) {
        $select = $this->getBaseQuery()
            ->fields('base_table', [
            'path',
            'alias',
        ]);
        if (!empty($preloaded)) {
            $conditions = new Condition('OR');
            foreach ($preloaded as $preloaded_item) {
                $conditions->condition('base_table.path', $this->connection
                    ->escapeLike($preloaded_item), 'LIKE');
            }
            $select->condition($conditions);
        }
        $this->addLanguageFallback($select, $langcode);
        // We 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.
        $select->orderBy('base_table.id', 'ASC');
        return $select->execute()
            ->fetchAllKeyed();
    }
    
    /**
     * {@inheritdoc}
     */
    public function lookupPathAlias($path, $langcode) {
        // See the queries above. Use LIKE for case-insensitive matching.
        $select = $this->getBaseQuery()
            ->fields('base_table', [
            'alias',
        ])
            ->condition('base_table.path', $this->connection
            ->escapeLike($path), 'LIKE');
        $this->addLanguageFallback($select, $langcode);
        $select->orderBy('base_table.id', 'DESC');
        return $select->execute()
            ->fetchField();
    }
    
    /**
     * {@inheritdoc}
     */
    public function lookupPathSource($alias, $langcode) {
        // See the queries above. Use LIKE for case-insensitive matching.
        $select = $this->getBaseQuery()
            ->fields('base_table', [
            'path',
        ])
            ->condition('base_table.alias', $this->connection
            ->escapeLike($alias), 'LIKE');
        $this->addLanguageFallback($select, $langcode);
        $select->orderBy('base_table.id', 'DESC');
        return $select->execute()
            ->fetchField();
    }
    
    /**
     * Adds path alias language fallback conditions to a select query object.
     *
     * @param \Drupal\Core\Database\Query\SelectInterface $query
     *   A Select query object.
     * @param string $langcode
     *   Language code to search the path with. If there's no path defined for
     *   that language it will search paths without language.
     */
    protected function addLanguageFallback(SelectInterface $query, $langcode) {
        // 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.
        $langcode_list = [
            $langcode,
            LanguageInterface::LANGCODE_NOT_SPECIFIED,
        ];
        if ($langcode === LanguageInterface::LANGCODE_NOT_SPECIFIED) {
            array_pop($langcode_list);
        }
        elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
            $query->orderBy('base_table.langcode', 'DESC');
        }
        else {
            $query->orderBy('base_table.langcode', 'ASC');
        }
        $query->condition('base_table.langcode', $langcode_list, 'IN');
    }
    
    /**
     * {@inheritdoc}
     */
    public function aliasExists($alias, $langcode, $source = NULL) {
        // Use LIKE and NOT LIKE for case-insensitive matching.
        $query = $this->connection
            ->select(static::TABLE)
            ->condition('alias', $this->connection
            ->escapeLike($alias), 'LIKE')
            ->condition('langcode', $langcode);
        if (!empty($source)) {
            $query->condition('path', $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 {' . static::TABLE . '} WHERE langcode <> :langcode', 0, 1, [
            ':langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
        ])
            ->fetchField();
    }
    
    /**
     * {@inheritdoc}
     */
    public function getAliasesForAdminListing($header, $keys = NULL) {
        $query = $this->connection
            ->select(static::TABLE)
            ->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');
        }
        $query->addField(static::TABLE, 'id', 'pid');
        $query->addField(static::TABLE, 'path', 'source');
        return $query->fields(static::TABLE, [
            'alias',
            'langcode',
        ])
            ->orderByHeader($header)
            ->limit(50)
            ->execute()
            ->fetchAll();
    }
    
    /**
     * {@inheritdoc}
     */
    public function pathHasMatchingAlias($initial_substring) {
        if (!$this->moduleHandler
            ->moduleExists('path_alias')) {
            return FALSE;
        }
        $query = $this->getBaseQuery();
        $query->addExpression(1);
        return (bool) $query->condition('base_table.path', $this->connection
            ->escapeLike($initial_substring) . '%', 'LIKE')
            ->range(0, 1)
            ->execute()
            ->fetchField();
    }
    
    /**
     * Returns the path alias entity storage handler.
     *
     * We can not store it in the constructor because that leads to a circular
     * dependency in the service container.
     *
     * @return \Drupal\Core\Entity\EntityStorageInterface
     *   The path alias entity storage.
     */
    protected function getPathAliasEntityStorage() {
        return $this->entityTypeManager
            ->getStorage('path_alias');
    }

}

Classes

Title Deprecated Summary
AliasStorage

\Drupal\Core\Path\AliasStorage is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the "path_alias.repository" service instead, or the entity storage handler for the "path_alias" entity type for CRUD methods.

Provides a class for CRUD operations on path aliases.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.