diff --git a/core/includes/language.inc b/core/includes/language.inc index 74d5752..11b71c9 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. @@ -49,6 +54,7 @@ * unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); * } * @endcode + * @todo Fix documentation with the locked parameter. * * Every language type can have a different set of language negotiation methods * assigned to it. Different language types often share the same language @@ -165,34 +171,15 @@ 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 = config('system.language.types')->get('configurable'); + $configurable = empty($configurable) ? array() : array_keys(array_filter($configurable)); } - - if (!$stored) { - $result = array(); - foreach (language_types_info() as $type => $info) { - if (!isset($info['fixed'])) { - $result[] = $type; - } - } - return $result; - } - return $configurable; } @@ -214,37 +201,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 +377,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 59f1efa..45a523c 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,21 @@ 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( + 'library' => array( + array('language', 'language.admin') + ), + ), + ); + } $negotiation_info = $form['#language_negotiation_info']; $enabled_methods = variable_get("language_negotiation_$type", array()); @@ -519,12 +540,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 +559,17 @@ 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'); + $method_weights_type = array(); + 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]) { @@ -549,13 +577,24 @@ function language_negotiation_configure_form_submit($form, &$form_state) { } } - language_negotiation_set($type, $method_weights); + $method_weights_type[$type] = $method_weights; variable_set("language_negotiation_methods_weight_$type", $method_weights_input); } // Update non-configurable language types and the related language negotiation // configuration. - language_types_set(); + language_types_set($configurable); + + // Update the language negotiations after setting the configurability. + foreach ($method_weights_type as $type => $method_weights) { + language_negotiation_set($type, $method_weights); + } + + // 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..dda9a65 --- /dev/null +++ b/core/modules/language/language.admin.js @@ -0,0 +1,32 @@ +(function ($, Drupal) { + +"use strict"; + +/** + * Makes language negotiation inherit user interface negotiation. + */ +Drupal.behaviors.negotiationLanguage = { + attach: function () { + var $configForm = $('#language-negotiation-configure-form'); + var inputSelector = 'input[name$="[configurable]"]'; + // Given a customization checkbox derive the language type being changed. + function toggleTable (checkbox) { + var $checkbox = $(checkbox); + // Get the language detection type such as User interface text language + // detection or Content language detection. + $checkbox.closest('.table-language-group') + .find('table, .tabledrag-toggle-weight') + .toggle($checkbox.prop('checked')); + } + // Bind hide/show + rearrange to customization checkboxes. + $configForm.once('negotiation-language-admin-bind').on('change', inputSelector, function (event) { + toggleTable(event.target); + }); + // Initially, hide language detection types that are not customized. + $configForm.find(inputSelector + ':not(:checked)').each(function (index, element) { + toggleTable(element); + }); + } +}; + +})(jQuery, Drupal); 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 6294f31..9f51812 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -560,6 +560,27 @@ function language_delete($langcode) { } /** + * Implements hook_library_info(). + */ +function language_library_info() { + $libraries['language.admin'] = array( + 'title' => 'Language detection admin', + 'version' => VERSION, + 'js' => array( + drupal_get_path('module', 'language') . '/language.admin.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('system', 'jquery.once'), + ), + ); + + return $libraries; +} + + +/** * Implements hook_css_alter(). * * This function checks all CSS files currently added via drupal_add_css() and @@ -607,14 +628,17 @@ 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, ), ); } @@ -714,8 +738,9 @@ function language_negotiation_include() { * Implements hook_modules_enabled(). */ 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 2182f4d..f376d4e 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..7064d52 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php @@ -55,8 +55,8 @@ function testInfoAlterations() { state()->set('language_test.content_language_type', TRUE); $this->languageNegotiationUpdate(); $type = LANGUAGE_TYPE_CONTENT; - $language_types = variable_get('language_types', language_types_get_default()); - $this->assertTrue($language_types[$type], 'Content language type is configurable.'); + $language_types = language_types_get_configurable(); + $this->assertTrue(in_array($type, $language_types), 'Content language type is configurable.'); // Enable some core and custom language negotiation methods. The test // language type is supposed to be configurable. @@ -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..1adb323 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,12 @@ function language_test_language_types_info() { */ function language_test_language_types_info_alter(array &$language_types) { if (state()->get('language_test.content_language_type')) { + $language_types[LANGUAGE_TYPE_CONTENT]['locked'] = FALSE; unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); + // By default languages are not configurable. Make LANGUAGE_TYPE_CONTENT configurable. + $configurable = config('system.language.types')->get('configurable'); + $configurable[LANGUAGE_TYPE_CONTENT] = TRUE; + config('system.language.types')->set('configurable', $configurable)->save(); } } 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/system/language.api.php b/core/modules/system/language.api.php index 7c7375f..7f5a996 100644 --- a/core/modules/system/language.api.php +++ b/core/modules/system/language.api.php @@ -44,10 +44,13 @@ function hook_language_switch_links_alter(array &$links, $type, $path) { * may contain the following elements: * - name: The human-readable language type identifier. * - description: A description of the language type. + * - locked: A boolean indicating if the user can choose wether to configure + * the language type or not using the UI. * - fixed: A fixed array of language negotiation method identifiers to use to - * initialize this language. Defining this key makes the language type - * non-configurable, so it will always use the specified methods in the - * given priority order. Omit to make the language type configurable. + * initialize this language. If locked is set to TRUE and fixed is set, it + * will always use the specified methods in the given priority order. If not + * present and locked is TRUE then LANGUAGE_NEGOTIATION_INTERFACE will be + * used. * * @see hook_language_types_info_alter() * @ingroup language_negotiation @@ -57,8 +60,10 @@ function hook_language_types_info() { 'custom_language_type' => array( 'name' => t('Custom language'), 'description' => t('A custom language type.'), + 'locked' => FALSE, ), 'fixed_custom_language_type' => array( + 'locked' => TRUE, 'fixed' => array('custom_language_negotiation_method'), ), ); diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index 82bd66d..d816445 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -63,8 +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. + // Make content language negotiation configurable by removing unsetting the + // 'locked' flag. + $language_types[LANGUAGE_TYPE_CONTENT]['locked'] = FALSE; unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); }