Commit b2194392 authored by alexpott's avatar alexpott
Browse files

Issue #1888424 by katbailey, steveoliver, twistor, beejeebus, effulgentsia:...

Issue #1888424 by katbailey, steveoliver, twistor, beejeebus, effulgentsia: Change notice: Make Drupal's URL generation logic available to HttpKernel, and minimize code repetition/divergence.
parent 9d735990
......@@ -198,12 +198,16 @@ services:
arguments: ['@router.route_provider']
calls:
- [setFinalMatcher, ['@router.matcher.final_matcher']]
router.generator:
url_generator:
class: Drupal\Core\Routing\UrlGenerator
arguments: ['@router.route_provider', '@path.alias_manager.cached']
arguments: ['@router.route_provider', '@path_processor_manager', '@config.factory', '@settings']
calls:
- [setRequest, ['@?request']]
tags:
- { name: persist }
router.dynamic:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments: ['@router.request_context', '@router.matcher', '@router.generator']
arguments: ['@router.request_context', '@router.matcher', '@url_generator']
legacy_generator:
class: Drupal\Core\Routing\NullGenerator
legacy_url_matcher:
......@@ -390,11 +394,13 @@ services:
class: Drupal\Core\PathProcessor\PathProcessorFront
tags:
- { name: path_processor_inbound, priority: 200 }
- { name: path_processor_outbound, priority: 200 }
arguments: ['@config.factory']
path_processor_alias:
class: Drupal\Core\PathProcessor\PathProcessorAlias
tags:
- { name: path_processor_inbound, priority: 100 }
- { name: path_processor_outbound, priority: 300 }
arguments: ['@path.alias_manager']
transliteration:
class: Drupal\Core\Transliteration\PHPTransliteration
......
......@@ -13,6 +13,7 @@
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Database\Database;
use Drupal\Core\Routing\GeneratorNotInitializedException;
use Drupal\Core\SystemListingInfo;
use Drupal\Core\Template\Attribute;
......@@ -498,42 +499,14 @@ function drupal_get_query_array($query) {
/**
* Parses an array into a valid, rawurlencoded query string.
*
* This differs from http_build_query() as we need to rawurlencode() (instead of
* urlencode()) all query parameters.
*
* @param $query
* The query parameter array to be processed, e.g. $_GET.
* @param $parent
* Internal use only. Used to build the $query array key for nested items.
*
* @return
* A rawurlencoded string which can be used as or appended to the URL query
* string.
*
* @see \Drupal\Core\Routing\PathBasedGeneratorInterface::httpBuildQuery()
* @see drupal_get_query_parameters()
* @deprecated as of Drupal 8.0. Use
* Drupal::urlGenerator()->httpBuildQuery() instead.
* @ingroup php_wrappers
*/
function drupal_http_build_query(array $query, $parent = '') {
$params = array();
foreach ($query as $key => $value) {
$key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
// Recurse into children.
if (is_array($value)) {
$params[] = drupal_http_build_query($value, $key);
}
// If a query parameter value is NULL, only append its key.
elseif (!isset($value)) {
$params[] = $key;
}
else {
// For better readability of paths in query strings, we decode slashes.
$params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
}
}
return implode('&', $params);
return Drupal::urlGenerator()->httpBuildQuery($query, $parent);
}
/**
......@@ -564,7 +537,7 @@ function drupal_get_destination() {
}
else {
$path = current_path();
$query = drupal_http_build_query(drupal_get_query_parameters());
$query = Drupal::urlGenerator()->httpBuildQuery(drupal_get_query_parameters());
if ($query != '') {
$path .= '?' . $query;
}
......@@ -725,8 +698,7 @@ function drupal_goto($path = '', array $options = array(), $http_response_code =
// The 'Location' HTTP header must be absolute.
$options['absolute'] = TRUE;
$url = url($path, $options);
$url = Drupal::urlGenerator()->generateFromPath($path, $options);
header('Location: ' . $url, TRUE, $http_response_code);
// The "Location" header sends a redirect status code to the HTTP daemon. In
......@@ -1423,153 +1395,24 @@ function datetime_default_format_type() {
* When creating links in modules, consider whether l() could be a better
* alternative than url().
*
* @param $path
* (optional) The internal path or external URL being linked to, such as
* "node/34" or "http://example.com/foo". The default value is equivalent to
* passing in '<front>'. A few notes:
* - If you provide a full URL, it will be considered an external URL.
* - If you provide only the path (e.g. "node/34"), it will be
* considered an internal link. In this case, it should be a system URL,
* and it will be replaced with the alias, if one exists. Additional query
* arguments for internal paths must be supplied in $options['query'], not
* included in $path.
* - If you provide an internal path and $options['alias'] is set to TRUE, the
* path is assumed already to be the correct path alias, and the alias is
* not looked up.
* - The special string '<front>' generates a link to the site's base URL.
* - If your external URL contains a query (e.g. http://example.com/foo?a=b),
* then you can either URL encode the query keys and values yourself and
* include them in $path, or use $options['query'] to let this function
* URL encode them.
* @param $options
* (optional) An associative array of additional options, with the following
* elements:
* - 'query': An array of query key/value-pairs (without any URL-encoding) to
* append to the URL.
* - 'fragment': A fragment identifier (named anchor) to append to the URL.
* Do not include the leading '#' character.
* - 'absolute': Defaults to FALSE. Whether to force the output to be an
* absolute link (beginning with http:). Useful for links that will be
* displayed outside the site, such as in an RSS feed.
* - 'alias': Defaults to FALSE. Whether the given path is a URL alias
* already.
* - 'external': Whether the given path is an external URL.
* - 'language': An optional language object. If the path being linked to is
* internal to the site, $options['language'] is used to look up the alias
* for the URL. If $options['language'] is omitted, the language will be
* obtained from language(Language::TYPE_URL).
* - 'https': Whether this URL should point to a secure location. If not
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can
* only be enforced when the variable 'https' is set to TRUE.
* - 'base_url': Only used internally, to modify the base URL when a language
* dependent URL requires so.
* - 'prefix': Only used internally, to modify the path when a language
* dependent URL requires so.
* - 'script': Added to the URL between the base path and the path prefix.
* Defaults to empty string when clean URLs are in effect, and to
* 'index.php/' when they are not.
* - 'entity_type': The entity type of the object that called url(). Only
* set if url() is invoked by Drupal\Core\Entity\Entity::uri().
* - 'entity': The entity object (such as a node) for which the URL is being
* generated. Only set if url() is invoked by Drupal\Core\Entity\Entity::uri().
*
* @return
* A string containing a URL to the given path.
* @see \Drupal\Core\Routing\PathBasedGeneratorInterface::generateFromPath().
*/
function url($path = NULL, array $options = array()) {
// Merge in defaults.
$options += array(
'fragment' => '',
'query' => array(),
'absolute' => FALSE,
'alias' => FALSE,
'prefix' => '',
'script' => $GLOBALS['script_path'],
);
if (!isset($options['external'])) {
// Return an external link if $path contains an allowed absolute URL. Only
// call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
// before any / ? or #. Note: we could use url_is_external($path) here, but
// that would require another function call, and performance inside url() is
// critical.
$colonpos = strpos($path, ':');
$options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
}
// Preserve the original path before altering or aliasing.
$original_path = $path;
// Allow other modules to alter the outbound URL and options.
drupal_alter('url_outbound', $path, $options, $original_path);
if (isset($options['fragment']) && $options['fragment'] !== '') {
$options['fragment'] = '#' . $options['fragment'];
}
if ($options['external']) {
// Split off the fragment.
if (strpos($path, '#') !== FALSE) {
list($path, $old_fragment) = explode('#', $path, 2);
// If $options contains no fragment, take it over from the path.
if (isset($old_fragment) && !$options['fragment']) {
$options['fragment'] = '#' . $old_fragment;
}
}
// Append the query.
if ($options['query']) {
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']);
}
if (isset($options['https']) && settings()->get('mixed_mode_sessions', FALSE)) {
if ($options['https'] === TRUE) {
$path = str_replace('http://', 'https://', $path);
}
elseif ($options['https'] === FALSE) {
$path = str_replace('https://', 'http://', $path);
}
}
// Reassemble.
return $path . $options['fragment'];
}
global $base_url, $base_secure_url, $base_insecure_url;
// The base_url might be rewritten from the language rewrite in domain mode.
if (!isset($options['base_url'])) {
if (isset($options['https']) && settings()->get('mixed_mode_sessions', FALSE)) {
if ($options['https'] === TRUE) {
$options['base_url'] = $base_secure_url;
$options['absolute'] = TRUE;
}
elseif ($options['https'] === FALSE) {
$options['base_url'] = $base_insecure_url;
$options['absolute'] = TRUE;
}
}
else {
$options['base_url'] = $base_url;
}
}
// The special path '<front>' links to the default front page.
if ($path == '<front>') {
$path = '';
}
elseif (!empty($path) && !$options['alias']) {
$langcode = isset($options['language']) && isset($options['language']->langcode) ? $options['language']->langcode : '';
$alias = drupal_container()->get('path.alias_manager')->getPathAlias($original_path, $langcode);
if ($alias != $original_path) {
$path = $alias;
}
}
$base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
$prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
$path = drupal_encode_path($prefix . $path);
$query = $options['query'] ? ('?' . drupal_http_build_query($options['query'])) : '';
return $base . $options['script'] . $path . $query . $options['fragment'];
$generator = Drupal::urlGenerator();
try {
$url = $generator->generateFromPath($path, $options);
}
catch (GeneratorNotInitializedException $e) {
// Fallback to using globals.
// @todo Remove this once there is no code that calls url() when there is
// no request.
global $base_url, $base_path, $script_path;
$generator->setBasePath($base_path);
$generator->setBaseUrl($base_url . '/');
$generator->setScriptPath($script_path);
$url = $generator->generateFromPath($path, $options);
}
return $url;
}
/**
......
......@@ -1320,7 +1320,8 @@ function drupal_redirect_form($form_state) {
$function($form_state['redirect']);
}
}
drupal_goto(current_path(), array('query' => drupal_container()->get('request')->query->all()));
$request = Drupal::request();
drupal_goto($request->attributes->get('system_path'), array('query' => $request->query->all()));
}
}
......
......@@ -410,6 +410,8 @@ function install_begin_request(&$install_state) {
// Register Twig template engine for use during install.
CoreBundle::registerTwig($container);
$container->register('url_generator', 'Drupal\Core\Routing\NullGenerator');
Drupal::setContainer($container);
}
......@@ -1948,7 +1950,14 @@ function install_finished(&$install_state) {
$messages = drupal_set_message();
$output = '<p>' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>';
$output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => url(''))) : st('<a href="@url">Visit your new site</a>.', array('@url' => url('')))) . '</p>';
// Ensure the URL that is generated for the home page does not have 'install.php'
// in it.
$request = Request::createFromGlobals();
$generator = Drupal::urlGenerator();
$generator->setBasePath(str_replace('/core', '', $request->getBasePath()) . '/');
$generator->setScriptPath('');
$url = $generator->generateFromPath('');
$output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => $url)) : st('<a href="@url">Visit your new site</a>.', array('@url' => $url))) . '</p>';
// Run cron to populate update status tables (if available) so that users
// will be warned if they've installed an out of date Drupal version.
......
......@@ -361,4 +361,14 @@ public static function token() {
return static::$container->get('token');
}
/**
* Returns the url generator service.
*
* @return \Drupal\Core\Routing\UrlGenerator
* The url generator service.
*/
public static function urlGenerator() {
return static::$container->get('url_generator');
}
}
......@@ -27,9 +27,15 @@ public function process(ContainerBuilder $container) {
return;
}
$manager = $container->getDefinition('path_processor_manager');
// Add inbound path processors.
foreach ($container->findTaggedServiceIds('path_processor_inbound') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addInbound', array(new Reference($id), $priority));
}
// Add outbound path processors.
foreach ($container->findTaggedServiceIds('path_processor_outbound') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addOutbound', array(new Reference($id), $priority));
}
}
}
......@@ -9,6 +9,7 @@
use Drupal\Core\CacheDecorator\AliasManagerCacheDecorator;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Routing\PathBasedGeneratorInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
......@@ -20,7 +21,18 @@
*/
class PathSubscriber extends PathListenerBase implements EventSubscriberInterface {
/**
* The alias manager that caches alias lookups based on the request.
*
* @var \Drupal\Core\CacheDecorator\AliasManagerCacheDecorator
*/
protected $aliasManager;
/**
* A path processor manager for resolving the system path.
*
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
*/
protected $pathProcessor;
public function __construct(AliasManagerCacheDecorator $alias_manager, InboundPathProcessorInterface $path_processor) {
......@@ -39,6 +51,14 @@ public function onKernelRequestConvertPath(GetResponseEvent $event) {
$path = trim($request->getPathInfo(), '/');
$path = $this->pathProcessor->processInbound($path, $request);
$request->attributes->set('system_path', $path);
// Also set an attribute that indicates whether we are using clean URLs.
$clean_urls = TRUE;
$base_url = $request->getBaseUrl();
if (!empty($base_url) && strpos($base_url, $request->getScriptName()) !== FALSE) {
$clean_urls = FALSE;
}
$request->attributes->set('clean_urls', $clean_urls);
// Set the cache key on the alias manager cache decorator.
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$this->aliasManager->setCacheKey($path);
}
......
......@@ -134,7 +134,7 @@ public function reset($type = NULL) {
* @return bool
* TRUE if more than one language is enabled, FALSE otherwise.
*/
protected function isMultilingual() {
public function isMultilingual() {
return variable_get('language_count', 1) > 1;
}
......
<?php
/**
* @file
* Contains Drupal\Core\PathProcessor\OutboundPathProcessorInterface.
*/
namespace Drupal\Core\PathProcessor;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines an interface for classes that process the outbound path.
*/
interface OutboundPathProcessorInterface {
/**
* Processes the outbound path.
*
* @param string $path
* The path to process.
*
* @param array $options
* An array of options such as would be passed to the generator's
* generateFromPath() method.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request.
*
* @return
* The processed path.
*/
public function processOutbound($path, &$options = array(), Request $request = NULL);
}
......@@ -13,7 +13,7 @@
/**
* Processes the inbound path using path alias lookups.
*/
class PathProcessorAlias implements InboundPathProcessorInterface {
class PathProcessorAlias implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
/**
* An alias manager for looking up the system path.
......@@ -40,4 +40,12 @@ public function processInbound($path, Request $request) {
return $path;
}
/**
* Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
*/
public function processOutbound($path, &$options = array(), Request $request = NULL) {
$langcode = isset($options['language']) ? $options['language']->langcode : NULL;
$path = $this->aliasManager->getPathAlias($path, $langcode);
return $path;
}
}
......@@ -13,7 +13,7 @@
/**
* Processes the inbound path by resolving it to the front page if empty.
*/
class PathProcessorFront implements InboundPathProcessorInterface {
class PathProcessorFront implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
/**
* A config factory for retrieving required config settings.
......@@ -45,4 +45,15 @@ public function processInbound($path, Request $request) {
return $path;
}
/**
* Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
*/
public function processOutbound($path, &$options = array(), Request $request = NULL) {
// The special path '<front>' links to the default front page.
if ($path == '<front>') {
$path = '';
}
return $path;
}
}
......@@ -7,7 +7,6 @@
namespace Drupal\Core\PathProcessor;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
......@@ -16,10 +15,10 @@
* Holds an array of path processor objects and uses them to sequentially process
* a path, in order of processor priority.
*/
class PathProcessorManager implements InboundPathProcessorInterface {
class PathProcessorManager implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
/**
* Holds the array of processors to cycle through.
* Holds the array of inbound processors to cycle through.
*
* @var array
* An array whose keys are priorities and whose values are arrays of path
......@@ -28,13 +27,31 @@ class PathProcessorManager implements InboundPathProcessorInterface {
protected $inboundProcessors = array();
/**
* Holds the array of processors, sorted by priority.
* Holds the array of inbound processors, sorted by priority.
*
* @var array
* An array of path processor objects.
*/
protected $sortedInbound = array();
/**
* Holds the array of outbound processors to cycle through.
*
* @var array
* An array whose keys are priorities and whose values are arrays of path
* processor objects.
*/
protected $outboundProcessors = array();
/**
* Holds the array of outbound processors, sorted by priority.
*
* @var array
* An array of path processor objects.
*/
protected $sortedOutbound = array();
/**
* Adds an inbound processor object to the $inboundProcessors property.
*
......@@ -74,6 +91,46 @@ protected function getInbound() {
return $this->sortedInbound;
}
/**
* Adds an outbound processor object to the $outboundProcessors property.
*
* @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $processor
* The processor object to add.
*
* @param int $priority
* The priority of the processor being added.
*/
public function addOutbound(OutboundPathProcessorInterface $processor, $priority = 0) {
$this->outboundProcessors[$priority][] = $processor;
$this->sortedOutbound = array();
}
/**
* Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
*/
public function processOutbound($path, &$options = array(), Request $request = NULL) {
$processors = $this->getOutbound();
foreach ($processors as $processor) {
$path = $processor->processOutbound($path, $options, $request);
}
return $path;
}
/**
* Returns the sorted array of outbound processors.
*
* @return array
* An array of processor objects.
*/
protected function getOutbound() {
if (empty($this->sortedOutbound)) {
$this->sortedOutbound = $this->sortProcessors('outboundProcessors');
}
return $this->sortedOutbound;
}
/**
* Sorts the processors according to priority.
*
......
<?php
/**
* @file
* Definition of Drupal\Core\Routing\GeneratorNotInitializedException.
*/
namespace Drupal\Core\Routing;
use Exception;
/**
* Class for exceptions thrown when the generator has not been initialized.
*/
class GeneratorNotInitializedException extends Exception { }
......@@ -6,6 +6,7 @@
*/
namespace Drupal\Core\Routing;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
......@@ -13,17 +14,23 @@
/**
* No-op implementation of a Url Generator, needed for backward compatibility.
*/
class NullGenerator implements UrlGeneratorInterface {