Issue #1269742 by katbailey, beejeebus, pwolanin, g.oechsler, ksenzee: Make path lookup code into an pluggable class.

8.0.x
Dries 2012-11-21 10:29:04 -05:00
parent daf490d3a4
commit e3d863834c
40 changed files with 1147 additions and 681 deletions

View File

@ -2490,6 +2490,10 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
->register('keyvalue.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseFactory')
->addArgument(new Reference('database'));
$container->register('path.alias_manager', 'Drupal\Core\Path\AliasManager')
->addArgument(new Reference('database'))
->addArgument(new Reference('keyvalue.database'));
// Register the EntityManager.
$container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager');
}

View File

@ -2208,7 +2208,7 @@ function url($path = NULL, array $options = array()) {
}
elseif (!empty($path) && !$options['alias']) {
$langcode = isset($options['language']) && isset($options['language']->langcode) ? $options['language']->langcode : '';
$alias = drupal_get_path_alias($original_path, $langcode);
$alias = drupal_container()->get('path.alias_manager')->getPathAlias($original_path, $langcode);
if ($alias != $original_path) {
$path = $alias;
}
@ -4922,9 +4922,6 @@ function _drupal_bootstrap_full($skip = FALSE) {
// current_path().
drupal_language_initialize();
// Initialize current_path() prior to invoking hook_init().
drupal_path_initialize();
// Let all modules take action before the menu system handles the request.
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {

View File

@ -2994,7 +2994,7 @@ function _menu_delete_item($item, $force = FALSE) {
* @param $item
* An associative array representing a menu link item, with elements:
* - link_path: (required) The path of the menu item, which should be
* normalized first by calling drupal_get_normal_path() on it.
* normalized first by calling drupal_container()->get('path.alias_manager')->getSystemPath() on it.
* - link_title: (required) Title to appear in menu for the link.
* - menu_name: (optional) The machine name of the menu for the link.
* Defaults to 'tools'.

View File

@ -2,296 +2,13 @@
/**
* @file
* Functions to handle paths in Drupal, including path aliasing.
* Functions to handle paths in Drupal.
*
* These functions are not loaded for cached pages, but modules that need
* to use them in hook_boot() or hook exit() can make them available, by
* executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);".
*/
/**
* Initializes the current path to the proper normal path.
*/
function drupal_path_initialize() {
// At this point, the current path is either the request path (due to
// drupal_environment_initialize()) or some modified version of it due to
// other bootstrap code (e.g., language negotiation), but it has not yet been
// normalized by drupal_get_normal_path().
$path = _current_path();
// If on the front page, resolve to the front page path, including for calls
// to current_path() while drupal_get_normal_path() is in progress.
if (empty($path)) {
$path = config('system.site')->get('page.front');
_current_path($path);
}
// Normalize the path.
_current_path(drupal_get_normal_path($path));
}
/**
* Given an alias, return its Drupal system URL if one exists. Given a Drupal
* system URL return one of its aliases if such a one exists. Otherwise,
* return FALSE.
*
* @param $action
* One of the following values:
* - wipe: delete the alias cache.
* - alias: return an alias for a given Drupal system path (if one exists).
* - source: return the Drupal system URL for a path alias (if one exists).
* @param $path
* The path to investigate for corresponding aliases or 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
* Either a Drupal system path, an aliased path, or FALSE if no path was
* found.
*/
function drupal_lookup_path($action, $path = '', $langcode = NULL) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
}
$cache = &$drupal_static_fast['cache'];
if (!isset($cache)) {
$cache = array(
'map' => array(),
'no_source' => array(),
'whitelist' => NULL,
'system_paths' => array(),
'no_aliases' => array(),
'first_call' => TRUE,
);
}
// Retrieve the path alias whitelist.
if (!isset($cache['whitelist'])) {
$cache['whitelist'] = state()->get('system.path_alias_whitelist', NULL);
if (!isset($cache['whitelist'])) {
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
}
}
// 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.
$langcode = $langcode ? $langcode : language(LANGUAGE_TYPE_URL)->langcode;
if ($action == 'wipe') {
$cache = array();
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
}
elseif ($cache['whitelist'] && $path != '') {
if ($action == 'alias') {
// During the first call to drupal_lookup_path() per language, load the
// expected system paths for the page from cache.
if (!empty($cache['first_call'])) {
$cache['first_call'] = FALSE;
$cache['map'][$langcode] = array();
// Load system paths from cache.
$cid = current_path();
if ($cached = cache('path')->get($cid)) {
$cache['system_paths'] = $cached->data;
// Now fetch the aliases corresponding to these system paths.
$args = array(
':system' => $cache['system_paths'],
':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 = db_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 = db_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 = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid ASC', $args);
}
$cache['map'][$langcode] = $result->fetchAllKeyed();
// Keep a record of paths with no alias to avoid querying twice.
$cache['no_aliases'][$langcode] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$langcode])));
}
}
// If the alias has already been loaded, return it.
if (isset($cache['map'][$langcode][$path])) {
return $cache['map'][$langcode][$path];
}
// Check the path whitelist, if the top_level part before the first /
// is not in the list, then there is no need to do anything further,
// it is not in the database.
elseif (!isset($cache['whitelist'][strtok($path, '/')])) {
return FALSE;
}
// For system paths which were not cached, query aliases individually.
elseif (!isset($cache['no_aliases'][$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 = db_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 = db_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 = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args)->fetchField();
}
$cache['map'][$langcode][$path] = $alias;
return $alias;
}
}
// Check $no_source for this $path in case we've already determined that there
// isn't a path that has this alias
elseif ($action == 'source' && !isset($cache['no_source'][$langcode][$path])) {
// Look for the value $path within the cached $map
$source = FALSE;
if (!isset($cache['map'][$langcode]) || !($source = array_search($path, $cache['map'][$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 = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode = :langcode_undetermined ORDER BY pid DESC", $args);
}
elseif ($langcode > LANGUAGE_NOT_SPECIFIED) {
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args);
}
else {
$result = db_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()) {
$cache['map'][$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.
$cache['no_source'][$langcode][$path] = TRUE;
}
}
return $source;
}
}
return FALSE;
}
/**
* Cache system paths for a page.
*
* Cache an array of the system paths available on each page. We assume
* that aliases will be needed for the majority of these paths during
* subsequent requests, and load them in a single query during
* drupal_lookup_path().
*/
function drupal_cache_system_paths() {
// Check if the system paths for this page were loaded from cache in this
// request to avoid writing to cache on every request.
$cache = &drupal_static('drupal_lookup_path', array());
if (empty($cache['system_paths']) && !empty($cache['map'])) {
// @todo Because we are not within the request scope at this time, we cannot
// use current_path(), which would give us the system path to use as the
// key. Instead we call _current_path(), which may give us the alias
// instead. However, at lookup time the system path will be used as the
// key, because it uses current_path(), and so it will be a cache miss.
// There is a critical issue for fixing the path alias logic here:
// http://drupal.org/node/1269742
// Generate a cache ID (cid) specifically for this page.
$cid = _current_path();
// The static $map array used by drupal_lookup_path() includes all
// system paths for the page request.
if ($paths = current($cache['map'])) {
$data = array_keys($paths);
$expire = REQUEST_TIME + (60 * 60 * 24);
cache('path')->set($cid, $data, $expire);
}
}
}
/**
* Given an internal Drupal path, return the alias set by the administrator.
*
* If no path is provided, the function will return the alias of the current
* page.
*
* @param $path
* An internal Drupal path.
* @param $langcode
* An optional language code to look up the path in.
*
* @return
* An aliased path if one was found, or the original path if no alias was
* found.
*/
function drupal_get_path_alias($path = NULL, $langcode = NULL) {
// If no path is specified, use the current page's path.
if ($path == NULL) {
$path = current_path();
}
$result = $path;
if ($alias = drupal_lookup_path('alias', $path, $langcode)) {
$result = $alias;
}
return $result;
}
/**
* Given a path alias, return the internal path it represents.
*
* @param $path
* A Drupal path alias.
* @param $langcode
* An optional language code to look up the path in.
*
* @return
* The internal path represented by the alias, or the original alias if no
* internal path was found.
*/
function drupal_get_normal_path($path, $langcode = NULL) {
$original_path = $path;
// Lookup the path alias first.
if ($source = drupal_lookup_path('source', $path, $langcode)) {
$path = $source;
}
// Allow other modules to alter the inbound URL. We cannot use drupal_alter()
// here because we need to run hook_url_inbound_alter() in the reverse order
// of hook_url_outbound_alter().
foreach (array_reverse(module_implements('url_inbound_alter')) as $module) {
$function = $module . '_url_inbound_alter';
$function($path, $original_path, $langcode);
}
return $path;
}
/**
* Check if the current page is the front page.
*
@ -378,36 +95,6 @@ function current_path() {
return _current_path();
}
/**
* 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.
*/
function drupal_path_alias_whitelist_rebuild($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)) {
$whitelist = 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 = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}");
foreach ($result as $row) {
$whitelist[$row->path] = TRUE;
}
state()->set('system.path_alias_whitelist', $whitelist);
return $whitelist;
}
/**
* Fetch a specific URL alias from the database.
*
@ -415,13 +102,7 @@ function drupal_path_alias_whitelist_rebuild($source = NULL) {
* A string representing the source, a number representing the pid, or an
* array of query conditions.
*
* @return
* FALSE if no alias was found or an associative array containing the
* following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: Unique path alias identifier.
* - langcode: The language code of the alias.
* @see \Drupal\Core\Path\Path::load()
*/
function path_load($conditions) {
if (is_numeric($conditions)) {
@ -433,68 +114,7 @@ function path_load($conditions) {
elseif (!is_array($conditions)) {
return FALSE;
}
$select = db_select('url_alias');
foreach ($conditions as $field => $value) {
$select->condition($field, $value);
}
return $select
->fields('url_alias')
->execute()
->fetchAssoc();
}
/**
* Save a path alias to the database.
*
* @param $path
* An associative array containing the following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: (optional) Unique path alias identifier.
* - langcode: (optional) The language code of the alias.
*/
function path_save(&$path) {
$path += array('langcode' => LANGUAGE_NOT_SPECIFIED);
// Load the stored alias, if any.
if (!empty($path['pid']) && !isset($path['original'])) {
$path['original'] = path_load($path['pid']);
}
if (empty($path['pid'])) {
drupal_write_record('url_alias', $path);
module_invoke_all('path_insert', $path);
}
else {
drupal_write_record('url_alias', $path, array('pid'));
module_invoke_all('path_update', $path);
}
// Clear internal properties.
unset($path['original']);
// Clear the static alias cache.
drupal_clear_path_cache($path['source']);
}
/**
* Delete a URL alias.
*
* @param $criteria
* A number representing the pid or an array of criteria.
*/
function path_delete($criteria) {
if (!is_array($criteria)) {
$criteria = array('pid' => $criteria);
}
$path = path_load($criteria);
$query = db_delete('url_alias');
foreach ($criteria as $field => $value) {
$query->condition($field, $value);
}
$query->execute();
module_invoke_all('path_delete', $path);
drupal_clear_path_cache($path['source']);
return drupal_container()->get('path.crud')->load($conditions);
}
/**
@ -597,14 +217,3 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) {
return $item && $item['access'];
}
/**
* Clear the path cache.
*
* @param $source
* An optional system path for which an alias is being changed.
*/
function drupal_clear_path_cache($source = NULL) {
// Clear the drupal_lookup_path() static cache.
drupal_static_reset('drupal_lookup_path');
drupal_path_alias_whitelist_rebuild($source);
}

View File

@ -0,0 +1,124 @@
<?php
/**
* @file
* Contains Drupal\Core\CacheDecorator\AliasManagerCacheDecorator.
*/
namespace Drupal\Core\CacheDecorator;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Path\AliasManagerInterface;
/**
* Class used by the PathSubscriber to get the system path and cache path lookups.
*/
class AliasManagerCacheDecorator implements CacheDecoratorInterface, AliasManagerInterface {
/**
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* @var \Drupal\Core\Cache\CacheBackendInterface;
*/
protected $cache;
/**
* Stack of request paths for use as cids when caching system paths.
*
* @var array
*/
protected $cacheKeys = array();
/**
* Holds an array of previously cached paths based on a request path.
*
* @var array
*/
protected $preloadedPathLookups = array();
/**
* Whether the cache needs to be written.
*
* @var boolean
*/
protected $cacheNeedsWriting = TRUE;
/**
* Constructs a \Drupal\Core\CacheDecorator\AliasManagerCacheDecorator.
*/
public function __construct(AliasManagerInterface $alias_manager, CacheBackendInterface $cache) {
$this->aliasManager = $alias_manager;
$this->cache = $cache;
}
/**
* Implements \Drupal\Core\CacheDecorator\CacheDecoratorInterface::setCacheKey().
*/
public function setCacheKey($key) {
$this->cacheKeys[] = $key;
}
/**
* Implements \Drupal\Core\CacheDecorator\CacheDecoratorInterface::writeCache().
*
* Cache an array of the system paths available on each page. We assume
* that aliases will be needed for the majority of these paths during
* subsequent requests, and load them in a single query during path alias
* lookup.
*/
public function writeCache() {
$path_lookups = $this->getPathLookups();
// Check if the system paths for this page were loaded from cache in this
// request to avoid writing to cache on every request.
if ($this->cacheNeedsWriting && !empty($path_lookups) && !empty($this->cacheKeys)) {
// Use the system path of the current request for the cache ID (cid).
$cid = end($this->cacheKeys);
// Set the path cache to expire in 24 hours.
$expire = REQUEST_TIME + (60 * 60 * 24);
$this->cache->set($cid, $path_lookups, $expire);
}
// We are at the end of the request, so pop off the last request path.
array_pop($this->cacheKeys);
}
/**
* Implements \Drupal\Core\Path\AliasManagerInterface::getSystemPath().
*/
public function getSystemPath($path, $path_language = NULL) {
$system_path = $this->aliasManager->getSystemPath($path, $path_language);
// We need to pass on the list of previously cached system paths for this
// key to the alias manager for use in subsequent lookups.
$cached = $this->cache->get($system_path);
$cached_paths = array();
if ($cached) {
$cached_paths = $cached->data;
$this->cacheNeedsWriting = FALSE;
}
$this->preloadPathLookups($cached_paths);
return $system_path;
}
/**
* Implements \Drupal\Core\Path\AliasManagerInterface::getPathAlias().
*/
public function getPathAlias($path, $path_language = NULL) {
return $this->aliasManager->getPathAlias($path, $path_language);
}
/**
* Implements \Drupal\Core\Path\AliasManagerInterface::getPathLookups().
*/
public function getPathLookups() {
return $this->aliasManager->getPathLookups();
}
/**
* Implements \Drupal\Core\Path\AliasManagerInterface::preloadPathLookups().
*/
public function preloadPathLookups(array $path_list) {
$this->aliasManager->preloadPathLookups($path_list);
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* @file
* Contains Drupal\Core\CacheDecorator\CacheDecoratorInterface.
*/
namespace Drupal\Core\CacheDecorator;
/**
* Defines an interface for cache decorator implementations.
*/
interface CacheDecoratorInterface {
/**
* Specify the key to use when writing the cache.
*/
public function setCacheKey($key);
/**
* Write the cache.
*/
public function writeCache();
}

View File

@ -13,6 +13,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Scope;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@ -82,6 +83,20 @@ class CoreBundle extends Bundle
$container->register('nested_matcher', 'Drupal\Core\Routing\NestedMatcher')
->addTag('chained_matcher', array('priority' => 5));
$container
->register('cache.path', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('path');
$container->register('path.alias_manager.cached', 'Drupal\Core\CacheDecorator\AliasManagerCacheDecorator')
->addArgument(new Reference('path.alias_manager'))
->addArgument(new Reference('cache.path'));
$container->register('path.crud', 'Drupal\Core\Path\Path')
->addArgument(new Reference('database'))
->addArgument(new Reference('path.alias_manager'));
// The following services are tagged as 'nested_matcher' services and are
// processed in the RegisterNestedMatchersPass compiler pass. Each one
// needs to be set on the matcher using a different method, so we use a
@ -110,6 +125,7 @@ class CoreBundle extends Bundle
$container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber')
->addTag('event_subscriber');
$container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber')
->addArgument(new Reference('path.alias_manager.cached'))
->addTag('event_subscriber');
$container->register('legacy_request_subscriber', 'Drupal\Core\EventSubscriber\LegacyRequestSubscriber')
->addTag('event_subscriber');

View File

@ -7,8 +7,10 @@
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\CacheDecorator\AliasManagerCacheDecorator;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
@ -16,23 +18,31 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class PathSubscriber extends PathListenerBase implements EventSubscriberInterface {
protected $aliasManager;
public function __construct(AliasManagerCacheDecorator $alias_manager) {
$this->aliasManager = $alias_manager;
}
/**
* Resolve the system path.
*
* @todo The path system should be objectified to remove the function calls in
* this method.
*
* @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestPathResolve(GetResponseEvent $event) {
$request = $event->getRequest();
$path = $this->extractPath($request);
$path = drupal_get_normal_path($path);
$path = $this->aliasManager->getSystemPath($path);
$this->setPath($request, $path);
$this->aliasManager->setCacheKey($path);
}
/**
* Ensures system paths for the request get cached.
*/
public function onKernelTerminate(PostResponseEvent $event) {
$this->aliasManager->writeCache();
}
/**
@ -116,6 +126,7 @@ class PathSubscriber extends PathListenerBase implements EventSubscriberInterfac
$events[KernelEvents::REQUEST][] = array('onKernelRequestLanguageResolve', 150);
$events[KernelEvents::REQUEST][] = array('onKernelRequestFrontPageResolve', 101);
$events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100);
$events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
return $events;
}

View File

@ -29,7 +29,6 @@ class RequestCloseSubscriber implements EventSubscriberInterface {
*/
public function onTerminate(PostResponseEvent $event) {
module_invoke_all('exit');
drupal_cache_system_paths();
module_implements_write_cache();
system_run_automated_cron();
}
@ -41,7 +40,7 @@ class RequestCloseSubscriber implements EventSubscriberInterface {
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::TERMINATE][] = array('onTerminate');
$events[KernelEvents::TERMINATE][] = array('onTerminate', 100);
return $events;
}

View File

@ -101,7 +101,7 @@ class ExceptionController extends ContainerAware {
$system_path = $request->attributes->get('system_path');
watchdog('access denied', $system_path, array(), WATCHDOG_WARNING);
$path = drupal_get_normal_path(config('system.site')->get('page.403'));
$path = $this->container->get('path.alias_manager')->getSystemPath(config('system.site')->get('page.403'));
if ($path && $path != $system_path) {
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_GET['destination'])) {
@ -173,7 +173,7 @@ class ExceptionController extends ContainerAware {
$_GET['destination'] = $system_path;
}
$path = drupal_get_normal_path(config('system.site')->get('page.404'));
$path = $this->container->get('path.alias_manager')->getSystemPath(config('system.site')->get('page.404'));
if ($path && $path != $system_path) {
// @todo Um, how do I specify an override URL again? Totally not clear. Do
// that and sub-call the kernel rather than using meah().

View File

@ -0,0 +1,320 @@
<?php
/**
* @file
* Contains Drupal\Core\Path\AliasManager.
*/
namespace Drupal\Core\Path;
use Drupal\Core\Database\Connection;
use Drupal\Core\KeyValueStore\KeyValueDatabaseFactory;
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, KeyValueDatabaseFactory $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];
}
// Check the path whitelist, if the top-level part before the first /
// is not in the list, then there is no need to do anything further,
// it is not in the database.
elseif (!isset($this->whitelist[strtok($path, '/')])) {
return FALSE;
}
// For system paths which were not cached, query aliases individually.
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;
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains Drupal\Core\Path\AliasManagerInterface.
*/
namespace Drupal\Core\Path;
interface AliasManagerInterface {
/**
* Given a path alias, return the internal path it represents.
*
* @param $path
* A Drupal path alias.
* @param $path_language
* An optional language code to look up the path in.
*
* @return
* The internal path represented by the alias, or the original alias if no
* internal path was found.
*/
public function getSystemPath($path, $path_language = NULL);
/**
* Given an internal Drupal path, return the alias set by the administrator.
*
* @param $path
* An internal Drupal path.
*
* @param $path_language
* An optional language code to look up the path in.
*
* @return
* An aliased path if one was found, or the original path if no alias was
* found.
*/
public function getPathAlias($path, $path_language = NULL);
/**
* Returns an array of system paths that have been looked up.
*
* @return array
* An array of all system paths that have been looked up during the current
* request.
*/
public function getPathLookups();
/**
* Preload a set of paths for bulk alias lookups.
*
* @param $path_list
* An array of system paths.
*/
public function preloadPathLookups(array $path_list);
}

View File

@ -0,0 +1,144 @@
<?php
/**
* @file
* Contains Drupal\Core\Path\Path.
*/
namespace Drupal\Core\Path;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Connection;
/**
* Defines a class for CRUD operations on path aliases.
*/
class Path {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a Path CRUD object.
*
* @param \Drupal\Core\Database\Connection $connection
* A database connection for reading and writing path aliases.
*
* @param \Drupal\Core\Path\AliasManager $alias_manager
* An alias manager with an internal cache of stored aliases.
*
* @todo This class should not take an alias manager in its constructor. Once
* we move to firing an event for CRUD operations instead of invoking a
* hook, we can have a listener that calls cacheClear() on the alias manager.
*/
public function __construct(Connection $connection, AliasManager $alias_manager) {
$this->connection = $connection;
$this->alias_manager = $alias_manager;
}
/**
* Saves a path alias to the database.
*
* @param string $source
* The internal system path.
*
* @param string $alias
* The URL alias.
*
* @param string $langcode
* The language code of the alias.
*
* @param int $pid
* Unique path alias identifier.
*
* @return
* FALSE if the path could not be saved or an associative array containing
* the following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: Unique path alias identifier.
* - langcode: The language code of the alias.
*/
public function save($source, $alias, $langcode = LANGUAGE_NOT_SPECIFIED, $pid = NULL) {
$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;
// @todo: Find a correct place to invoke hook_path_insert().
$hook = 'path_insert';
}
else {
$fields['pid'] = $pid;
$query = $this->connection->update('url_alias')
->fields($fields)
->condition('pid', $pid);
$pid = $query->execute();
// @todo: figure out where we can invoke hook_path_update()
$hook = 'path_update';
}
if ($pid) {
// @todo Switch to using an event for this instead of a hook.
module_invoke_all($hook, $fields);
$this->alias_manager->cacheClear();
return $fields;
}
return FALSE;
}
/**
* Fetches a specific URL alias from the database.
*
* @param $conditions
* An array of query conditions.
*
* @return
* FALSE if no alias was found or an associative array containing the
* following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: Unique path alias identifier.
* - langcode: The language code of the alias.
*/
public function load($conditions) {
$select = $this->connection->select('url_alias');
foreach ($conditions as $field => $value) {
$select->condition($field, $value);
}
return $select
->fields('url_alias')
->execute()
->fetchAssoc();
}
/**
* Deletes a URL alias.
*
* @param array $conditions
* An array of criteria.
*/
public function delete($conditions) {
$path = $this->load($conditions);
$query = $this->connection->delete('url_alias');
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
$deleted = $query->execute();
// @todo Switch to using an event for this instead of a hook.
module_invoke_all('path_delete', $path);
$this->alias_manager->cacheClear();
return $deleted;
}
}

View File

@ -851,7 +851,7 @@ function block_block_list_alter(&$blocks) {
if ($block->visibility < BLOCK_VISIBILITY_PHP) {
// Compare the lowercase path alias (if any) and internal path.
$path = current_path();
$path_alias = drupal_strtolower(drupal_get_path_alias($path));
$path_alias = drupal_strtolower(drupal_container()->get('path.alias_manager')->getPathAlias($path));
$page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
// When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED),
// the block is displayed on all pages except those listed in $block->pages.

View File

@ -109,13 +109,13 @@ class LocalePathTest extends WebTestBase {
'alias' => $custom_path,
'langcode' => LANGUAGE_NOT_SPECIFIED,
);
path_save($edit);
$lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en');
drupal_container()->get('path.crud')->save($edit['source'], $edit['alias'], $edit['langcode']);
$lookup_path = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $node->nid, 'en');
$this->assertEqual($english_path, $lookup_path, t('English language alias has priority.'));
// Same check for language 'xx'.
$lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix);
$lookup_path = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $node->nid, $prefix);
$this->assertEqual($custom_language_path, $lookup_path, t('Custom language alias has priority.'));
path_delete($edit);
drupal_container()->get('path.crud')->delete($edit);
// Create language nodes to check priority of aliases.
$first_node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
@ -127,7 +127,7 @@ class LocalePathTest extends WebTestBase {
'alias' => $custom_path,
'langcode' => 'en',
);
path_save($edit);
drupal_container()->get('path.crud')->save($edit['source'], $edit['alias'], $edit['langcode']);
// Assign a custom path alias to second node with LANGUAGE_NOT_SPECIFIED.
$edit = array(
@ -135,7 +135,7 @@ class LocalePathTest extends WebTestBase {
'alias' => $custom_path,
'langcode' => LANGUAGE_NOT_SPECIFIED,
);
path_save($edit);
drupal_container()->get('path.crud')->save($edit['source'], $edit['alias'], $edit['langcode']);
// Test that both node titles link to our path alias.
$this->drupalGet('<front>');

View File

@ -417,7 +417,7 @@ function menu_edit_item($form, &$form_state, $type, $item, $menu) {
*/
function menu_edit_item_validate($form, &$form_state) {
$item = &$form_state['values'];
$normal_path = drupal_get_normal_path($item['link_path']);
$normal_path = drupal_container()->get('path.alias_manager')->getSystemPath($item['link_path']);
if ($item['link_path'] != $normal_path) {
drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $item['link_path'], '%normal_path' => $normal_path)));
$item['link_path'] = $normal_path;

View File

@ -57,9 +57,7 @@ class PathAliasTest extends PathTestBase {
// Visit the alias for the node and confirm a cache entry is created.
cache('path')->flush();
$this->drupalGet($edit['alias']);
// @todo The alias should actually have been cached with the system path as
// the key, see the todo in drupal_cache_system_paths() in path.inc.
$this->assertTrue(cache('path')->get($edit['alias']), 'Cache entry was created.');
$this->assertTrue(cache('path')->get($edit['source']), 'Cache entry was created.');
}
/**
@ -94,7 +92,7 @@ class PathAliasTest extends PathTestBase {
$this->assertText($node1->label(), 'Changed alias works.');
$this->assertResponse(200);
drupal_static_reset('drupal_lookup_path');
drupal_container()->get('path.alias_manager')->cacheClear();
// Confirm that previous alias no longer works.
$this->drupalGet($previous);
$this->assertNoText($node1->label(), 'Previous alias no longer works.');

View File

@ -82,7 +82,7 @@ class PathLanguageTest extends PathTestBase {
$this->drupalPost(NULL, $edit, t('Save'));
// Clear the path lookup cache.
drupal_lookup_path('wipe');
drupal_container()->get('path.alias_manager')->cacheClear();
// Ensure the node was created.
$french_node = $this->drupalGetNodeByTitle($edit["title"]);
@ -121,7 +121,7 @@ class PathLanguageTest extends PathTestBase {
// We need to ensure that the user language preference is not taken into
// account while determining the path alias language, because if this
// happens we have no way to check that the path alias is valid: there is no
// path alias for French matching the english alias. So drupal_lookup_path()
// path alias for French matching the english alias. So the alias manager
// needs to use the URL language to check whether the alias is valid.
$this->drupalGet($english_alias);
$this->assertText($english_node->label(), 'Alias for English translation works.');
@ -145,20 +145,20 @@ class PathLanguageTest extends PathTestBase {
$this->drupalGet($french_alias);
$this->assertResponse(404, 'Alias for French translation is unavailable when URL language negotiation is disabled.');
// drupal_lookup_path() has an internal static cache. Check to see that
// The alias manager has an internal path lookup cache. Check to see that
// it has the appropriate contents at this point.
drupal_lookup_path('wipe');
$french_node_path = drupal_lookup_path('source', $french_alias, $french_node->langcode);
drupal_container()->get('path.alias_manager')->cacheClear();
$french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, $french_node->langcode);
$this->assertEqual($french_node_path, 'node/' . $french_node->nid, 'Normal path works.');
// Second call should return the same path.
$french_node_path = drupal_lookup_path('source', $french_alias, $french_node->langcode);
$french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, $french_node->langcode);
$this->assertEqual($french_node_path, 'node/' . $french_node->nid, 'Normal path is the same.');
// Confirm that the alias works.
$french_node_alias = drupal_lookup_path('alias', 'node/' . $french_node->nid, $french_node->langcode);
$french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, $french_node->langcode);
$this->assertEqual($french_node_alias, $french_alias, 'Alias works.');
// Second call should return the same alias.
$french_node_alias = drupal_lookup_path('alias', 'node/' . $french_node->nid, $french_node->langcode);
$french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, $french_node->langcode);
$this->assertEqual($french_node_alias, $french_alias, 'Alias is the same.');
}
}

View File

@ -75,7 +75,7 @@ function path_admin_overview($keys = NULL) {
// If the system path maps to a different URL alias, highlight this table
// row to let the user know of old aliases.
if ($data->alias != drupal_get_path_alias($data->source, $data->langcode)) {
if ($data->alias != drupal_container()->get('path.alias_manager')->getPathAlias($data->source, $data->langcode)) {
$row['class'] = array('warning');
}
@ -217,7 +217,7 @@ function path_admin_form_delete_submit($form, &$form_state) {
*/
function path_admin_form_validate($form, &$form_state) {
$source = &$form_state['values']['source'];
$source = drupal_get_normal_path($source);
$source = drupal_container()->get('path.alias_manager')->getSystemPath($source);
$alias = $form_state['values']['alias'];
$pid = isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0;
// Language is only set if language.module is enabled, otherwise save for all
@ -249,7 +249,15 @@ function path_admin_form_submit($form, &$form_state) {
// Remove unnecessary values.
form_state_values_clean($form_state);
path_save($form_state['values']);
$pid = isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0;
$source = &$form_state['values']['source'];
$source = drupal_container()->get('path.alias_manager')->getSystemPath($source);
$alias = $form_state['values']['alias'];
// Language is only set if language.module is enabled, otherwise save for all
// languages.
$langcode = isset($form_state['values']['langcode']) ? $form_state['values']['langcode'] : LANGUAGE_NOT_SPECIFIED;
drupal_container()->get('path.crud')->save($source, $alias, $langcode, $pid);
drupal_set_message(t('The alias has been saved.'));
$form_state['redirect'] = 'admin/config/search/path';
@ -281,7 +289,7 @@ function path_admin_delete_confirm($form, &$form_state, $path) {
*/
function path_admin_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
path_delete($form_state['path']['pid']);
drupal_container()->get('path.crud')->delete(array('pid' => $form_state['path']['pid']));
$form_state['redirect'] = 'admin/config/search/path';
}
}

View File

@ -20,7 +20,7 @@
* - pid: Unique path alias identifier.
* - langcode: The language code of the alias.
*
* @see path_save()
* @see \Drupal\Core\Path\Path::save()
*/
function hook_path_insert($path) {
db_insert('mytable')
@ -41,7 +41,7 @@ function hook_path_insert($path) {
* - pid: Unique path alias identifier.
* - langcode: The language code of the alias.
*
* @see path_save()
* @see \Drupal\Core\Path\Path::save()
*/
function hook_path_update($path) {
db_update('mytable')
@ -60,7 +60,7 @@ function hook_path_update($path) {
* - pid: Unique path alias identifier.
* - langcode: The language code of the alias.
*
* @see path_delete()
* @see \Drupal\Core\Path\Path::delete()
*/
function hook_path_delete($path) {
db_delete('mytable')

View File

@ -107,7 +107,7 @@ function path_form_node_form_alter(&$form, $form_state) {
if ($node->langcode != LANGUAGE_NOT_SPECIFIED) {
$conditions['langcode'] = $node->langcode;
}
$path = path_load($conditions);
$path = drupal_container()->get('path.crud')->load($conditions);
if ($path === FALSE) {
$path = array();
}
@ -190,14 +190,13 @@ function path_form_element_validate($element, &$form_state, $complete_form) {
*/
function path_node_insert(Node $node) {
if (isset($node->path)) {
$path = $node->path;
$path['alias'] = trim($path['alias']);
$alias = trim($node->path['alias']);
// Only save a non-empty alias.
if (!empty($path['alias'])) {
if (!empty($alias)) {
// Ensure fields for programmatic executions.
$path['source'] = 'node/' . $node->nid;
$path['langcode'] = isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED;
path_save($path);
$source = 'node/' . $node->nid;
$langcode = isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED;
drupal_container()->get('path.crud')->save($source, $alias, $langcode);
}
}
}
@ -208,17 +207,17 @@ function path_node_insert(Node $node) {
function path_node_update(Node $node) {
if (isset($node->path)) {
$path = $node->path;
$path['alias'] = trim($path['alias']);
$alias = trim($path['alias']);
// Delete old alias if user erased it.
if (!empty($path['pid']) && empty($path['alias'])) {
path_delete($path['pid']);
drupal_container()->get('path.crud')->delete(array('pid' => $path['pid']));
}
// Only save a non-empty alias.
if (!empty($path['alias'])) {
// Ensure fields for programmatic executions.
$path['source'] = 'node/' . $node->nid;
$path['langcode'] = isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED;
path_save($path);
$source = 'node/' . $node->nid;
$langcode = isset($node->langcode) ? $node->langcode : LANGUAGE_NOT_SPECIFIED;
drupal_container()->get('path.crud')->save($source, $alias, $langcode, $path['pid']);
}
}
}
@ -228,7 +227,7 @@ function path_node_update(Node $node) {
*/
function path_node_predelete(Node $node) {
// Delete all aliases associated with this node.
path_delete(array('source' => 'node/' . $node->nid));
drupal_container()->get('path.crud')->delete(array('source' => 'node/' . $node->nid));
}
/**
@ -238,7 +237,7 @@ function path_form_taxonomy_term_form_alter(&$form, $form_state) {
// Make sure this does not show up on the delete confirmation form.
if (empty($form_state['confirm_delete'])) {
$term = $form_state['controller']->getEntity($form_state);
$path = (isset($term->tid) ? path_load('taxonomy/term/' . $term->tid) : array());
$path = (isset($term->tid) ? drupal_container()->get('path.crud')->load(array('source' => 'taxonomy/term/' . $term->tid)) : array());
if ($path === FALSE) {
$path = array();
}
@ -279,7 +278,7 @@ function path_taxonomy_term_insert(Term $term) {
// Ensure fields for programmatic executions.
$path['source'] = 'taxonomy/term/' . $term->tid;
$path['langcode'] = LANGUAGE_NOT_SPECIFIED;
path_save($path);
drupal_container()->get('path.crud')->save($path['source'], $path['alias'], $path['langcode']);
}
}
}
@ -293,14 +292,15 @@ function path_taxonomy_term_update(Term $term) {
$path['alias'] = trim($path['alias']);
// Delete old alias if user erased it.
if (!empty($path['pid']) && empty($path['alias'])) {
path_delete($path['pid']);
drupal_container()->get('path.crud')->delete(array('pid' => $path['pid']));
}
// Only save a non-empty alias.
if (!empty($path['alias'])) {
$pid = (!empty($path['pid']) ? $path['pid'] : NULL);
// Ensure fields for programmatic executions.
$path['source'] = 'taxonomy/term/' . $term->tid;
$path['langcode'] = LANGUAGE_NOT_SPECIFIED;
path_save($path);
drupal_container()->get('path.crud')->save($path['source'], $path['alias'], $path['langcode'], $pid);
}
}
}
@ -310,7 +310,7 @@ function path_taxonomy_term_update(Term $term) {
*/
function path_taxonomy_term_delete(Term $term) {
// Delete all aliases associated with this term.
path_delete(array('source' => 'taxonomy/term/' . $term->tid));
drupal_container()->get('path.crud')->delete(array('source' => 'taxonomy/term/' . $term->tid));
}
/**

View File

@ -643,7 +643,7 @@ function search_index($sid, $module, $text, $langcode) {
if ($tagname == 'a') {
// Check if link points to a node on this site
if (preg_match($node_regexp, $value, $match)) {
$path = drupal_get_normal_path($match[1]);
$path = drupal_container()->get('path.alias_manager')->getSystemPath($match[1]);
if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) {
$linknid = $match[1];
if ($linknid > 0) {

View File

@ -31,7 +31,7 @@ class ShortcutLinksTest extends ShortcutTestBase {
'source' => 'node/' . $this->node->nid,
'alias' => $this->randomName(8),
);
path_save($path);
drupal_container()->get('path.crud')->save($path['source'], $path['alias']);
// Create some paths to test.
$test_cases = array(
@ -54,7 +54,7 @@ class ShortcutLinksTest extends ShortcutTestBase {
$saved_set = shortcut_set_load($set->set_name);
$paths = $this->getShortcutInformation($saved_set, 'link_path');
$test_path = empty($test['path']) ? '<front>' : $test['path'];
$this->assertTrue(in_array(drupal_get_normal_path($test_path), $paths), 'Shortcut created: '. $test['path']);
$this->assertTrue(in_array(drupal_container()->get('path.alias_manager')->getSystemPath($test_path), $paths), 'Shortcut created: '. $test['path']);
$this->assertLink($title, 0, 'Shortcut link found on the page.');
}
}

View File

@ -430,7 +430,7 @@ function _shortcut_link_form_elements($shortcut_link = NULL) {
);
}
else {
$shortcut_link['link_path'] = ($shortcut_link['link_path'] == '<front>') ? '' : drupal_get_path_alias($shortcut_link['link_path']);
$shortcut_link['link_path'] = ($shortcut_link['link_path'] == '<front>') ? '' : drupal_container()->get('path.alias_manager')->getPathAlias($shortcut_link['link_path']);
}
$form['shortcut_link']['#tree'] = TRUE;
@ -477,7 +477,7 @@ function shortcut_link_edit_validate($form, &$form_state) {
*/
function shortcut_link_edit_submit($form, &$form_state) {
// Normalize the path in case it is an alias.
$shortcut_path = drupal_get_normal_path($form_state['values']['shortcut_link']['link_path']);
$shortcut_path = drupal_container()->get('path.alias_manager')->getSystemPath($form_state['values']['shortcut_link']['link_path']);
if (empty($shortcut_path)) {
$shortcut_path = '<front>';
}
@ -517,7 +517,7 @@ function shortcut_link_add_submit($form, &$form_state) {
*/
function shortcut_admin_add_link($shortcut_link, &$shortcut_set) {
// Normalize the path in case it is an alias.
$shortcut_link['link_path'] = drupal_get_normal_path($shortcut_link['link_path']);
$shortcut_link['link_path'] = drupal_container()->get('path.alias_manager')->getSystemPath($shortcut_link['link_path']);
if (empty($shortcut_link['link_path'])) {
$shortcut_link['link_path'] = '<front>';
}

View File

@ -616,7 +616,7 @@ function shortcut_set_title_exists($title) {
*/
function shortcut_valid_link($path) {
// Do not use URL aliases.
$normal_path = drupal_get_normal_path($path);
$normal_path = drupal_container()->get('path.alias_manager')->getSystemPath($path);
if ($path != $normal_path) {
$path = $normal_path;
}

View File

@ -760,6 +760,12 @@ abstract class WebTestBase extends TestBase {
variable_set('mail_system', array('default-system' => 'Drupal\Core\Mail\VariableLog'));
drupal_set_time_limit($this->timeLimit);
// Temporary fix so that when running from run-tests.sh we don't get an
// empty current path which would indicate we're on the home page.
$path = current_path();
if (empty($path)) {
_current_path('run-tests');
}
$this->setup = TRUE;
}

View File

@ -388,7 +388,7 @@ function statistics_block_view($delta = '') {
* A string as a link, truncated to the width, linked to the given $path.
*/
function _statistics_link($path, $width = 35) {
$title = drupal_get_path_alias($path);
$title = drupal_container()->get('path.alias_manager')->getPathAlias($path);
$title = truncate_utf8($title, $width, FALSE, TRUE);
return l($title, $path);
}

View File

@ -176,7 +176,7 @@ class JavaScriptTest extends WebTestBase {
$this->drupalGet('common-test/query-string');
$this->assertPattern('@<script>.+drupalSettings.+"currentPath":"common-test\\\/query-string"@s', 'currentPath is in the JS settings');
$path = array('source' => 'common-test/query-string', 'alias' => 'common-test/currentpath-check');
path_save($path);
drupal_container()->get('path.crud')->save($path['source'], $path['alias']);
$this->drupalGet('common-test/currentpath-check');
$this->assertPattern('@<script>.+drupalSettings.+"currentPath":"common-test\\\/query-string"@s', 'currentPath is in the JS settings for an aliased path');
}

View File

@ -0,0 +1,165 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Path\CrudTest.
*/
namespace Drupal\system\Tests\Path;
use Drupal\simpletest\UnitTestBase;
use Drupal\Core\Database\Database;
use Drupal\Core\KeyValueStore\KeyValueDatabaseFactory;
use Drupal\Core\Path\Path;
use Drupal\Core\Path\AliasManager;
/**
* Tests path alias CRUD and lookup functionality.
*/
class AliasTest extends UnitTestBase {
public static function getInfo() {
return array(
'name' => t('Path Alias Unit Tests'),
'description' => t('Tests path alias CRUD and lookup functionality.'),
'group' => t('Path API'),
);
}
function __construct($test_id = NULL) {
parent::__construct($test_id);
$this->fixtures = new UrlAliasFixtures();
}
public function tearDown() {
$this->fixtures->dropTables(Database::getConnection());
parent::tearDown();
}
function testCRUD() {
//Prepare database table.
$connection = Database::getConnection();
$this->fixtures->createTables($connection);
//Create AliasManager and Path object.
$aliasManager = new AliasManager($connection, new KeyValueDatabaseFactory($connection));
$path = new Path($connection, $aliasManager);
$aliases = $this->fixtures->sampleUrlAliases();
//Create a few aliases
foreach ($aliases as $idx => $alias) {
$path->save($alias['source'], $alias['alias'], $alias['langcode']);
$result = $connection->query('SELECT * FROM {url_alias} WHERE source = :source AND alias= :alias AND langcode = :langcode', array(':source' => $alias['source'], ':alias' => $alias['alias'], ':langcode' => $alias['langcode']));
$rows = $result->fetchAll();
$this->assertEqual(count($rows), 1, format_string('Created an entry for %alias.', array('%alias' => $alias['alias'])));
//Cache the pid for further tests.
$aliases[$idx]['pid'] = $rows[0]->pid;
}
//Load a few aliases
foreach ($aliases as $alias) {
$pid = $alias['pid'];
$loadedAlias = $path->load(array('pid' => $pid));
$this->assertEqual($loadedAlias, $alias, format_string('Loaded the expected path with pid %pid.', array('%pid' => $pid)));
}
//Update a few aliases
foreach ($aliases as $alias) {
$path->save($alias['source'], $alias['alias'] . '_updated', $alias['langcode'], $alias['pid']);
$result = $connection->query('SELECT pid FROM {url_alias} WHERE source = :source AND alias= :alias AND langcode = :langcode', array(':source' => $alias['source'], ':alias' => $alias['alias'] . '_updated', ':langcode' => $alias['langcode']));
$pid = $result->fetchField();
$this->assertEqual($pid, $alias['pid'], format_string('Updated entry for pid %pid.', array('%pid' => $pid)));
}
//Delete a few aliases
foreach ($aliases as $alias) {
$pid = $alias['pid'];
$path->delete(array('pid' => $pid));
$result = $connection->query('SELECT * FROM {url_alias} WHERE pid = :pid', array(':pid' => $pid));
$rows = $result->fetchAll();
$this->assertEqual(count($rows), 0, format_string('Deleted entry with pid %pid.', array('%pid' => $pid)));
}
}
function testLookupPath() {
//Prepare database table.
$connection = Database::getConnection();
$this->fixtures->createTables($connection);
//Create AliasManager and Path object.
$aliasManager = new AliasManager($connection, new KeyValueDatabaseFactory($connection));
$pathObject = new Path($connection, $aliasManager);
// Test the situation where the source is the same for multiple aliases.
// Start with a language-neutral alias, which we will override.
$path = array(
'source' => "user/1",
'alias' => 'foo',
);
$pathObject->save($path['source'], $path['alias']);
$this->assertEqual($aliasManager->getPathAlias($path['source']), $path['alias'], 'Basic alias lookup works.');
$this->assertEqual($aliasManager->getSystemPath($path['alias']), $path['source'], 'Basic source lookup works.');
// Create a language specific alias for the default language (English).
$path = array(
'source' => "user/1",
'alias' => "users/Dries",
'langcode' => 'en',
);
$pathObject->save($path['source'], $path['alias'], $path['langcode']);
$this->assertEqual($aliasManager->getPathAlias($path['source']), $path['alias'], 'English alias overrides language-neutral alias.');
$this->assertEqual($aliasManager->getSystemPath($path['alias']), $path['source'], 'English source overrides language-neutral source.');
// Create a language-neutral alias for the same path, again.
$path = array(
'source' => "user/1",
'alias' => 'bar',
);
$pathObject->save($path['source'], $path['alias']);
$this->assertEqual($aliasManager->getPathAlias($path['source']), "users/Dries", 'English alias still returned after entering a language-neutral alias.');
// Create a language-specific (xx-lolspeak) alias for the same path.
$path = array(
'source' => "user/1",
'alias' => 'LOL',
'langcode' => 'xx-lolspeak',
);
$pathObject->save($path['source'], $path['alias'], $path['langcode']);
$this->assertEqual($aliasManager->getPathAlias($path['source']), "users/Dries", 'English alias still returned after entering a LOLspeak alias.');
// The LOLspeak alias should be returned if we really want LOLspeak.
$this->assertEqual($aliasManager->getPathAlias($path['source'], 'xx-lolspeak'), 'LOL', 'LOLspeak alias returned if we specify xx-lolspeak to the alias manager.');
// Create a new alias for this path in English, which should override the
// previous alias for "user/1".
$path = array(
'source' => "user/1",
'alias' => 'users/my-new-path',
'langcode' => 'en',
);
$pathObject->save($path['source'], $path['alias'], $path['langcode']);
$this->assertEqual($aliasManager->getPathAlias($path['source']), $path['alias'], 'Recently created English alias returned.');
$this->assertEqual($aliasManager->getSystemPath($path['alias']), $path['source'], 'Recently created English source returned.');
// Remove the English aliases, which should cause a fallback to the most
// recently created language-neutral alias, 'bar'.
$pathObject->delete(array('langcode' => 'en'));
$this->assertEqual($aliasManager->getPathAlias($path['source']), 'bar', 'Path lookup falls back to recently created language-neutral alias.');
// Test the situation where the alias and language are the same, but
// the source differs. The newer alias record should be returned.
$pathObject->save('user/2', 'bar');
$this->assertEqual($aliasManager->getSystemPath('bar'), 'user/2', 'Newer alias record is returned when comparing two LANGUAGE_NOT_SPECIFIED paths with the same alias.');
}
}

View File

@ -1,100 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Path\LookupTest.
*/
namespace Drupal\system\Tests\Path;
use Drupal\simpletest\WebTestBase;
/**
* Unit test for drupal_lookup_path().
*/
class LookupTest extends WebTestBase {
public static function getInfo() {
return array(
'name' => t('Path lookup'),
'description' => t('Tests that drupal_lookup_path() returns correct paths.'),
'group' => t('Path API'),
);
}
/**
* Test that drupal_lookup_path() returns the correct path.
*/
function testDrupalLookupPath() {
$account = $this->drupalCreateUser();
$uid = $account->uid;
$name = $account->name;
// Test the situation where the source is the same for multiple aliases.
// Start with a language-neutral alias, which we will override.
$path = array(
'source' => "user/$uid",
'alias' => 'foo',
);
path_save($path);
$this->assertEqual(drupal_lookup_path('alias', $path['source']), $path['alias'], 'Basic alias lookup works.');
$this->assertEqual(drupal_lookup_path('source', $path['alias']), $path['source'], 'Basic source lookup works.');
// Create a language specific alias for the default language (English).
$path = array(
'source' => "user/$uid",
'alias' => "users/$name",
'langcode' => 'en',
);
path_save($path);
$this->assertEqual(drupal_lookup_path('alias', $path['source']), $path['alias'], 'English alias overrides language-neutral alias.');
$this->assertEqual(drupal_lookup_path('source', $path['alias']), $path['source'], 'English source overrides language-neutral source.');
// Create a language-neutral alias for the same path, again.
$path = array(
'source' => "user/$uid",
'alias' => 'bar',
);
path_save($path);
$this->assertEqual(drupal_lookup_path('alias', $path['source']), "users/$name", 'English alias still returned after entering a language-neutral alias.');
// Create a language-specific (xx-lolspeak) alias for the same path.
$path = array(
'source' => "user/$uid",
'alias' => 'LOL',
'langcode' => 'xx-lolspeak',
);
path_save($path);
$this->assertEqual(drupal_lookup_path('alias', $path['source']), "users/$name", 'English alias still returned after entering a LOLspeak alias.');
// The LOLspeak alias should be returned if we really want LOLspeak.
$this->assertEqual(drupal_lookup_path('alias', $path['source'], 'xx-lolspeak'), 'LOL', 'LOLspeak alias returned if we specify xx-lolspeak to drupal_lookup_path().');
// Create a new alias for this path in English, which should override the
// previous alias for "user/$uid".
$path = array(
'source' => "user/$uid",
'alias' => 'users/my-new-path',
'langcode' => 'en',
);
path_save($path);
$this->assertEqual(drupal_lookup_path('alias', $path['source']), $path['alias'], 'Recently created English alias returned.');
$this->assertEqual(drupal_lookup_path('source', $path['alias']), $path['source'], 'Recently created English source returned.');
// Remove the English aliases, which should cause a fallback to the most
// recently created language-neutral alias, 'bar'.
db_delete('url_alias')
->condition('langcode', 'en')
->execute();
drupal_clear_path_cache();
$this->assertEqual(drupal_lookup_path('alias', $path['source']), 'bar', 'Path lookup falls back to recently created language-neutral alias.');
// Test the situation where the alias and language are the same, but
// the source differs. The newer alias record should be returned.
$account2 = $this->drupalCreateUser();
$path = array(
'source' => 'user/' . $account2->uid,
'alias' => 'bar',
);
path_save($path);
$this->assertEqual(drupal_lookup_path('source', $path['alias']), $path['source'], 'Newer alias record is returned when comparing two LANGUAGE_NOT_SPECIFIED paths with the same alias.');
}
}

View File

@ -1,63 +0,0 @@
<?php
/**
* @file
* Definition of Drupal\system\Tests\Path\SaveTest.
*/
namespace Drupal\system\Tests\Path;
use Drupal\simpletest\WebTestBase;
/**
* Tests the path_save() function.
*/
class SaveTest extends WebTestBase {
/**
* Enable a helper module that implements hook_path_update().
*
* @var array
*/
public static $modules = array('path_test');
public static function getInfo() {
return array(
'name' => t('Path save'),
'description' => t('Tests that path_save() exposes the previous alias value.'),
'group' => t('Path API'),
);
}
function setUp() {
parent::setUp();
path_test_reset();
}
/**
* Tests that path_save() makes the original path available to modules.
*/
function testDrupalSaveOriginalPath() {
$account = $this->drupalCreateUser();
$uid = $account->uid;
$name = $account->name;
// Create a language-neutral alias.
$path = array(
'source' => "user/$uid",
'alias' => 'foo',
);
$path_original = $path;
path_save($path);
// Alter the path.
$path['alias'] = 'bar';
path_save($path);
// Test to see if the original alias is available to modules during
// hook_path_update().
$results = variable_get('path_test_results', array());
$this->assertIdentical($results['hook_path_update']['original']['alias'], $path_original['alias'], 'Old path alias available to modules during hook_path_update.');
$this->assertIdentical($results['hook_path_update']['original']['source'], $path_original['source'], 'Old path alias available to modules during hook_path_update.');
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Drupal\system\Tests\Path;
use Drupal\Core\Database\Connection;
/**
* Utility methods to generate sample data, database configuration, etc.
*/
class UrlAliasFixtures {
/**
* Create the tables required for the sample data.
*
* @param Drupal\Core\Database\Connection $connection
* The connection to use to create the tables.
*/
public function createTables(Connection $connection) {
$tables = $this->urlAliasTableDefinition();
$schema = $connection->schema();
foreach ($tables as $name => $table) {
$schema->dropTable($name);
$schema->createTable($name, $table);
}
}
/**
* Drop the tables used for the sample data.
*
* @param Drupal\Core\Database\Connection $connection
* The connection to use to drop the tables.
*/
public function dropTables(Connection $connection) {
$tables = $this->urlAliasTableDefinition();
$schema = $connection->schema();
foreach ($tables as $name => $table) {
$schema->dropTable($name);
}
}
/**
* Returns an array of URL aliases for testing.
*
* @return array of URL alias definitions.
*/
public function sampleUrlAliases() {
return array(
array(
'source' => 'node/1',
'alias' => 'alias_for_node_1_en',
'langcode' => 'en'
),
array(
'source' => 'node/2',
'alias' => 'alias_for_node_2_en',
'langcode' => 'en'
),
array(
'source' => 'node/1',
'alias' => 'alias_for_node_1_fr',
'langcode' => 'fr'
),
array(
'source' => 'node/1',
'alias' => 'alias_for_node_1_und',
'langcode' => 'und'
)
);
}
/**
* Returns the table definition for the URL alias fixtures.
*
* @return array
* Table definitions.
*/
public function urlAliasTableDefinition() {
$tables = array();
module_load_install('system');
$schema = system_schema();
$tables['url_alias'] = $schema['url_alias'];
$tables['key_value'] = $schema['key_value'];
return $tables;
}
}

View File

@ -40,41 +40,43 @@ class UrlAlterFunctionalTest extends WebTestBase {
$name = $account->name;
// Test a single altered path.
$this->assertUrlInboundAlter("user/$name", "user/$uid");
$this->drupalGet("user/$name");
$this->assertResponse('200', 'The user/username path gets resolved correctly');
$this->assertUrlOutboundAlter("user/$uid", "user/$name");
// Test that a path always uses its alias.
$path = array('source' => "user/$uid/test1", 'alias' => 'alias/test1');
path_save($path);
drupal_container()->get('path.crud')->save($path['source'], $path['alias']);
$this->assertUrlInboundAlter('alias/test1', "user/$uid/test1");
$this->assertUrlOutboundAlter("user/$uid/test1", 'alias/test1');
// Test that alias source paths are normalized in the interface.
$edit = array('source' => "user/$name/edit", 'alias' => 'alias/test2');
// Test adding an alias via the UI.
$edit = array('source' => "user/$uid/edit", 'alias' => 'alias/test2');
$this->drupalPost('admin/config/search/path/add', $edit, t('Save'));
$this->assertText(t('The alias has been saved.'));
// Test that a path always uses its alias.
$this->assertUrlInboundAlter('alias/test2', "user/$uid/edit");
$this->drupalGet('alias/test2');
$this->assertResponse('200', 'The path alias gets resolved correctly');
$this->assertUrlOutboundAlter("user/$uid/edit", 'alias/test2');
// Test a non-existent user is not altered.
$uid++;
$this->assertUrlInboundAlter("user/$uid", "user/$uid");
$this->assertUrlOutboundAlter("user/$uid", "user/$uid");
// Test that 'forum' is altered to 'community' correctly, both at the root
// level and for a specific existing forum.
$this->assertUrlInboundAlter('community', 'forum');
$this->drupalGet('community');
$this->assertText('General discussion', 'The community path gets resolved correctly');
$this->assertUrlOutboundAlter('forum', 'community');
$forum_vid = config('forum.settings')->get('vocabulary');
$term_name = $this->randomName();
$tid = db_insert('taxonomy_term_data')
->fields(array(
'name' => $this->randomName(),
'name' => $term_name,
'vid' => $forum_vid,
))
->execute();
$this->assertUrlInboundAlter("community/$tid", "forum/$tid");
$this->drupalGet("community/$tid");
$this->assertText($term_name, 'The community/{tid} path gets resolved correctly');
$this->assertUrlOutboundAlter("forum/$tid", "community/$tid");
}
@ -87,14 +89,6 @@ class UrlAlterFunctionalTest extends WebTestBase {
$this->assertRaw('current_path=url-alter-test/foo', 'current_path() returns the internal path.');
}
/**
* Tests that current_path() is initialized when the request path is empty.
*/
function testGetQInitialized() {
$this->drupalGet('');
$this->assertText("current_path() is non-empty with an empty request path.", 'current_path() is initialized with an empty request path.');
}
/**
* Assert that an outbound path is altered to an expected value.
*
@ -118,7 +112,7 @@ class UrlAlterFunctionalTest extends WebTestBase {
*
* @param $original
* A string with the aliased or un-normal path that is run through
* drupal_get_normal_path().
* drupal_container()->get('path.alias_manager')->getSystemPath().
* @param $final
* A string with the expected result after url().
* @return
@ -126,7 +120,7 @@ class UrlAlterFunctionalTest extends WebTestBase {
*/
protected function assertUrlInboundAlter($original, $final) {
// Test inbound altering.
$result = drupal_get_normal_path($original);
$result = drupal_container()->get('path.alias_manager')->getSystemPath($original);
$this->assertIdentical($result, $final, format_string('Altered inbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $result)));
}
}

View File

@ -1410,10 +1410,11 @@ function system_site_information_settings($form, &$form_state) {
'#type' => 'fieldset',
'#title' => t('Front page'),
);
$front_page = $site_config->get('page.front') != 'user' ? drupal_container()->get('path.alias_manager')->getPathAlias($site_config->get('page.front')) : '';
$form['front_page']['site_frontpage'] = array(
'#type' => 'textfield',
'#title' => t('Default front page'),
'#default_value' => ($site_config->get('page.front') != 'user' ? drupal_get_path_alias($site_config->get('page.front')) : ''),
'#default_value' => $front_page,
'#size' => 40,
'#description' => t('Optionally, specify a relative URL to display as the front page. Leave blank to display the default front page.'),
'#field_prefix' => url(NULL, array('absolute' => TRUE)),
@ -1455,7 +1456,7 @@ function system_site_information_settings_validate($form, &$form_state) {
}
else {
// Get the normal path of the front page.
form_set_value($form['front_page']['site_frontpage'], drupal_get_normal_path($form_state['values']['site_frontpage']), $form_state);
form_set_value($form['front_page']['site_frontpage'], drupal_container()->get('path.alias_manager')->getSystemPath($form_state['values']['site_frontpage']), $form_state);
}
// Validate front page path.
if (!drupal_valid_path($form_state['values']['site_frontpage'])) {
@ -1463,10 +1464,10 @@ function system_site_information_settings_validate($form, &$form_state) {
}
// Get the normal paths of both error pages.
if (!empty($form_state['values']['site_403'])) {
form_set_value($form['error_page']['site_403'], drupal_get_normal_path($form_state['values']['site_403']), $form_state);
form_set_value($form['error_page']['site_403'], drupal_container()->get('path.alias_manager')->getSystemPath($form_state['values']['site_403']), $form_state);
}
if (!empty($form_state['values']['site_404'])) {
form_set_value($form['error_page']['site_404'], drupal_get_normal_path($form_state['values']['site_404']), $form_state);
form_set_value($form['error_page']['site_404'], drupal_container()->get('path.alias_manager')->getSystemPath($form_state['values']['site_404']), $form_state);
}
// Validate 403 error path.
if (!empty($form_state['values']['site_403']) && !drupal_valid_path($form_state['values']['site_403'])) {

View File

@ -3476,7 +3476,7 @@ function hook_system_themes_page_alter(&$theme_groups) {
* @param $path_language
* The language of the path.
*
* @see drupal_get_normal_path()
* @see \Drupal\Core\Path\AliasManager::getSystemPath()
*/
function hook_url_inbound_alter(&$path, $original_path, $path_language) {
// Create the path user/me/edit, which allows a user to edit their account.

View File

@ -0,0 +1,59 @@
<?php
/**
* @file
* Contains Drupal\url_alter_test\PathSubscriber.
*/
namespace Drupal\url_alter_test;
use Drupal\Core\EventSubscriber\PathListenerBase;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Path subscriber for url_alter_test.
*/
class PathSubscriber extends PathListenerBase implements EventSubscriberInterface {
/**
* Resolve the system path based on some arbitrary rules.
*
* @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestPathResolve(GetResponseEvent $event) {
$request = $event->getRequest();
$path = $this->extractPath($request);
// Rewrite user/username to user/uid.
if (preg_match('!^user/([^/]+)(/.*)?!', $path, $matches)) {
if ($account = user_load_by_name($matches[1])) {
$matches += array(2 => '');
$path = 'user/' . $account->uid . $matches[2];
}
}
// Rewrite community/ to forum/.
if ($path == 'community' || strpos($path, 'community/') === 0) {
$path = 'forum' . substr($path, 9);
}
if ($path == 'url-alter-test/bar') {
$path = 'url-alter-test/foo';
}
$this->setPath($request, $path);
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100);
return $events;
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @file
* Contains Drupal\url_alter_test\UrlAlterTestBundle.
*/
namespace Drupal\url_alter_test;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Test bundle class for url_alter_test.
*
* Used to register an event subscriber that resolves a path alias to a system
* path based on an arbitrary set of rules.
*
* @see \Drupal\url_alter_test\PathSubscriber
*/
class UrlAlterTestBundle extends Bundle
{
public function build(ContainerBuilder $container) {
$container->register('url_alter_test.path_subscriber', 'Drupal\url_alter_test\PathSubscriber')
->addTag('event_subscriber');
}
}

View File

@ -26,32 +26,6 @@ function url_alter_test_foo() {
exit;
}
/**
* Implements hook_url_inbound_alter().
*/
function url_alter_test_url_inbound_alter(&$path, $original_path, $path_language) {
if (!request_path() && current_path()) {
drupal_set_message("current_path() is non-empty with an empty request path.");
}
// Rewrite user/username to user/uid.
if (preg_match('!^user/([^/]+)(/.*)?!', $path, $matches)) {
if ($account = user_load_by_name($matches[1])) {
$matches += array(2 => '');
$path = 'user/' . $account->uid . $matches[2];
}
}
// Rewrite community/ to forum/.
if ($path == 'community' || strpos($path, 'community/') === 0) {
$path = 'forum' . substr($path, 9);
}
if ($path == 'url-alter-test/bar') {
$path = 'url-alter-test/foo';
}
}
/**
* Implements hook_url_outbound_alter().
*/

View File

@ -718,7 +718,7 @@ function views_ui_views_analyze($view) {
continue;
}
if ($display->hasPath() && $path = $display->getOption('path')) {
$normal_path = drupal_get_normal_path($path);
$normal_path = drupal_container()->get('path.alias_manager')->getSystemPath($path);
if ($path != $normal_path) {
$ret[] = Analyzer::formatMessage(t('You have configured display %display with a path which is an path alias as well. This might lead to unwanted effects so better use an internal path.', array('%display' => $display['display_title'])), 'warning');
}

View File

@ -252,7 +252,7 @@ for ($i = 0; $i < 12; $i++) {
'alias' => "content/poll/$i/results",
'source' => "node/$node->nid/results",
);
path_save($path);
drupal_container()->get('path.crud')->save($path['source'], $path['alias']);
// Add some votes
$node = node_load($node->nid);