Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 161 |
AliasStorage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 11 |
992 | |
0.00% |
0 / 161 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
save | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 35 |
|||
load | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 16 |
|||
delete | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 15 |
|||
preloadPathAlias | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 23 |
|||
lookupPathAlias | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 18 |
|||
lookupPathSource | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 18 |
|||
aliasExists | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 10 |
|||
languageAliasExists | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getAliasesForAdminListing | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 13 |
|||
pathHasMatchingAlias | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 8 |
<?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(); | |
} | |
} |