diff --git a/core/modules/block/block.admin.inc b/core/modules/block/block.admin.inc index 8a5553d..53f15fe 100644 --- a/core/modules/block/block.admin.inc +++ b/core/modules/block/block.admin.inc @@ -400,6 +400,72 @@ function block_admin_configure($form, &$form_state, $module, $delta) { ); } + // Configure the block visibility per language. + if (module_exists('language') && language_multilingual()) { + $configurable_language_types = language_types_get_configurable(); + $existing_language_settings = db_query("SELECT type, langcode FROM {block_language} WHERE module = :module AND delta = :delta", array( + ':module' => $form['module']['#value'], + ':delta' => $form['delta']['#value'], + ))->fetchAll(); + $default_langcode_options = array(); + $default_language_type = $configurable_language_types[0]; + foreach ($existing_language_settings as $setting) { + $default_langcode_options[] = $setting->langcode; + // Overwrite default language type if we have it set. Although this + // theoretically would allow per language type association, our UI + // only allows language type association overall for a block, so we + // only need a single value. + $default_language_type = $setting->type; + } + + // Fetch the enabled languages. + $enabled_languages = language_list(TRUE); + foreach ($enabled_languages as $language) { + // @TODO $language->name is not wrapped with t(), it should be replaced + // by CMI translation implementation. + $langcodes_options[$language->langcode] = $language->name; + } + $form['visibility']['language'] = array( + '#type' => 'fieldset', + '#title' => t('Languages'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#group' => 'visibility', + '#weight' => 5, + ); + // If there are multiple configurable language types, let the user pick + // which one should be applied to this visibility setting. This way users + // can limit blocks by interface language or content language for exmaple. + $language_types = language_types_info(); + $language_type_options = array(); + foreach ($configurable_language_types as $type_key) { + $language_type_options[$type_key] = $language_types[$type_key]['name']; + } + if (count($language_type_options) > 1) { + // More than one configurable language type. + $form['visibility']['language']['language_type'] = array( + '#type' => 'radios', + '#title' => t('Language type'), + '#options' => $language_type_options, + '#default_value' => $default_language_type, + ); + } + else { + // Only one configurable language type, save with that. + $form['visibility']['language']['language_type'] = array( + '#type' => 'value', + '#value' => $configurable_language_types[0], + ); + } + $form['visibility']['language']['langcodes'] = array( + '#type' => 'checkboxes', + '#title' => t('Show this block only for specific languages'), + '#default_value' => $default_langcode_options, + '#options' => $langcodes_options, + '#description' => t('Show this block only for the selected language(s). If you select no languages, the block will be visibile in all languages.'), + ); + } + // Per-role visibility. $default_role_options = db_query("SELECT rid FROM {block_role} WHERE module = :module AND delta = :delta", array( ':module' => $block->module, @@ -505,7 +571,7 @@ function block_admin_configure_submit($form, &$form_state) { } $query->execute(); - // Store regions per theme for this block + // Store regions per theme for this block. foreach ($form_state['values']['regions'] as $theme => $region) { db_merge('block') ->key(array('theme' => $theme, 'delta' => $form_state['values']['delta'], 'module' => $form_state['values']['module'])) @@ -517,6 +583,27 @@ function block_admin_configure_submit($form, &$form_state) { ->execute(); } + // Update the block visibility settings if we have settings to store + // for the existing languages. + if (module_exists('language') && isset($form_state['values']['langcodes'])) { + db_delete('block_language') + ->condition('module', $form_state['values']['module']) + ->condition('delta', $form_state['values']['delta']) + ->execute(); + $query = db_insert('block_language')->fields(array( + 'type', 'langcode', 'module', 'delta' + )); + foreach (array_filter($form_state['values']['langcodes']) as $langcode) { + $query->values(array( + 'type' => $form_state['values']['language_type'], + 'langcode' => $langcode, + 'module' => $form_state['values']['module'], + 'delta' => $form_state['values']['delta'], + )); + } + $query->execute(); + } + module_invoke($form_state['values']['module'], 'block_save', $form_state['values']['delta'], $form_state['values']); } catch (Exception $e) { @@ -604,7 +691,7 @@ function block_add_block_form_submit($form, &$form_state) { } $query->execute(); - // Store regions per theme for this block + // Store regions per theme for this block. foreach ($form_state['values']['regions'] as $theme => $region) { db_merge('block') ->key(array('theme' => $theme, 'delta' => $delta, 'module' => $form_state['values']['module'])) @@ -616,6 +703,23 @@ function block_add_block_form_submit($form, &$form_state) { ->execute(); } + // Update the block visibility settings if we have settings to store + // for the existing languages. + if (module_exists('language') && isset($form_state['values']['langcodes'])) { + $query = db_insert('block_language')->fields(array( + 'type', 'langcode', 'module', 'delta' + )); + foreach (array_filter($form_state['values']['langcodes']) as $langcode) { + $query->values(array( + 'type' => $form_state['values']['language_type'], + 'langcode' => $langcode, + 'module' => $form_state['values']['module'], + 'delta' => $form_state['values']['delta'], + )); + } + $query->execute(); + } + drupal_set_message(t('The block has been created.')); cache_clear_all(); $form_state['redirect'] = 'admin/structure/block'; @@ -659,6 +763,11 @@ function block_custom_block_delete_submit($form, &$form_state) { ->condition('module', 'block') ->condition('delta', $form_state['values']['bid']) ->execute(); + db_delete('block_language') + ->condition('module', 'block') + ->condition('delta', $form_state['values']['bid']) + ->execute(); + drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['info']))); cache_clear_all(); $form_state['redirect'] = 'admin/structure/block'; diff --git a/core/modules/block/block.install b/core/modules/block/block.install index c2d4185..1b450cd 100644 --- a/core/modules/block/block.install +++ b/core/modules/block/block.install @@ -166,6 +166,37 @@ function block_schema() { 'primary key' => array('bid'), ); + $schema['block_language'] = array( + 'description' => 'Sets up display criteria for blocks based on langcode', + 'fields' => array( + 'module' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => "The block's origin module, from {block}.module.", + ), + 'delta' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => "The block's unique delta within module, from {block}.delta.", + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => "Language type name. Applied to filter the block by that type.", + ), + 'langcode' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => "The machine-readable name of this language from {language}.langcode.", + ), + ), + 'primary key' => array('module', 'delta', 'type', 'langcode'), + ); + $schema['cache_block'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_block']['description'] = 'Cache table for the Block module to store already built blocks, identified by module, delta, and various contexts which may change the block, such as theme, locale, and caching mode defined for the block.'; @@ -199,6 +230,43 @@ function block_update_8000() { } /** + * Creates table {block_language} for language visibility settings per block. + */ +function block_update_8001() { + $schema = array( + 'description' => 'Sets up display criteria for blocks based on langcode.', + 'fields' => array( + 'module' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => "The block's origin module, from {block}.module.", + ), + 'delta' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => "The block's unique delta within module, from {block}.delta.", + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => "Language type name. Applied to filter the block by that type.", + ), + 'langcode' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'description' => "The machine-readable name of this language from {language}.langcode.", + ), + ), + 'primary key' => array('module', 'delta', 'type', 'langcode'), + ); + db_create_table('block_language', $schema); +} + +/** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/block/block.js b/core/modules/block/block.js index 7dd3c9b..097b9a7 100644 --- a/core/modules/block/block.js +++ b/core/modules/block/block.js @@ -33,6 +33,18 @@ Drupal.behaviors.blockSettingsSummary = { return vals.join(', '); }); + $context.find('fieldset#edit-language').drupalSetSummary(function (context) { + var vals = []; + $(context).find('input[type="checkbox"]:checked').each(function () { + vals.push($.trim($(this).next('label').text())); + }); + if (!vals.length) { + vals.push(Drupal.t('Not restricted')); + } + return vals.join(', '); + }); + + $context.find('fieldset#edit-role').drupalSetSummary(function (context) { var vals = []; $(context).find('input[type="checkbox"]:checked').each(function () { diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 25bd3b1..839f86e 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -785,6 +785,13 @@ function block_block_list_alter(&$blocks) { $block_roles[$record->module][$record->delta][] = $record->rid; } + // Build an array of langcodes allowed per block. + $result = db_query('SELECT module, delta, type, langcode FROM {block_language}'); + $block_langcodes = array(); + foreach ($result as $record) { + $block_langcodes[$record->module][$record->delta][$record->type][$record->langcode] = TRUE; + } + foreach ($blocks as $key => $block) { if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) { // This block was added by a contrib module, leave it in the list. @@ -854,7 +861,23 @@ function block_block_list_alter(&$blocks) { } if (!$page_match) { unset($blocks[$key]); + continue; + } + + // Language visibility settings. + // No language setting for this block, leave it in the list. + if (!isset($block_langcodes[$block->module][$block->delta])) { + continue; + } + foreach ($block_langcodes[$block->module][$block->delta] as $language_type => $langcodes) { + if (isset($langcodes[$GLOBALS[$language_type]->langcode])) { + // Found a language type - langcode combination in the configuration + // that is applicable to the current request. + continue 2; + } } + // Had language configuration but none matched. + unset($blocks[$key]); } } @@ -1021,7 +1044,8 @@ function block_admin_paths() { /** * Implements hook_modules_uninstalled(). * - * Cleans up {block} and {block_role} tables from modules' blocks. + * Cleans up {block}, {block_role} and {block_language} tables + * from modules' blocks. */ function block_modules_uninstalled($modules) { db_delete('block') @@ -1030,4 +1054,19 @@ function block_modules_uninstalled($modules) { db_delete('block_role') ->condition('module', $modules, 'IN') ->execute(); + db_delete('block_language') + ->condition('module', $modules, 'IN') + ->execute(); +} + +/** + * Implements hook_language_delete(). + * + * Delete the potential block visibility settings of the deleted language. + */ +function block_language_delete($language) { + // Remove the block visibility settings for the deleted language. + db_delete('block_language') + ->condition('langcode', $language->langcode) + ->execute(); } diff --git a/core/modules/language/language.test b/core/modules/language/language.test index 6c64c29..b95ecbe 100644 --- a/core/modules/language/language.test +++ b/core/modules/language/language.test @@ -179,3 +179,192 @@ class LanguageListTest extends DrupalWebTestCase { $this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), t('The English language has been removed.')); } } + + +/** + * Functional tests for the language list configuration forms. + */ +class LanguageBlockVisibilityTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Language block visibility', + 'description' => 'Tests if a block can be configure to be only visibile on a particular language.', + 'group' => 'Language', + ); + } + + function setUp() { + parent::setUp('language', 'locale', 'block'); + } + + /** + * Tests the visibility settings for the blocks based on language. + */ + public function testLanguageBlockVisibility() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Check if the visibility setting is available. + $this->drupalGet('admin/structure/block/add'); + $this->assertField('langcodes[en]', t('Language visibility field is visible.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[en]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Change the default language. + $edit = array( + 'site_default' => 'fr', + ); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); + + // Reset the static cache of the language list. + drupal_static_reset('language_list'); + + // Check that a page has a block + $this->drupalGet('', array('language' => language_load('en'))); + $this->assertText($body, t('The body of the custom block appears on the page.')); + + // Check that a page doesn't has a block for the current language anymore + $this->drupalGet('', array('language' => language_load('fr'))); + $this->assertNoText($body, t('The body of the custom block does not appear on the page.')); + } + + /** + * Tests if the visibility settings are clean from the database if the + * language is removed. + */ + public function testLanguageBlockVisibilityLanguageDelete() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[fr]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Check that we have an entry in the database after saving the setting. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); + + // Delete the language. + $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); + + // Check that the setting related to this language has been deleted. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); + } + + /** + * Tests if the visibility settings are clean from the database + * if the block is deleted. + */ + public function testLanguageBlockVisibilityBlockDelete() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[fr]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Check that we have an entry in the database after saving the setting. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); + + // Delete the custom block. + $this->drupalPost('admin/structure/block/manage/block/1/delete', array(), t('Delete')); + + // Check that the setting related to this block has been deleted. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); + } +}