diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index ef53eaf4302482e2614ea16142329bb4f9cd3b6d..a991ed1d414ee3c193d93bc69d16027c85a60ea7 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -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'); } diff --git a/core/includes/common.inc b/core/includes/common.inc index f8a54e97dfdb98680e46a889512ee1bcdf2c5032..1eb3b945d0dec790766dd220d7c8263f92603c2e 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -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') { diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 091d8986e100b70062e8687e5c8e4e02ed051704..2e553665f3d00839e681cd4380c265abc2476d99 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -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'. diff --git a/core/includes/path.inc b/core/includes/path.inc index dc878c0bc7011b7087b275682df221889472b2e5..ba7c0348cf75d687e916e4a9c9eb4cef1d182f28 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -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); -} diff --git a/core/lib/Drupal/Core/CacheDecorator/AliasManagerCacheDecorator.php b/core/lib/Drupal/Core/CacheDecorator/AliasManagerCacheDecorator.php new file mode 100644 index 0000000000000000000000000000000000000000..ae5c8bca4fd0d72dddf49d9daef61baa7445bcdf --- /dev/null +++ b/core/lib/Drupal/Core/CacheDecorator/AliasManagerCacheDecorator.php @@ -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); + } +} diff --git a/core/lib/Drupal/Core/CacheDecorator/CacheDecoratorInterface.php b/core/lib/Drupal/Core/CacheDecorator/CacheDecoratorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..574d81ab26672ce1cd9900fde44803739c7386df --- /dev/null +++ b/core/lib/Drupal/Core/CacheDecorator/CacheDecoratorInterface.php @@ -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(); + +} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 48684e50d0eb2430cf8c883af77f4237025fc0ee..a32bf90b3914ff658b830c0c5f2678702ef02435 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -13,6 +13,7 @@ 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 @@ public function build(ContainerBuilder $container) { $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 @@ public function build(ContainerBuilder $container) { $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'); diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php index 7688107ce30d748045b00a5f6c4e21229e179367..8f0359d3cef3ddcf9cb8a640de1e5147f5f4b41f 100644 --- a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -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 @@ */ 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 @@ static function getSubscribedEvents() { $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; } diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php index 175ce79bb5b3eca801e2ac9dd431801b65feb7e3..8d5f87a6ac46e0d06353ec6932998550adb5bdf1 100644 --- a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php @@ -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 @@ public function onTerminate(PostResponseEvent $event) { * An array of event listener definitions. */ static function getSubscribedEvents() { - $events[KernelEvents::TERMINATE][] = array('onTerminate'); + $events[KernelEvents::TERMINATE][] = array('onTerminate', 100); return $events; } diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php index e311c93aad6e51db60a18c294bf8cde7adb5b262..92d44547045085d38b05a76215b5fb82a46e165d 100644 --- a/core/lib/Drupal/Core/ExceptionController.php +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -101,7 +101,7 @@ public function on403Html(FlattenException $exception, Request $request) { $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 @@ public function on404Html(FlattenException $exception, Request $request) { $_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(). diff --git a/core/lib/Drupal/Core/Path/AliasManager.php b/core/lib/Drupal/Core/Path/AliasManager.php new file mode 100644 index 0000000000000000000000000000000000000000..02433ce126c189e3176a28774f42cd3b85b8da9a --- /dev/null +++ b/core/lib/Drupal/Core/Path/AliasManager.php @@ -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; + } +} diff --git a/core/lib/Drupal/Core/Path/AliasManagerInterface.php b/core/lib/Drupal/Core/Path/AliasManagerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..3dedd3e9737519f0acea1f90db668786ded09070 --- /dev/null +++ b/core/lib/Drupal/Core/Path/AliasManagerInterface.php @@ -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); +} diff --git a/core/lib/Drupal/Core/Path/Path.php b/core/lib/Drupal/Core/Path/Path.php new file mode 100644 index 0000000000000000000000000000000000000000..a7fd55aeade58fd0c57dd34d1813785e75db7004 --- /dev/null +++ b/core/lib/Drupal/Core/Path/Path.php @@ -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; + } +} diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 2978146d45f4a8488910ad805946384a46177185..7340c6bb87198127b37d2b90f913ac33387fb602 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -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. diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php index bafb62078dcc86515cda9e8a8ce6d71daef70903..d1ad5f069529a208292c10823fe70d5d8d912546 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocalePathTest.php @@ -109,13 +109,13 @@ function testPathLanguageConfiguration() { '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 @@ function testPathLanguageConfiguration() { '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 @@ function testPathLanguageConfiguration() { '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>'); diff --git a/core/modules/menu/menu.admin.inc b/core/modules/menu/menu.admin.inc index 3b67b97e59da79fdb8fde1af92051c2a5eb3b9fb..605f8983690c8c72f663760c1cc647ed5d49463e 100644 --- a/core/modules/menu/menu.admin.inc +++ b/core/modules/menu/menu.admin.inc @@ -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; diff --git a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php index 181bd727248b762c63ebcb76b5da9f2fbaf29a7c..416827518eee64ee800a5df3feb36b8f2eec4f78 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php @@ -57,9 +57,7 @@ function testPathCache() { // 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 @@ function testAdminAlias() { $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.'); diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php index e6fd10decec08b15136fe5b70d2cb0084bcba2e3..83be7b5b6669e8a6e203106b586b8e2bb6c6dde8 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php @@ -82,7 +82,7 @@ function testAliasTranslation() { $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 @@ function testAliasTranslation() { // 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 @@ function testAliasTranslation() { $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.'); } } diff --git a/core/modules/path/path.admin.inc b/core/modules/path/path.admin.inc index 32c5e4d510209c1c672c23137e4594c7ad860aa4..8c87dee0f55acdbfe67987a09482a430db13a87e 100644 --- a/core/modules/path/path.admin.inc +++ b/core/modules/path/path.admin.inc @@ -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'; } } diff --git a/core/modules/path/path.api.php b/core/modules/path/path.api.php index f2c5ecef745e02680c7344979eb924c1302c12c7..c69b97d1fc7278ac7a4fef4f9946d871bd1bc677 100644 --- a/core/modules/path/path.api.php +++ b/core/modules/path/path.api.php @@ -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') diff --git a/core/modules/path/path.module b/core/modules/path/path.module index 942fbb65dfd69b1080f56b3ba3972a7fb98441ae..dcbe3be888e4eff70390a50cec277a2ef3d3c6f8 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -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)); } /** diff --git a/core/modules/search/search.module b/core/modules/search/search.module index a32b5c30d40a9f7a49193c3aac80ddda0a9f2b55..866536ed6e30e4c66b88cdd085adc85b6d1e5111 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -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) { diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php index 54eeab44de9d66a2858218f0aa875a42ed1ab841..ccf9834af4cc7d149f5b13b1a97553d22f9fe010 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php @@ -31,7 +31,7 @@ function testShortcutLinkAdd() { '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 @@ function testShortcutLinkAdd() { $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.'); } } diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc index e805acfc014ad6b98a6f150cb04f721ae2f5cde9..18ec1c3a8ed248d0e5dd05afb0f727aac6e93f7f 100644 --- a/core/modules/shortcut/shortcut.admin.inc +++ b/core/modules/shortcut/shortcut.admin.inc @@ -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>'; } diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 9bbb12a81e70d216170eae035c01f70f7da1cdc0..2a079898faf08372c4f6ce35efa8547c745d0771 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -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; } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 7d14ca029434c23a1fa28f0d5b054ab6e1b507cc..964b16ba50d2705113b01d97a38433dd643cbd95 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -760,6 +760,12 @@ protected function setUp() { 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; } diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index db45bee615383b46a25457ec97d91e462468e013..a3599818a82fe89f8f6a5a828003d9ce4407b13e 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -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); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php index f52907f7ab939c6f5de6d6c58c69015ebde84930..7ce84cbf6418687e44a8d98c50f7da0e5d579aea 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php @@ -176,7 +176,7 @@ function testHeaderSetting() { $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'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bbca543755f8845bc1aebd24a5d23d7fbd60e01f --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php @@ -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.'); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/LookupTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/LookupTest.php deleted file mode 100644 index e319b9459b04840737d8c42ea81f33881c338d9c..0000000000000000000000000000000000000000 --- a/core/modules/system/lib/Drupal/system/Tests/Path/LookupTest.php +++ /dev/null @@ -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.'); - } -} diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/SaveTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/SaveTest.php deleted file mode 100644 index b95893a233276e7a9ffefa3accf3f77349a58fc2..0000000000000000000000000000000000000000 --- a/core/modules/system/lib/Drupal/system/Tests/Path/SaveTest.php +++ /dev/null @@ -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.'); - } -} diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/UrlAliasFixtures.php b/core/modules/system/lib/Drupal/system/Tests/Path/UrlAliasFixtures.php new file mode 100644 index 0000000000000000000000000000000000000000..6fb02da4992743363c9fd099a6a17fd41f0e0554 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Path/UrlAliasFixtures.php @@ -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; + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php index 72091e514a46adae459154518d59153384044c7c..353ba1897adc32d917ae8e3259cf7cf8fac5b714 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php @@ -40,41 +40,43 @@ function testUrlAlter() { $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 @@ function testCurrentUrlRequestedPath() { $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 @@ protected function assertUrlOutboundAlter($original, $final) { * * @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 @@ protected function assertUrlOutboundAlter($original, $final) { */ 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))); } } diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 3a07fa2fee447159b7909f1dbf4fd5fec78ef169..25691bd692abf8fcb208a3f8e6f1c68b7abfc2d3 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -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'])) { diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index e9c086086e73caca533de1de673b6d5795bfb79b..239d3a27dd9c108f0fa8840cf42e345309f583b7 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -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. diff --git a/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathSubscriber.php b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..3f4f728a91aeabfe81136fa674b41858e2ecaaaa --- /dev/null +++ b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathSubscriber.php @@ -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; + } +} diff --git a/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/UrlAlterTestBundle.php b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/UrlAlterTestBundle.php new file mode 100644 index 0000000000000000000000000000000000000000..cd028e8deadd162646ddecdfe9f43ca1b5c117a4 --- /dev/null +++ b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/UrlAlterTestBundle.php @@ -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'); + } +} diff --git a/core/modules/system/tests/modules/url_alter_test/url_alter_test.module b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module index a5ca13deb4c67c8e3a156a955092b5fb4a87f821..61cc625a79070a852932757eb73fa3a12acfa159 100644 --- a/core/modules/system/tests/modules/url_alter_test/url_alter_test.module +++ b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module @@ -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(). */ diff --git a/core/modules/views/views_ui/views_ui.module b/core/modules/views/views_ui/views_ui.module index b7040acda179a3687736f08b384137ccc449f296..9a814d53c04516681d59efb7261fd69fd5d6e07c 100644 --- a/core/modules/views/views_ui/views_ui.module +++ b/core/modules/views/views_ui/views_ui.module @@ -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'); } diff --git a/core/scripts/generate-d7-content.sh b/core/scripts/generate-d7-content.sh index acfc1a3168de82e9c99bb7ecaf9ffc638640f89f..5f03ab2209a58a836f451b4411a30a69a89d1a3f 100644 --- a/core/scripts/generate-d7-content.sh +++ b/core/scripts/generate-d7-content.sh @@ -252,7 +252,7 @@ '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);