diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 0d07356..e3e9acf 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -2194,6 +2194,7 @@ function drupal_handle_request($test_only = FALSE) { // Create a request object from the HttpFoundation. $request = Request::createFromGlobals(); + $response = $kernel->handle($request)->prepare($request)->send(); $kernel->terminate($request, $response); diff --git a/core/includes/common.inc b/core/includes/common.inc index 14e4357..d6bdf05 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -475,42 +475,12 @@ 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() * @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_container()->get('router.generator')->httpBuildQuery($query, $parent); } /** @@ -1211,49 +1181,11 @@ function valid_number_step($value, $step, $offset = 0.0) { * Drupal\Core\Template\Attribute, or another function that will call * check_plain() separately. * - * @param $uri - * A plain-text URI that might contain dangerous protocols. - * - * @return - * A plain-text URI stripped of dangerous protocols. As with all plain-text - * strings, this return value must not be output to an HTML page without - * check_plain() being called on it. However, it can be passed to functions - * expecting plain-text strings. - * + * @see \Drupal\Core\Routing\PathBasedGeneratorInterface::stripDisallowedProtocols() * @see check_url() */ function drupal_strip_dangerous_protocols($uri) { - static $allowed_protocols; - - if (!isset($allowed_protocols)) { - // filter_xss_admin() is called by the installer and update.php, in which - // case the configuration may not exist (yet). Provide a minimal default set - // of allowed protocols for these cases. - $allowed_protocols = array_flip(config('system.filter')->get('protocols') ?: array('http', 'https')); - } - - // Iteratively remove any invalid protocol found. - do { - $before = $uri; - $colonpos = strpos($uri, ':'); - if ($colonpos > 0) { - // We found a colon, possibly a protocol. Verify. - $protocol = substr($uri, 0, $colonpos); - // If a colon is preceded by a slash, question mark or hash, it cannot - // possibly be part of the URL scheme. This must be a relative URL, which - // inherits the (safe) protocol of the base document. - if (preg_match('![/?#]!', $protocol)) { - break; - } - // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 - // (URI Comparison) scheme comparison must be case-insensitive. - if (!isset($allowed_protocols[strtolower($protocol)])) { - $uri = substr($uri, $colonpos + 1); - } - } - } while ($before != $uri); - - return $uri; + return drupal_container()->get('router.generator')->stripDisallowedProtocols($uri); } /** @@ -1274,7 +1206,7 @@ function drupal_strip_dangerous_protocols($uri) { * @see drupal_strip_dangerous_protocols() */ function check_url($uri) { - return check_plain(drupal_strip_dangerous_protocols($uri)); + return check_plain(drupal_container()->get('router.generator')->stripDisallowedProtocols($uri)); } /** @@ -1553,7 +1485,7 @@ function filter_xss_bad_protocol($string, $decode = TRUE) { $string = decode_entities($string); } - return check_plain(drupal_strip_dangerous_protocols($string)); + return check_plain(drupal_container()->get('router.generator')->stripDisallowedProtocols($string)); } /** @@ -1988,155 +1920,13 @@ 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 ''. 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 '' 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']) && variable_get('https', 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']) && variable_get('https', 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 '' links to the default front page. - if ($path == '') { - $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']; + return drupal_container()->get('router.generator')->generateFromPath($path, $options); } + /** * Returns TRUE if a path is external to Drupal (e.g. http://example.com). * diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 34a9940..9afd001 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -374,6 +374,8 @@ function install_begin_request(&$install_state) { )) ->addMethodCall('setUserAgent', array('Drupal (+http://drupal.org/)')); + $container->register('router.generator', 'Drupal\Core\Routing\NullGenerator') + ->addArgument(new Reference('config.factory')); drupal_container($container); } diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 6f7ff31..154c625 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -266,6 +266,7 @@ public function build(ContainerBuilder $container) { $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber') ->addArgument(new Reference('path.alias_manager.cached')) ->addArgument(new Reference('path_processor_manager')) + ->addArgument(new Reference('router.generator')) ->addTag('event_subscriber'); $container->register('legacy_request_subscriber', 'Drupal\Core\EventSubscriber\LegacyRequestSubscriber') ->addTag('event_subscriber'); @@ -349,13 +350,16 @@ protected function registerRouting(ContainerBuilder $container) { ->addMethodCall('setFinalMatcher', array(new Reference('router.matcher.final_matcher'))); $container->register('router.generator', 'Drupal\Core\Routing\UrlGenerator') ->addArgument(new Reference('router.route_provider')) - ->addArgument(new Reference('path.alias_manager.cached')); + ->addArgument(new Reference('path_processor_manager')) + ->addArgument(NULL) + ->addArgument(new Reference('config.factory')); $container->register('router.dynamic', 'Symfony\Cmf\Component\Routing\DynamicRouter') ->addArgument(new Reference('router.request_context')) ->addArgument(new Reference('router.matcher')) ->addArgument(new Reference('router.generator')); - $container->register('legacy_generator', 'Drupal\Core\Routing\NullGenerator'); + $container->register('legacy_generator', 'Drupal\Core\Routing\NullGenerator') + ->addArgument(new Reference('config.factory')); $container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher'); $container->register('legacy_router', 'Symfony\Cmf\Component\Routing\DynamicRouter') ->addArgument(new Reference('router.request_context')) @@ -411,11 +415,13 @@ protected function registerPathProcessors(ContainerBuilder $container) { // Register the processor that resolves the front page. $container->register('path_processor_front', 'Drupal\Core\PathProcessor\PathProcessorFront') ->addArgument(new Reference('config.factory')) - ->addTag('path_processor_inbound', array('priority' => 200)); + ->addTag('path_processor_inbound', array('priority' => 200)) + ->addTag('path_processor_outbound', array('priority' => 200)); // Register the alias path processor. $container->register('path_processor_alias', 'Drupal\Core\PathProcessor\PathProcessorAlias') ->addArgument(new Reference('path.alias_manager')) - ->addTag('path_processor_inbound', array('priority' => 100)); + ->addTag('path_processor_inbound', array('priority' => 100)) + ->addTag('path_processor_outbound', array('priority' => 300)); // Add the compiler pass that will process the tagged services. $container->addCompilerPass(new RegisterPathProcessorsPass()); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php index 6e298da..70de42d 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php @@ -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)); + } } } diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php index 5915d4b..34fab09 100644 --- a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -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; @@ -22,10 +23,12 @@ class PathSubscriber extends PathListenerBase implements EventSubscriberInterfac protected $aliasManager; protected $pathProcessor; + protected $generator; - public function __construct(AliasManagerCacheDecorator $alias_manager, InboundPathProcessorInterface $path_processor) { + public function __construct(AliasManagerCacheDecorator $alias_manager, InboundPathProcessorInterface $path_processor, PathBasedGeneratorInterface $generator) { $this->aliasManager = $alias_manager; $this->pathProcessor = $path_processor; + $this->generator = $generator; } /** @@ -42,6 +45,7 @@ public function onKernelRequestConvertPath(GetResponseEvent $event) { if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { $this->aliasManager->setCacheKey($path); } + $this->generator->setRequest($request); } /** diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index fa94205..c6b337c 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -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; } diff --git a/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php new file mode 100644 index 0000000..327fe3e --- /dev/null +++ b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php @@ -0,0 +1,31 @@ +aliasManager->getPathAlias($path); + return $path; + } } diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php index 872885a..d03d19b 100644 --- a/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php +++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php @@ -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 '' links to the default front page. + if ($path == '') { + $path = ''; + } + return $path; + } + } diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php index 1d0adff..db8b799 100644 --- a/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php +++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php @@ -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. * diff --git a/core/lib/Drupal/Core/Routing/NullGenerator.php b/core/lib/Drupal/Core/Routing/NullGenerator.php index 7228514..9a4c127 100644 --- a/core/lib/Drupal/Core/Routing/NullGenerator.php +++ b/core/lib/Drupal/Core/Routing/NullGenerator.php @@ -6,6 +6,8 @@ */ namespace Drupal\Core\Routing; + +use Drupal\Core\Config\ConfigFactory; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -13,17 +15,31 @@ /** * No-op implementation of a Url Generator, needed for backward compatibility. */ -class NullGenerator implements UrlGeneratorInterface { +class NullGenerator extends UrlGenerator { + + /** + * A config factory for retrieving required config settings. + * + * @var \Drupal\Core\Config\ConfigFactory + */ + protected $config; + + /** + * Override the parent constructor. + */ + public function __construct(ConfigFactory $config) { + $this->config = $config; + } /** - * Implements Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(); + * Overrides Drupal\Core\Routing\UrlGenerator::generate(); */ public function generate($name, $parameters = array(), $absolute = FALSE) { throw new RouteNotFoundException(); } /** - * Implements Symfony\Component\Routing\RequestContextAwareInterface::setContext(); + * Overrides Drupal\Core\Routing\UrlGenerator::setContext(); */ public function setContext(RequestContext $context) { } @@ -33,4 +49,11 @@ public function setContext(RequestContext $context) { */ public function getContext() { } + + /** + * Overrides Drupal\Core\Routing\UrlGenerator::processPath(). + */ + protected function processPath($path, &$options = array()) { + return $path; + } } diff --git a/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php new file mode 100644 index 0000000..e497295 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php @@ -0,0 +1,119 @@ +'. 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 '' 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. + */ + public function generateFromPath($path = NULL, $options = array()); + + /** + * Sets the $request property. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The HttpRequest object representing the current request. + */ + public function setRequest(Request $request); + + /** + * 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_get_query_parameters() + * @ingroup php_wrappers + */ + public function httpBuildQuery(array $query, $parent = ''); + + /** + * Strips dangerous protocols (e.g. 'javascript:') from a URI. + * + * @param $uri + * A plain-text URI that might contain dangerous protocols. + * + * @return + * A plain-text URI stripped of dangerous protocols. As with all plain-text + * strings, this return value must not be output to an HTML page without + * check_plain() being called on it. However, it can be passed to functions + * expecting plain-text strings. + * + */ + public function stripDisallowedProtocols($uri); + +} diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php index 75779a2..518c02d 100644 --- a/core/lib/Drupal/Core/Routing/UrlGenerator.php +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php @@ -7,24 +7,40 @@ namespace Drupal\Core\Routing; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Cmf\Component\Routing\ProviderBasedGenerator; use Symfony\Cmf\Component\Routing\RouteProviderInterface; -use Drupal\Core\Path\AliasManagerInterface; +use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; /** * A Generator creates URL strings based on a specified route. */ -class UrlGenerator extends ProviderBasedGenerator { +class UrlGenerator extends ProviderBasedGenerator implements PathBasedGeneratorInterface { /** - * The alias manager that will be used to alias generated URLs. + * A request object. * - * @var AliasManagerInterface + * @var \Symfony\Component\HttpFoundation\Request */ - protected $aliasManager; + protected $request; + + /** + * The path processor to convert the system path to one suitable for urls. + * + * @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface + */ + protected $pathProcessor; + + /** + * A config factory for retrieving required config settings. + * + * @var \Drupal\Core\Config\ConfigFactory + */ + protected $config; /** * Constructs a new generator object. @@ -36,22 +52,188 @@ class UrlGenerator extends ProviderBasedGenerator { * @param \Symfony\Component\HttpKernel\Log\LoggerInterface $logger * An optional logger for recording errors. */ - public function __construct(RouteProviderInterface $provider, AliasManagerInterface $alias_manager, LoggerInterface $logger = NULL) { + public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, LoggerInterface $logger = NULL, ConfigFactory $config) { parent::__construct($provider, $logger); - $this->aliasManager = $alias_manager; + $this->pathProcessor = $path_processor; + $this->config = $config; } /** - * Implements Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(); + * Implements Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate(). */ public function generate($name, $parameters = array(), $absolute = FALSE) { $path = parent::generate($name, $parameters, $absolute); + $path = $this->processPath($path); + + return $path; + } + + /** + * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setRequest(). + */ + public function setRequest(Request $request) { + $this->request = $request; + } + + /** + * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::generateFromPath(). + */ + public function generateFromPath($path = NULL, $options = array()) { + // Merge in defaults. + $options += array( + 'fragment' => '', + 'query' => array(), + 'absolute' => 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)) && $this->stripDisallowedProtocols($path) == $path); + } - // This method is expected to return a path with a leading /, whereas - // the alias manager has no leading /. - $path = '/' . $this->aliasManager->getPathAlias(trim($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 ? '&' : '?') . $this->httpBuildQuery($options['query']); + } + if (isset($options['https']) && variable_get('https', 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']; + } + else { + $path = ltrim($this->processPath($path, $options), '/'); + } + + if (isset($this->request)) { + $base_url = $this->request->getSchemeAndHttpHost() . $this->request->getBaseUrl(); + $base_secure_url = str_replace('http://', 'https://', $base_url); + $base_insecure_url = str_replace('https://', 'http://', $base_url); + } + else { + // @todo Figure out how to deal with url() calls when there is no request. + 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']) && variable_get('https', 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; + } + } + $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); + $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; + + $path = str_replace('%2F', '/', rawurlencode($prefix . $path)); + $query = $options['query'] ? ('?' . $this->httpBuildQuery($options['query'])) : ''; + return $base . $options['script'] . $path . $query . $options['fragment']; + } + + /** + * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::stripDisallowedProtocols(). + */ + public function stripDisallowedProtocols($uri) { + if (!isset($this->allowed_protocols)) { + // filter_xss_admin() is called by the installer and update.php, in which + // case the configuration may not exist (yet). Provide a minimal default set + // of allowed protocols for these cases. + $this->allowed_protocols = array_flip($this->config->get('system.filter')->get('protocols') ?: array('http', 'https')); + } + + // Iteratively remove any invalid protocol found. + do { + $before = $uri; + $colonpos = strpos($uri, ':'); + if ($colonpos > 0) { + // We found a colon, possibly a protocol. Verify. + $protocol = substr($uri, 0, $colonpos); + // If a colon is preceded by a slash, question mark or hash, it cannot + // possibly be part of the URL scheme. This must be a relative URL, which + // inherits the (safe) protocol of the base document. + if (preg_match('![/?#]!', $protocol)) { + break; + } + // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 + // (URI Comparison) scheme comparison must be case-insensitive. + if (!isset($this->allowed_protocols[strtolower($protocol)])) { + $uri = substr($uri, $colonpos + 1); + } + } + } while ($before != $uri); + + return $uri; + } + + /** + * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::httpBuildQuery(). + */ + public function httpBuildQuery(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[] = $this->httpBuildQuery($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); + } + + /** + * Passes the path to a processor manager to allow alterations. + */ + protected function processPath($path, &$options = array()) { + $path_parts = explode('?', $path); + $path = '/' . $this->pathProcessor->processOutbound(trim($path_parts[0], '/'), $options, $this->request); + if (isset($path_parts[1])) { + $path .= '?' . $path_parts[1]; + } return $path; } diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 81b9715..64e8f3c 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -645,7 +645,6 @@ function language_language_negotiation_info() { 'callbacks' => array( 'negotiation' => 'language_from_url', 'language_switch' => 'language_switcher_url', - 'url_rewrite' => 'language_url_rewrite_url', ), 'file' => $file, 'weight' => -8, @@ -782,56 +781,6 @@ function language_preprocess_block(&$variables) { } /** - * Implements hook_url_outbound_alter(). - * - * Rewrite outbound URLs with language based prefixes. - */ -function language_url_outbound_alter(&$path, &$options, $original_path) { - // Only modify internal URLs. - if (!$options['external'] && language_multilingual()) { - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['callbacks'] = &drupal_static(__FUNCTION__); - } - $callbacks = &$drupal_static_fast['callbacks']; - - if (!isset($callbacks)) { - $callbacks = array(); - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - - foreach (language_types_get_configurable() as $type) { - // Get URL rewriter callbacks only from enabled language methods. - $negotiation = variable_get("language_negotiation_$type", array()); - - foreach ($negotiation as $method_id => $method) { - if (isset($method['callbacks']['url_rewrite'])) { - if (isset($method['file'])) { - require_once DRUPAL_ROOT . '/' . $method['file']; - } - // Avoid duplicate callback entries. - $callbacks[$method['callbacks']['url_rewrite']] = TRUE; - } - } - } - - $callbacks = array_keys($callbacks); - } - - // No language dependent path allowed in this mode. - if (empty($callbacks)) { - unset($options['language']); - return; - } - - foreach ($callbacks as $callback) { - if (function_exists($callback)) { - $callback($path, $options); - } - } - } -} - -/** * Returns language mappings between browser and Drupal language codes. * * @return array diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc index 6565c6d..252bf47 100644 --- a/core/modules/language/language.negotiation.inc +++ b/core/modules/language/language.negotiation.inc @@ -423,85 +423,6 @@ function language_switcher_session($type, $path) { } /** - * Rewrite URLs for the URL language negotiation method. - */ -function language_url_rewrite_url(&$path, &$options) { - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['languages'] = &drupal_static(__FUNCTION__); - } - $languages = &$drupal_static_fast['languages']; - - if (!isset($languages)) { - $languages = language_list(); - $languages = array_flip(array_keys($languages)); - } - - // Language can be passed as an option, or we go for current URL language. - if (!isset($options['language'])) { - $language_url = language(LANGUAGE_TYPE_URL); - $options['language'] = $language_url; - } - // We allow only enabled languages here. - elseif (is_object($options['language']) && !isset($languages[$options['language']->langcode])) { - unset($options['language']); - return; - } - - if (isset($options['language'])) { - switch (config('language.negotiation')->get('url.source')) { - case LANGUAGE_NEGOTIATION_URL_DOMAIN: - $domains = language_negotiation_url_domains(); - if (is_object($options['language']) && !empty($domains[$options['language']->langcode])) { - global $is_https; - - // Save the original base URL. If it contains a port, we need to - // retain it below. - if (!empty($options['base_url'])) { - // The colon in the URL scheme messes up the port checking below. - $normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']); - - } - - // Ask for an absolute URL with our modified base URL. - $url_scheme = ($is_https) ? 'https://' : 'http://'; - $options['absolute'] = TRUE; - $options['base_url'] = $url_scheme . $domains[$options['language']->langcode]; - - // In case either the original base URL or the HTTP host contains a - // port, retain it. - $http_host = $_SERVER['HTTP_HOST']; - if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) { - list($host, $port) = explode(':', $normalized_base_url); - $options['base_url'] .= ':' . $port; - } - elseif (strpos($http_host, ':') !== FALSE) { - list($host, $port) = explode(':', $http_host); - $options['base_url'] .= ':' . $port; - } - - if (isset($options['https']) && variable_get('https', FALSE)) { - if ($options['https'] === TRUE) { - $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); - } - elseif ($options['https'] === FALSE) { - $options['base_url'] = str_replace('https://', 'http://', $options['base_url']); - } - } - } - break; - - case LANGUAGE_NEGOTIATION_URL_PREFIX: - $prefixes = language_negotiation_url_prefixes(); - if (is_object($options['language']) &&!empty($prefixes[$options['language']->langcode])) { - $options['prefix'] = $prefixes[$options['language']->langcode] . '/'; - } - break; - } - } -} - -/** * Reads language prefixes and uses the langcode if no prefix is set. */ function language_negotiation_url_prefixes() { diff --git a/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php b/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php index ad16ac6..0081856 100644 --- a/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php +++ b/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php @@ -7,31 +7,128 @@ namespace Drupal\language\HttpKernel; -use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\Language\LanguageManager; use Drupal\Core\PathProcessor\InboundPathProcessorInterface; +use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpFoundation\Request; /** * Processes the inbound path using path alias lookups. */ -class PathProcessorLanguage implements InboundPathProcessorInterface { +class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPathProcessorInterface { - protected $moduleHandler; + /** + * A config factory for retrieving required config settings. + * + * @var \Drupal\Core\Config\ConfigFactory + */ + protected $config; - public function __construct(ModuleHandlerInterface $module_handler) { - $this->moduleHandler = $module_handler; + /** + * Language manager for retrieving the url language type. + * + * @var \Drupal\Core\Language\LanguageManager + */ + protected $languageManager; + + public function __construct(ConfigFactory $config, LanguageManager $language_manager) { + $this->config = $config; + $this->languageManager = $language_manager; + $this->languages = language_list(); } /** * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processInbound(). */ public function processInbound($path, Request $request) { - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - $this->moduleHandler->loadInclude('language', 'inc', 'language.negotiation'); - $languages = language_list(); - list($language, $path) = language_url_split_prefix($path, $languages); + if (!empty($path)) { + $args = explode('/', $path); + $prefix = array_shift($args); + + // Search prefix within enabled languages. + $prefixes = $this->config->get('language.negotiation')->get('url.prefixes'); + foreach ($this->languages as $language) { + if (isset($prefixes[$language->langcode]) && $prefixes[$language->langcode] == $prefix) { + // Rebuild $path with the language removed. + return implode('/', $args); + } + } + } return $path; } + /** + * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processOutbound(). + */ + public function processOutbound($path, &$options = array(), Request $request = NULL) { + if (!$this->languageManager->isMultilingual()) { + return $path; + } + $languages = array_flip(array_keys($this->languages)); + // Language can be passed as an option, or we go for current URL language. + if (!isset($options['language'])) { + $language_url = $this->languageManager->getLanguage(LANGUAGE_TYPE_URL); + $options['language'] = $language_url; + } + // We allow only enabled languages here. + elseif (is_object($options['language']) && !isset($languages[$options['language']->langcode])) { + return $path; + } + $url_source = $this->config->get('language.negotiation')->get('url.source'); + // @todo Go back to using a constant instead of the string 'path_prefix' once we can use a class + // constant. + if ($url_source == 'path_prefix') { + $prefixes = $this->config->get('language.negotiation')->get('url.prefixes'); + if (is_object($options['language']) && !empty($prefixes[$options['language']->langcode])) { + return empty($path) ? $prefixes[$options['language']->langcode] : $prefixes[$options['language']->langcode] . '/' . $path; + } + } + elseif ($url_source == 'domain') { + $domains = $this->config->get('language.negotiation')->get('url.domains'); + if (is_object($options['language']) && !empty($domains[$options['language']->langcode])) { + $is_https = FALSE; + $http_host = NULL; + if ($request !== NULL) { + $https = $request->server->get('HTTPS'); + $is_https = $https && strtolower($https) == 'on'; + $http_host = $request->server->get('HTTP_HOST'); + } + + // Save the original base URL. If it contains a port, we need to + // retain it below. + if (!empty($options['base_url'])) { + // The colon in the URL scheme messes up the port checking below. + $normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']); + } + + // Ask for an absolute URL with our modified base URL. + $url_scheme = ($is_https) ? 'https://' : 'http://'; + $options['absolute'] = TRUE; + $options['base_url'] = $url_scheme . $domains[$options['language']->langcode]; + + // In case either the original base URL or the HTTP host contains a + // port, retain it. + if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) { + list($host, $port) = explode(':', $normalized_base_url); + $options['base_url'] .= ':' . $port; + } + elseif (strpos($http_host, ':') !== FALSE) { + list($host, $port) = explode(':', $http_host); + $options['base_url'] .= ':' . $port; + } + + if (isset($options['https']) && variable_get('https', FALSE)) { + if ($options['https'] === TRUE) { + $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); + } + elseif ($options['https'] === FALSE) { + $options['base_url'] = str_replace('https://', 'http://', $options['base_url']); + } + } + } + } + return $path; + } } diff --git a/core/modules/language/lib/Drupal/language/LanguageBundle.php b/core/modules/language/lib/Drupal/language/LanguageBundle.php index 1e788aa..a0a76f5 100644 --- a/core/modules/language/lib/Drupal/language/LanguageBundle.php +++ b/core/modules/language/lib/Drupal/language/LanguageBundle.php @@ -22,8 +22,10 @@ class LanguageBundle extends Bundle { public function build(ContainerBuilder $container) { // Register the language-based path processor. $container->register('path_processor_language', 'Drupal\language\HttpKernel\PathProcessorLanguage') - ->addArgument(new Reference('module_handler')) - ->addTag('path_processor_inbound', array('priority' => 300)); + ->addArgument(new Reference('config.factory')) + ->addArgument(new Reference('language_manager')) + ->addTag('path_processor_inbound', array('priority' => 300)) + ->addTag('path_processor_outbound', array('priority' => 100)); } } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php index 89a1bd6..d9f703b 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguagePathMonolingualTest.php @@ -40,15 +40,18 @@ function setUp() { $edit = array(); $edit['predefined_langcode'] = 'fr'; $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->rebuildContainer(); // Make French the default language. $edit = array( 'site_default_language' => 'fr', ); $this->drupalpost('admin/config/regional/settings', $edit, t('Save configuration')); + $this->rebuildContainer(); // Delete English. $this->drupalPost('admin/config/regional/language/delete/en', array(), t('Delete')); + $this->rebuildContainer(); // Verify that French is the only language. $this->assertFalse(language_multilingual(), 'Site is mono-lingual'); @@ -57,7 +60,7 @@ function setUp() { // Set language detection to URL. $edit = array('language_interface[enabled][language-url]' => TRUE); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); - + $this->rebuildContainer(); // Force languages to be initialized. drupal_language_initialize(); } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php index e614da2..421ac97 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php @@ -9,6 +9,7 @@ use Drupal\simpletest\WebTestBase; use Drupal\Core\Language\Language; +use Symfony\Component\HttpFoundation\Request; /** * Test UI language negotiation @@ -414,7 +415,9 @@ function testUrlLanguageFallback() { // Check that the language switcher active link matches the given browser // language. - $args = array(':id' => 'block-test-language-block', ':url' => base_path() . $GLOBALS['script_path'] . $langcode_browser_fallback); + $url = base_path() . $GLOBALS['script_path'] . $langcode_browser_fallback; + debug($url); + $args = array(':id' => 'block-test-language-block', ':url' => $url); $fields = $this->xpath('//div[@id=:id]//a[@class="language-link active" and starts-with(@href, :url)]', $args); $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->name, 'The browser language is the URL active language'); @@ -448,6 +451,7 @@ function testLanguageDomain() { 'domain[it]' => 'it.example.com', ); $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); + $this->rebuildContainer(); // Build the link we're going to test. $link = 'it.example.com/admin'; @@ -463,17 +467,19 @@ function testLanguageDomain() { // Test HTTPS via options. variable_set('https', TRUE); + $this->rebuildContainer(); + $italian_url = url('admin', array('https' => TRUE, 'language' => $languages['it'], 'script' => '')); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, format_string('The url() function returns the right HTTPS URL (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url))); variable_set('https', FALSE); // Test HTTPS via current URL scheme. - $temp_https = $is_https; - $is_https = TRUE; + $generator = $this->container->get('router.generator'); + $request = Request::create('', 'GET', array(), array(), array(), array('HTTPS' => 'on')); + $generator->setRequest($request); $italian_url = url('admin', array('language' => $languages['it'], 'script' => '')); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, format_string('The url() function returns the right URL (via current URL scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url))); - $is_https = $temp_https; } } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUrlRewritingTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUrlRewritingTest.php index 3effed4..7b3fa09 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUrlRewritingTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUrlRewritingTest.php @@ -8,6 +8,7 @@ namespace Drupal\language\Tests; use Drupal\simpletest\WebTestBase; +use Symfony\Component\HttpFoundation\Request; /** * Test that URL rewriting works as expected. @@ -47,8 +48,6 @@ function setUp() { // Reset static caching. drupal_static_reset('language_list'); - drupal_static_reset('language_url_outbound_alter'); - drupal_static_reset('language_url_rewrite_url'); } /** @@ -109,6 +108,9 @@ function testDomainNameNegotiationPort() { 'domain[fr]' => $language_domain ); $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); + // Rebuild the container so that the new language gets picked up by services + // that hold the list of languages. + $this->rebuildContainer(); // Enable domain configuration. config('language.negotiation') @@ -117,17 +119,16 @@ function testDomainNameNegotiationPort() { // Reset static caching. drupal_static_reset('language_list'); - drupal_static_reset('language_url_outbound_alter'); drupal_static_reset('language_url_rewrite_url'); // In case index.php is part of the URLs, we need to adapt the asserted // URLs as well. $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE; - // Remember current HTTP_HOST. - $http_host = $_SERVER['HTTP_HOST']; - // Fake a different port. - $_SERVER['HTTP_HOST'] .= ':88'; + $http_host = $this->container->get('request')->server->get('HTTP_HOST'); + $generator = $this->container->get('router.generator'); + $request = Request::create('', 'GET', array(), array(), array(), array('HTTP_HOST' => $http_host . ':88')); + $generator->setRequest($request); // Create an absolute French link. $language = language_load('fr'); @@ -137,7 +138,6 @@ function testDomainNameNegotiationPort() { )); $expected = $index_php ? 'http://example.fr:88/index.php/' : 'http://example.fr:88/'; - $this->assertEqual($url, $expected, 'The right port is used.'); // If we set the port explicitly in url(), it should not be overriden. @@ -151,8 +151,6 @@ function testDomainNameNegotiationPort() { $this->assertEqual($url, $expected, 'A given port is not overriden.'); - // Restore HTTP_HOST. - $_SERVER['HTTP_HOST'] = $http_host; } } diff --git a/core/modules/language/tests/language_test/language_test.module b/core/modules/language/tests/language_test/language_test.module index cb97937..cc290b2 100644 --- a/core/modules/language/tests/language_test/language_test.module +++ b/core/modules/language/tests/language_test/language_test.module @@ -117,5 +117,7 @@ function language_test_menu() { * Page callback. Uses a subrequest to retrieve the 'user' page. */ function language_test_subrequest() { - return drupal_container()->get('http_kernel')->handle(Request::create('/user'), HttpKernelInterface::SUB_REQUEST); + $request = drupal_container()->get('request'); + $subrequest = Request::create('/user', 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all()); + return drupal_container()->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); } diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 0bcf093..c12c235 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -849,6 +849,10 @@ function locale_update_8011() { * Renames language_default language negotiation method to language_selected. */ function locale_update_8013() { + // @todo We only need language.inc here because LANGUAGE_NEGOTIATION_SELECTED + // is defined there. Remove this line once that has been converted to a class + // constant. + require_once DRUPAL_ROOT . '/core/includes/language.inc'; $weight = update_variable_get('language_negotiation_methods_weight_language_interface', NULL); if ($weight !== NULL) { $weight[LANGUAGE_NEGOTIATION_SELECTED] = $weight['language-default']; diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php index 455a3f6..5e875ce 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php @@ -95,8 +95,7 @@ function testAliasTranslation() { // Confirm that the alias is returned by url(). Languages are cached on // many levels, and we need to clear those caches. drupal_static_reset('language_list'); - drupal_static_reset('language_url_outbound_alter'); - drupal_static_reset('language_url_rewrite_url'); + $this->rebuildContainer(); $languages = language_list(); $url = url('node/' . $french_node->nid, array('language' => $languages[$french_node->langcode])); $this->assertTrue(strpos($url, $edit['path[alias]']), 'URL contains the path alias.'); diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php index 2c92696..a20440a 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php @@ -49,7 +49,7 @@ public function __construct($site_schema, $entity_type, $bundle) { */ public function getUri() { $path = str_replace(array('{entity_type}', '{bundle}'), array($this->entityType, $this->bundle), static::$uriPattern); - return $this->siteSchema->getUri() . $path; + return $this->siteSchema->getUri() . '/' . $path; } /** diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php index 48a69fc..282c904 100644 --- a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php +++ b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/EntitySchema.php @@ -58,7 +58,7 @@ public function getGraph() { */ public function getUri() { $path = str_replace('{entity_type}', $this->entityType , static::$uriPattern); - return $this->siteSchema->getUri() . $path; + return $this->siteSchema->getUri() . '/' . $path; } /** diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php index 0cfe07f..986118b 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/SiteSchemaTest.php @@ -47,7 +47,6 @@ function testSiteSchema() { 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' => 'http://www.w3.org/2000/01/rdf-schema#class', 'http://www.w3.org/2000/01/rdf-schema#subClassOf' => url("$schema_path$entity_type", array('absolute' => TRUE)), ); - $this->assertEqual($bundle_schema->getUri(), $bundle_uri, 'Bundle term URI is generated correctly.'); $this->assertEqual($bundle_schema->getProperties(), $bundle_properties, 'Bundle term properties are generated correctly.'); } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index 6dbf439..a824570 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -167,6 +167,16 @@ public function containerBuild($container) { ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory') ->addArgument(new Reference('service_container')); } + + if ($container->hasDefinition('path_processor_alias')) { + // Prevent the alias-based path processor, which requires a url_alias db + // table, from being registered to the path processor manager. We do this + // by removing the tags that the compiler pass looks for. This means the + // url generator can safely be used within DUTB tests. + $definition = $container->getDefinition('path_processor_alias'); + $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound'); + } + } /** diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index dce2ceb..15152b5 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -1141,7 +1141,7 @@ protected function parse() { * @param $path * Drupal path or URL to load into internal browser * @param $options - * Options to be forwarded to url(). + * Options to be forwarded to the url generator. * @param $headers * An array containing additional HTTP request headers, each formatted as * "name: value". @@ -1154,7 +1154,8 @@ protected function drupalGet($path, array $options = array(), array $headers = a // We re-using a CURL connection here. If that connection still has certain // options set, it might change the GET into a POST. Make sure we clear out // previous options. - $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers)); + $url = $this->container->get('router.generator')->generateFromPath($path, $options); + $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $url, CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers)); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. // Replace original page output with new output from redirected page(s). @@ -1246,7 +1247,7 @@ protected function drupalGetAJAX($path, array $options = array(), array $headers * textfield: under these conditions, no button information is added to the * POST data. * @param $options - * Options to be forwarded to url(). + * Options to be forwarded to the url generator. * @param $headers * An array containing additional HTTP request headers, each formatted as * "name: value". @@ -1365,7 +1366,7 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a * element. In the absence of both the triggering element's Ajax path and * $ajax_path 'system/ajax' will be used. * @param $options - * (optional) Options to be forwarded to url(). + * (optional) Options to be forwarded to the url generator. * @param $headers * (optional) An array containing additional HTTP request headers, each * formatted as "name: value". Forwarded to drupalPost(). @@ -1572,7 +1573,7 @@ protected function checkForMetaRefresh() { * @param $path * Drupal path or URL to load into internal browser * @param $options - * Options to be forwarded to url(). + * Options to be forwarded to the url generator. * @param $headers * An array containing additional HTTP request headers, each formatted as * "name: value". @@ -1581,7 +1582,8 @@ protected function checkForMetaRefresh() { */ protected function drupalHead($path, array $options = array(), array $headers = array()) { $options['absolute'] = TRUE; - $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers)); + $url = $this->container->get('router.generator')->generateFromPath($path, $options); + $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers)); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. return $out; } @@ -2181,7 +2183,7 @@ protected function createTypedData($definition, $value = NULL, $context = array( * @param $path * The expected system path. * @param $options - * (optional) Any additional options to pass for $path to url(). + * (optional) Any additional options to pass for $path to the url generator. * @param $message * (optional) A message to display with the assertion. Do not translate * messages: use format_string() to embed variables in the message text, not @@ -2198,11 +2200,11 @@ protected function createTypedData($definition, $value = NULL, $context = array( protected function assertUrl($path, array $options = array(), $message = '', $group = 'Other') { if (!$message) { $message = t('Current URL is @url.', array( - '@url' => var_export(url($path, $options), TRUE), + '@url' => var_export($this->container->get('router.generator')->generateFromPath($path, $options), TRUE), )); } $options['absolute'] = TRUE; - return $this->assertEqual($this->getUrl(), url($path, $options), $message, $group); + return $this->assertEqual($this->getUrl(), $this->container->get('router.generator')->generateFromPath($path, $options), $message, $group); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php index b2670fc..629336d 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php @@ -152,6 +152,7 @@ function testDrupalHTTPRequestHeaders() { 'name' => 'French', )); language_save($language); + $this->rebuildContainer(); // Request front page in French and check for matching Content-language. $request = drupal_http_request(url('', array('absolute' => TRUE, 'language' => $language))); diff --git a/core/modules/system/lib/Drupal/system/Tests/PathProcessor/PathProcessorTest.php b/core/modules/system/lib/Drupal/system/Tests/PathProcessor/PathProcessorTest.php index 5b10c2a..901ba81 100644 --- a/core/modules/system/lib/Drupal/system/Tests/PathProcessor/PathProcessorTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/PathProcessor/PathProcessorTest.php @@ -46,14 +46,10 @@ function testProcessInbound() { $this->fixtures->createTables($connection); // Create dependecies needed by various path processors. - $alias_manager = new AliasManager($connection, $this->container->get('state'), $this->container->get('language_manager')); + $language_manager = $this->container->get('language_manager'); + $alias_manager = new AliasManager($connection, $this->container->get('state'), $language_manager); $module_handler = $this->container->get('module_handler'); - - // Create the processors. - $alias_processor = new PathProcessorAlias($alias_manager); - $decode_processor = new PathProcessorDecode(); - $front_processor = new PathProcessorFront($this->container->get('config.factory')); - $language_processor = new PathProcessorLanguage($module_handler); + $config = $this->container->get('config.factory'); // Add a url alias for testing the alias-based processor. $path_crud = new Path($connection, $alias_manager); @@ -67,6 +63,12 @@ function testProcessInbound() { $language->name = 'French'; language_save($language); + // Create the processors. + $alias_processor = new PathProcessorAlias($alias_manager); + $decode_processor = new PathProcessorDecode(); + $front_processor = new PathProcessorFront($config); + $language_processor = new PathProcessorLanguage($config, $language_manager); + // First, test the processor manager with the processors in the incorrect // order. The alias processor will run before the language processor, meaning // aliases will not be found. diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php index 0e9f13e..37bc483 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php @@ -7,6 +7,12 @@ namespace Drupal\system\Tests\Routing; +use Drupal\Core\Config\ConfigFactory; +use Drupal\Core\Config\NullStorage; +use Drupal\Core\Config\Context\ConfigContextFactory; +use Drupal\Core\PathProcessor\PathProcessorAlias; +use Drupal\Core\PathProcessor\PathProcessorManager; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -47,7 +53,12 @@ function setUp() { $context = new RequestContext(); $context->fromRequest(Request::create('/some/path')); - $generator = new UrlGenerator($provider, $this->aliasManager); + $processor = new PathProcessorAlias($this->aliasManager); + $processor_manager = new PathProcessorManager(); + $processor_manager->addOutbound($processor, 1000); + $config_context_factory = new ConfigContextFactory(new EventDispatcher()); + $config = new ConfigFactory(new NullStorage(), $config_context_factory->get()); + $generator = new UrlGenerator($provider, $processor_manager, NULL, $config); $generator->setContext($context); $this->generator = $generator; @@ -67,9 +78,7 @@ public function testAliasGeneration() { */ public function testAliasGenerationWithParameters() { $this->aliasManager->addAlias('test/two/5', 'goodbye/cruel/world'); - $url = $this->generator->generate('test_2', array('narf' => '5')); - $this->assertEqual($url, '/goodbye/cruel/world', 'Correct URL generated including alias and parameters.'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php index 6b669a5..82d2fdb 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php @@ -118,6 +118,11 @@ public function testLanguageUpgrade() { $this->assertTrue(isset($current_weights['language-selected']), 'Language-selected is present.'); $this->assertFalse(isset($current_weights['language-default']), 'Language-default is not present.'); + // @todo We only need language.inc here because LANGUAGE_NEGOTIATION_SELECTED + // is defined there. Remove this line once that has been converted to a class + // constant. + require_once DRUPAL_ROOT . '/core/includes/language.inc'; + // Check that negotiation callback was added to language_negotiation_language_interface. $language_negotiation_language_interface = update_variable_get('language_negotiation_language_interface', NULL); $this->assertTrue(isset($language_negotiation_language_interface[LANGUAGE_NEGOTIATION_SELECTED]['callbacks']['negotiation']), 'Negotiation callback was added to language_negotiation_language_interface.'); diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php index 0d96896..b62b54f 100644 --- a/core/modules/system/tests/https.php +++ b/core/modules/system/tests/https.php @@ -3,6 +3,9 @@ /** * @file * Fake an HTTPS request, for use during testing. + * + * @todo Fix this to use a new request rather than modifying server variables, + * see http.php. */ // Set a global variable to indicate a mock HTTPS request. diff --git a/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathProcessorTest.php b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathProcessorTest.php new file mode 100644 index 0000000..e649f6d --- /dev/null +++ b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathProcessorTest.php @@ -0,0 +1,61 @@ + ''); + $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'; + } + return $path; + } + + /** + * Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound(). + */ + public function processOutbound($path, &$options = array(), Request $request = NULL) { + // Rewrite user/uid to user/username. + if (preg_match('!^user/([0-9]+)(/.*)?!', $path, $matches)) { + if ($account = user_load($matches[1])) { + $matches += array(2 => ''); + $path = 'user/' . $account->name . $matches[2]; + } + } + + // Rewrite forum/ to community/. + if ($path == 'forum' || strpos($path, 'forum/') === 0) { + $path = 'community' . substr($path, 5); + } + return $path; + } + +} 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 index cd028e8..d4a691d 100644 --- 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 @@ -21,7 +21,8 @@ class UrlAlterTestBundle extends Bundle { public function build(ContainerBuilder $container) { - $container->register('url_alter_test.path_subscriber', 'Drupal\url_alter_test\PathSubscriber') - ->addTag('event_subscriber'); + $container->register('url_alter_test.path_processor', 'Drupal\url_alter_test\PathProcessorTest') + ->addTag('path_processor_inbound', array('priority' => 50)) + ->addTag('path_processor_outbound', array('priority' => 200)); } } 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 8bacb9b..a7567a3 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 @@ -25,21 +25,3 @@ function url_alter_test_foo() { print 'current_path=' . current_path() . ' request_path=' . request_path(); exit; } - -/** - * Implements hook_url_outbound_alter(). - */ -function url_alter_test_url_outbound_alter(&$path, &$options, $original_path) { - // Rewrite user/uid to user/username. - if (preg_match('!^user/([0-9]+)(/.*)?!', $path, $matches)) { - if ($account = user_load($matches[1])) { - $matches += array(2 => ''); - $path = 'user/' . $account->name . $matches[2]; - } - } - - // Rewrite forum/ to community/. - if ($path == 'forum' || strpos($path, 'forum/') === 0) { - $path = 'community' . substr($path, 5); - } -} diff --git a/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php b/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php index 6e3dccf..1f3dbce 100644 --- a/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php +++ b/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php @@ -280,8 +280,7 @@ function testTranslateOwnContentRole() { */ function resetCaches() { drupal_static_reset('language_list'); - drupal_static_reset('language_url_outbound_alter'); - drupal_static_reset('language_url_rewrite_url'); + $this->rebuildContainer(); } /** diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationTestBase.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationTestBase.php index 13e1f59..2f3e0fa 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationTestBase.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationTestBase.php @@ -82,6 +82,10 @@ function setUp() { $this->setupTestFields(); $this->controller = translation_entity_controller($this->entityType); + + // Rebuild the container so that the new languages are picked up by services + // that hold a list of languages. + $this->rebuildContainer(); } /** diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationWorkflowsTest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationWorkflowsTest.php index 9957552..c0044c0 100644 --- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationWorkflowsTest.php +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationWorkflowsTest.php @@ -67,6 +67,7 @@ protected function setupEntity() { $this->drupalLogin($this->translator); $add_translation_path = $this->controller->getBasePath($this->entity) . "/translations/add/$default_langcode/{$this->langcodes[2]}"; $this->drupalPost($add_translation_path, array(), t('Save')); + $this->rebuildContainer(); } /**