From 1f2622c8b8bdb35342f72a150dbbfb4af13bb0e8 Mon Sep 17 00:00:00 2001 From: Dries Date: Wed, 18 Apr 2012 14:30:50 -0400 Subject: [PATCH] - Patch #1497230 by Rob Loach, pdrake, effulgentsia: Use Dependency Injection to handle object definitions. --- core/includes/bootstrap.inc | 73 ++++++++++++--- core/includes/common.inc | 14 +-- core/includes/language.inc | 4 + core/includes/menu.inc | 12 ++- core/includes/path.inc | 3 +- core/includes/theme.inc | 21 +++-- .../modules/language/language.negotiation.inc | 7 +- core/modules/language/language.test | 89 ++++++++++++++++++- core/modules/locale/locale.module | 3 +- core/modules/system/language.api.php | 4 +- core/modules/system/tests/common.test | 4 +- core/update.php | 7 ++ 12 files changed, 196 insertions(+), 45 deletions(-) diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index f50dd39fd5d..369fdfc1617 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -3,6 +3,7 @@ use Drupal\Core\Database\Database; use Symfony\Component\ClassLoader\UniversalClassLoader; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; /** * @file @@ -1353,12 +1354,11 @@ function drupal_unpack($obj, $field = 'data') { * @ingroup sanitization */ function t($string, array $args = array(), array $options = array()) { - global $language_interface; static $custom_strings; // Merge in default. if (empty($options['langcode'])) { - $options['langcode'] = isset($language_interface->langcode) ? $language_interface->langcode : LANGUAGE_SYSTEM; + $options['langcode'] = drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode; } if (empty($options['context'])) { $options['context'] = ''; @@ -2303,6 +2303,40 @@ function drupal_get_bootstrap_phase() { return drupal_bootstrap(); } +/** + * Retrieves the Drupal Container to standardize object construction. + * + * Example: + * @code + * // Register the LANGUAGE_TYPE_INTERFACE definition. Registered definitions + * // do not necessarily need to be named by a constant. + * // See http://symfony.com/doc/current/components/dependency_injection.html + * // for usage examples of adding object initialization code after register(). + * $container = drupal_container(); + * $container->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language'); + * + * // Retrieve the LANGUAGE_TYPE_INTERFACE object. + * $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + * @endcode + * + * @param $reset + * TRUE or FALSE depending on whether the Container instance is to be reset. + * + * @return Symfony\Component\DependencyInjection\ContainerBuilder + * The instance of the Drupal Container used to set up and maintain object + * instances. + */ +function drupal_container($reset = FALSE) { + // We do not use drupal_static() here because we do not have a mechanism by + // which to reinitialize the stored objects, so a drupal_static_reset() call + // would leave Drupal in a nonfunctional state. + static $container = NULL; + if ($reset || !isset($container)) { + $container = new ContainerBuilder(); + } + return $container; +} + /** * Returns the test prefix if this is an internal request from SimpleTest. * @@ -2445,27 +2479,44 @@ function get_t() { /** * Initializes all the defined language types. + * + * @see Drupal\Core\Language\Language */ function drupal_language_initialize() { $types = language_types_get_all(); + $container = drupal_container(); - // Ensure the language is correctly returned, even without multilanguage - // support. Also make sure we have a $language fallback, in case a language - // negotiation callback needs to do a full bootstrap. - // Useful for eg. XML/HTML 'lang' attributes. - $default = language_default(); - foreach ($types as $type) { - $GLOBALS[$type] = $default; - } + // Ensure a language object is registered for each language type, whether the + // site is multilingual or not. if (language_multilingual()) { include_once DRUPAL_ROOT . '/core/includes/language.inc'; foreach ($types as $type) { - $GLOBALS[$type] = language_types_initialize($type); + $language = language_types_initialize($type); + $container->set($type, NULL); + $container->register($type, 'Drupal\\Core\\Language\\Language') + ->addMethodCall('extend', array($language)); } // Allow modules to react on language system initialization in multilingual // environments. bootstrap_invoke_all('language_init'); } + else { + $default = language_default(); + foreach ($types as $type) { + $container->set($type, NULL); + $container->register($type, 'Drupal\\Core\\Language\\Language') + ->addMethodCall('extend', array($default)); + } + } + + // @todo Temporary backwards compatibility for code still using globals. + // Remove after these issues: + // - $language_interface: http://drupal.org/node/1510686 + // - $language_url: http://drupal.org/node/1512310 + // - $language_content: http://drupal.org/node/1512308 + foreach ($types as $type) { + $GLOBALS[$type] = $container->get($type); + } } /** diff --git a/core/includes/common.inc b/core/includes/common.inc index 32f5a7eed96..c4ae468425e 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1653,8 +1653,7 @@ function filter_xss_bad_protocol($string, $decode = TRUE) { * Arbitrary elements may be added using the $args associative array. */ function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) { - global $language_content; - $langcode = $langcode ? $langcode : $language_content->langcode; + $langcode = $langcode ? $langcode : drupal_container()->get(LANGUAGE_TYPE_CONTENT)->langcode; $output = "\n"; $output .= ' ' . check_plain($title) . "\n"; @@ -1955,10 +1954,8 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL $timezones[$timezone] = timezone_open($timezone); } - // Use the default langcode if none is set. - global $language_interface; if (empty($langcode)) { - $langcode = isset($language_interface->langcode) ? $language_interface->langcode : LANGUAGE_SYSTEM; + $langcode = drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode; } switch ($type) { @@ -2398,7 +2395,6 @@ function drupal_attributes(array $attributes = array()) { * An HTML string containing a link to the given path. */ function l($text, $path, array $options = array()) { - global $language_url; static $use_theme = NULL; // Merge in defaults. @@ -2409,7 +2405,7 @@ function l($text, $path, array $options = array()) { // Append active class. if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) && - (empty($options['language']) || $options['language']->langcode == $language_url->langcode)) { + (empty($options['language']) || $options['language']->langcode == drupal_container()->get(LANGUAGE_TYPE_URL)->langcode)) { $options['attributes']['class'][] = 'active'; } @@ -2565,9 +2561,7 @@ function drupal_deliver_html_page($page_callback_result) { drupal_add_http_header('X-UA-Compatible', 'IE=edge,chrome=1'); } - // Send appropriate HTTP-Header for browsers and search engines. - global $language_interface; - drupal_add_http_header('Content-Language', $language_interface->langcode); + drupal_add_http_header('Content-Language', drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode); // Menu status constants are integers; page content is a string or array. if (is_int($page_callback_result)) { diff --git a/core/includes/language.inc b/core/includes/language.inc index 2c46c76fa81..01d418a318f 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -25,6 +25,10 @@ function language_types_initialize($type) { $negotiation = variable_get("language_negotiation_$type", array()); foreach ($negotiation as $method_id => $method) { + // Skip negotiation methods not appropriate for this type. + if (isset($method['types']) && !in_array($type, $method['types'])) { + continue; + } $language = language_negotiation_method_invoke($method_id, $method); if ($language) { // Remember the method ID used to detect the language. diff --git a/core/includes/menu.inc b/core/includes/menu.inc index fecd1581301..96791e39ef7 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -1096,10 +1096,12 @@ function menu_tree_output($tree) { function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { $tree = &drupal_static(__FUNCTION__, array()); + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + // Use $mlid as a flag for whether the data being loaded is for the whole tree. $mlid = isset($link['mlid']) ? $link['mlid'] : 0; // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth. - $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $GLOBALS['language_interface']->langcode . ':' . (int) $max_depth; + $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->langcode . ':' . (int) $max_depth; if (!isset($tree[$cid])) { // If the static variable doesn't have the data, check {cache_menu}. @@ -1205,6 +1207,8 @@ function menu_tree_get_path($menu_name) { function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) { $tree = &drupal_static(__FUNCTION__, array()); + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + // Check if the active trail has been overridden for this menu tree. $active_path = menu_tree_get_path($menu_name); // Load the menu item corresponding to the current page. @@ -1213,7 +1217,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = $max_depth = min($max_depth, MENU_MAX_DEPTH); } // Generate a cache ID (cid) specific for this page. - $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $GLOBALS['language_interface']->langcode . ':' . (int) $item['access'] . ':' . (int) $max_depth; + $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $language_interface->langcode . ':' . (int) $item['access'] . ':' . (int) $max_depth; // If we are asked for the active trail only, and $menu_name has not been // built and cached for this page yet, then this likely means that it // won't be built anymore, as this function is invoked from @@ -1360,12 +1364,14 @@ function _menu_build_tree($menu_name, array $parameters = array()) { // Static cache of already built menu trees. $trees = &drupal_static(__FUNCTION__, array()); + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + // Build the cache id; sort parents to prevent duplicate storage and remove // default parameter values. if (isset($parameters['expanded'])) { sort($parameters['expanded']); } - $tree_cid = 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language_interface']->langcode . ':' . hash('sha256', serialize($parameters)); + $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->langcode . ':' . hash('sha256', serialize($parameters)); // If we do not have this tree in the static cache, check {cache_menu}. if (!isset($trees[$tree_cid])) { diff --git a/core/includes/path.inc b/core/includes/path.inc index 223ab0473d8..d6a0201fbe8 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -43,7 +43,6 @@ function drupal_path_initialize() { * found. */ function drupal_lookup_path($action, $path = '', $langcode = NULL) { - global $language_url; // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { @@ -74,7 +73,7 @@ function drupal_lookup_path($action, $path = '', $langcode = 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. - $langcode = $langcode ? $langcode : $language_url->langcode; + $langcode = $langcode ? $langcode : drupal_container()->get(LANGUAGE_TYPE_URL)->langcode; if ($action == 'wipe') { $cache = array(); diff --git a/core/includes/theme.inc b/core/includes/theme.inc index f5c25f9fb68..68726006563 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1684,7 +1684,7 @@ function theme_link($variables) { * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. */ function theme_links($variables) { - global $language_url; + $language_url = drupal_container()->get(LANGUAGE_TYPE_URL); $links = $variables['links']; $attributes = $variables['attributes']; @@ -2466,6 +2466,8 @@ function template_process(&$variables, $hook) { * @see html.tpl.php */ function template_preprocess_html(&$variables) { + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + // Compile a list of classes that are going to be applied to the body element. // This allows advanced theming based on context (home page, node of certain type, etc.). // Add a class that tells us whether we're on the front page or not. @@ -2509,8 +2511,8 @@ function template_preprocess_html(&$variables) { $variables['body_attributes_array'] = array(); // HTML element attributes. - $variables['html_attributes_array']['lang'] = $GLOBALS['language_interface']->langcode; - $variables['html_attributes_array']['dir'] = $GLOBALS['language_interface']->direction ? 'rtl' : 'ltr'; + $variables['html_attributes_array']['lang'] = $language_interface->langcode; + $variables['html_attributes_array']['dir'] = $language_interface->direction ? 'rtl' : 'ltr'; // Add favicon. if (theme_get_setting('toggle_favicon')) { @@ -2559,6 +2561,8 @@ function template_preprocess_html(&$variables) { * @see page.tpl.php */ function template_preprocess_page(&$variables) { + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + // Move some variables to the top level for themer convenience and template cleanliness. $variables['show_messages'] = $variables['page']['#show_messages']; @@ -2580,8 +2584,8 @@ function template_preprocess_page(&$variables) { $variables['base_path'] = base_path(); $variables['front_page'] = url(); $variables['feed_icons'] = drupal_get_feeds(); - $variables['language'] = $GLOBALS['language_interface']; - $variables['language']->dir = $GLOBALS['language_interface']->direction ? 'rtl' : 'ltr'; + $variables['language'] = $language_interface; + $variables['language']->dir = $language_interface->direction ? 'rtl' : 'ltr'; $variables['logo'] = theme_get_setting('logo'); $variables['main_menu'] = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array(); $variables['secondary_menu'] = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array(); @@ -2782,8 +2786,7 @@ function template_preprocess_maintenance_page(&$variables) { } } - // set the default language if necessary - $language = isset($GLOBALS['language_interface']) ? $GLOBALS['language_interface'] : language_default(); + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); $variables['head_title_array'] = $head_title; $variables['head_title'] = implode(' | ', $head_title); @@ -2792,8 +2795,8 @@ function template_preprocess_maintenance_page(&$variables) { $variables['breadcrumb'] = ''; $variables['feed_icons'] = ''; $variables['help'] = ''; - $variables['language'] = $language; - $variables['language']->dir = $language->direction ? 'rtl' : 'ltr'; + $variables['language'] = $language_interface; + $variables['language']->dir = $language_interface->direction ? 'rtl' : 'ltr'; $variables['logo'] = theme_get_setting('logo'); $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; $variables['main_menu'] = array(); diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc index c90457f0842..56deb3ec726 100644 --- a/core/modules/language/language.negotiation.inc +++ b/core/modules/language/language.negotiation.inc @@ -52,8 +52,7 @@ const LANGUAGE_NEGOTIATION_URL_DOMAIN = 1; * The current interface language code. */ function language_from_interface() { - global $language_interface; - return isset($language_interface->langcode) ? $language_interface->langcode : FALSE; + return drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode; } /** @@ -284,7 +283,7 @@ function language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_ return $default->langcode; } else { - return $GLOBALS[$language_type]->langcode; + return drupal_container()->get($language_type)->langcode; } } @@ -361,7 +360,7 @@ function language_url_rewrite_url(&$path, &$options) { // Language can be passed as an option, or we go for current URL language. if (!isset($options['language'])) { - global $language_url; + $language_url = drupal_container()->get(LANGUAGE_TYPE_URL); $options['language'] = $language_url; } // We allow only enabled languages here. diff --git a/core/modules/language/language.test b/core/modules/language/language.test index 6c64c2983ec..31951418f27 100644 --- a/core/modules/language/language.test +++ b/core/modules/language/language.test @@ -6,9 +6,9 @@ * * The test file includes: * - a functional test for the language configuration forms; + * - comparison of $GLOBALS default language against dependency injection; */ - /** * Functional tests for the language list configuration forms. */ @@ -179,3 +179,90 @@ class LanguageListTest extends DrupalWebTestCase { $this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), t('The English language has been removed.')); } } + +/** + * Test for dependency injected language object. + */ +class LanguageDependencyInjectionTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Language dependency injection', + 'description' => 'Compares the default language from $GLOBALS against the dependency injected language object.', + 'group' => 'Language', + ); + } + + function setUp() { + parent::setUp('language'); + + // Set up a new container to ensure we are building a new Language object + // for each test. + drupal_container(TRUE); + } + + /** + * Test dependency injected Language against the GLOBAL language object. + * + * @todo Once the PHP global is gone, we won't need this test as the same + * test is done without the PHP global in the following test. + */ + function testDependencyInjectedLanguage() { + // Initialize the language system. + drupal_language_initialize(); + + $expected = $GLOBALS[LANGUAGE_TYPE_INTERFACE]; + $result = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + foreach ($expected as $property => $value) { + $this->assertEqual($expected->$property, $result->$property, t('The dependency injected language object %prop property equals the $GLOBAL language object %prop property.', array('%prop' => $property))); + } + } + + /** + * Test dependency injected languages against a new Language object. + * + * @see Drupal\Core\Language\Language + */ + function testDependencyInjectedNewLanguage() { + // Initialize the language system. + drupal_language_initialize(); + + $expected = new Drupal\Core\Language\Language((array) language_default()); + $result = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + foreach ($expected as $property => $value) { + $this->assertEqual($expected->$property, $result->$property, t('The dependency injected language object %prop property equals the new Language object %prop property.', array('%prop' => $property))); + } + } + + /** + * Test dependency injected Language object against a new default language + * object. + * + * @see Drupal\Core\Language\Language + */ + function testDependencyInjectedNewDefaultLanguage() { + // Change the language default object to different values. + $new_language_default = (object) array( + 'langcode' => 'fr', + 'name' => 'French', + 'direction' => 0, + 'enabled' => 1, + 'weight' => 0, + 'default' => TRUE, + ); + variable_set('language_default', $new_language_default); + + // Initialize the language system. + drupal_language_initialize(); + + // The langauge system creates a Language object which contains the + // same properties as the new default language object. + $expected = $new_language_default; + $result = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + foreach ($expected as $property => $value) { + $this->assertEqual($expected->$property, $result->$property, t('The dependency injected language object %prop property equals the default language object %prop property.', array('%prop' => $property))); + } + + // Delete the language_default variable we previously set. + variable_del('language_default'); + } +} diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 6f814804cad..bc3452485a7 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -1064,8 +1064,7 @@ function _locale_invalidate_js($langcode = NULL) { */ function _locale_rebuild_js($langcode = NULL) { if (!isset($langcode)) { - global $language_interface; - $language = $language_interface; + $language = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); } else { // Get information about the locale. diff --git a/core/modules/system/language.api.php b/core/modules/system/language.api.php index aa8fd2fa67f..9f28619a2d3 100644 --- a/core/modules/system/language.api.php +++ b/core/modules/system/language.api.php @@ -24,9 +24,9 @@ * did not happen yet and thus they cannot rely on translated variables. */ function hook_language_init() { - global $language_interface, $conf; + global $conf; - switch ($language_interface->langcode) { + switch (drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode) { case 'it': $conf['site_name'] = 'Il mio sito Drupal'; break; diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test index dbb471e7c11..cc51a017ec5 100644 --- a/core/modules/system/tests/common.test +++ b/core/modules/system/tests/common.test @@ -2438,7 +2438,9 @@ class CommonFormatDateTestCase extends DrupalWebTestCase { * Tests for the format_date() function. */ function testFormatDate() { - global $user, $language_interface; + global $user; + + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); $timestamp = strtotime('2007-03-26T00:00:00+00:00'); $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', t('Test all parameters.')); diff --git a/core/update.php b/core/update.php index 3d858a22b6c..9797833d87b 100644 --- a/core/update.php +++ b/core/update.php @@ -382,8 +382,15 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); // The interface language global has been renamed in D8, we must ensure that it // contains a valid value while language settings are upgraded. +// @todo Remove this globals reference entirely: http://drupal.org/node/1510686 $GLOBALS[LANGUAGE_TYPE_INTERFACE] = language_default(); +// Ensure the default language is properly registered within the Dependency +// Injection container during the upgrade process. +$default = language_default(); +drupal_container()->register(LANGUAGE_TYPE_INTERFACE, 'Drupal\\Core\\Language\\Language') + ->addMethodCall('extend', array($default)); + // Only allow the requirements check to proceed if the current user has access // to run updates (since it may expose sensitive information about the site's // configuration).