diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 60b086a..b505a7b 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -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); } /** @@ -3016,6 +2972,21 @@ function language_default() { } /** + * Stores or retrieves the path derived during language negotiation. + * + * @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. * * Examples: diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 86a45f7..8cc661f 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -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. diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index 4684695..b4f3f90 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -10,6 +10,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass; +use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass; use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -98,7 +99,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 +144,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') @@ -228,6 +231,7 @@ public function build(ContainerBuilder $container) { ->addTag('event_subscriber'); $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber') ->addArgument(new Reference('path.alias_manager.cached')) + ->addArgument(new Reference('path_processor_manager')) ->addTag('event_subscriber'); $container->register('legacy_request_subscriber', 'Drupal\Core\EventSubscriber\LegacyRequestSubscriber') ->addTag('event_subscriber'); @@ -242,6 +246,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')) @@ -250,6 +257,8 @@ public function build(ContainerBuilder $container) { ->addTag('event_subscriber') ->addArgument(array(new Reference('exception_controller'), 'execute')); + $this->registerPathProcessors($container); + $container ->register('transliteration', 'Drupal\Core\Transliteration\PHPTransliteration'); @@ -368,4 +377,27 @@ protected function registerTwig(ContainerBuilder $container) { // @see http://drupal.org/node/1804998 ->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug'))); } + + /** + * Register services related to path manipulation. + */ + protected function registerPathProcessors(ContainerBuilder $container) { + // Register the path manipulator manager service. + $container->register('path_processor_manager', 'Drupal\Core\PathProcessor\PathProcessorManager'); + // Register the manipulator that urldecodes the path. + $container->register('path_processor_decode', 'Drupal\Core\PathProcessor\PathProcessorDecode') + ->addTag('incoming_path_processor', array('priority' => 300)); + // Register the manipulator that resolves the front page. + $container->register('path_processor_front', 'Drupal\Core\PathProcessor\PathProcessorFront') + ->addArgument(new Reference('config.factory')) + ->addTag('incoming_path_processor', array('priority' => 200)); + // Register the alias path manipulator. + $container->register('path_processor_alias', 'Drupal\Core\PathProcessor\PathProcessorAlias') + ->addArgument(new Reference('path.alias_manager')) + ->addTag('incoming_path_processor', array('priority' => 100)); + + // Add the compiler pass that will process the tagged services. + $container->addCompilerPass(new RegisterPathProcessorsPass()); + } + } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php new file mode 100644 index 0000000..08d31c1 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterPathProcessorsPass.php @@ -0,0 +1,35 @@ +hasDefinition('path_processor_manager')) { + return; + } + $manager = $container->getDefinition('path_processor_manager'); + foreach ($container->findTaggedServiceIds('incoming_path_processor') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $manager->addMethodCall('addIncoming', array(new Reference($id), $priority)); + } + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/LanguageRequestSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LanguageRequestSubscriber.php new file mode 100644 index 0000000..9a05a28 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LanguageRequestSubscriber.php @@ -0,0 +1,63 @@ +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; + } + +} diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php index be0e779..42fa304 100644 --- a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -8,6 +8,7 @@ namespace Drupal\Core\EventSubscriber; use Drupal\Core\CacheDecorator\AliasManagerCacheDecorator; +use Drupal\Core\PathProcessor\IncomingPathProcessorInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -20,24 +21,24 @@ class PathSubscriber extends PathListenerBase implements EventSubscriberInterface { protected $aliasManager; + protected $manipulatorManager; - public function __construct(AliasManagerCacheDecorator $alias_manager) { + public function __construct(AliasManagerCacheDecorator $alias_manager, IncomingPathProcessorInterface $manipulator) { $this->aliasManager = $alias_manager; + $this->manipulator = $manipulator; } /** - * Resolve the system path. + * Converts the request path to a system path. * * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event * The Event to process. */ - public function onKernelRequestPathResolve(GetResponseEvent $event) { + public function onKernelRequestConvertPath(GetResponseEvent $event) { $request = $event->getRequest(); - $path = $this->extractPath($request); - $path = $this->aliasManager->getSystemPath($path); - $this->setPath($request, $path); - // If this is the master request, set the cache key for the caching of all - // system paths looked up during the request. + $path = trim($request->getPathInfo(), '/'); + $path = $this->manipulator->processIncoming($path, $request); + $request->attributes->set('system_path', $path); if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { $this->aliasManager->setCacheKey($path); } @@ -51,88 +52,14 @@ public function onKernelTerminate(PostResponseEvent $event) { } /** - * Resolve the front-page default path. - * - * @todo The path system should be objectified to remove the function calls in - * this method. - * - * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event - * The Event to process. - */ - public function onKernelRequestFrontPageResolve(GetResponseEvent $event) { - $request = $event->getRequest(); - $path = $this->extractPath($request); - - if (empty($path)) { - // @todo Temporary hack. Fix when configuration is injectable. - $path = config('system.site')->get('page.front'); - if (empty($path)) { - $path = 'user'; - } - } - - $this->setPath($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 - * 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(); - } - - /** - * Decodes the path of the request. - * - * Parameters in the URL sometimes represent code-meaningful strings. It is - * therefore useful to always urldecode() those values so that individual - * controllers need not concern themselves with it. This is Drupal-specific - * logic and may not be familiar for developers used to other Symfony-family - * projects. - * - * @todo Revisit whether or not this logic is appropriate for here or if - * controllers should be required to implement this logic themselves. If we - * decide to keep this code, remove this TODO. - * - * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event - * The Event to process. - */ - public function onKernelRequestDecodePath(GetResponseEvent $event) { - $request = $event->getRequest(); - $path = $this->extractPath($request); - - $path = urldecode($path); - - $this->setPath($request, $path); - } - - /** * 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('onKernelRequestDecodePath', 200); - $events[KernelEvents::REQUEST][] = array('onKernelRequestLanguageResolve', 150); - $events[KernelEvents::REQUEST][] = array('onKernelRequestFrontPageResolve', 101); - $events[KernelEvents::REQUEST][] = array('onKernelRequestPathResolve', 100); + $events[KernelEvents::REQUEST][] = array('onKernelRequestConvertPath', 200); $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200); - return $events; } } diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index ff0177d..6209b3f 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -2,52 +2,168 @@ /** * @file - * Definition of Drupal\Core\Language\LanguageManager. + * Contains \Drupal\Core\Language\LanguageManager. */ namespace Drupal\Core\Language; +use Drupal\Core\Config\ConfigFactory; use Symfony\Component\HttpFoundation\Request; /** * 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 langauge 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)); + } + } diff --git a/core/lib/Drupal/Core/Path/AliasManager.php b/core/lib/Drupal/Core/Path/AliasManager.php index 47bf77b..45bdca2 100644 --- a/core/lib/Drupal/Core/Path/AliasManager.php +++ b/core/lib/Drupal/Core/Path/AliasManager.php @@ -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; diff --git a/core/lib/Drupal/Core/PathProcessor/IncomingPathProcessorInterface.php b/core/lib/Drupal/Core/PathProcessor/IncomingPathProcessorInterface.php new file mode 100644 index 0000000..a634210 --- /dev/null +++ b/core/lib/Drupal/Core/PathProcessor/IncomingPathProcessorInterface.php @@ -0,0 +1,28 @@ +aliasManager = $alias_manager; + } + + /** + * Implements Drupal\Core\PathProcessor\IncomingPathProcessorInterface::processIncoming(). + */ + public function processIncoming($path, Request $request) { + $path = $this->aliasManager->getSystemPath($path); + return $path; + } + +} diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorDecode.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorDecode.php new file mode 100644 index 0000000..cfc8039 --- /dev/null +++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorDecode.php @@ -0,0 +1,35 @@ +config = $config; + } + + /** + * Implements Drupal\Core\PathProcessor\IncomingPathProcessorInterface::processIncoming(). + */ + public function processIncoming($path, Request $request) { + if (empty($path)) { + $path = $this->config->get('system.site')->get('page.front'); + if (empty($path)) { + $path = 'user'; + } + } + return $path; + } + +} diff --git a/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php new file mode 100644 index 0000000..6b5e6cb --- /dev/null +++ b/core/lib/Drupal/Core/PathProcessor/PathProcessorManager.php @@ -0,0 +1,96 @@ +incomingProcessors[$priority])) { + $this->incomingProcessors[$priority] = array(); + } + + $this->incomingProcessors[$priority][] = $processor; + $this->sortedIncoming = array(); + } + + /** + * Implements Drupal\Core\PathProcessor\IncomingPathProcessorInterface::processIncoming(). + */ + public function processIncoming($path, Request $request) { + $processors = $this->getIncoming(); + foreach ($processors as $processor) { + $path = $processor->processIncoming($path, $request); + } + return $path; + } + + /** + * Returns the sorted array of incoming processors. + * + * @return array + * An array of processor objects. + */ + protected function getIncoming() { + if (empty($this->sortedIncoming)) { + $this->sortedIncoming = $this->sortProcessors('incomingProcessors'); + } + + return $this->sortedIncoming; + } + + /** + * Sorts the processors according to priority. + * + * @param string $type + * The processor type to sort, e.g. 'incomingProcessors'. + */ + protected function sortProcessors($type) { + $sorted = array(); + krsort($this->{$type}); + + foreach ($this->{$type} as $processors) { + $sorted = array_merge($sorted, $processors); + } + return $sorted; + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php b/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php index 350a166..20f835d 100644 --- a/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php +++ b/core/modules/config/lib/Drupal/config/Tests/LocaleConfigOverride.php @@ -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'); } } diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 3486ff4..d0b91bf 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -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); } diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc index 8bf45a6..e89658f 100644 --- a/core/modules/language/language.negotiation.inc +++ b/core/modules/language/language.negotiation.inc @@ -1,5 +1,7 @@ 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,24 @@ 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. * * @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(), '/'); - } - - 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); + $request_path = urldecode(trim($request->getPathInfo(), '/')); + list($language, $path) = language_url_split_prefix($request_path, $languages); if ($language !== FALSE) { $language_url = $language->langcode; diff --git a/core/modules/language/lib/Drupal/language/LanguageBundle.php b/core/modules/language/lib/Drupal/language/LanguageBundle.php new file mode 100644 index 0000000..d16b334 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/LanguageBundle.php @@ -0,0 +1,29 @@ +register('path_processor_language', 'Drupal\language\PathProcessorLanguage') + ->addArgument(new Reference('module_handler')) + ->addTag('incoming_path_processor', array('priority' => 250)); + } + +} diff --git a/core/modules/language/lib/Drupal/language/PathProcessorLanguage.php b/core/modules/language/lib/Drupal/language/PathProcessorLanguage.php new file mode 100644 index 0000000..f380639 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/PathProcessorLanguage.php @@ -0,0 +1,37 @@ +moduleHandler = $module_handler; + } + + /** + * Implements Drupal\Core\PathProcessor\IncomingPathProcessorInterface::processIncoming(). + */ + public function processIncoming($path, Request $request) { + // Sigh. + include_once DRUPAL_ROOT . '/core/includes/language.inc'; + $this->moduleHandler->loadInclude('language', 'inc', 'language.negotiation'); + $languages = language_list(); + list($language, $path) = language_url_split_prefix($path, $languages); + return $path; + } + +} diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php index f72c51a..92a419b 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageDependencyInjectionTest.php @@ -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(); } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php index e86ad6c..9200d89 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php @@ -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']))); diff --git a/core/modules/locale/lib/Drupal/locale/LocaleBundle.php b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php new file mode 100644 index 0000000..4bd8dc0 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php @@ -0,0 +1,28 @@ +register('locale_config_subscriber', 'Drupal\locale\LocaleConfigSubscriber') + ->addArgument(new Reference('language_manager')) + ->addTag('event_subscriber'); + } + +} diff --git a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php index 3d2fd4a..54d91e7 100644 --- a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php +++ b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php @@ -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); diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php index e22e65d..c9ae95a 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php @@ -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. diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 5ba05d2..579819e 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -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()); -} diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index c6bb562..4493cd1 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -1025,13 +1025,6 @@ protected function tearDown() { // this second reset is guranteed to reset everything to nothing. drupal_static_reset(); - // Reset static in language(). - // Restoring drupal_container() makes language() return the proper languages - // already, but it contains an additional static that needs to be reset. The - // reset can happen before the container is restored, as it is unnecessary - // to reset the language_manager service. - language(NULL, TRUE); - // Restore original in-memory configuration. $conf = $this->originalConf; new Settings($this->originalSettings); diff --git a/core/modules/system/language.api.php b/core/modules/system/language.api.php index eb8bc3c..ed81b92 100644 --- a/core/modules/system/language.api.php +++ b/core/modules/system/language.api.php @@ -11,33 +11,6 @@ */ /** - * Allows modules to act after language initialization has been performed. - * - * This is primarily needed to provide translation for configuration variables - * in the proper bootstrap phase. Variables are user-defined strings and - * therefore should not be translated via t(), since the source string can - * change without notice and any previous translation would be lost. Moreover, - * since variables can be used in the bootstrap phase, we need a bootstrap hook - * to provide a translation early enough to avoid misalignments between code - * using the original values and code using the translated values. However - * modules implementing hook_boot() should be aware that language initialization - * did not happen yet and thus they cannot rely on translated variables. - */ -function hook_language_init() { - global $conf; - - switch (language(LANGUAGE_TYPE_INTERFACE)->langcode) { - case 'it': - $conf['system.site']['name'] = 'Il mio sito Drupal'; - break; - - case 'fr': - $conf['system.site']['name'] = 'Mon site Drupal'; - break; - } -} - -/** * Perform alterations on language switcher links. * * A language switcher link may need to point to a different path or use a diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php index 21a129a..275e1c4 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php @@ -43,7 +43,7 @@ function testCRUD() { $this->fixtures->createTables($connection); //Create AliasManager and Path object. - $aliasManager = new AliasManager($connection, $this->container->get('keyvalue')); + $aliasManager = new AliasManager($connection, $this->container->get('state'), $this->container->get('language_manager')); $path = new Path($connection, $aliasManager); $aliases = $this->fixtures->sampleUrlAliases(); @@ -96,7 +96,7 @@ function testLookupPath() { $this->fixtures->createTables($connection); //Create AliasManager and Path object. - $aliasManager = new AliasManager($connection, $this->container->get('keyvalue')); + $aliasManager = new AliasManager($connection, $this->container->get('state'), $this->container->get('language_manager')); $pathObject = new Path($connection, $aliasManager); // Test the situation where the source is the same for multiple aliases. diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/DateUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/DateUpgradePathTest.php index f5d2d9f..e1720d1 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/DateUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/DateUpgradePathTest.php @@ -37,21 +37,21 @@ public function testDateUpgrade() { $expected_formats['short'] = array( 'name' => 'Short', 'pattern' => array( - 'php' => 'Y/m/d - H:i', + 'php' => 'm/d/Y - H:i', ), 'locked' => '1', ); $expected_formats['medium'] = array( 'name' => 'Medium', 'pattern' => array( - 'php' => 'D, d/m/Y - H:i', + 'php' => 'D, m/d/Y - H:i', ), 'locked' => '1', ); $expected_formats['long'] = array( 'name' => 'Long', 'pattern' => array( - 'php' => 'l, Y, F j - H:i', + 'php' => 'l, j F, Y - H:i', ), 'locked' => '1', ); @@ -76,43 +76,24 @@ public function testDateUpgrade() { $this->assertNull(update_variable_get('date_format_' . $type), format_string('Date format variable for @type was deleted.', array('@type' => $type))); } - $expected_locale_formats = array( + $expected_de_formats = array( array( - 'langcode' => 'en', - 'type' => 'long', - 'format' => 'l, j F, Y - H:i', - ), - array( - 'langcode' => 'en', - 'type' => 'medium', - 'format' => 'D, m/d/Y - H:i', - ), - array( - 'langcode' => 'en', - 'type' => 'short', - 'format' => 'm/d/Y - H:i', - ), - array( - 'langcode' => 'de', 'type' => 'long', 'format' => 'l, j. F, Y - H:i', ), array( - 'langcode' => 'de', 'type' => 'medium', 'format' => 'D, d/m/Y - H:i', ), array( - 'langcode' => 'de', 'type' => 'short', 'format' => 'd/m/Y - H:i', ), ); - $config['en'] = config('locale.config.en.system.date'); - $config['de'] = config('locale.config.de.system.date'); - foreach ($expected_locale_formats as $locale_format) { - $format = $config[$locale_format['langcode']]->get('formats.' . $locale_format['type'] . '.pattern.php'); + $config = config('locale.config.de.system.date'); + foreach ($expected_de_formats as $locale_format) { + $format = $config->get('formats.' . $locale_format['type'] . '.pattern.php'); $this->assertEqual($locale_format['format'], $format); } } 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 c4dda65..454e3a2 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php @@ -126,6 +126,13 @@ protected function setUp() { drupal_save_session(FALSE); // Login as uid 1. $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject(); + // Load roles for the user object. + $roles = array(DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID); + $result = db_query('SELECT rid, uid FROM {users_roles} WHERE uid = :uid', array(':uid' => 1)); + foreach ($result as $record) { + $roles[$record->rid] = $record->rid; + } + $user->roles = $roles; // Generate and set a D8-compatible session cookie. $this->prepareD8Session(); @@ -288,6 +295,7 @@ protected function performUpgrade($register_errors = TRUE) { * @see WebTestBase::drupalGet() */ protected function getUpdatePhp() { + $this->rebuildContainer(); $path = $GLOBALS['base_url'] . '/core/update.php'; $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $path, CURLOPT_NOBODY => FALSE)); // Ensure that any changes to variables in the other thread are picked up.