diff --git a/core/includes/language.inc b/core/includes/language.inc index 74d5752..43e837e 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -13,6 +13,11 @@ const LANGUAGE_NEGOTIATION_SELECTED = 'language-selected'; /** + * The language is determined using the current interface language. + */ +const LANGUAGE_NEGOTIATION_INTERFACE = 'language-interface'; + +/** * @defgroup language_negotiation Language Negotiation API functionality * @{ * Functions to customize the language types and the negotiation process. @@ -165,34 +170,14 @@ function language_types_info() { * whose language negotiation methods are module-defined and not altered through * the user interface. * - * @param $stored - * (optional) By default, retrieves values from the 'language_types' variable - * to avoid unnecessary hook invocations. If set to FALSE, retrieves values - * from the actual language type definitions. This allows reaction to - * alterations performed on the definitions by modules installed after the - * 'language_types' variable is set. - * * @return * An array of language type names. */ -function language_types_get_configurable($stored = TRUE) { +function language_types_get_configurable() { $configurable = &drupal_static(__FUNCTION__); - - if ($stored && !isset($configurable)) { - $types = variable_get('language_types', language_types_get_default()); - $configurable = array_keys(array_filter($types)); + if (!isset($configurable)) { + $configurable = array_keys(array_filter(config('system.language.types')->get('configurable'))); } - - if (!$stored) { - $result = array(); - foreach (language_types_info() as $type => $info) { - if (!isset($info['fixed'])) { - $result[] = $type; - } - } - return $result; - } - return $configurable; } @@ -214,37 +199,62 @@ function language_types_disable($types) { /** * Updates the language type configuration. + * + * @param array $configurable + * The configuration object containing the user defined preferences for + * language type customizability. + * @todo + * We are using variable_set('language_types', $language_types) and + * config('system.language.types')->set('configurable', $configurable)->save() + * at the same time. This is redundant. We need to change variable_set to + * configuration. */ -function language_types_set() { +function language_types_set($configurable) { // Ensure that we are getting the defined language negotiation information. An // invocation of module_enable() or module_disable() could outdate the cached // information. drupal_static_reset('language_types_info'); drupal_static_reset('language_negotiation_info'); - // Determine which language types are configurable and which not by checking - // whether the 'fixed' key is defined. Non-configurable (fixed) language types - // have their language negotiation settings stored there. $language_types = array(); $negotiation_info = language_negotiation_info(); foreach (language_types_info() as $type => $info) { - if (isset($info['fixed'])) { - $language_types[$type] = FALSE; - $method_weights = array(); - foreach ($info['fixed'] as $weight => $method_id) { - if (isset($negotiation_info[$method_id])) { - $method_weights[$method_id] = $weight; + // Check if the language is locked. The configuration of a locked language + // type cannot be changed using the UI. + if ($locked = !empty($info['locked'])) { + // If a locked language has default settings (by using the 'fixed' + // property) it will be always configurable. If it has no default + // settings, then it won't be configurable. + $configurable[$type] = empty($info['fixed']); + $language_types[$type] = !$locked; + if (!$configurable[$type]) { + $method_weights = array(); + foreach ($info['fixed'] as $weight => $method_id) { + if (isset($negotiation_info[$method_id])) { + $method_weights[$method_id] = $weight; + } } + language_negotiation_set($type, $method_weights); } - language_negotiation_set($type, $method_weights); } else { - $language_types[$type] = TRUE; + // The configuration of an unlocked language will be based on the + // $configurable array (stored in configuration). These values may be + // altered using the UI. + $language_types[$type] = !empty($configurable[$type]); + if (!$language_types[$type] && empty($info['fixed'])) { + // If the language is not configurable and there is no default language + // negotiation setting then provide one. + $method_weights = array(LANGUAGE_NEGOTIATION_INTERFACE); + $method_weights = array_flip($method_weights); + language_negotiation_set($type, $method_weights); + } } } // Save enabled language types. variable_set('language_types', $language_types); + config('system.language.types')->set('configurable', $configurable)->save(); // Ensure that subsequent calls of language_types_get_configurable() return // the updated language type information. @@ -365,7 +375,7 @@ function language_negotiation_set($type, $method_weights) { $negotiation = array(); $negotiation_info = language_negotiation_info(); - $default_types = language_types_get_configurable(FALSE); + $default_types = language_types_get_configurable(); // Order the language negotiation method list by weight. asort($method_weights); diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index f59fb38..94271cc 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -357,11 +357,17 @@ function language_negotiation_configure_form() { $form = array( '#submit' => array('language_negotiation_configure_form_submit'), '#theme' => 'language_negotiation_configure_form', - '#language_types' => language_types_get_configurable(FALSE), '#language_types_info' => language_types_info(), '#language_negotiation_info' => language_negotiation_info(), ); - + $form['#language_types'] = array(); + $configurable = config('system.language.types')->get('configurable'); + foreach ($form['#language_types_info'] as $type => $info) { + // Show locked language types only if they they are configurable. + if (empty($info['locked']) || $configurable[$type]) { + $form['#language_types'][] = $type; + } + } foreach ($form['#language_types'] as $type) { language_negotiation_configure_form_table($form, $type); } @@ -389,6 +395,19 @@ function language_negotiation_configure_form_table(&$form, $type) { '#show_operations' => FALSE, 'weight' => array('#tree' => TRUE), ); + // Only show configurability checkbox for the unlocked language types. + if (empty($info['locked'])) { + $configurable = config('system.language.types')->get('configurable'); + $table_form['configurable'] = array( + '#type' => 'checkbox', + '#title' => t('Customize %language_name language detection to differ from User interface text language detection settings.', array('%language_name' => $info['name'])), + '#default_value' => !empty($configurable[$type]), + '#attributes' => array('class' => array('language-customization-checkbox')), + '#attached' => array( + 'js' => array(drupal_get_path('module', 'language') . '/language.admin.js' => array('type' => 'file')), + ), + ); + } $negotiation_info = $form['#language_negotiation_info']; $enabled_methods = variable_get("language_negotiation_$type", array()); @@ -519,12 +538,13 @@ function theme_language_negotiation_configure_form($variables) { 'rows' => $rows, 'attributes' => array('id' => "language-negotiation-methods-$type"), ); - $table = theme('table', $variables); + $table = drupal_render($form[$type]['configurable']); + $table .= theme('table', $variables); $table .= drupal_render_children($form[$type]); drupal_add_tabledrag("language-negotiation-methods-$type", 'order', 'sibling', "language-method-weight-$type"); - $output .= '
' . $title . $description . $table . '
'; + $output .= '
' . $title . $description . $table . '
'; } $output .= drupal_render_children($form); @@ -537,11 +557,16 @@ function theme_language_negotiation_configure_form($variables) { function language_negotiation_configure_form_submit($form, &$form_state) { $configurable_types = $form['#language_types']; + $configurable = config('system.language.types')->get('configurable'); + foreach ($configurable_types as $type) { $method_weights = array(); $enabled_methods = $form_state['values'][$type]['enabled']; $enabled_methods[LANGUAGE_NEGOTIATION_SELECTED] = TRUE; $method_weights_input = $form_state['values'][$type]['weight']; + if (isset($form_state['values'][$type]['configurable'])) { + $configurable[$type] = !empty($form_state['values'][$type]['configurable']); + } foreach ($method_weights_input as $method_id => $weight) { if ($enabled_methods[$method_id]) { @@ -555,7 +580,13 @@ function language_negotiation_configure_form_submit($form, &$form_state) { // Update non-configurable language types and the related language negotiation // configuration. - language_types_set(); + language_types_set($configurable); + + // Clear block definitions cache since the available blocks and their names + // may have been changed based on the configurable types. + if (module_exists('block')) { + drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); + } $form_state['redirect'] = 'admin/config/regional/language/detection'; drupal_set_message(t('Language negotiation configuration saved.')); diff --git a/core/modules/language/language.admin.js b/core/modules/language/language.admin.js new file mode 100644 index 0000000..68e4d3e --- /dev/null +++ b/core/modules/language/language.admin.js @@ -0,0 +1,35 @@ +(function ($) { + +"use strict"; + +/** + * Makes language negotiation inherit user interface negotiation. + */ +Drupal.behaviors.negotiationLanguage = { + attach: function (context) { + var $context = $(context); + // Given a customization checkbox derive the language type being changed. + var toggleTable = function ($checkbox) { + // Get the language detection type such as User interface text language + // detection or Content language detection. + var detectiontype = $checkbox.attr('name').replace('[configurable]', ''); + var $table = $('.table-' + detectiontype + '-wrapper'); + if ($checkbox.is(':checked')) { + $table.find('table, .tabledrag-toggle-weight-wrapper').show(); + } + else { + $table.find('table, .tabledrag-toggle-weight-wrapper').hide(); + } + }; + // Bind hide/show + rearrange to customization checkboxes. + $('body').once('negotiation-language-admin-bind').on('click', '#language-negotiation-configure-form :input.language-customization-checkbox', function (e) { + toggleTable($(e.target)); + }); + // Initially, hide language detection types that are not customized. + $('#language-negotiation-configure-form :input.language-customization-checkbox:not(:checked)').each(function (index, element) { + toggleTable($(element)); + }); + } +}; + +})(jQuery); diff --git a/core/modules/language/language.install b/core/modules/language/language.install index b16c53b..78d3a36 100644 --- a/core/modules/language/language.install +++ b/core/modules/language/language.install @@ -22,7 +22,7 @@ function language_install() { // Enable URL language detection for each configurable language type. require_once DRUPAL_ROOT . '/core/includes/language.inc'; - foreach (language_types_get_configurable(FALSE) as $type) { + foreach (language_types_get_configurable() as $type) { language_negotiation_set($type, array(LANGUAGE_NEGOTIATION_URL => 0)); } } diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 6bbb5a7..8a918d3 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -607,14 +607,16 @@ function language_language_types_info() { LANGUAGE_TYPE_INTERFACE => array( 'name' => t('User interface text'), 'description' => t('Order of language detection methods for user interface text. If a translation of user interface text is available in the detected language, it will be displayed.'), + 'locked' => TRUE, ), LANGUAGE_TYPE_CONTENT => array( 'name' => t('Content'), 'description' => t('Order of language detection methods for content. If a version of content is available in the detected language, it will be displayed.'), - 'fixed' => array(LANGUAGE_NEGOTIATION_INTERFACE), + 'locked' => TRUE, ), LANGUAGE_TYPE_URL => array( 'fixed' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_URL_FALLBACK), + 'locked' => TRUE, ), ); } @@ -715,7 +717,9 @@ function language_negotiation_include() { */ function language_modules_enabled($modules) { language_negotiation_include(); - language_types_set(); + // Load configurability options from configuration. + $configurable = config('system.language.types')->get('configurable'); + language_types_set($configurable); language_negotiation_purge(); } diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc index ca41c78..4e29e47 100644 --- a/core/modules/language/language.negotiation.inc +++ b/core/modules/language/language.negotiation.inc @@ -18,11 +18,6 @@ const LANGUAGE_NEGOTIATION_BROWSER = 'language-browser'; /** - * The language is determined using the current interface language. - */ -const LANGUAGE_NEGOTIATION_INTERFACE = 'language-interface'; - -/** * If no URL language, language is determined using an already detected one. */ const LANGUAGE_NEGOTIATION_URL_FALLBACK = 'language-url-fallback'; diff --git a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php index d451b27..f5260f9 100644 --- a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php +++ b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php @@ -38,11 +38,17 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin public function getDerivativeDefinitions(array $base_plugin_definition) { include_once DRUPAL_ROOT . '/core/includes/language.inc'; $info = language_types_info(); - foreach (language_types_get_configurable(FALSE) as $type) { + $configurable_types = language_types_get_configurable(); + foreach ($configurable_types as $type) { $this->derivatives[$type] = $base_plugin_definition; $this->derivatives[$type]['admin_label'] = t('Language switcher (!type)', array('!type' => $info[$type]['name'])); $this->derivatives[$type]['cache'] = DRUPAL_NO_CACHE; } + // If there is just one configurable type then change the title of the + // block. + if (count($configurable_types) == 1) { + $this->derivatives[reset($configurable_types)]['admin_label'] = t('Language switcher'); + } return $this->derivatives; } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php index 8aea763..5357139 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php @@ -68,6 +68,7 @@ function testInfoAlterations() { $form_field => TRUE, $type . '[enabled][' . $test_method_id . ']' => TRUE, $test_type . '[enabled][' . $test_method_id . ']' => TRUE, + $test_type . '[configurable]' => TRUE, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); @@ -157,8 +158,9 @@ protected function languageNegotiationUpdate($op = 'enable') { */ protected function checkFixedLanguageTypes() { drupal_static_reset('language_types_info'); + $configurable = language_types_get_configurable(); foreach (language_types_info() as $type => $info) { - if (isset($info['fixed'])) { + if (!in_array($type, $configurable) && isset($info['fixed'])) { $negotiation = variable_get("language_negotiation_$type", array()); $equal = count($info['fixed']) == count($negotiation); while ($equal && list($id) = each($negotiation)) { diff --git a/core/modules/language/tests/language_test/language_test.module b/core/modules/language/tests/language_test/language_test.module index cb97937..0b98ed5 100644 --- a/core/modules/language/tests/language_test/language_test.module +++ b/core/modules/language/tests/language_test/language_test.module @@ -30,6 +30,7 @@ function language_test_language_types_info() { ), 'fixed_test_language_type' => array( 'fixed' => array('test_language_negotiation_method'), + 'locked' => TRUE, ), ); } @@ -40,7 +41,7 @@ function language_test_language_types_info() { */ function language_test_language_types_info_alter(array &$language_types) { if (state()->get('language_test.content_language_type')) { - unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); + $language_types[LANGUAGE_TYPE_CONTENT]['locked'] = FALSE; } } diff --git a/core/modules/system/config/system.language.types.yml b/core/modules/system/config/system.language.types.yml new file mode 100644 index 0000000..a0ed7ac --- /dev/null +++ b/core/modules/system/config/system.language.types.yml @@ -0,0 +1,4 @@ +configurable: + language_interface: '1' + language_content: '0' + language_url: '0' diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index cb75cb2..52e0c2c 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -63,9 +63,9 @@ function translation_entity_module_implements_alter(&$implementations, $hook) { * Implements hook_language_type_info_alter(). */ function translation_entity_language_types_info_alter(array &$language_types) { - // Make content language negotiation configurable by removing its predefined - // configuration. - unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); + // Make content language negotiation configurable by removing unsetting the + // 'locked' flag. + $language_types[LANGUAGE_TYPE_CONTENT]['locked'] = FALSE; } /**