diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 70c837cdb9a9e344d4b9b78bfbcf5f966f7fa94e..1b38308808ecb11c4efaed51fd20d4ba2aa3fed1 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,26 @@ -Drupal 7.0, xxxx-xx-xx (development version) +Drupal 7.3-dev, xxxx-xx-xx (development version) +---------------------- + +Drupal 7.2, 2011-05-25 +---------------------- +- Added a default .gitignore file. +- Improved PostgreSQL and SQLite support. +- Numerous critical performance improvements. +- Numerous critical fixes to the upgrade path. +- Numerous fixes to language and translation systems. +- Numerous fixes to AJAX and #states systems. +- Improvements to the locking system. +- Numerous documentation fixes. +- Numerous styling and theme system fixes. +- Numerous fixes for schema mis-matches between Drupal 6 and 7. +- Minor internal API clean-ups. + +Drupal 7.1, 2011-05-25 +---------------------- +- Fixed security issues (Cross site scripting, File access bypass), see SA-CORE-2011-001. + +Drupal 7.0, 2011-01-05 ---------------------- - Database: * Fully rewritten database layer utilizing PHP 5's PDO abstraction layer. @@ -218,6 +239,159 @@ Drupal 7.0, xxxx-xx-xx (development version) * Added a locking framework to coordinate long-running operations across requests. +Drupal 6.23-dev, xxxx-xx-xx (development release) +----------------------- + +Drupal 6.22, 2011-05-25 +----------------------- +- Made Drupal 6 work better with IIS and Internet Explorer. +- Fixed .po file imports to work better with custom textgroups. +- Improved code documentation at various places. +- Fixed a variety of other bugs. + +Drupal 6.21, 2011-05-25 +---------------------- +- Fixed security issues (Cross site scripting), see SA-CORE-2011-001. + +Drupal 6.20, 2010-12-15 +---------------------- +- Fixed a variety of small bugs, improved code documentation. + +Drupal 6.19, 2010-08-11 +---------------------- +- Fixed a variety of small bugs, improved code documentation. + +Drupal 6.18, 2010-08-11 +---------------------- +- Fixed security issues (OpenID authentication bypass, File download access + bypass, Comment unpublishing bypass, Actions cross site scripting), + see SA-CORE-2010-002. + +Drupal 6.17, 2010-06-02 +---------------------- +- Improved PostgreSQL compatibility +- Better PHP 5.3 and PHP 4 compatibility +- Better browser compatibility of CSS and JS aggregation +- Improved logging for login failures +- Fixed an incompatibility with some contributed modules and the locking system +- Fixed a variety of other bugs. + +Drupal 6.16, 2010-03-03 +---------------------- +- Fixed security issues (Installation cross site scripting, Open redirection, + Locale module cross site scripting, Blocked user session regeneration), + see SA-CORE-2010-001. +- Better support for updated jQuery versions. +- Reduced resource usage of update.module. +- Fixed several issues relating to support of install profiles and + distributions. +- Added a locking framework to avoid data corruption on long operations. +- Fixed a variety of other bugs. + +Drupal 6.15, 2009-12-16 +---------------------- +- Fixed security issues (Cross site scripting), see SA-CORE-2009-009. +- Fixed a variety of other bugs. + +Drupal 6.14, 2009-09-16 +---------------------- +- Fixed security issues (OpenID association cross site request forgeries, + OpenID impersonation and File upload), see SA-CORE-2009-008. +- Changed the system modules page to not run all cache rebuilds; use the + button on the performance settings page to achieve the same effect. +- Added support for PHP 5.3.0 out of the box. +- Fixed a variety of small bugs. + +Drupal 6.13, 2009-07-01 +---------------------- +- Fixed security issues (Cross site scripting, Input format access bypass and + Password leakage in URL), see SA-CORE-2009-007. +- Fixed a variety of small bugs. + +Drupal 6.12, 2009-05-13 +---------------------- +- Fixed security issues (Cross site scripting), see SA-CORE-2009-006. +- Fixed a variety of small bugs. + +Drupal 6.11, 2009-04-29 +---------------------- +- Fixed security issues (Cross site scripting and limited information + disclosure), see SA-CORE-2009-005 +- Fixed performance issues with the menu router cache, the update + status cache and improved cache invalidation +- Fixed a variety of small bugs. + +Drupal 6.10, 2009-02-25 +---------------------- +- Fixed a security issue, (Local file inclusion on Windows), + see SA-CORE-2009-003 +- Fixed node_feed() so custom fields can show up in RSS feeds. +- Improved PostgreSQL compatibility. +- Fixed a variety of small bugs. + +Drupal 6.9, 2009-01-14 +---------------------- +- Fixed security issues, (Access Bypass, Validation Bypass and Hardening + against SQL injection), see SA-CORE-2009-001 +- Made HTTP request checking more robust and informative. +- Fixed HTTP_HOST checking to work again with HTTP 1.0 clients and + basic shell scripts. +- Removed t() calls from all schema documentation. Suggested best practice + changed for contributed modules, see http://drupal.org/node/322731. +- Fixed a variety of small bugs. + +Drupal 6.8, 2008-12-11 +---------------------- +- Removed a previous change incompatible with PHP 5.1.x and lower. + +Drupal 6.7, 2008-12-10 +---------------------- +- Fixed security issues, (Cross site request forgery and Cross site scripting), see SA-2008-073 +- Updated robots.txt and .htaccess to match current file use. +- Fixed a variety of small bugs. + +Drupal 6.6, 2008-10-22 +---------------------- +- Fixed security issues, (File inclusion, Cross site scripting), see SA-2008-067 +- Fixed a variety of small bugs. + +Drupal 6.5, 2008-10-08 +---------------------- +- Fixed security issues, (File upload access bypass, Access rules bypass, + BlogAPI access bypass), see SA-2008-060. +- Fixed a variety of small bugs. + +Drupal 6.4, 2008-08-13 +---------------------- +- Fixed a security issue (Cross site scripting, Arbitrary file uploads via + BlogAPI, Cross site request forgeries and Various Upload module + vulnerabilities), see SA-2008-047. +- Improved error messages during installation. +- Fixed a bug that prevented AHAH handlers to be attached to radios widgets. +- Fixed a variety of small bugs. + +Drupal 6.3, 2008-07-09 +---------------------- +- Fixed security issues, (Cross site scripting, cross site request forgery, + session fixation and SQL injection), see SA-2008-044. +- Slightly modified installation process to prevent file ownership issues on + shared hosts. +- Improved PostgreSQL compatibility (rewritten queries; custom blocks). +- Upgraded to jQuery 1.2.6. +- Performance improvements to search, menu handling and form API caches. +- Fixed Views compatibility issues (Views for Drupal 6 requires Drupal 6.3+). +- Fixed a variety of small bugs. + +Drupal 6.2, 2008-04-09 +---------------------- +- Fixed a variety of small bugs. +- Fixed a security issue (Access bypasses), see SA-2008-026. + +Drupal 6.1, 2008-02-27 +---------------------- +- Fixed a variety of small bugs. +- Fixed a security issue (Cross site scripting), see SA-2008-018. + Drupal 6.0, 2008-02-13 ---------------------- - New, faster and better menu system. @@ -320,6 +494,95 @@ Drupal 6.0, 2008-02-13 - Removed old system updates. Updates from Drupal versions prior to 5.x will require upgrading to 5.x before upgrading to 6.x. +Drupal 5.23, 2010-08-11 +----------------------- +- Fixed security issues (File download access bypass, Comment unpublishing + bypass), see SA-CORE-2010-002. + +Drupal 5.22, 2010-03-03 +----------------------- +- Fixed security issues (Open redirection, Locale module cross site scripting, + Blocked user session regeneration), see SA-CORE-2010-001. + +Drupal 5.21, 2009-12-16 +----------------------- +- Fixed a security issue (Cross site scripting), see SA-CORE-2009-009. +- Fixed a variety of small bugs. + +Drupal 5.20, 2009-09-16 +----------------------- +- Avoid security problems resulting from writing Drupal 6-style menu + declarations. +- Fixed security issues (session fixation), see SA-CORE-2009-008. +- Fixed a variety of small bugs. + +Drupal 5.19, 2009-07-01 +----------------------- +- Fixed security issues (Cross site scripting and Password leakage in URL), see + SA-CORE-2009-007. +- Fixed a variety of small bugs. + +Drupal 5.18, 2009-05-13 +----------------------- +- Fixed security issues (Cross site scripting), see SA-CORE-2009-006. +- Fixed a variety of small bugs. + +Drupal 5.17, 2009-04-29 +----------------------- +- Fixed security issues (Cross site scripting and limited information + disclosure) see SA-CORE-2009-005. +- Fixed a variety of small bugs. + +Drupal 5.16, 2009-02-25 +----------------------- +- Fixed a security issue, (Local file inclusion on Windows), see SA-CORE-2009-004. +- Fixed a variety of small bugs. + +Drupal 5.15, 2009-01-14 +----------------------- +- Fixed security issues, (Hardening against SQL injection), see + SA-CORE-2009-001 +- Fixed HTTP_HOST checking to work again with HTTP 1.0 clients and basic shell + scripts. +- Fixed a variety of small bugs. + +Drupal 5.14, 2008-12-11 +----------------------- +- removed a previous change incompatible with PHP 5.1.x and lower. + +Drupal 5.13, 2008-12-10 +----------------------- +- fixed a variety of small bugs. +- fixed security issues, (Cross site request forgery and Cross site scripting), see SA-2008-073 +- updated robots.txt and .htaccess to match current file use. + +Drupal 5.12, 2008-10-22 +----------------------- +- fixed security issues, (File inclusion), see SA-2008-067 + +Drupal 5.11, 2008-10-08 +----------------------- +- fixed a variety of small bugs. +- fixed security issues, (File upload access bypass, Access rules bypass, + BlogAPI access bypass, Node validation bypass), see SA-2008-060 + +Drupal 5.10, 2008-08-13 +----------------------- +- fixed a variety of small bugs. +- fixed security issues, (Cross site scripting, Arbitrary file uploads via + BlogAPI and Cross site request forgery), see SA-2008-047 + +Drupal 5.9, 2008-07-23 +---------------------- +- fixed a variety of small bugs. +- fixed security issues, (Session fixation), see SA-2008-046 + +Drupal 5.8, 2008-07-09 +---------------------- +- fixed a variety of small bugs. +- fixed security issues, (Cross site scripting, cross site request forgery, and + session fixation), see SA-2008-044 + Drupal 5.7, 2008-01-28 ---------------------- - fixed the input format configuration page. diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index bbddde2a7649fed5774b7b57f2a47b23d5ead74c..a5dec6997138aa94b7f96155d21b6235a3ace708 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.0-dev'); +define('VERSION', '7.3-dev'); /** * Core API compatibility. diff --git a/includes/language.inc b/includes/language.inc index eb13eacc3746a6a6d4f83067f5d0c841932f9f47..b7057f2a138cc69b64303d45b1ed245ed1f00aad 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -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) { diff --git a/modules/block/block.module b/modules/block/block.module index 2f7e372fe3379d4d2cac051a23b8f11485493e6c..73eba3311618f0cbb9d9d1731f5a00c4e568321c 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -943,7 +943,15 @@ function template_preprocess_block(&$variables) { $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->region; $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module; - $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module . '__' . $variables['block']->delta; + // Hyphens (-) and underscores (_) play a special role in theme suggestions. + // Theme suggestions should only contain underscores, because within + // drupal_find_theme_templates(), underscores are converted to hyphens to + // match template file names, and then converted back to underscores to match + // pre-processing and other function names. So if your theme suggestion + // contains a hyphen, it will end up as an underscore after this conversion, + // and your function names won't be recognized. So, we need to convert + // hyphens to underscores in block deltas for the theme suggestions. + $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module . '__' . strtr($variables['block']->delta, '-', '_'); // Create a valid HTML ID and make sure it is unique. $variables['block_html_id'] = drupal_html_id('block-' . $variables['block']->module . '-' . $variables['block']->delta); diff --git a/modules/block/block.test b/modules/block/block.test index af118a940d3bf4af96ef3837cca37e705fcb5f27..022bf383031d7556d491ae9b6e87e0767a2e54f1 100644 --- a/modules/block/block.test +++ b/modules/block/block.test @@ -666,3 +666,45 @@ class BlockHTMLIdTestCase extends DrupalWebTestCase { $this->assertRaw('block-block-test-test-html-id', t('HTML id for test block is valid.')); } } + + +/** + * Unit tests for template_preprocess_block(). + */ +class BlockTemplateSuggestionsUnitTest extends DrupalUnitTestCase { + public static function getInfo() { + return array( + 'name' => 'Block template suggestions', + 'description' => 'Test the template_preprocess_block() function.', + 'group' => 'Block', + ); + } + + /** + * Test if template_preprocess_block() handles the suggestions right. + */ + function testBlockThemeHookSuggestions() { + // Define block delta with underscore to be preprocessed + $block1 = new stdClass(); + $block1->module = 'block'; + $block1->delta = 'underscore_test'; + $block1->region = 'footer'; + $variables1 = array(); + $variables1['elements']['#block'] = $block1; + $variables1['elements']['#children'] = ''; + template_preprocess_block($variables1); + $this->assertEqual($variables1['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__underscore_test'), t('Found expected block suggestions for delta with underscore')); + + // Define block delta with hyphens to be preprocessed. Hyphens should be + // replaced with underscores. + $block2 = new stdClass(); + $block2->module = 'block'; + $block2->delta = 'hyphen-test'; + $block2->region = 'footer'; + $variables2 = array(); + $variables2['elements']['#block'] = $block2; + $variables2['elements']['#children'] = ''; + template_preprocess_block($variables2); + $this->assertEqual($variables2['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__hyphen_test'), t('Hyphens (-) in block delta were replaced by underscore (_)')); + } +} diff --git a/modules/color/color.install b/modules/color/color.install index 5705ade3f8b4c1d2955c07bac3ef179304ad01be..b0eb95ef6a57f47f2ce91289e7f01fc8201bbe5a 100644 --- a/modules/color/color.install +++ b/modules/color/color.install @@ -40,3 +40,18 @@ function color_requirements($phase) { return $requirements; } + +/** + * Warn site administrator if unsafe CSS color codes are found in the database. + */ +function color_update_7001() { + $theme_palettes = db_query("SELECT name FROM {variable} WHERE name LIKE 'color_%_palette'")->fetchCol(); + foreach ($theme_palettes as $name) { + $palette = variable_get($name, array()); + foreach ($palette as $key => $color) { + if (!preg_match('/^#([a-f0-9]{3}){1,2}$/iD', $color)) { + drupal_set_message('Some of the custom CSS color codes specified via the color module are invalid. Please examine the themes which are making use of the color module at the <a href="'. url('admin/appearance/settings') .'">Appearance settings</a> page to verify their CSS color values.', 'warning'); + } + } + } +} diff --git a/modules/color/color.module b/modules/color/color.module index ff6c70e6cb7617f56ebc1c9674ab1575ac696b92..f3fafe7b7e6fbd876b6f1fc022bbf4f1bd515bed 100644 --- a/modules/color/color.module +++ b/modules/color/color.module @@ -42,6 +42,7 @@ function color_form_system_theme_settings_alter(&$form, &$form_state) { '#theme' => 'color_scheme_form', ); $form['color'] += color_scheme_form($form, $form_state, $theme); + $form['#validate'][] = 'color_scheme_form_validate'; $form['#submit'][] = 'color_scheme_form_submit'; } } @@ -270,6 +271,18 @@ function theme_color_scheme_form($variables) { return $output; } +/** + * Validation handler for color change form. + */ +function color_scheme_form_validate($form, &$form_state) { + // Only accept hexadecimal CSS color strings to avoid XSS upon use. + foreach ($form_state['values']['palette'] as $key => $color) { + if (!preg_match('/^#([a-f0-9]{3}){1,2}$/iD', $color)) { + form_set_error('palette][' . $key, t('%name must be a valid hexadecimal CSS color value.', array('%name' => $form['color']['palette'][$key]['#title']))); + } + } +} + /** * Submit handler for color change form. */ diff --git a/modules/file/file.module b/modules/file/file.module index 400270178642487a8d02924b720c099c2ae8ad93..3e4525119611cf49c3ff46f864e6a0a72989cf13 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -976,7 +976,7 @@ function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISI } } - return isset($field) ? $references[$field['field_name']] : $references; + return isset($field) ? $references[$field['field_name']] : array_filter($references); } /** diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index de16133fb5a9e7115a7434643dfcfa0384f21374..d8201dbf227bd7cb06799470c951402cf86e80f2 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -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.')); } diff --git a/modules/locale/locale.module b/modules/locale/locale.module index c1cdd434bd05fbe960dd26ae90ef6f035f71b624..07884614a51e6b2d84ae1c91b842bbb83df46200 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -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. diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 5e9e8336cf427fc824606da7cc620093d7e6cf7d..42a6dbc48063e34adc90df51f88707e0e5d05df3 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -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))); + } + } + } +} diff --git a/modules/locale/tests/locale_test.module b/modules/locale/tests/locale_test.module index f256b5c8682fd6a4a20f77ce2aa734d2c4558cd5..14a2588dd739305d16c0f70b26558f7bae48d6ed 100644 --- a/modules/locale/tests/locale_test.module +++ b/modules/locale/tests/locale_test.module @@ -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'; +} diff --git a/modules/translation/translation.pages.inc b/modules/translation/translation.pages.inc index 102d1b8823096ecc3264a4331415c148345afe8e..7e4f0af266aedc22713b4e47211e322fb5155814 100644 --- a/modules/translation/translation.pages.inc +++ b/modules/translation/translation.pages.inc @@ -37,12 +37,12 @@ function translation_node_overview($node) { $translation_node = node_load($translations[$langcode]->nid); $path = 'node/' . $translation_node->nid; $links = language_negotiation_get_switch_links($type, $path); - $title = empty($links->links[$langcode]) ? l($translation_node->title, $path) : l($translation_node->title, $links->links[$langcode]['href'], $links->links[$langcode]); + $title = empty($links->links[$langcode]['href']) ? l($translation_node->title, $path) : l($translation_node->title, $links->links[$langcode]['href'], $links->links[$langcode]); if (node_access('update', $translation_node)) { $text = t('edit'); $path = 'node/' . $translation_node->nid . '/edit'; $links = language_negotiation_get_switch_links($type, $path); - $options[] = empty($links->links[$langcode]) ? l($text, $path) : l($text, $links->links[$langcode]['href'], $links->links[$langcode]); + $options[] = empty($links->links[$langcode]['href']) ? l($text, $path) : l($text, $links->links[$langcode]['href'], $links->links[$langcode]); } $status = $translation_node->status ? t('Published') : t('Not published'); $status .= $translation_node->translate ? ' - <span class="marker">' . t('outdated') . '</span>' : ''; @@ -58,7 +58,7 @@ function translation_node_overview($node) { $path = 'node/add/' . str_replace('_', '-', $node->type); $links = language_negotiation_get_switch_links($type, $path); $query = array('query' => array('translation' => $node->nid, 'target' => $langcode)); - $options[] = empty($links->links[$langcode]) ? l($text, $path, $query) : l($text, $links->links[$langcode]['href'], array_merge_recursive($links->links[$langcode], $query)); + $options[] = empty($links->links[$langcode]['href']) ? l($text, $path, $query) : l($text, $links->links[$langcode]['href'], array_merge_recursive($links->links[$langcode], $query)); } $status = t('Not translated'); } diff --git a/modules/translation/translation.test b/modules/translation/translation.test index fa8c6b63fc5b1f769ac6823b9c475081a2b08c0c..54b53d9fdc4e6856820a5333e94503b66a425093 100644 --- a/modules/translation/translation.test +++ b/modules/translation/translation.test @@ -20,7 +20,7 @@ class TranslationTestCase extends DrupalWebTestCase { parent::setUp('locale', 'translation', 'translation_test'); // Setup users. - $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages')); + $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate content')); $this->translator = $this->drupalCreateUser(array('create page content', 'edit own page content', 'translate content')); $this->drupalLogin($this->admin_user); @@ -67,6 +67,14 @@ class TranslationTestCase extends DrupalWebTestCase { $node_body = $this->randomName(); $node = $this->createPage($node_title, $node_body, 'en'); + // Unpublish the original node to check that this has no impact on the + // translation overview page, publish it again afterwards. + $this->drupalLogin($this->admin_user); + $this->drupalPost('node/' . $node->nid . '/edit', array('status' => FALSE), t('Save')); + $this->drupalGet('node/' . $node->nid . '/translate'); + $this->drupalPost('node/' . $node->nid . '/edit', array('status' => NODE_PUBLISHED), t('Save')); + $this->drupalLogin($this->translator); + // Check that the "add translation" link uses a localized path. $languages = language_list(); $this->drupalGet('node/' . $node->nid . '/translate');