Skip to content
Snippets Groups Projects
Commit afcb39e1 authored by Dries Buytaert's avatar Dries Buytaert
Browse files

- Patch #1096340 by plach: stale language types/negotation info after enabling/disabling modules.

parent 36b3501e
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
......@@ -83,6 +83,44 @@ function language_types_disable($types) {
variable_set('language_types', $enabled_types);
}
/**
* Updates the language type configuration.
*/
function language_types_set() {
// 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.
$defined_providers = language_negotiation_info();
foreach (language_types_info() as $type => $info) {
if (isset($info['fixed'])) {
$language_types[$type] = FALSE;
$negotiation = array();
foreach ($info['fixed'] as $weight => $id) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $weight;
}
}
language_negotiation_set($type, $negotiation);
}
else {
$language_types[$type] = TRUE;
}
}
// Save language types.
variable_set('language_types', $language_types);
// Ensure that subsequent calls of language_types_configurable() return the
// updated language type information.
drupal_static_reset('language_types_configurable');
}
/**
* Check if a language provider is enabled.
*
......@@ -173,6 +211,28 @@ function language_negotiation_get_switch_links($type, $path) {
return $links;
}
/**
* Updates language configuration to remove any language provider that is no longer defined.
*/
function language_negotiation_purge() {
// 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_negotiation_info');
drupal_static_reset('language_types_info');
$defined_providers = language_negotiation_info();
foreach (language_types_info() as $type => $type_info) {
$weight = 0;
$negotiation = array();
foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $weight++;
}
}
language_negotiation_set($type, $negotiation);
}
}
/**
* Save a list of language providers.
......@@ -180,7 +240,8 @@ function language_negotiation_get_switch_links($type, $path) {
* @param $type
* The language negotiation type.
* @param $language_providers
* An array of language provider ids.
* An array of language provider weights keyed by id.
* @see language_provider_weight()
*/
function language_negotiation_set($type, $language_providers) {
// Save only the necessary fields.
......@@ -189,7 +250,7 @@ function language_negotiation_set($type, $language_providers) {
$negotiation = array();
$providers_weight = array();
$defined_providers = language_negotiation_info();
$default_types = language_types_configurable();
$default_types = language_types_configurable(FALSE);
// Initialize the providers weight list.
foreach ($language_providers as $id => $provider) {
......
......@@ -541,6 +541,12 @@ function _locale_languages_configure_form_language_table(&$form, $type) {
asort($providers_weight);
foreach ($providers_weight as $id => $weight) {
// A language provider might be no more available if the defining module has
// been disabled after the last configuration saving.
if (!isset($language_providers[$id])) {
continue;
}
$enabled = isset($enabled_providers[$id]);
$provider = $language_providers[$id];
......@@ -658,7 +664,6 @@ function theme_locale_languages_configure_form($variables) {
* Submit handler for language negotiation settings.
*/
function locale_languages_configure_form_submit($form, &$form_state) {
$language_types = array();
$configurable_types = $form['#language_types'];
foreach ($configurable_types as $type) {
......@@ -666,7 +671,6 @@ function locale_languages_configure_form_submit($form, &$form_state) {
$enabled_providers = $form_state['values'][$type]['enabled'];
$enabled_providers[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE;
$providers_weight = $form_state['values'][$type]['weight'];
$language_types[$type] = TRUE;
foreach ($providers_weight as $id => $weight) {
if ($enabled_providers[$id]) {
......@@ -680,27 +684,11 @@ function locale_languages_configure_form_submit($form, &$form_state) {
variable_set("locale_language_providers_weight_$type", $providers_weight);
}
// Save non-configurable language types negotiation.
$language_types_info = language_types_info();
$defined_providers = $form['#language_providers'];
foreach ($language_types_info as $type => $info) {
if (isset($info['fixed'])) {
$language_types[$type] = FALSE;
$negotiation = array();
foreach ($info['fixed'] as $weight => $id) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $defined_providers[$id];
$negotiation[$id]['weight'] = $weight;
}
}
language_negotiation_set($type, $negotiation);
}
}
// Save language types.
variable_set('language_types', $language_types);
// Update non-configurable language types and the related language negotiation
// configuration.
language_types_set();
$form_state['redirect'] = 'admin/config/regional/language';
$form_state['redirect'] = 'admin/config/regional/language/configure';
drupal_set_message(t('Language negotiation configuration saved.'));
}
......
......@@ -513,6 +513,8 @@ function locale_language_types_info() {
'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.'),
),
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(LOCALE_LANGUAGE_NEGOTIATION_INTERFACE),
),
LANGUAGE_TYPE_URL => array(
......@@ -593,6 +595,22 @@ function locale_language_negotiation_info() {
return $providers;
}
/**
* Implements hook_modules_enabled().
*/
function locale_modules_enabled($modules) {
include_once DRUPAL_ROOT . '/includes/language.inc';
language_types_set();
language_negotiation_purge();
}
/**
* Implements hook_modules_disabled().
*/
function locale_modules_disabled($modules) {
locale_modules_enabled($modules);
}
// ---------------------------------------------------------------------------------
// Locale core functionality
......@@ -928,7 +946,7 @@ function locale_block_info() {
include_once DRUPAL_ROOT . '/includes/language.inc';
$block = array();
$info = language_types_info();
foreach (language_types_configurable() as $type) {
foreach (language_types_configurable(FALSE) as $type) {
$block[$type] = array(
'info' => t('Language switcher (@type)', array('@type' => $info[$type]['name'])),
// Not worth caching.
......
......@@ -17,6 +17,7 @@
* - a functional test for multilingual support by content type and on nodes.
* - a functional test for multilingual fields.
* - a functional test for comment language.
* - a functional test fot language types/negotiation info.
*/
......@@ -2248,10 +2249,13 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase {
variable_set('locale_test_content_language_type', TRUE);
// Set interface language detection to user and content language detection
// to URL.
// to URL. Disable inheritance from interface language to ensure content
// language will fall back to the default language if no URL language can be
// detected.
$edit = array(
'language[enabled][locale-user]' => TRUE,
'language_content[enabled][locale-url]' => TRUE
'language_content[enabled][locale-url]' => TRUE,
'language_content[enabled][locale-interface]' => FALSE,
);
$this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
......@@ -2373,3 +2377,158 @@ class LocaleDateFormatsFunctionalTest extends DrupalWebTestCase {
$this->assertText($french_date, t('French date format appears'));
}
}
/**
* Functional test for language types/negotiation info.
*/
class LocaleLanguageNegotiationInfoFunctionalTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Language negotiation info',
'description' => 'Tests alterations to language types/negotiation info.',
'group' => 'Locale',
);
}
function setUp() {
parent::setUp('locale');
require_once DRUPAL_ROOT .'/includes/language.inc';
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
$this->drupalLogin($admin_user);
$this->drupalPost('admin/config/regional/language/add', array('langcode' => 'it'), t('Add language'));
}
/**
* Tests alterations to language types/negotiation info.
*/
function testInfoAlterations() {
// Enable language type/negotiation info alterations.
variable_set('locale_test_language_types', TRUE);
variable_set('locale_test_language_negotiation_info', TRUE);
$this->languageNegotiationUpdate();
// Check that fixed language types are properly configured without the need
// of saving the language negotiation settings.
$this->checkFixedLanguageTypes();
// Make the content language type configurable by updating the language
// negotiation settings with the proper flag enabled.
variable_set('locale_test_content_language_type', TRUE);
$this->languageNegotiationUpdate();
$type = LANGUAGE_TYPE_CONTENT;
$language_types = variable_get('language_types', drupal_language_types());
$this->assertTrue($language_types[$type], t('Content language type is configurable.'));
// Enable some core and custom language providers. The test language type is
// supposed to be configurable.
$test_type = 'test_language_type';
$provider = LOCALE_LANGUAGE_NEGOTIATION_INTERFACE;
$test_provider = 'test_language_provider';
$form_field = $type . '[enabled]['. $provider .']';
$edit = array(
$form_field => TRUE,
$type . '[enabled][' . $test_provider . ']' => TRUE,
$test_type . '[enabled][' . $test_provider . ']' => TRUE,
);
$this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
// Remove the interface language provider by updating the language
// negotiation settings with the proper flag enabled.
variable_set('locale_test_language_negotiation_info_alter', TRUE);
$this->languageNegotiationUpdate();
$negotiation = variable_get("language_negotiation_$type", array());
$this->assertFalse(isset($negotiation[$provider]), t('Interface language provider removed from the stored settings.'));
$this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, t('Interface language provider unavailable.'));
// Check that type-specific language providers can be assigned only to the
// corresponding language types.
foreach (language_types_configurable() as $type) {
$form_field = $type . '[enabled][test_language_provider_ts]';
if ($type == $test_type) {
$this->assertFieldByXPath("//input[@name=\"$form_field\"]", NULL, t('Type-specific test language provider available for %type.', array('%type' => $type)));
}
else {
$this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, t('Type-specific test language provider unavailable for %type.', array('%type' => $type)));
}
}
// Check language negotiation results.
$this->drupalGet('');
$last = variable_get('locale_test_language_negotiation_last', array());
foreach (language_types() as $type) {
$langcode = $last[$type];
$value = $type == LANGUAGE_TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en';
$this->assertEqual($langcode, $value, t('The negotiated language for %type is %language', array('%type' => $type, '%language' => $langcode)));
}
// Disable locale_test and check that everything is set back to the original
// status.
$this->languageNegotiationUpdate('disable');
// Check that only the core language types are available.
foreach (language_types() as $type) {
$this->assertTrue(strpos($type, 'test') === FALSE, t('The %type language is still available', array('%type' => $type)));
}
// Check that fixed language types are properly configured, even those
// previously set to configurable.
$this->checkFixedLanguageTypes();
// Check that unavailable language providers are not present in the
// negotiation settings.
$negotiation = variable_get("language_negotiation_$type", array());
$this->assertFalse(isset($negotiation[$test_provider]), t('The disabled test language provider is not part of the content language negotiation settings.'));
// Check that configuration page presents the correct options and settings.
$this->assertNoRaw(t('Test language detection'), t('No test language type configuration available.'));
$this->assertNoRaw(t('This is a test language provider'), t('No test language provider available.'));
}
/**
* Update language types/negotiation information.
*
* Manually invoke locale_modules_enabled()/locale_modules_disabled() since
* they would not be invoked after enabling/disabling locale_test the first
* time.
*/
private function languageNegotiationUpdate($op = 'enable') {
static $last_op = NULL;
$modules = array('locale_test');
// Enable/disable locale_test only if we did not already before.
if ($last_op != $op) {
$function = "module_{$op}";
$function($modules);
// Reset hook implementation cache.
module_implements(NULL, FALSE, TRUE);
}
drupal_static_reset('language_types_info');
drupal_static_reset('language_negotiation_info');
$function = "locale_modules_{$op}d";
if (function_exists($function)) {
$function($modules);
}
$this->drupalGet('admin/config/regional/language/configure');
}
/**
* Check that language negotiation for fixed types matches the stored one.
*/
private function checkFixedLanguageTypes() {
drupal_static_reset('language_types_info');
foreach (language_types_info() as $type => $info) {
if (isset($info['fixed'])) {
$negotiation = variable_get("language_negotiation_$type", array());
$equal = count($info['fixed']) == count($negotiation);
while ($equal && list($id) = each($negotiation)) {
list(, $info_id) = each($info['fixed']);
$equal = $info_id == $id;
}
$this->assertTrue($equal, t('language negotiation for %type is properly set up', array('%type' => $type)));
}
}
}
}
......@@ -27,14 +27,89 @@ function locale_test_boot() {
}
}
/**
* Implements hook_init().
*/
function locale_test_init() {
locale_test_store_language_negotiation();
}
/**
* Implements hook_language_types_info().
*/
function locale_test_language_types_info() {
if (variable_get('locale_test_language_types', FALSE)) {
return array(
'test_language_type' => array(
'name' => t('Test'),
'description' => t('A test language type.'),
),
'fixed_test_language_type' => array(
'fixed' => array('test_language_provider'),
),
);
}
}
/**
* Implements hook_language_types_info_alter().
*/
function locale_test_language_types_info_alter(array &$language_types) {
if (variable_get('locale_test_content_language_type', FALSE)) {
$language_types[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.'),
unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
}
}
/**
* Implements hook_language_negotiation_info().
*/
function locale_test_language_negotiation_info() {
if (variable_get('locale_test_language_negotiation_info', FALSE)) {
$info = array(
'callbacks' => array(
'language' => 'locale_test_language_provider',
),
'file' => drupal_get_path('module', 'locale_test') .'/locale_test.module',
'weight' => -10,
'description' => t('This is a test language provider.'),
);
return array(
'test_language_provider' => array(
'name' => t('Test'),
'types' => array(LANGUAGE_TYPE_CONTENT, 'test_language_type', 'fixed_test_language_type'),
) + $info,
'test_language_provider_ts' => array(
'name' => t('Type-specific test'),
'types' => array('test_language_type'),
) + $info,
);
}
}
/**
* Implements hook_language_negotiation_info_alter().
*/
function locale_test_language_negotiation_info_alter(array &$language_providers) {
if (variable_get('locale_test_language_negotiation_info_alter', FALSE)) {
unset($language_providers[LOCALE_LANGUAGE_NEGOTIATION_INTERFACE]);
}
}
/**
* Store the last negotiated languages.
*/
function locale_test_store_language_negotiation() {
$last = array();
foreach (language_types() as $type) {
$last[$type] = $GLOBALS[$type]->language;
}
variable_set('locale_test_language_negotiation_last', $last);
}
/**
* Test language provider.
*/
function locale_test_language_provider($languages) {
return 'it';
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment