Commit 3e478b03 authored by Dries's avatar Dries

Issue #1899994 by katbailey, plach, YesCT: Disentangle language initialization...

Issue #1899994 by katbailey, plach, YesCT: Disentangle language initialization from path resolution.
parent cf882741
...@@ -2744,73 +2744,29 @@ function get_t() { ...@@ -2744,73 +2744,29 @@ function get_t() {
* @see language() * @see language()
*/ */
function drupal_language_initialize() { function drupal_language_initialize() {
if (language_multilingual()) { drupal_container()->get('language_manager')->init();
$types = language_types_get_all();
foreach ($types as $type) {
language($type);
}
// Allow modules to react on language system initialization in multilingual
// environments.
bootstrap_invoke_all('language_init');
// @todo D8: Remove after http://drupal.org/node/1859110.
drupal_container()->get('config.factory')->reset();
}
} }
/** /**
* Returns the language object for a given language type. * Returns the language object for a given language type.
* *
* The 'language_manager' service is only available within the scope of a kernel
* request. When it's not available, we return a default language object,
* regardless of the type passed in.
*
* @see Drupal\Core\Language\LanguageManager * @see Drupal\Core\Language\LanguageManager
* *
* @param string $type * @param string $type
* The type of language object needed, e.g. LANGUAGE_TYPE_INTERFACE. * The type of language object needed, e.g. LANGUAGE_TYPE_INTERFACE.
* @param bool $reset */
* TRUE to reset the statically cached language object for the type, or for function language($type) {
* all types if $type is NULL. $container = drupal_container();
*/ if (!$container) {
function language($type, $reset = FALSE) { return language_default();
// We don't use drupal_static() here because resetting is not a simple case of
// drupal_static_reset().
static $languages = array();
// Reset the language manager's cache and our own.
if ($reset) {
if (drupal_container()->isScopeActive('request')) {
drupal_container()->get('language_manager')->reset($type);
}
if (!isset($type)) {
$languages = array();
}
elseif (isset($languages[$type])) {
unset($languages[$type]);
}
}
// If no type is passed (most likely when resetting all types), return.
if (!isset($type)) {
return;
}
// When the language_manager service exists (is both defined and the 'request'
// scope is active in the container), use it to get the language. Otherwise
// return the default language.
if (drupal_container()->isScopeActive('request')) {
$language_manager = drupal_container()->get('language_manager', Container::NULL_ON_INVALID_REFERENCE);
}
if (isset($language_manager)) {
return $language_manager->getLanguage($type);
} }
else { if (!$container->has('language_manager')) {
if (!isset($languages[$type])) { // This happens in rare situations when the container has not been built by
$languages[$type] = language_default(); // a kernel and has no services, e.g., when t() is called during unit tests
} // for assertions.
return $languages[$type]; $container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
} }
return $container->get('language_manager')->getLanguage($type);
} }
/** /**
...@@ -3015,6 +2971,24 @@ function language_default() { ...@@ -3015,6 +2971,24 @@ function language_default() {
return new Language($info); return new Language($info);
} }
/**
* Stores or retrieves the path derived during language negotiation.
*
* @param string $new_path
* The altered path.
*
* @todo Replace this with a path processor in language module. See
* http://drupal.org/node/1888424.
*/
function _language_resolved_path($new_path = NULL) {
$path = &drupal_static(__FUNCTION__, NULL);
if ($new_path === NULL) {
return $path;
}
$path = $new_path;
return $path;
}
/** /**
* Returns the requested URL path of the page being viewed. * Returns the requested URL path of the page being viewed.
* *
......
...@@ -339,6 +339,9 @@ function install_begin_request(&$install_state) { ...@@ -339,6 +339,9 @@ function install_begin_request(&$install_state) {
->addArgument(new Reference('config.storage')) ->addArgument(new Reference('config.storage'))
->addArgument(new Reference('event_dispatcher')); ->addArgument(new Reference('event_dispatcher'));
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
// The install process cannot use the database lock backend since the database // The install process cannot use the database lock backend since the database
// is not fully up, so we use a null backend implementation during the // is not fully up, so we use a null backend implementation during the
// installation process. This will also speed up the installation process. // installation process. This will also speed up the installation process.
......
...@@ -98,7 +98,8 @@ public function build(ContainerBuilder $container) { ...@@ -98,7 +98,8 @@ public function build(ContainerBuilder $container) {
$container->register('path.alias_manager', 'Drupal\Core\Path\AliasManager') $container->register('path.alias_manager', 'Drupal\Core\Path\AliasManager')
->addArgument(new Reference('database')) ->addArgument(new Reference('database'))
->addArgument(new Reference('keyvalue')); ->addArgument(new Reference('state'))
->addArgument(new Reference('language_manager'));
$container->register('http_client_simpletest_subscriber', 'Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber'); $container->register('http_client_simpletest_subscriber', 'Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber');
$container->register('http_default_client', 'Guzzle\Http\Client') $container->register('http_default_client', 'Guzzle\Http\Client')
...@@ -142,9 +143,10 @@ public function build(ContainerBuilder $container) { ...@@ -142,9 +143,10 @@ public function build(ContainerBuilder $container) {
->addArgument(new Reference('event_dispatcher')) ->addArgument(new Reference('event_dispatcher'))
->addArgument(new Reference('service_container')) ->addArgument(new Reference('service_container'))
->addArgument(new Reference('controller_resolver')); ->addArgument(new Reference('controller_resolver'));
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager')
->addArgument(new Reference('request')) // Register the 'language_manager' service.
->setScope('request'); $container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
$container->register('database.slave', 'Drupal\Core\Database\Connection') $container->register('database.slave', 'Drupal\Core\Database\Connection')
->setFactoryClass('Drupal\Core\Database\Database') ->setFactoryClass('Drupal\Core\Database\Database')
->setFactoryMethod('getConnection') ->setFactoryMethod('getConnection')
...@@ -245,6 +247,9 @@ public function build(ContainerBuilder $container) { ...@@ -245,6 +247,9 @@ public function build(ContainerBuilder $container) {
->addTag('event_subscriber'); ->addTag('event_subscriber');
$container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber') $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber')
->addTag('event_subscriber'); ->addTag('event_subscriber');
$container->register('language_request_subscriber', 'Drupal\Core\EventSubscriber\LanguageRequestSubscriber')
->addArgument(new Reference('language_manager'))
->addTag('event_subscriber');
$container->register('exception_controller', 'Drupal\Core\ExceptionController') $container->register('exception_controller', 'Drupal\Core\ExceptionController')
->addArgument(new Reference('content_negotiation')) ->addArgument(new Reference('content_negotiation'))
......
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\LanguageRequestSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Language\LanguageManager;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Sets the $request property on the language manager.
*/
class LanguageRequestSubscriber implements EventSubscriberInterface {
/**
* The language manager service.
*
* @var \Drupal\Core\Language\LanguageManager
*/
protected $languageManager;
/**
* Constructs a LanguageRequestSubscriber object.
*
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager service.
*
*/
public function __construct(LanguageManager $language_manager) {
$this->languageManager = $language_manager;
}
/**
* Sets the request on the language manager.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestLanguage(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$this->languageManager->setRequest($event->getRequest());
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestLanguage', 300);
return $events;
}
}
...@@ -77,22 +77,15 @@ public function onKernelRequestFrontPageResolve(GetResponseEvent $event) { ...@@ -77,22 +77,15 @@ public function onKernelRequestFrontPageResolve(GetResponseEvent $event) {
/** /**
* Decode language information embedded in the request path. * Decode language information embedded in the request path.
* *
* @todo Refactor this entire method to inline the relevant portions of
* drupal_language_initialize(). See the inline comment for more details.
*
* @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process. * The Event to process.
*/ */
public function onKernelRequestLanguageResolve(GetResponseEvent $event) { public function onKernelRequestLanguageResolve(GetResponseEvent $event) {
// drupal_language_initialize() combines: $request = $event->getRequest();
// - Determination of language from $request information (e.g., path). $path = _language_resolved_path();
// - Determination of language from other information (e.g., site default). if ($path !== NULL) {
// - Population of determined language into drupal_container(). $this->setPath($request, $path);
// - Removal of language code from _current_path(). }
// @todo Decouple the above, but for now, invoke it and update the path
// prior to front page and alias resolution. When above is decoupled, also
// add 'langcode' (determined from $request only) to $request->attributes.
drupal_language_initialize();
} }
/** /**
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/** /**
* @file * @file
* Definition of Drupal\Core\Language\LanguageManager. * Contains \Drupal\Core\Language\LanguageManager.
*/ */
namespace Drupal\Core\Language; namespace Drupal\Core\Language;
...@@ -11,43 +11,158 @@ ...@@ -11,43 +11,158 @@
/** /**
* Class responsible for initializing each language type. * Class responsible for initializing each language type.
*
* This service is dependent on the 'request' service and can therefore pass the
* Request object to the code that deals with each particular language type.
* This means the Request can be used directly for things like URL-based
* language negotiation.
*/ */
class LanguageManager { class LanguageManager {
private $request; /**
private $languages; * A request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
public function __construct(Request $request = NULL) { /**
* An array of language objects keyed by language type.
*
* @var array
*/
protected $languages;
/**
* Whether or not the language manager has been initialized.
*
* @var bool
*/
protected $initialized = FALSE;
/**
* Whether already in the process of language initialization.
*
* @todo This is only needed due to the circular dependency between language
* and config. See http://drupal.org/node/1862202 for the plan to fix this.
*
* @var bool
*/
protected $initializing = FALSE;
/**
* Initializes each language type to a language object.
*/
public function init() {
if ($this->initialized) {
return;
}
if ($this->isMultilingual()) {
foreach ($this->getLanguageTypes() as $type) {
$this->getLanguage($type);
}
}
$this->initialized = TRUE;
}
/**
* Sets the $request property and resets all language types.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request.
*/
public function setRequest(Request $request) {
$this->request = $request; $this->request = $request;
$this->reset();
$this->init();
} }
/**
* Returns a language object for the given type.
*
* @param string $type
* The language type, e.g. LANGUAGE_TYPE_INTERFACE.
*
* @return \Drupal\Core\Language\Language
* A language object for the given type.
*/
public function getLanguage($type) { public function getLanguage($type) {
if (isset($this->languages[$type])) { if (isset($this->languages[$type])) {
return $this->languages[$type]; return $this->languages[$type];
} }
// @todo Objectify the language system so that we don't have to do this. if ($this->isMultilingual() && $this->request) {
if (language_multilingual()) { if (!$this->initializing) {
include_once DRUPAL_ROOT . '/core/includes/language.inc'; $this->initializing = TRUE;
$this->languages[$type] = language_types_initialize($type, $this->request); // @todo Objectify the language system so that we don't have to load an
// include file and call out to procedural code. See
// http://drupal.org/node/1862202
include_once DRUPAL_ROOT . '/core/includes/language.inc';
$this->languages[$type] = language_types_initialize($type, $this->request);
$this->initializing = FALSE;
}
else {
// Config has called getLanguage() during initialization of a language
// type. Simply return the default language without setting it on the
// $this->languages property. See the TODO in the docblock for the
// $initializing property.
return $this->getLanguageDefault();
}
} }
else { else {
$this->languages[$type] = language_default(); $this->languages[$type] = $this->getLanguageDefault();
} }
return $this->languages[$type]; return $this->languages[$type];
} }
/**
* Resets the given language type or all types if none specified.
*
* @param string|null $type
* (optional) The language type to reset as a string, e.g.,
* LANGUAGE_TYPE_INTERFACE, or NULL to reset all language types. Defaults to
* NULL.
*/
public function reset($type = NULL) { public function reset($type = NULL) {
if (!isset($type)) { if (!isset($type)) {
$this->languages = array(); $this->languages = array();
$this->initialized = FALSE;
} }
elseif (isset($this->languages[$type])) { elseif (isset($this->languages[$type])) {
unset($this->languages[$type]); unset($this->languages[$type]);
} }
} }
/**
* Returns whether or not the site has more than one language enabled.
*
* @return bool
* TRUE if more than one language is enabled, FALSE otherwise.
*/
protected function isMultilingual() {
return variable_get('language_count', 1) > 1;
}
/**
* Returns an array of the available language types.
*
* @return array()
* An array of all language types.
*/
protected function getLanguageTypes() {
return array_keys(variable_get('language_types', language_types_get_default()));
}
/**
* Returns a language object representing the site's default language.
*
* @return Drupal\Core\Language\Language
* A language object.
*/
protected function getLanguageDefault() {
$default_info = variable_get('language_default', array(
'langcode' => 'en',
'name' => 'English',
'direction' => 0,
'weight' => 0,
'locked' => 0,
));
return new Language($default_info + array('default' => TRUE));
}
} }
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
namespace Drupal\Core\Path; namespace Drupal\Core\Path;
use Drupal\Core\Database\Connection; use Drupal\Core\Database\Connection;
use Drupal\Core\KeyValueStore\KeyValueFactory; use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Language\LanguageManager;
class AliasManager implements AliasManagerInterface { class AliasManager implements AliasManagerInterface {
...@@ -27,11 +28,11 @@ class AliasManager implements AliasManagerInterface { ...@@ -27,11 +28,11 @@ class AliasManager implements AliasManagerInterface {
protected $state; protected $state;
/** /**
* The default langcode to use when none is specified for path lookups. * Language manager for retrieving the default langcode when none is specified.
* *
* @var string * @var \Drupal\Core\Language\LanguageManager
*/ */
protected $langcode; protected $languageManager;
/** /**
* Holds the map of path lookups per language. * Holds the map of path lookups per language.
...@@ -78,11 +79,12 @@ class AliasManager implements AliasManagerInterface { ...@@ -78,11 +79,12 @@ class AliasManager implements AliasManagerInterface {
*/ */
protected $preloadedPathLookups = array(); protected $preloadedPathLookups = array();
public function __construct(Connection $connection, KeyValueFactory $keyvalue) { public function __construct(Connection $connection, KeyValueStoreInterface $state, LanguageManager $language_manager) {
$this->connection = $connection; $this->connection = $connection;
$this->state = $keyvalue->get('state'); $this->state = $state;
$this->langcode = language(LANGUAGE_TYPE_URL)->langcode; $this->languageManager = $language_manager;
$this->whitelist = $this->state->get('system.path_alias_whitelist', NULL); $this->whitelist = $this->state->get('system.path_alias_whitelist', NULL);
if (!isset($this->whitelist)) { if (!isset($this->whitelist)) {
$this->whitelist = $this->pathAliasWhitelistRebuild(); $this->whitelist = $this->pathAliasWhitelistRebuild();
} }
...@@ -96,7 +98,7 @@ public function getSystemPath($path, $path_language = NULL) { ...@@ -96,7 +98,7 @@ public function getSystemPath($path, $path_language = NULL) {
// language. If we used a language different from the one conveyed by the // language. If we used a language different from the one conveyed by the
// requested URL, we might end up being unable to check if there is a path // requested URL, we might end up being unable to check if there is a path
// alias matching the URL path. // alias matching the URL path.
$path_language = $path_language ?: $this->langcode; $path_language = $path_language ?: $this->languageManager->getLanguage(LANGUAGE_TYPE_URL)->langcode;
$original_path = $path; $original_path = $path;
// Lookup the path alias first. // Lookup the path alias first.
if (!empty($path) && $source = $this->lookupPathSource($path, $path_language)) { if (!empty($path) && $source = $this->lookupPathSource($path, $path_language)) {
...@@ -114,7 +116,7 @@ public function getPathAlias($path, $path_language = NULL) { ...@@ -114,7 +116,7 @@ public function getPathAlias($path, $path_language = NULL) {
// language. If we used a language different from the one conveyed by the // language. If we used a language different from the one conveyed by the
// requested URL, we might end up being unable to check if there is a path // requested URL, we might end up being unable to check if there is a path
// alias matching the URL path. // alias matching the URL path.
$path_language = $path_language ?: $this->langcode; $path_language = $path_language ?: $this->languageManager->getLanguage(LANGUAGE_TYPE_URL)->langcode;
$result = $path; $result = $path;
if (!empty($path) && $alias = $this->lookupPathAlias($path, $path_language)) { if (!empty($path) && $alias = $this->lookupPathAlias($path, $path_language)) {
$result = $alias; $result = $alias;
......
...@@ -33,10 +33,6 @@ function testLocaleConfigOverride() { ...@@ -33,10 +33,6 @@ function testLocaleConfigOverride() {
$name = 'config_test.system'; $name = 'config_test.system';
// Verify the default configuration values exist. // Verify the default configuration values exist.
$config = config($name); $config = config($name);
$this->assertIdentical($config->get('foo'), 'bar');
// Spoof multilingual.
$GLOBALS['conf']['language_count'] = 2;
drupal_language_initialize();
$this->assertIdentical($config->get('foo'), 'en bar'); $this->assertIdentical($config->get('foo'), 'en bar');
} }
} }
...@@ -507,7 +507,6 @@ function language_save($language) { ...@@ -507,7 +507,6 @@ function language_save($language) {
if (!empty($language->default)) { if (!empty($language->default)) {
// Set the new version of this language as default in a variable. // Set the new version of this language as default in a variable.
$default_language = language_default();
variable_set('language_default', (array) $language); variable_set('language_default', (array) $language);
} }
......
<?php <?php
use \Symfony\Component\HttpFoundation\Request;
/** /**
* @file * @file
* Language negotiation functions. * Language negotiation functions.
...@@ -206,14 +208,19 @@ function language_from_user($languages) { ...@@ -206,14 +208,19 @@ function language_from_user($languages) {
* @param $languages * @param $languages
* An array of valid language objects. * An array of valid language objects.
* *
* @param \Symfony\Component\HttpFoundation\Request|null $request
* (optional) The HttpRequest object representing the current request.
* Defaults to NULL.
*
* @return * @return
* A valid language code on success, FALSE otherwise. * A valid language code on success, FALSE otherwise.
*/ */
function language_from_user_admin($languages) { function language_from_user_admin(array $languages, Request $request = NULL) {
// User preference (only for authenticated users). // User preference (only for authenticated users).
global $user; global $user;
if ($user->uid && !empty($user->preferred_admin_langcode) && isset($languages[$user->preferred_admin_langcode]) && path_is_admin(current_path())) { $request_path = $request ? urldecode(trim($request->getPathInfo(), '/')) : _current_path();
if ($user->uid && !empty($user->preferred_admin_langcode) && isset($languages[$user->preferred_admin_langcode]) && path_is_admin($request_path)) {
return $user->preferred_admin_langcode; return $user->preferred_admin_langcode;
} }
...@@ -257,31 +264,29 @@ function language_from_session($languages) { ...@@ -257,31 +264,29 @@ function language_from_session($languages) {
* @param $languages * @param $languages
* An array of valid language objects. * An array of valid language objects.
* *
* @param $request * @param \Symfony\Component\HttpFoundation\Request|null $request
* The HttpRequest object representing the current request. * (optional) The HttpRequest object representing the current request.
* Defaults to NULL.
* *
* @return * @return
* A valid language code on success, FALSE otherwise. * A valid language code on success, FALSE otherwise.
*/ */
function language_from_url($languages, $request) { function language_from_url($languages, Request $request = NULL) {
$language_url = FALSE; $language_url = FALSE;
if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL)) { if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL) || !$request) {
return $language_url; return $language_url;
} }
switch (config('language.negotiation')->get('url.source')) { switch (config('language.negotiation')->get('url.source')) {
case LANGUAGE_NEGOTIATION_URL_PREFIX: case LANGUAGE_NEGOTIATION_URL_PREFIX:
$current_path = $request->attributes->get('system_path'); $request_path = urldecode(trim($request->getPathInfo(), '/'));
if (!isset($current_path)) {
$current_path = trim($request->getPathInfo(), '/');
}
list($language, $path) = language_url_split_prefix($current_path, $languages); list($language, $path) = language_url_split_prefix($request_path, $languages);
// Store the correct system path, i.e. minus the path prefix, in the // Store the correct system path, i.e., the request path without the
// request. // language prefix.
$request->attributes->set('system_path', $path); _language_resolved_path($path);
if ($language !== FALSE) { if ($language !== FALSE) {
$language_url = $language->langcode; $language_url = $language->langcode;
......
...@@ -35,7 +35,7 @@ function setUp() { ...@@ -35,7 +35,7 @@ function setUp() {
parent::setUp(); parent::setUp();
// Ensure we are building a new Language object for each test. // Ensure we are building a new Language object for each test.
language(NULL, TRUE); $this->container->get('language_manager')->reset();
} }