From b21943922d94261dfa8de34995d206d5e8e9c3d7 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Thu, 6 Jun 2013 09:14:16 +0100
Subject: [PATCH] Issue #1888424 by katbailey, steveoliver, twistor, beejeebus,
 effulgentsia: Change notice: Make Drupal's URL generation logic available to
 HttpKernel, and minimize code repetition/divergence.

---
 core/core.services.yml                        |  12 +-
 core/includes/common.inc                      | 203 ++--------
 core/includes/form.inc                        |   3 +-
 core/includes/install.core.inc                |  11 +-
 core/lib/Drupal.php                           |  10 +
 .../Compiler/RegisterPathProcessorsPass.php   |   6 +
 .../Core/EventSubscriber/PathSubscriber.php   |  20 +
 .../Drupal/Core/Language/LanguageManager.php  |   2 +-
 .../OutboundPathProcessorInterface.php        |  35 ++
 .../Core/PathProcessor/PathProcessorAlias.php |  10 +-
 .../Core/PathProcessor/PathProcessorFront.php |  13 +-
 .../PathProcessor/PathProcessorManager.php    |  65 +++-
 .../GeneratorNotInitializedException.php      |  14 +
 .../lib/Drupal/Core/Routing/NullGenerator.php |  20 +-
 .../Routing/PathBasedGeneratorInterface.php   |  93 +++++
 core/lib/Drupal/Core/Routing/UrlGenerator.php | 367 +++++++++++++++++-
 .../contextual/Tests/ContextualUnitTest.php   |   4 +-
 .../lib/Drupal/file/Tests/DownloadTest.php    |  19 +-
 .../lib/Drupal/forum/Tests/ForumTest.php      |   2 +-
 core/modules/image/image.module               |  17 +-
 .../image/Tests/ImageStylesPathAndUrlTest.php |  18 +-
 core/modules/language/language.module         |  51 ---
 .../modules/language/language.negotiation.inc |  77 ----
 core/modules/language/language.services.yml   |   3 +-
 .../HttpKernel/PathProcessorLanguage.php      |  95 ++++-
 .../LanguageUILanguageNegotiationTest.php     |   9 +-
 .../Tests/LanguageUrlRewritingTest.php        |  20 +-
 .../tests/language_test/language_test.module  |  15 +-
 core/modules/locale/locale.install            |   4 +
 .../Drupal/path/Tests/PathLanguageTest.php    |   6 +-
 .../Drupal/rdf/SiteSchema/BundleSchema.php    |   2 +-
 .../Drupal/rdf/SiteSchema/EntitySchema.php    |   2 +-
 .../lib/Drupal/rdf/Tests/SiteSchemaTest.php   |   1 -
 .../Drupal/simpletest/DrupalUnitTestBase.php  |  10 +
 .../lib/Drupal/simpletest/WebTestBase.php     |  77 +++-
 .../Drupal/system/Tests/Common/UrlTest.php    |  45 +--
 .../Tests/Path/UrlAlterFunctionalTest.php     |   5 +-
 .../system/Tests/Routing/UrlGeneratorTest.php |  76 ----
 .../Tests/Upgrade/LanguageUpgradePathTest.php |   5 +
 .../Tests/Upgrade/UpgradePathTestBase.php     |  34 ++
 core/modules/system/tests/https.php           |   3 +
 .../url_alter_test/PathProcessorTest.php      |  61 +++
 .../url_alter_test/url_alter_test.module      |  18 -
 .../url_alter_test.services.yml               |   5 +-
 .../translation/Tests/TranslationTest.php     |   3 +-
 .../Tests/EntityTranslationTestBase.php       |   4 +
 .../Tests/EntityTranslationWorkflowsTest.php  |   1 +
 core/scripts/run-tests.sh                     |   6 +-
 .../Core/PathProcessor/PathProcessorTest.php  |  11 +-
 .../Tests/Core/Routing/UrlGeneratorTest.php   | 167 ++++++++
 50 files changed, 1222 insertions(+), 538 deletions(-)
 create mode 100644 core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php
 create mode 100644 core/lib/Drupal/Core/Routing/GeneratorNotInitializedException.php
 create mode 100644 core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php
 delete mode 100644 core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php
 create mode 100644 core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathProcessorTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 856c1982d9c4..a54bb4a50a4e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -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
diff --git a/core/includes/common.inc b/core/includes/common.inc
index a8ad43a1bba3..0f8a90ea0bf3 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -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;
 }
 
 /**
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 36e165449f62..b3286259dcb6 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -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()));
   }
 }
 
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 686facbca6cd..225def291fa7 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -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.
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index d8a68badec7f..1b3dd4c43dcb 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -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');
+  }
+
 }
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php
index 6e298da910da..70de42da3815 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 5915d4bed596..a5dc9551e374 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;
@@ -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);
     }
diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php
index 673e8e307dca..3dea52a2f1af 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 000000000000..347a8777574b
--- /dev/null
+++ b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php
@@ -0,0 +1,35 @@
+<?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);
+
+}
diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php
index 7020710b7cbd..c663cf19b511 100644
--- a/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php
+++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorAlias.php
@@ -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;
+  }
 }
diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorFront.php
index 872885ad6001..d03d19bf19a1 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 '<front>' links to the default front page.
+    if ($path == '<front>') {
+      $path = '';
+    }
+    return $path;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php
index 1d0adff9e342..db8b79913483 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/GeneratorNotInitializedException.php b/core/lib/Drupal/Core/Routing/GeneratorNotInitializedException.php
new file mode 100644
index 000000000000..ab2922c677a2
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/GeneratorNotInitializedException.php
@@ -0,0 +1,14 @@
+<?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 { }
diff --git a/core/lib/Drupal/Core/Routing/NullGenerator.php b/core/lib/Drupal/Core/Routing/NullGenerator.php
index 7228514b9047..8970247b9771 100644
--- a/core/lib/Drupal/Core/Routing/NullGenerator.php
+++ b/core/lib/Drupal/Core/Routing/NullGenerator.php
@@ -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 {
+class NullGenerator extends UrlGenerator {
+
+  /**
+   * Override the parent constructor.
+   */
+  public function __construct() {
+  }
 
   /**
-   * 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 +40,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 000000000000..5c91e6251cd0
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Routing\PathBasedGeneratorInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Defines an interface for generating a url from a path as opposed to a route.
+ */
+interface PathBasedGeneratorInterface {
+
+  /**
+   * Generates an internal or external URL.
+   *
+   * @param $path
+   *   (optional) The internal path or external URL being linked to, such as
+   *   "node/34" or "http://example.com/foo".
+   *
+   * @param $options
+   *   (optional) An associative array of additional options.
+   *
+   * @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);
+
+  /**
+   * Sets the baseUrl property.
+   *
+   * This property is made up of scheme, host and base_path, e.g.
+   *   'http://www.example.com/mydrupalinstall/'
+   *
+   * The base url is usually set by the request but we allow it to be set
+   * independent of the request so that code that calls url() outside the context
+   * of a request can use the global $base_url variable to set this value.
+   *
+   * @todo Remove this once the url() function no longer supports being called
+   *   when there is no request.
+   *
+   * @var string $url
+   *   The base url to use for url generation.
+   */
+  public function setBaseUrl($url);
+
+  /**
+   * Sets the basePath property.
+   *
+   * This will be either '/' or '[subdir]/', where [subdir] is the name of the
+   * subdirectory that Drupal is running in.
+   *
+   * The base path is usually set by the request but we allow it to be set
+   * independent of the request so that code that calls url() outside the context
+   * of a request can use the global $base_url variable to set this value.
+   *
+   * @todo Remove this once the url() function no longer supports being called
+   *   when there is no request.
+   *
+   * @var string $path
+   *   The base path to use for url generation.
+   */
+  public function setBasePath($path);
+
+  /**
+   * Sets the scriptPath property.
+   *
+   * The script path is usually set by the request and is either 'index.php' or
+   * the empty string, depending on whether the request path actually contains
+   * the script path or not. We allow it to be set independent of the request so
+   * that code that calls url() outside the context of a request can use the global
+   * $script_path variable to set this value.
+   *
+   * @todo Remove this once the url() function no longer supports being called
+   *   when there is no request.
+   *
+   * @var string $path
+   *   The script path to use for url generation.
+   */
+  public function setScriptPath($path);
+
+}
diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php
index 75779a213cda..e6466fb5b926 100644
--- a/core/lib/Drupal/Core/Routing/UrlGenerator.php
+++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
@@ -7,24 +7,66 @@
 
 namespace Drupal\Core\Routing;
 
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Log\LoggerInterface;
 
+use Symfony\Component\Routing\Route as SymfonyRoute;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+
 use Symfony\Cmf\Component\Routing\ProviderBasedGenerator;
 use Symfony\Cmf\Component\Routing\RouteProviderInterface;
 
-use Drupal\Core\Path\AliasManagerInterface;
+use Drupal\Component\Utility\Settings;
+use Drupal\Component\Utility\UrlValidator;
+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 {
+
+  /**
+   * A request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The path processor to convert the system path to one suitable for urls.
+   *
+   * @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface
+   */
+  protected $pathProcessor;
+
+  /**
+   * The base path to use for urls.
+   *
+   * @var string
+   */
+  protected $basePath;
+
+  /**
+   * The base url to use for urls.
+   *
+   * @var string
+   */
+  protected $baseUrl;
+
+  /**
+   * The script path to use for urls.
+   *
+   * @var string
+   */
+  protected $scriptPath;
 
   /**
-   * The alias manager that will be used to alias generated URLs.
+   * Whether both secure and insecure session cookies can be used simultaneously.
    *
-   * @var AliasManagerInterface
+   * @var bool
    */
-  protected $aliasManager;
+  protected $mixedModeSessions;
 
   /**
    *  Constructs a new generator object.
@@ -36,23 +78,324 @@ 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, ConfigFactory $config, Settings $settings, LoggerInterface $logger = NULL) {
     parent::__construct($provider, $logger);
 
-    $this->aliasManager = $alias_manager;
+    $this->pathProcessor = $path_processor;
+    $this->mixedModeSessions = $settings->get('mixed_mode_sessions', FALSE);
+    $allowed_protocols = $config->get('system.filter')->get('protocols') ?: array('http', 'https');
+    UrlValidator::setAllowedProtocols($allowed_protocols);
+  }
+
+  /**
+   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setRequest().
+   */
+  public function setRequest(Request $request) {
+    $this->request = $request;
+    // Set some properties, based on the request, that are used during path-based
+    // url generation.
+    $this->basePath = $request->getBasePath() . '/';
+    $this->baseUrl = $request->getSchemeAndHttpHost() . $this->basePath;
+    $this->scriptPath = '';
+    $base_path_with_script = $request->getBaseUrl();
+    $script_name = $request->getScriptName();
+    if (!empty($base_path_with_script) && strpos($base_path_with_script, $script_name) !== FALSE) {
+      $length = strlen($this->basePath);
+      $this->scriptPath = ltrim(substr($script_name, $length), '/') . '/';
+    }
   }
 
   /**
-   * 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);
 
-    // 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 ($name instanceof SymfonyRoute) {
+      $route = $name;
+    }
+    elseif (null === $route = $this->provider->getRouteByName($name, $parameters)) {
+      throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
+    }
+
+    // The Route has a cache of its own and is not recompiled as long as it does
+    // not get modified.
+    $compiledRoute = $route->compile();
+    $hostTokens = $compiledRoute->getHostTokens();
+
+    $route_requirements = $route->getRequirements();
+    // We need to bypass the doGenerate() method's handling of absolute URLs as
+    // we handle that ourselves after processing the path.
+    if (isset($route_requirements['_scheme'])) {
+      $scheme_req = $route_requirements['_scheme'];
+      unset($route_requirements['_scheme']);
+    }
+    $path = $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route_requirements, $compiledRoute->getTokens(), $parameters, $name, FALSE, $hostTokens);
+
+    // The URL returned from doGenerate() will include the base path if there is
+    // one (i.e., if running in a subdirectory) so we need to strip that off
+    // before processing the path.
+    $base_url = $this->context->getBaseUrl();
+    if (!empty($base_url) && strpos($path, $base_url) === 0) {
+      $path = substr($path, strlen($base_url));
+    }
+
+    $path = $this->processPath($path);
+    if (!$absolute || !$host = $this->context->getHost()) {
+      return $base_url . $path;
+    }
+
+    // Prepare an absolute URL by getting the correct scheme, host and port from
+    // the request context.
+    $scheme = $this->context->getScheme();
+    if (isset($scheme_req) && ($req = strtolower($scheme_req)) && $scheme !== $req) {
+      $scheme = $req;
+    }
+    $port = '';
+    if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
+      $port = ':' . $this->context->getHttpPort();
+    } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) {
+      $port = ':' . $this->context->getHttpsPort();
+    }
+    return $scheme . '://' . $host . $port . $base_url . $path;
+  }
+
+  /**
+   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::generateFromPath().
+   *
+   * @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 method
+   *     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.
+   *
+   * @throws \Drupal\Core\Routing\GeneratorNotInitializedException.
+   */
+  public function generateFromPath($path = NULL, $options = array()) {
+
+    if (!$this->initialized()) {
+      throw new GeneratorNotInitializedException();
+    }
+
+    // Merge in defaults.
+    $options += array(
+      'fragment' => '',
+      'query' => array(),
+      'absolute' => FALSE,
+      'prefix' => '',
+    );
+
+    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)) && UrlValidator::stripDangerousProtocols($path) == $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']) && $this->mixedModeSessions) {
+        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($options['script'])) {
+      $options['script'] = $this->scriptPath;
+    }
+    // The base_url might be rewritten from the language rewrite in domain mode.
+    if (!isset($options['base_url'])) {
+      if (isset($options['https']) && $this->mixedModeSessions) {
+        if ($options['https'] === TRUE) {
+          $options['base_url'] = str_replace('http://', 'https://', $this->baseUrl);
+          $options['absolute'] = TRUE;
+        }
+        elseif ($options['https'] === FALSE) {
+          $options['base_url'] = str_replace('https://', 'http://', $this->baseUrl);
+          $options['absolute'] = TRUE;
+        }
+      }
+      else {
+        $options['base_url'] = $this->baseUrl;
+      }
+    }
+    elseif (rtrim($options['base_url'], '/') == $options['base_url']) {
+      $options['base_url'] .= '/';
+    }
+    $base = $options['absolute'] ? $options['base_url'] : $this->basePath;
+    $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::setBaseUrl().
+   */
+  public function setBaseUrl($url) {
+    $this->baseUrl = $url;
+  }
+
+  /**
+   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setBasePath().
+   */
+  public function setBasePath($path) {
+    $this->basePath = $path;
+  }
+
+  /**
+   * Implements \Drupal\Core\Routing\PathBasedGeneratorInterface::setScriptPath().
+   */
+  public function setScriptPath($path) {
+    $this->scriptPath = $path;
+  }
+
+  /**
+   * 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 = '') {
+    $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()) {
+    // Router-based paths may have a querystring on them.
+    if ($query_pos = strpos($path, '?')) {
+      // We don't need to do a strict check here because position 0 would mean we
+      // have no actual path to work with.
+      $actual_path = substr($path, 0, $query_pos);
+      $query_string = substr($path, $query_pos);
+    }
+    else {
+      $actual_path = $path;
+      $query_string = '';
+    }
+    $path = '/' . $this->pathProcessor->processOutbound(trim($actual_path, '/'), $options, $this->request);
+    $path .= $query_string;
     return $path;
   }
 
+  /**
+   * Returns whether or not the url generator has been initialized.
+   *
+   * @return bool
+   *   Returns TRUE if the basePath, baseUrl and scriptPath properties have been
+   *   set, FALSE otherwise.
+   */
+  protected function initialized() {
+    return isset($this->basePath) && isset($this->baseUrl) && isset($this->scriptPath);
+  }
+
 }
diff --git a/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php
index 51e9a515b1e7..d2018ac503b2 100644
--- a/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php
+++ b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\contextual\Tests;
 
-use Drupal\simpletest\UnitTestBase;
+use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
  * Tests _contextual_links_to_id() & _contextual_id_to_links().
  */
-class ContextualUnitTest extends UnitTestBase {
+class ContextualUnitTest extends DrupalUnitTestBase {
   public static function getInfo() {
     return array(
       'name' => 'Conversion to and from "contextual id"s (for placeholders)',
diff --git a/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php b/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php
index 0763507cc4fc..2c1278f5e4dd 100644
--- a/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/DownloadTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\file\Tests;
 
+use Symfony\Component\HttpFoundation\Request;
+
 /**
  * Tests for download/file transfer functions.
  */
@@ -84,7 +86,6 @@ function testPrivateFileTransfer() {
    * Test file_create_url().
    */
   function testFileCreateUrl() {
-    global $base_url, $script_path;
 
     // Tilde (~) is excluded from this test because it is encoded by
     // rawurlencode() in PHP 5.2 but not in PHP 5.3, as per RFC 3986.
@@ -100,12 +101,18 @@ function testFileCreateUrl() {
     // generated by url(), whereas private files should be served by Drupal, so
     // their URLs should be generated by url(). The difference is most apparent
     // when $script_path is not empty (i.e., when not using clean URLs).
-    $script_path_original = $script_path;
-    foreach (array('', 'index.php/') as $script_path) {
-      $this->checkUrl('public', '', $basename, $base_url . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded);
-      $this->checkUrl('private', '', $basename, $base_url . '/' . $script_path . 'system/files/' . $basename_encoded);
+    $clean_url_settings = array(
+      'clean' => '',
+      'unclean' => 'index.php/',
+    );
+    $generator = $this->container->get('url_generator');
+    foreach ($clean_url_settings as $clean_url_setting => $script_path) {
+      $clean_urls = $clean_url_setting == 'clean';
+      $request = $this->prepareRequestForGenerator($clean_urls);
+      $base_path = $request->getSchemeAndHttpHost() . $request->getBasePath();
+      $this->checkUrl('public', '', $basename, $base_path . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded);
+      $this->checkUrl('private', '', $basename, $base_path . '/' . $script_path . 'system/files/' . $basename_encoded);
     }
-    $script_path = $script_path_original;
   }
 
   /**
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
index 9f4b89a37c5e..f8c0479de700 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
@@ -467,7 +467,7 @@ function testForumWithNewPost() {
     // Login as the first user.
     $this->drupalLogin($this->admin_user);
     // Create a forum container.
-    $this->container = $this->createForum('container');
+    $this->forumContainer = $this->createForum('container');
     // Create a forum.
     $this->forum = $this->createForum('forum');
     // Create a topic.
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 17bb491d0152..2304c7d1f6de 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -723,12 +723,14 @@ function image_style_flush($style) {
  *   The name of the style to be used with this image.
  * @param $path
  *   The path to the image.
+ * @param $clean_urls
+ *   (optional) Whether clean URLs are in use.
  * @return
  *   The absolute URL where a style image can be downloaded, suitable for use
  *   in an <img> tag. Requesting the URL will cause the image to be created.
  * @see image_style_deliver()
  */
-function image_style_url($style_name, $path) {
+function image_style_url($style_name, $path, $clean_urls = NULL) {
   $uri = image_style_path($style_name, $path);
   // The token query is added even if the
   // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so that
@@ -743,11 +745,22 @@ function image_style_url($style_name, $path) {
     $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, file_stream_wrapper_uri_normalize($path)));
   }
 
+  if ($clean_urls === NULL) {
+    // Assume clean URLs unless the request tells us otherwise.
+    $clean_urls = TRUE;
+    try {
+      $request = Drupal::request();
+      $clean_urls = $request->attributes->get('clean_urls');
+    }
+    catch (ServiceNotFoundException $e) {
+    }
+  }
+
   // If not using clean URLs, the image derivative callback is only available
   // with the script path. If the file does not exist, use url() to ensure
   // that it is included. Once the file exists it's fine to fall back to the
   // actual file path, this avoids bootstrapping PHP once the files are built.
-  if ($GLOBALS['script_path'] && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
+  if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
     $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath();
     return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query));
   }
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
index 52d3f4abba88..1b9e2dbb75de 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Tests;
 
 use Drupal\simpletest\WebTestBase;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Tests the functions for generating paths and URLs for image styles.
@@ -104,8 +105,7 @@ function testImageStyleUrlForMissingSourceImage() {
    * Tests image_style_url().
    */
   function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) {
-    $script_path_original = $GLOBALS['script_path'];
-    $GLOBALS['script_path'] = $clean_url ? '' : 'index.php/';
+    $request = $this->prepareRequestForGenerator($clean_url);
 
     // Make the default scheme neither "public" nor "private" to verify the
     // functions work for other than the default scheme.
@@ -129,7 +129,7 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA
     // Get the URL of a file that has not been generated and try to create it.
     $generated_uri = image_style_path($this->style_name, $original_uri);
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
-    $generate_url = image_style_url($this->style_name, $original_uri);
+    $generate_url = image_style_url($this->style_name, $original_uri, $clean_url);
 
     // Ensure that the tests still pass when the file is generated by accessing
     // a poorly constructed (but still valid) file URL that has an extra slash
@@ -137,11 +137,10 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA
     if ($extra_slash) {
       $modified_uri = str_replace('://', ':///', $original_uri);
       $this->assertNotEqual($original_uri, $modified_uri, 'An extra slash was added to the generated file URI.');
-      $generate_url = image_style_url($this->style_name, $modified_uri);
+      $generate_url = image_style_url($this->style_name, $modified_uri, $clean_url);
     }
-
-    if ($GLOBALS['script_path']) {
-      $this->assertTrue(strpos($generate_url, $GLOBALS['script_path']) !== FALSE, 'When using non-clean URLS, the system path contains the script name.');
+    if (!$clean_url) {
+      $this->assertTrue(strpos($generate_url, 'index.php/') !== FALSE, 'When using non-clean URLS, the system path contains the script name.');
     }
     // Add some extra chars to the token.
     $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
@@ -194,7 +193,7 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA
         $this->assertNoRaw( chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.');
       }
     }
-    elseif (!$GLOBALS['script_path']) {
+    elseif ($clean_url) {
       // Add some extra chars to the token.
       $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
       $this->assertResponse(200, 'Existing image was accessible at the URL wih an invalid token.');
@@ -219,11 +218,10 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FA
     config('image.settings')->set('suppress_itok_output', TRUE)->save();
     $generated_uri = image_style_path($this->style_name, $original_uri);
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
-    $generate_url = image_style_url($this->style_name, $original_uri);
+    $generate_url = image_style_url($this->style_name, $original_uri, $clean_url);
     $this->assertIdentical(strpos($generate_url, IMAGE_DERIVATIVE_TOKEN . '='), FALSE, 'The security token does not appear in the image style URL.');
     $this->drupalGet($generate_url);
     $this->assertResponse(200, 'Image was accessible at the URL with a missing token.');
 
-    $GLOBALS['script_path'] = $script_path_original;
   }
 }
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index a6ab9d284e7b..6b45fa1348b3 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -634,7 +634,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,
@@ -770,56 +769,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.
  *
diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc
index d205d76aec34..82bf713c7500 100644
--- a/core/modules/language/language.negotiation.inc
+++ b/core/modules/language/language.negotiation.inc
@@ -424,83 +424,6 @@ function language_switcher_session($type, $path) {
   return $links;
 }
 
-/**
- * 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])) {
-          // 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 = Drupal::request()->isSecure() ? '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']) && settings()->get('mixed_mode_sessions', 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.
  */
diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml
index 576f4f8c326d..d6599b330ac2 100644
--- a/core/modules/language/language.services.yml
+++ b/core/modules/language/language.services.yml
@@ -1,6 +1,7 @@
 services:
   path_processor_language:
     class: Drupal\language\HttpKernel\PathProcessorLanguage
+    arguments: ['@config.factory', '@settings', '@language_manager']
     tags:
       - { name: path_processor_inbound, priority: 300 }
-    arguments: ['@config.factory']
+      - { name: path_processor_outbound, priority: 100 }
diff --git a/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php b/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php
index 006b31357d58..21bbbc80619b 100644
--- a/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php
+++ b/core/modules/language/lib/Drupal/language/HttpKernel/PathProcessorLanguage.php
@@ -7,15 +7,19 @@
 
 namespace Drupal\language\HttpKernel;
 
+use Drupal\Component\Utility\Settings;
 use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Language\Language;
+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 {
 
   /**
    * A config factory for retrieving required config settings.
@@ -24,6 +28,20 @@ class PathProcessorLanguage implements InboundPathProcessorInterface {
    */
   protected $config;
 
+  /**
+   * Whether both secure and insecure session cookies can be used simultaneously.
+   *
+   * @var bool
+   */
+  protected $mixedModeSessions;
+
+  /**
+   * Language manager for retrieving the url language type.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
   /**
    * An array of enabled languages.
    *
@@ -31,6 +49,7 @@ class PathProcessorLanguage implements InboundPathProcessorInterface {
    */
   protected $languages;
 
+
   /**
    * Constructs a PathProcessorLanguage object.
    *
@@ -41,8 +60,10 @@ class PathProcessorLanguage implements InboundPathProcessorInterface {
    *   An array of languages, keyed by language code, representing the languages
    *   currently enabled on the site.
    */
-  public function __construct(ConfigFactory $config, array $languages = array()) {
+  public function __construct(ConfigFactory $config, Settings $settings, LanguageManager $language_manager, array $languages = array()) {
     $this->config = $config;
+    $this->mixedModeSessions = $settings->get('mixed_mode_sessions', FALSE);
+    $this->languageManager = $language_manager;
     if (empty($languages)) {
       $languages = language_list();
     }
@@ -69,4 +90,74 @@ public function processInbound($path, Request $request) {
     return $path;
   }
 
+  /**
+   * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processOutbound().
+   */
+  public function processOutbound($path, &$options = array(), Request $request = NULL) {
+    if (!$this->languageManager->isMultilingual()) {
+      return $path;
+    }
+    $url_scheme = 'http';
+    $port = 80;
+    if ($request) {
+      $url_scheme = $request->getScheme();
+      $port = $request->getPort();
+    }
+    $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])) {
+
+        // 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.
+        $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 ($port != 80) {
+          $options['base_url'] .= ':' . $port;
+        }
+
+        if (isset($options['https']) && $this->mixedModeSessions) {
+          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/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
index 799d8d5e4283..8548f8eae664 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
@@ -452,6 +452,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';
@@ -466,17 +467,19 @@ function testLanguageDomain() {
 
     // Test HTTPS via options.
     $this->settingsSet('mixed_mode_sessions', 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)));
     $this->settingsSet('mixed_mode_sessions', FALSE);
 
     // Test HTTPS via current URL scheme.
-    $temp_https = $this->request->server->get('HTTPS');
-    $this->request->server->set('HTTPS', 'on');
+    $generator = $this->container->get('url_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)));
-    $this->request->server->set('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 3effed4fb804..07164841b3e2 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');
   }
 
   /**
@@ -60,6 +59,7 @@ function testUrlRewritingEdgeCases() {
     $non_existing->langcode = $this->randomName();
     $this->checkUrl($non_existing, 'Path language is ignored if language is not installed.', 'URL language negotiation does not work with non-installed languages');
 
+    $request = $this->prepareRequestForGenerator();
     // Check that URL rewriting is not applied to subrequests.
     $this->drupalGet('language_test/subrequest');
     $this->assertText($this->web_user->name, 'Page correctly retrieved');
@@ -109,6 +109,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 +120,12 @@ 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';
+    $request = $this->prepareRequestForGenerator(TRUE, array('SERVER_PORT' => '88'));
 
     // Create an absolute French link.
     $language = language_load('fr');
@@ -137,22 +135,18 @@ 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.
     $url = url('', array(
       'absolute' => TRUE,
       'language' => $language,
-      'base_url' => $GLOBALS['base_url'] . ':90',
+      'base_url' => $request->getBaseUrl() . ':90',
     ));
 
     $expected = $index_php ? 'http://example.fr:90/index.php/' : 'http://example.fr:90/';
-
     $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 150e024f5ccd..a77767de2c0f 100644
--- a/core/modules/language/tests/language_test/language_test.module
+++ b/core/modules/language/tests/language_test/language_test.module
@@ -119,5 +119,18 @@ 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 = Request::createFromGlobals();
+  $server = $request->server->all();
+  if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
+    // We need this for when the test is executed by run-tests.sh.
+    // @todo Remove this once run-tests.sh has been converted to use a Request
+    //   object.
+    $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'];
+    $base_path = ltrim($server['REQUEST_URI'], '/');
+  }
+  else {
+    $base_path = $request->getBasePath();
+  }
+  $subrequest = Request::create($base_path . '/user', 'GET', $request->query->all(), $request->cookies->all(), array(), $server);
+  return Drupal::service('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
 }
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index 04452ac1b6dd..92b28ead9b1d 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -851,6 +851,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 f592b724b225..c3d13cf60022 100644
--- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php
+++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php
@@ -97,10 +97,10 @@ 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]));
+    $url = $this->container->get('url_generator')->generateFromPath('node/' . $french_node->nid, array('language' => $languages[$french_node->langcode]));
+
     $this->assertTrue(strpos($url, $edit['path[alias]']), 'URL contains the path alias.');
 
     // Confirm that the alias works even when changing language negotiation
diff --git a/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php b/core/modules/rdf/lib/Drupal/rdf/SiteSchema/BundleSchema.php
index 2c926969d32b..a20440a6bcc8 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 48a69fc0cc82..282c904dd53c 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 0cfe07f87356..986118b658a3 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 b1102be3b626..2c80dcd8d502 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -174,6 +174,16 @@ public function containerBuild(ContainerBuilder $container) {
         ->setFactoryMethod('get')
         ->addArgument('state');
     }
+
+    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 da3d89352f22..21cabdf5297b 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -19,6 +19,7 @@
 use DOMXPath;
 use SimpleXMLElement;
 use Drupal\Core\Datetime\DrupalDateTime;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Test case for typical Drupal tests.
@@ -905,6 +906,16 @@ protected function writeSettings($settings) {
     }
   }
 
+  /**
+   * Overrides Drupal\simpletest\TestBase::rebuildContainer().
+   */
+  protected function rebuildContainer() {
+    parent::rebuildContainer();
+    // Make sure the url generator has a request object, otherwise calls to
+    // $this->drupalGet() will fail.
+    $this->prepareRequestForGenerator();
+  }
+
   /**
    * Reset all data structures after having enabled new modules.
    *
@@ -1181,7 +1192,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".
@@ -1194,7 +1205,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('url_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).
@@ -1311,7 +1323,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".
@@ -1436,7 +1448,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().
@@ -1644,7 +1656,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".
@@ -1653,7 +1665,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('url_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.
 
     if ($this->dumpHeaders) {
@@ -2241,7 +2254,7 @@ protected function drupalSetSettings($settings) {
    * @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
@@ -2258,11 +2271,11 @@ protected function drupalSetSettings($settings) {
   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('url_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('url_generator')->generateFromPath($path, $options), $message, $group);
   }
 
   /**
@@ -3252,4 +3265,50 @@ protected function verboseEmail($count = 1) {
       $this->verbose(t('Email:') . '<pre>' . print_r($mail, TRUE) . '</pre>');
     }
   }
+
+  /**
+   * Creates a mock request and sets it on the generator.
+   *
+   * This is used to manipulate how the generator generates paths during tests.
+   * It also ensures that calls to $this->drupalGet() will work when running
+   * from run-tests.sh because the url generator no longer looks at the global
+   * variables that are set there but relies on getting this information from a
+   * request object.
+   *
+   * @param bool $clean_urls
+   *   Whether to mock the request using clean urls.
+   *
+   * @param $override_server_vars
+   *   An array of server variables to override.
+   *
+   * @return $request
+   *   The mocked request object.
+   */
+  protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
+    $generator = $this->container->get('url_generator');
+    $request = Request::createFromGlobals();
+    $server = $request->server->all();
+    if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
+      // We need this for when the test is executed by run-tests.sh.
+      // @todo Remove this once run-tests.sh has been converted to use a Request
+      //   object.
+      $cwd = getcwd();
+      $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
+      $base_path = rtrim($server['REQUEST_URI'], '/');
+    }
+    else {
+      $base_path = $request->getBasePath();
+    }
+    if ($clean_urls) {
+      $request_path = $base_path ? $base_path . '/user' : 'user';
+    }
+    else {
+      $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
+    }
+    $server = array_merge($server, $override_server_vars);
+
+    $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
+    $generator->setRequest($request);
+    return $request;
+  }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php
index a502e82823a5..c3b46a25722c 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/UrlTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Common;
 
 use Drupal\simpletest\WebTestBase;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Tests for URL generation functions.
@@ -263,50 +264,6 @@ function testDrupalParseUrl() {
     $this->assertFalse(valid_url($parts['path'], TRUE), 'drupal_parse_url() correctly parsed a forged URL.');
   }
 
-  /**
-   * Tests url() functionality.
-   *
-   * Tests url() with/without query, with/without fragment, absolute on/off and
-   * asserts all that works when clean URLs are on and off.
-   */
-  function testUrl() {
-    global $base_url, $script_path;
-
-    $script_path_original = $script_path;
-    foreach (array('', 'index.php/') as $script_path) {
-      foreach (array(FALSE, TRUE) as $absolute) {
-        // Get the expected start of the path string.
-        $base = ($absolute ? $base_url . '/' : base_path()) . $script_path;
-        $absolute_string = $absolute ? 'absolute' : NULL;
-
-        $url = $base . 'node/123';
-        $result = url('node/123', array('absolute' => $absolute));
-        $this->assertEqual($url, $result, "$url == $result");
-
-        $url = $base . 'node/123#foo';
-        $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute));
-        $this->assertEqual($url, $result, "$url == $result");
-
-        $url = $base . 'node/123?foo';
-        $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute));
-        $this->assertEqual($url, $result, "$url == $result");
-
-        $url = $base . 'node/123?foo=bar&bar=baz';
-        $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute));
-        $this->assertEqual($url, $result, "$url == $result");
-
-        $url = $base . 'node/123?foo#bar';
-        $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
-        $this->assertEqual($url, $result, "$url == $result");
-
-        $url = $base;
-        $result = url('<front>', array('absolute' => $absolute));
-        $this->assertEqual($url, $result, "$url == $result");
-      }
-    }
-    $script_path = $script_path_original;
-  }
-
   /**
    * Tests external URL handling.
    */
diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php
index 6b53b2246c30..eef40d4e7349 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Path/UrlAlterFunctionalTest.php
@@ -46,7 +46,8 @@ function testUrlAlter() {
 
     // Test that a path always uses its alias.
     $path = array('source' => "user/$uid/test1", 'alias' => 'alias/test1');
-    drupal_container()->get('path.crud')->save($path['source'], $path['alias']);
+    $this->container->get('path.crud')->save($path['source'], $path['alias']);
+    $this->rebuildContainer();
     $this->assertUrlInboundAlter('alias/test1', "user/$uid/test1");
     $this->assertUrlOutboundAlter("user/$uid/test1", 'alias/test1');
 
@@ -100,7 +101,7 @@ function testCurrentUrlRequestedPath() {
    */
   protected function assertUrlOutboundAlter($original, $final) {
     // Test outbound altering.
-    $result = url($original);
+    $result = $this->container->get('url_generator')->generateFromPath($original);
     $base_path = base_path() . $GLOBALS['script_path'];
     $result = substr($result, strlen($base_path));
     $this->assertIdentical($result, $final, format_string('Altered outbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $result)));
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php
deleted file mode 100644
index 0e9f13e5f983..000000000000
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/UrlGeneratorTest.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\system\Tests\Routing\UrlGeneratorTest.
- */
-
-namespace Drupal\system\Tests\Routing;
-
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
-use Symfony\Component\Routing\RouteCollection;
-use Symfony\Component\Routing\RequestContext;
-
-use Drupal\simpletest\UnitTestBase;
-
-use Drupal\Core\Routing\UrlGenerator;
-
-/**
- * Basic tests for the Route.
- */
-class UrlGeneratorTest extends UnitTestBase {
-
-  protected $generator;
-
-  protected $aliasManager;
-
-  public static function getInfo() {
-    return array(
-      'name' => 'UrlGenerator',
-      'description' => 'Confirm that the UrlGenerator is functioning properly.',
-      'group' => 'Routing',
-    );
-  }
-
-  function setUp() {
-    parent::setUp();
-
-    $routes = new RouteCollection();
-    $routes->add('test_1', new Route('/test/one'));
-    $routes->add('test_2', new Route('/test/two/{narf}'));
-    $provider = new MockRouteProvider($routes);
-
-    $this->aliasManager = new MockAliasManager();
-    $this->aliasManager->addAlias('test/one', 'hello/world');
-
-    $context = new RequestContext();
-    $context->fromRequest(Request::create('/some/path'));
-
-    $generator = new UrlGenerator($provider, $this->aliasManager);
-    $generator->setContext($context);
-
-    $this->generator = $generator;
-  }
-
-  /**
-   * Confirms that generated routes will have aliased paths.
-   */
-  public function testAliasGeneration() {
-    $url = $this->generator->generate('test_1');
-
-    $this->assertEqual($url, '/hello/world', 'Correct URL generated including alias.');
-  }
-
-  /**
-   * Confirms that generated routes will have aliased paths.
-   */
-  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 47718699b9d1..ba62946b1a66 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php
@@ -120,6 +120,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/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index 73edd7aa938c..b613121d8884 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Database\Database;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DrupalKernel;
 use Drupal\simpletest\WebTestBase;
 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
 use Symfony\Component\HttpFoundation\Request;
@@ -18,6 +20,11 @@
  */
 abstract class UpgradePathTestBase extends WebTestBase {
 
+  /**
+   * @var array
+   */
+  protected $configDirectories;
+
   /**
    * The file path(s) to the dumped database(s) to load into the child site.
    *
@@ -103,6 +110,12 @@ protected function setUp() {
     $conf = array();
     drupal_static_reset();
 
+
+    // Build a minimal, partially mocked environment for unit tests.
+    $this->containerBuild(drupal_container());
+    // Make sure it survives kernel rebuilds.
+    $conf['container_bundles'][] = 'Drupal\simpletest\TestBundle';
+
     // Change the database prefix.
     // All static variables need to be reset before the database prefix is
     // changed, since Drupal\Core\Utility\CacheArray implementations attempt to
@@ -300,6 +313,27 @@ protected function performUpgrade($register_errors = TRUE) {
     return TRUE;
   }
 
+  /**
+   * Overrides some core services for the upgrade tests.
+   */
+  public function containerBuild(ContainerBuilder $container) {
+    // Keep the container object around for tests.
+    $this->container = $container;
+
+    $container
+      ->register('config.storage', 'Drupal\Core\Config\FileStorage')
+      ->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
+
+    if ($this->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 upgrade path tests.
+      $definition = $this->container->getDefinition('path_processor_alias');
+      $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound');
+    }
+  }
+
   /**
    * Gets update.php without calling url().
    *
diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php
index cc2bd45240cd..e509c157da31 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 000000000000..e649f6d05c4d
--- /dev/null
+++ b/core/modules/system/tests/modules/url_alter_test/lib/Drupal/url_alter_test/PathProcessorTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\url_alter_test\PathProcessorTest.
+ */
+
+namespace Drupal\url_alter_test;
+
+use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
+use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Path processor for url_alter_test.
+ */
+class PathProcessorTest implements InboundPathProcessorInterface, OutboundPathProcessorInterface {
+
+  /**
+   * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processInbound().
+   */
+  public function processInbound($path, Request $request) {
+    // Rewrite user/username to user/uid.
+    if (preg_match('!^user/([^/]+)(/.*)?!', $path, $matches)) {
+      if ($account = user_load_by_name($matches[1])) {
+        $matches += array(2 => '');
+        $path = 'user/' . $account->uid . $matches[2];
+      }
+    }
+
+    // Rewrite community/ to forum/.
+    if ($path == 'community' || strpos($path, 'community/') === 0) {
+      $path = 'forum' . substr($path, 9);
+    }
+
+    if ($path == 'url-alter-test/bar') {
+      $path = 'url-alter-test/foo';
+    }
+    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/url_alter_test.module b/core/modules/system/tests/modules/url_alter_test/url_alter_test.module
index 8bacb9b1e71f..a7567a3bac0a 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/system/tests/modules/url_alter_test/url_alter_test.services.yml b/core/modules/system/tests/modules/url_alter_test/url_alter_test.services.yml
index a05b3636c1fa..58b98bada783 100644
--- a/core/modules/system/tests/modules/url_alter_test/url_alter_test.services.yml
+++ b/core/modules/system/tests/modules/url_alter_test/url_alter_test.services.yml
@@ -1,5 +1,6 @@
 services:
-  url_alter_test.path_subscriber:
-    class: Drupal\url_alter_test\PathProcessor
+  url_alter_test.path_processor:
+    class: Drupal\url_alter_test\PathProcessorTest
     tags:
       - { name: path_processor_inbound, priority: 800 }
+      - { name: path_processor_outbound, priority: 100 }
diff --git a/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php b/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php
index 4fc436a2a569..ff55606a52cd 100644
--- a/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php
+++ b/core/modules/translation/lib/Drupal/translation/Tests/TranslationTest.php
@@ -281,8 +281,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 870321fc753c..31592c7b64de 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 de7db8529901..d28425b05d64 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
@@ -68,6 +68,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();
   }
 
   /**
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 62fca077f31c..49b1a4476e8e 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -285,8 +285,10 @@ function simpletest_script_init($server_software) {
   if (!empty($args['url'])) {
     $parsed_url = parse_url($args['url']);
     $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
-    $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
-
+    $path = isset($parsed_url['path']) ? rtrim($parsed_url['path']) : '';
+    if ($path == '/') {
+      $path = '';
+    }
     // If the passed URL schema is 'https' then setup the $_SERVER variables
     // properly so that testing will run under HTTPS.
     if ($parsed_url['scheme'] == 'https') {
diff --git a/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php b/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php
index f150ace9cef5..fdbcee62e66f 100644
--- a/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php
+++ b/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\PathProcessor;
 
+use Drupal\Component\Utility\Settings;
 use Drupal\Core\PathProcessor\PathProcessorAlias;
 use Drupal\Core\PathProcessor\PathProcessorDecode;
 use Drupal\Core\PathProcessor\PathProcessorFront;
@@ -22,6 +23,7 @@
 class PathProcessorTest extends UnitTestCase {
 
   protected $languages;
+  protected $languageManager;
 
   public static function getInfo() {
     return array(
@@ -43,6 +45,13 @@ public function setUp() {
     }
     $this->languages = $languages;
 
+    // Create a language manager stub.
+    $language_manager = $this->getMock('Drupal\Core\Language\LanguageManager');
+    $language_manager->expects($this->any())
+      ->method('getLanguage')
+      ->will($this->returnValue($languages['en']));
+
+    $this->languageManager = $language_manager;
   }
 
   /**
@@ -86,7 +95,7 @@ function testProcessInbound() {
     $alias_processor = new PathProcessorAlias($alias_manager);
     $decode_processor = new PathProcessorDecode();
     $front_processor = new PathProcessorFront($config_factory_stub);
-    $language_processor = new PathProcessorLanguage($config_factory_stub, $this->languages);
+    $language_processor = new PathProcessorLanguage($config_factory_stub, new Settings(array()), $this->languageManager, $this->languages);
 
     // First, test the processor manager with the processors in the incorrect
     // order. The alias processor will run before the language processor, meaning
diff --git a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
new file mode 100644
index 000000000000..079213e6a9c2
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Routing\UrlGeneratorTest.
+ */
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Component\Utility\Settings;
+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;
+use Symfony\Component\Routing\RequestContext;
+
+use Drupal\Tests\UnitTestCase;
+
+use Drupal\Core\Routing\UrlGenerator;
+
+/**
+ * Basic tests for the Route.
+ *
+ * @group Routing
+ */
+class UrlGeneratorTest extends UnitTestCase {
+
+  protected $generator;
+
+  protected $aliasManager;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'UrlGenerator',
+      'description' => 'Confirm that the UrlGenerator is functioning properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function setUp() {
+
+    $routes = new RouteCollection();
+    $first_route = new Route('/test/one');
+    $second_route = new Route('/test/two/{narf}');
+    $routes->add('test_1', $first_route);
+    $routes->add('test_2', $second_route);
+
+    // Create a route provider stub.
+    $provider = $this->getMockBuilder('Drupal\Core\Routing\RouteProvider')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $route_name_return_map = array(
+      array('test_1', array(), $first_route),
+      array('test_2', array('narf' => '5'), $second_route),
+    );
+    $provider->expects($this->any())
+      ->method('getRouteByName')
+      ->will($this->returnValueMap($route_name_return_map));
+    $routes_names_return_map = array(
+      array(array('test_1'), array(), array($first_route)),
+      array(array('test_2'), array('narf' => '5'), array($second_route)),
+    );
+    $provider->expects($this->any())
+      ->method('getRoutesByNames')
+      ->will($this->returnValueMap($routes_names_return_map));
+
+    // Create an alias manager stub.
+    $alias_manager = $this->getMockBuilder('Drupal\Core\Path\AliasManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $alias_map = array(
+      array('test/one', NULL, 'hello/world'),
+      array('test/two/5', NULL, 'goodbye/cruel/world'),
+      array('node/123', NULL, 'node/123'),
+    );
+    $alias_manager->expects($this->any())
+      ->method('getPathAlias')
+      ->will($this->returnValueMap($alias_map));
+
+    $this->aliasManager = $alias_manager;
+
+    $context = new RequestContext();
+    $context->fromRequest(Request::create('/some/path'));
+
+    $processor = new PathProcessorAlias($this->aliasManager);
+    $processor_manager = new PathProcessorManager();
+    $processor_manager->addOutbound($processor, 1000);
+
+    $config_factory_stub = $this->getConfigFactoryStub(array('system.filter' => array('protocols' => array('http', 'https'))));
+
+    $generator = new UrlGenerator($provider, $processor_manager, $config_factory_stub, new Settings(array()));
+    $generator->setContext($context);
+
+    $this->generator = $generator;
+  }
+
+  /**
+   * Confirms that generated routes will have aliased paths.
+   */
+  public function testAliasGeneration() {
+    $url = $this->generator->generate('test_1');
+    $this->assertEquals('/hello/world', $url);
+  }
+
+  /**
+   * Confirms that generated routes will have aliased paths.
+   */
+  public function testAliasGenerationWithParameters() {
+    $url = $this->generator->generate('test_2', array('narf' => '5'));
+    $this->assertEquals('/goodbye/cruel/world', $url, 'Correct URL generated including alias and parameters.');
+  }
+
+  /**
+   * Confirms that absolute URLs work with generated routes.
+   */
+  public function testAbsoluteURLGeneration() {
+    $url = $this->generator->generate('test_1', array(), TRUE);
+    $this->assertEquals('http://localhost/hello/world', $url);
+  }
+
+  /**
+   * Tests path-based URL generation.
+   */
+  public function testPathBasedURLGeneration() {
+    $base_path = '/subdir';
+    $base_url = 'http://www.example.com' . $base_path;
+    $this->generator->setBasePath($base_path . '/');
+    $this->generator->setBaseUrl($base_url . '/');
+    foreach (array('', 'index.php/') as $script_path) {
+      $this->generator->setScriptPath($script_path);
+      foreach (array(FALSE, TRUE) as $absolute) {
+        // Get the expected start of the path string.
+        $base = ($absolute ? $base_url . '/' : $base_path . '/') . $script_path;
+        $absolute_string = $absolute ? 'absolute' : NULL;
+        $url = $base . 'node/123';
+        $result = $this->generator->generateFromPath('node/123', array('absolute' => $absolute));
+        $this->assertEquals($url, $result, "$url == $result");
+
+        $url = $base . 'node/123#foo';
+        $result = $this->generator->generateFromPath('node/123', array('fragment' => 'foo', 'absolute' => $absolute));
+        $this->assertEquals($url, $result, "$url == $result");
+
+        $url = $base . 'node/123?foo';
+        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute));
+        $this->assertEquals($url, $result, "$url == $result");
+
+        $url = $base . 'node/123?foo=bar&bar=baz';
+        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute));
+        $this->assertEquals($url, $result, "$url == $result");
+
+        $url = $base . 'node/123?foo#bar';
+        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
+        $this->assertEquals($url, $result, "$url == $result");
+
+        $url = $base;
+        $result = $this->generator->generateFromPath('<front>', array('absolute' => $absolute));
+        $this->assertEquals($url, $result, "$url == $result");
+      }
+    }
+  }
+
+}
-- 
GitLab