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() {
* @see language()
*/
function drupal_language_initialize() {
if (language_multilingual()) {
$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();
}
drupal_container()->get('language_manager')->init();
}
/**
* 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
*
* @param string $type
* 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
* all types if $type is NULL.
*/
function language($type, $reset = FALSE) {
// 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);
*/
function language($type) {
$container = drupal_container();
if (!$container) {
return language_default();
}
else {
if (!isset($languages[$type])) {
$languages[$type] = language_default();
}
return $languages[$type];
if (!$container->has('language_manager')) {
// This happens in rare situations when the container has not been built by
// a kernel and has no services, e.g., when t() is called during unit tests
// for assertions.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
}
return $container->get('language_manager')->getLanguage($type);
}
/**
......@@ -3015,6 +2971,24 @@ function language_default() {
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.
*
......
......@@ -339,6 +339,9 @@ function install_begin_request(&$install_state) {
->addArgument(new Reference('config.storage'))
->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
// is not fully up, so we use a null backend implementation during the
// installation process. This will also speed up the installation process.
......
......@@ -98,7 +98,8 @@ public function build(ContainerBuilder $container) {
$container->register('path.alias_manager', 'Drupal\Core\Path\AliasManager')
->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_default_client', 'Guzzle\Http\Client')
......@@ -142,9 +143,10 @@ public function build(ContainerBuilder $container) {
->addArgument(new Reference('event_dispatcher'))
->addArgument(new Reference('service_container'))
->addArgument(new Reference('controller_resolver'));
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager')
->addArgument(new Reference('request'))
->setScope('request');
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
$container->register('database.slave', 'Drupal\Core\Database\Connection')
->setFactoryClass('Drupal\Core\Database\Database')
->setFactoryMethod('getConnection')
......@@ -245,6 +247,9 @@ public function build(ContainerBuilder $container) {
->addTag('event_subscriber');
$container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber')
->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')
->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) {
/**
* 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
* The Event to process.
*/
public function onKernelRequestLanguageResolve(GetResponseEvent $event) {
// drupal_language_initialize() combines:
// - Determination of language from $request information (e.g., path).
// - Determination of language from other information (e.g., site default).
// - Population of determined language into drupal_container().
// - 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();
$request = $event->getRequest();
$path = _language_resolved_path();
if ($path !== NULL) {
$this->setPath($request, $path);
}
}
/**
......
......@@ -2,7 +2,7 @@
/**
* @file
* Definition of Drupal\Core\Language\LanguageManager.
* Contains \Drupal\Core\Language\LanguageManager.
*/
namespace Drupal\Core\Language;
......@@ -11,43 +11,158 @@
/**
* 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 {
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->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) {
if (isset($this->languages[$type])) {
return $this->languages[$type];
}
// @todo Objectify the language system so that we don't have to do this.
if (language_multilingual()) {
include_once DRUPAL_ROOT . '/core/includes/language.inc';
$this->languages[$type] = language_types_initialize($type, $this->request);
if ($this->isMultilingual() && $this->request) {
if (!$this->initializing) {
$this->initializing = TRUE;
// @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 {
$this->languages[$type] = language_default();
$this->languages[$type] = $this->getLanguageDefault();
}
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) {
if (!isset($type)) {
$this->languages = array();
$this->initialized = FALSE;
}
elseif (isset($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 @@
namespace Drupal\Core\Path;
use Drupal\Core\Database\Connection;
use Drupal\Core\KeyValueStore\KeyValueFactory;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Drupal\Core\Language\LanguageManager;
class AliasManager implements AliasManagerInterface {
......@@ -27,11 +28,11 @@ class AliasManager implements AliasManagerInterface {
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.
......@@ -78,11 +79,12 @@ class AliasManager implements AliasManagerInterface {
*/
protected $preloadedPathLookups = array();
public function __construct(Connection $connection, KeyValueFactory $keyvalue) {
public function __construct(Connection $connection, KeyValueStoreInterface $state, LanguageManager $language_manager) {
$this->connection = $connection;
$this->state = $keyvalue->get('state');
$this->langcode = language(LANGUAGE_TYPE_URL)->langcode;
$this->state = $state;
$this->languageManager = $language_manager;
$this->whitelist = $this->state->get('system.path_alias_whitelist', NULL);
if (!isset($this->whitelist)) {
$this->whitelist = $this->pathAliasWhitelistRebuild();
}
......@@ -96,7 +98,7 @@ public function getSystemPath($path, $path_language = NULL) {
// language. If we used a language different from the one conveyed by the
// requested URL, we might end up being unable to check if there is a path
// alias matching the URL path.
$path_language = $path_language ?: $this->langcode;
$path_language = $path_language ?: $this->languageManager->getLanguage(LANGUAGE_TYPE_URL)->langcode;
$original_path = $path;
// Lookup the path alias first.
if (!empty($path) && $source = $this->lookupPathSource($path, $path_language)) {
......@@ -114,7 +116,7 @@ public function getPathAlias($path, $path_language = NULL) {
// language. If we used a language different from the one conveyed by the
// requested URL, we might end up being unable to check if there is a path
// alias matching the URL path.
$path_language = $path_language ?: $this->langcode;
$path_language = $path_language ?: $this->languageManager->getLanguage(LANGUAGE_TYPE_URL)->langcode;
$result = $path;
if (!empty($path) && $alias = $this->lookupPathAlias($path, $path_language)) {
$result = $alias;
......
......@@ -33,10 +33,6 @@ function testLocaleConfigOverride() {
$name = 'config_test.system';
// Verify the default configuration values exist.
$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');
}
}
......@@ -507,7 +507,6 @@ function language_save($language) {
if (!empty($language->default)) {
// Set the new version of this language as default in a variable.
$default_language = language_default();
variable_set('language_default', (array) $language);
}
......
<?php
use \Symfony\Component\HttpFoundation\Request;
/**
* @file
* Language negotiation functions.
......@@ -206,14 +208,19 @@ function language_from_user($languages) {
* @param $languages
* 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
* 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).
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;
}
......@@ -257,31 +264,29 @@ function language_from_session($languages) {
* @param $languages
* An array of valid language objects.
*
* @param $request
* The HttpRequest object representing the current request.
* @param \Symfony\Component\HttpFoundation\Request|null $request
* (optional) The HttpRequest object representing the current request.
* Defaults to NULL.
*
* @return
* 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;
if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL)) {
if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL) || !$request) {
return $language_url;
}
switch (config('language.negotiation')->get('url.source')) {
case LANGUAGE_NEGOTIATION_URL_PREFIX:
$current_path = $request->attributes->get('system_path');
if (!isset($current_path)) {
$current_path = trim($request->getPathInfo(), '/');
}
$request_path = urldecode(trim($request->getPathInfo(), '/'));
list($language, $path) = language_url_split_prefix($current_path, $languages);
// Store the correct system path, i.e. minus the path prefix, in the
// request.
$request->attributes->set('system_path', $path);
list($language, $path) = language_url_split_prefix($request_path, $languages);
// Store the correct system path, i.e., the request path without the
// language prefix.
_language_resolved_path($path);
if ($language !== FALSE) {
$language_url = $language->langcode;
......
......@@ -35,7 +35,7 @@ function setUp() {
parent::setUp();
// Ensure we are building a new Language object for each test.
language(NULL, TRUE);
$this->container->get('language_manager')->reset();
}
......
......@@ -369,6 +369,7 @@ protected function runTest($test) {
if (!empty($test['language_test_domain'])) {
state()->set('language_test.domain', $test['language_test_domain']);
}
$this->container->get('language_manager')->reset();
$this->drupalGet($test['path'], array(), $test['http_header']);
$this->assertText($test['expect'], $test['message']);
$this->assertText(t('Language negotiation method: @name', array('@name' => $test['expected_method_id'])));
......
<?php
/**
* @file
* Contains \Drupal\locale\LocaleBundle.
*/
namespace Drupal\locale;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Registers locale module's services to the container.
*/
class LocaleBundle extends Bundle {
/**
* Implements \Symfony\Component\HttpKernel\Bundle\BundleInterface::build().
*/
public function build(ContainerBuilder $container) {
$container->register('locale_config_subscriber', 'Drupal\locale\LocaleConfigSubscriber')
->addArgument(new Reference('language_manager'))
->addTag('event_subscriber');
}
}
......@@ -9,6 +9,7 @@
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigEvent;
use Drupal\Core\Config\StorageDispatcher;
use Drupal\Core\Language\LanguageManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -18,6 +19,24 @@
* $config is always a DrupalConfig object.
*/
class LocaleConfigSubscriber implements EventSubscriberInterface {
/**
* The language manager for retrieving the interface language.
*
* @var \Drupal\Core\Language\LanguageManager
*/
protected $languageManager;
/**
* Constructs a LocaleConfigSubscriber object.
*
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager service.
*/
public function __construct(LanguageManager $language_manager) {
$this->languageManager = $language_manager;
}
/**
* Override configuration values with localized data.
*
......@@ -26,7 +45,7 @@ class LocaleConfigSubscriber implements EventSubscriberInterface {
*/
public function configLoad(ConfigEvent $event) {
$config = $event->getConfig();
$language = language(LANGUAGE_TYPE_INTERFACE);
$language = $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE);
$locale_name = $this->getLocaleConfigName($config->getName(), $language);
if ($override = $config->getStorage()->read($locale_name)) {
$config->setOverride($override);
......
......@@ -56,10 +56,12 @@ function testUninstallProcess() {
'default' => $this->langcode == 'fr',
));
language_save($language);
// Reset statically cached language objects.
language(NULL, TRUE);
// Reset the language manager.
$language_manager = $this->container->get('language_manager');
$language_manager->reset();
$language_manager->init();
// Check the UI language.
drupal_language_initialize();
$this->assertEqual(language(LANGUAGE_TYPE_INTERFACE)->langcode, $this->langcode, t('Current language: %lang', array('%lang' => language(LANGUAGE_TYPE_INTERFACE)->langcode)));
// Enable multilingual workflow option for articles.
......@@ -101,13 +103,11 @@ function testUninstallProcess() {
// Uninstall Locale.
module_disable($locale_module);
module_uninstall($locale_module);
$this->rebuildContainer();
// Visit the front page.
$this->drupalGet('');
// Reset statically cached language objects.
language(NULL, TRUE);
// Check the init language logic.
drupal_language_initialize();
$this->assertEqual(language(LANGUAGE_TYPE_INTERFACE)->langcode, 'en', t('Language after uninstall: %lang', array('%lang' => language(LANGUAGE_TYPE_INTERFACE)->langcode)));
// Check JavaScript files deletion.
......
......@@ -1303,11 +1303,3 @@ function _locale_rebuild_js($langcode = NULL) {
return TRUE;
}
}
/**
* Implements hook_language_init().
*/
function locale_language_init() {
// Add locale helper to configuration subscribers.
drupal_container()->get('event_dispatcher')->addSubscriber(new LocaleConfigSubscriber());
}
......@@ -1025,13 +1025,6 @@ protected function tearDown() {
// this second reset is guranteed to reset everything to nothing.
drupal_static_reset();