diff --git a/modules/block/block.admin.inc b/modules/block/block.admin.inc index e3fb253..7952f0b 100644 --- a/modules/block/block.admin.inc +++ b/modules/block/block.admin.inc @@ -59,46 +59,45 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme) { $form['#action'] = arg(4) ? url('admin/structure/block/list/' . $theme) : url('admin/structure/block'); $form['#tree'] = TRUE; - foreach ($blocks as $i => $block) { - $key = $block['module'] . '_' . $block['delta']; - $form[$key]['module'] = array( + foreach ($blocks as $block) { + $form[$block['block_id']]['block_instance_id'] = array( '#type' => 'value', - '#value' => $block['module'], + '#value' => $block['instances'][$theme]['block_instance_id'], ); - $form[$key]['delta'] = array( + $form[$block['block_id']]['status'] = array( '#type' => 'value', - '#value' => $block['delta'], + '#value' => $block['status'], ); - $form[$key]['info'] = array( + $form[$block['block_id']]['info'] = array( '#markup' => check_plain($block['info']), ); - $form[$key]['theme'] = array( + $form[$block['block_id']]['theme'] = array( '#type' => 'hidden', '#value' => $theme, ); - $form[$key]['weight'] = array( + $form[$block['block_id']]['weight'] = array( '#type' => 'weight', - '#default_value' => $block['weight'], + '#default_value' => $block['instances'][$theme]['weight'], '#delta' => $weight_delta, ); - $form[$key]['region'] = array( + $form[$block['block_id']]['region'] = array( '#type' => 'select', - '#default_value' => $block['region'], + '#default_value' => $block['instances'][$theme]['region'], '#options' => $block_regions, ); - $form[$key]['configure'] = array( + $form[$block['block_id']]['configure'] = array( '#markup' => l(t('configure'), - 'admin/structure/block/configure/' . $block['module'] . '/' . $block['delta']), + 'admin/structure/block/configure/' . $block['block_id']), ); if ($block['module'] == 'block') { - $form[$key]['delete'] = array( + $form[$block['block_id']]['delete'] = array( '#markup' => l(t('delete'), - 'admin/structure/block/delete/' . $block['delta']), + 'admin/structure/block/delete/' . $block['block_id']), ); } } // Do not allow disabling the main system content block. - unset($form['system_main']['region']['#options'][BLOCK_REGION_NONE]); + unset($form[block_entity_id('system', 'main')]['region']['#options'][BLOCK_REGION_NONE]); $form['submit'] = array( '#type' => 'submit', @@ -112,20 +111,19 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme) { * Process main blocks administration form submissions. */ function block_admin_display_form_submit($form, &$form_state) { - foreach ($form_state['values'] as $block) { - $block['status'] = (int) ($block['region'] != BLOCK_REGION_NONE); - $block['region'] = $block['status'] ? $block['region'] : ''; - db_update('block') - ->fields(array( - 'status' => $block['status'], - 'weight' => $block['weight'], - 'region' => $block['region'], - )) - ->condition('module', $block['module']) - ->condition('delta', $block['delta']) - ->condition('theme', $block['theme']) - ->execute(); + foreach ($form_state['values'] as $block_id => $instance) { + $new_status = (int) ($instance['region'] != BLOCK_REGION_NONE); + if ($new_status != $instance['status']) { + db_update('block') + ->fields(array('status' => $new_status)) + ->condition('block_id', $block_id) + ->execute(); + } + + $instance['region'] = $new_status ? $instance['region'] : BLOCK_REGION_NONE; + block_instance_save($instance); } + drupal_set_message(t('The block settings have been updated.')); cache_clear_all(); } @@ -159,11 +157,11 @@ function _block_compare($a, $b) { return $status; } // Sort by region (in the order defined by theme .info file). - if ((!empty($a['region']) && !empty($b['region'])) && ($place = ($regions[$a['region']] - $regions[$b['region']]))) { + if (((isset($a['instances'][$theme_key]) && !empty($a['instances'][$theme_key]['region'])) && (isset($b['instances'][$theme_key]) && !empty($b['instances'][$theme_key]['region']))) && ($place = ($regions[$a['instances'][$theme_key]['region']] - $regions[$b['instances'][$theme_key]['region']]))) { return $place; } // Sort by weight. - $weight = $a['weight'] - $b['weight']; + $weight = $a['instances'][$theme_key]['weight'] - $b['instances'][$theme_key]['weight']; if ($weight) { return $weight; } @@ -174,43 +172,44 @@ function _block_compare($a, $b) { /** * Menu callback; displays the block configuration form. */ -function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) { +function block_admin_configure($form, &$form_state, $block) { + $form['block_id'] = array( + '#type' => 'value', + '#value' => (isset($block->block_id)) ? $block->block_id : NULL, + ); $form['module'] = array( '#type' => 'value', - '#value' => $module, + '#value' => $block->module, ); $form['delta'] = array( '#type' => 'value', - '#value' => $delta, + '#value' => isset($block->delta) ? $block->delta : NULL, ); - $edit = db_query("SELECT pages, visibility, custom, title FROM {block} WHERE module = :module AND delta = :delta", array( - ':module' => $module, - ':delta' => $delta, - ))->fetchAssoc(); - $form['block_settings'] = array( '#type' => 'fieldset', '#title' => t('Block specific settings'), '#collapsible' => TRUE, + '#weight' => field_attach_extra_weight($block->block_machine_name, 'block_settings'), ); $form['block_settings']['title'] = array( '#type' => 'textfield', '#title' => t('Block title'), '#maxlength' => 64, - '#description' => $module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <none> to display no title, or leave blank to use the default block title.'), - '#default_value' => $edit['title'], + '#description' => $block->module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <none> to display no title, or leave blank to use the default block title.'), + '#default_value' => isset($block->title) ? $block->title : '', '#weight' => -18, ); // Allow the user to define this block's region directly - $form['regions'] = array( + $form['instances'] = array( '#type' => 'fieldset', '#title' => t('Region settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#description' => t('Specify in which region this block is displayed.'), '#tree' => TRUE, + '#weight' => field_attach_extra_weight($block->block_machine_name, 'instances'), ); $theme_default = variable_get('theme_default', 'garland'); @@ -219,13 +218,9 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) foreach (list_themes() as $key => $theme) { // Only display enabled themes if ($theme->status) { - $region = db_query("SELECT region FROM {block} WHERE module = :module AND delta = :delta AND theme = :theme", array( - ':module' => $module, - ':delta' => $delta, - ':theme' => $key, - ))->fetchField(); - - $form['regions'][$key] = array( + $region = (isset($block->instances[$key]['region'])) ? $block->instances[$key]['region'] : NULL; + + $form['instances'][$key]['region'] = array( '#type' => 'select', '#title' => t('!theme region', array('!theme' => $theme->info['name'])), '#default_value' => (!empty($region) ? $region : BLOCK_REGION_NONE), @@ -233,20 +228,38 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) '#expandable' => ($key !== $theme_default), '#weight' => ($key == $theme_default ? 9 : 10), ); + $form['instances'][$key]['block_instance_id'] = array( + '#type' => 'value', + '#value' => (isset($block->instances[$key]['block_instance_id'])) ? $block->instances[$key]['block_instance_id'] : NULL, + ); + $form['instances'][$key]['theme'] = array( + '#type' => 'value', + '#value' => $key, + ); + $form['instances'][$key]['weight'] = array( + '#type' => 'value', + '#value' => (isset($block->instances[$key]['weight'])) ? $block->instances[$key]['weight'] : 0, + ); } } // Module-specific block configurations. - if ($settings = module_invoke($module, 'block_configure', $delta)) { + if ($settings = module_invoke($block->module, 'block_configure', $block->delta)) { foreach ($settings as $k => $v) { $form['block_settings'][$k] = $v; } } // Get the block subject for the page title. - $info = module_invoke($module, 'block_info'); - if (isset($info[$delta])) { - drupal_set_title(t("'%name' block", array('%name' => $info[$delta]['info'])), PASS_THROUGH); + $info = module_invoke($block->module, 'block_info'); + if (isset($info[$block->delta])) { + drupal_set_title(t("'%name' block", array('%name' => $info[$block->delta]['info'])), PASS_THROUGH); + } + + // Attach fields. + if (isset($block->block_id)) { + //field_attach_load('block', array($block->block_id => $block)); + field_attach_form('block', $block, $form, $form_state); } $form['page_vis_settings'] = array( @@ -254,13 +267,17 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) '#title' => t('Page specific visibility settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, + '#weight' => field_attach_extra_weight($block->block_machine_name, 'page_vis_settings'), ); $access = user_access('use PHP for settings'); - if ($edit['visibility'] == 2 && !$access) { + if (isset($block->visibility) && $block->visibility == 2 && !$access) { $form['page_vis_settings'] = array(); $form['page_vis_settings']['visibility'] = array('#type' => 'value', '#value' => 2); - $form['page_vis_settings']['pages'] = array('#type' => 'value', '#value' => $edit['pages']); + $form['page_vis_settings']['pages'] = array( + '#type' => 'value', + '#value' => isset($block->pages) ? $block->pages : '', + ); } else { $options = array(t('Every page except those specified below.'), t('Only the pages specified below.')); @@ -274,27 +291,25 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) '#type' => 'radios', '#title' => t('Show block on specific pages'), '#options' => $options, - '#default_value' => $edit['visibility'], + '#default_value' => isset($block->visibility) ? $block->visibility : NULL, ); $form['page_vis_settings']['pages'] = array( '#type' => 'textarea', '#title' => t('Pages'), - '#default_value' => $edit['pages'], + '#default_value' => isset($block->pages) ? $block->pages : '', '#description' => $description, ); } // Role-based visibility settings. - $default_role_options = db_query("SELECT rid FROM {block_role} WHERE module = :module AND delta = :delta", array( - ':module' => $module, - ':delta' => $delta, - ))->fetchCol(); + $default_role_options = (isset($block->roles)) ? $block->roles : array(); $role_options = db_query('SELECT rid, name FROM {role} ORDER BY name')->fetchAllKeyed(); $form['role_vis_settings'] = array( '#type' => 'fieldset', '#title' => t('Role specific visibility settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, + '#weight' => field_attach_extra_weight($block->block_machine_name, 'role_vis_settings'), ); $form['role_vis_settings']['roles'] = array( '#type' => 'checkboxes', @@ -305,15 +320,13 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) ); // Content type specific configuration. - $default_type_options = db_query("SELECT type FROM {block_node_type} WHERE module = :module AND delta = :delta", array( - ':module' => $module, - ':delta' => $delta, - ))->fetchCol(); + $default_type_options = (isset($block->node_types)) ? $block->node_types : array(); $form['content_type_vis_settings'] = array( '#type' => 'fieldset', '#title' => t('Content type specific visibility settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, + '#weight' => field_attach_extra_weight($block->block_machine_name, 'content_type_vis_settings'), ); $form['content_type_vis_settings']['types'] = array( '#type' => 'checkboxes', @@ -329,6 +342,7 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) '#title' => t('User specific visibility settings'), '#collapsible' => TRUE, '#collapsed' => TRUE, + '#weight' => field_attach_extra_weight($block->block_machine_name, 'user_vis_settings'), ); $form['user_vis_settings']['custom'] = array( '#type' => 'radios', @@ -339,12 +353,13 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) t('Hide this block by default but let individual users show it.') ), '#description' => t('Allow individual users to customize the visibility of this block in their account settings.'), - '#default_value' => $edit['custom'], + '#default_value' => isset($block->custom) ? $block->custom : NULL, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save block'), + '#weight' => 99, ); return $form; @@ -352,70 +367,67 @@ function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) function block_admin_configure_validate($form, &$form_state) { if ($form_state['values']['module'] == 'block') { - $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array( - ':bid' => $form_state['values']['delta'], + $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE block_id <> :block_id AND info = :info', 0, 1, array( + ':block_id' => $form_state['values']['block_id'], ':info' => $form_state['values']['info'], ))->fetchField(); if (empty($form_state['values']['info']) || $custom_block_exists) { form_set_error('info', t('Please ensure that each block description is unique.')); } } + + $block = (object) $form_state['values']; + $block->block_machine_name = $block->module . ':' . $block->delta; + field_attach_form_validate('block', $block, $form, $form_state); } function block_admin_configure_submit($form, &$form_state) { if (!form_get_errors()) { - db_update('block') - ->fields(array( - 'visibility' => (int) $form_state['values']['visibility'], - 'pages' => trim($form_state['values']['pages']), - 'custom' => (int) $form_state['values']['custom'], - 'title' => $form_state['values']['title'], - )) - ->condition('module', $form_state['values']['module']) - ->condition('delta', $form_state['values']['delta']) - ->execute(); + $block = $form_state['values']; + + $block['block_machine_name'] = $block['module'] . ':' . $block['delta']; + $block = (object) $block; + field_attach_submit('block', $block, $form, $form_state); + field_attach_presave('block', $block); + $block = (array) $block; + + // Make sure status = 1 any theme has the block in a valid region. + foreach ($block['instances'] as $instance) { + if ($instance['region'] != BLOCK_REGION_NONE) { + $block['status'] = 1; + break; + } + } + + block_save($block); db_delete('block_role') - ->condition('module', $form_state['values']['module']) - ->condition('delta', $form_state['values']['delta']) + ->condition('block_id', $block['block_id']) ->execute(); - $query = db_insert('block_role')->fields(array('rid', 'module', 'delta')); + $query = db_insert('block_role')->fields(array('block_id', 'rid')); foreach (array_filter($form_state['values']['roles']) as $rid) { $query->values(array( 'rid' => $rid, - 'module' => $form_state['values']['module'], - 'delta' => $form_state['values']['delta'], + 'block_id' => $block['block_id'], )); } $query->execute(); db_delete('block_node_type') - ->condition('module', $form_state['values']['module']) - ->condition('delta', $form_state['values']['delta']) + ->condition('block_id', $block['block_id']) ->execute(); - $query = db_insert('block_node_type')->fields(array('type', 'module', 'delta')); + $query = db_insert('block_node_type')->fields(array('type', 'block_id')); foreach (array_filter($form_state['values']['types']) as $type) { $query->values(array( 'type' => $type, - 'module' => $form_state['values']['module'], - 'delta' => $form_state['values']['delta'], + 'block_id' => $block['block_id'], )); } $query->execute(); - // 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'])) - ->fields(array( - 'region' => $region, - 'pages' => trim($form_state['values']['pages']), - 'status' => (int) ($region != BLOCK_REGION_NONE), - )) - ->execute(); - } + field_attach_update('block', (object) $block); - module_invoke($form_state['values']['module'], 'block_save', $form_state['values']['delta'], $form_state['values']); + module_invoke($form_state['values']['module'], 'block_save', $form_state['values']['delta'], $block); drupal_set_message(t('The block configuration has been saved.')); cache_clear_all(); $form_state['redirect'] = 'admin/structure/block'; @@ -425,8 +437,25 @@ function block_admin_configure_submit($form, &$form_state) { /** * Menu callback: display the custom block addition form. */ -function block_add_block_form($form, &$form_state) { - return block_admin_configure($form, $form_state, 'block', NULL); +function block_add_block_form(&$form_state) { + $block = array( + 'block_machine_name' => 'block_0', + 'module' => 'block', + 'delta' => NULL, + ); + + $form = block_admin_configure(array(), $form_state, (object) $block); + // When creating a new custom block the Field API bundle for the block and + // block_body field have not been created yet. Collect input for the + // block_body field here and place it in the new Field API field once it has + // been created. + $form['block_body'] = array( + '#type' => 'textarea', + '#title' => t('Block body'), + '#text_format' => filter_default_format(), + '#required' => TRUE, + ); + return $form; } function block_add_block_form_validate($form, &$form_state) { @@ -441,65 +470,81 @@ function block_add_block_form_validate($form, &$form_state) { * Save the new custom block. */ function block_add_block_form_submit($form, &$form_state) { - $delta = db_insert('block_custom') + $block = $form_state['values']; + block_save($block); + + // Update the {block}.delta to match the block_id. + db_update('block') + ->fields(array('delta' => $block['block_id'])) + ->condition('block_id', $block['block_id']) + ->execute(); + + db_insert('block_custom') ->fields(array( - 'body' => $form_state['values']['body'], + 'block_id' => $block['block_id'], 'info' => $form_state['values']['info'], - 'format' => $form_state['values']['body_format'], )) ->execute(); - $query = db_insert('block')->fields(array('visibility', 'pages', 'custom', 'title', 'module', 'theme', 'status', 'weight', 'delta', 'cache')); - foreach (list_themes() as $key => $theme) { - if ($theme->status) { - $query->values(array( - 'visibility' => (int) $form_state['values']['visibility'], - 'pages' => trim($form_state['values']['pages']), - 'custom' => (int) $form_state['values']['custom'], - 'title' => $form_state['values']['title'], - 'module' => $form_state['values']['module'], - 'theme' => $theme->name, - 'status' => 0, - 'weight' => 0, - 'delta' => $delta, - 'cache' => DRUPAL_NO_CACHE, - )); - } - } - $query->execute(); - - $query = db_insert('block_role')->fields(array('rid', 'module', 'delta')); + $query = db_insert('block_role')->fields(array('rid', 'block_id')); foreach (array_filter($form_state['values']['roles']) as $rid) { $query->values(array( 'rid' => $rid, - 'module' => $form_state['values']['module'], - 'delta' => $delta, + 'block_id' => $block['block_id'], )); } $query->execute(); - $query = db_insert('block_node_type')->fields(array('type', 'module', 'delta')); + $query = db_insert('block_node_type')->fields(array('type', 'block_id')); foreach (array_filter($form_state['values']['types']) as $type) { $query->values(array( 'type' => $type, - 'module' => $form_state['values']['module'], - 'delta' => $delta, + 'block_id' => $form_state['block_id'], )); } $query->execute(); - - // 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'])) - ->fields(array( - 'region' => $region, - 'pages' => trim($form_state['values']['pages']), - 'status' => (int) ($region != BLOCK_REGION_NONE), - )) - ->execute(); + + // Create the 'block_body' field if it doesn't already exist. + $field = field_info_field('block_body'); + if (empty($field)) { + $field = array( + 'field_name' => 'block_body', + 'type' => 'text_long', + ); + field_create_field($field); } + // Attach an instance of the 'block_body' field to the new block bundle. + $instance = array( + 'field_name' => 'block_body', + 'bundle' => 'block:' . $block['block_id'], + 'label' => t('Block body'), + 'description' => t('The content of the block as shown to the user.'), + 'required' => TRUE, + 'settings' => array('text_processing' => 1), + 'widget' => array( + 'type' => 'text_textarea', + 'label' => t('Block body'), + ), + 'display' => array( + 'full' => array('label' => 'hidden'), + ), + ); + field_create_instance($instance); + + // Move the content from the temporary block_body field to the new Field API + // block_body field. + $block['block_machine_name'] = 'block:' . $block['block_id']; + $block['block_body'] = array( + FIELD_LANGUAGE_NONE => array( + 0 => array( + 'value' => $form_state['values']['block_body'], + 'format' => $form_state['values']['block_body_format'], + ), + ), + ); + field_attach_insert('block', (object) $block); + drupal_set_message(t('The block has been created.')); cache_clear_all(); $form_state['redirect'] = 'admin/structure/block'; @@ -508,12 +553,16 @@ function block_add_block_form_submit($form, &$form_state) { /** * Menu callback; confirm deletion of custom blocks. */ -function block_custom_block_delete($form, &$form_state, $bid = 0) { - $custom_block = block_custom_block_get($bid); - $form['info'] = array('#type' => 'hidden', '#value' => $custom_block['info'] ? $custom_block['info'] : $custom_block['title']); - $form['bid'] = array('#type' => 'hidden', '#value' => $bid); +function block_custom_block_delete($form, &$form_state, $block) { + if ($block->module != 'block') { + drupal_set_message(t('The block %block can not be deleted. To remove this block disable the %module module', array('%block' => $block->info, '%module' => $block->module)), 'warning'); + drupal_goto('admin/structure/block'); + } + + $form['info'] = array('#type' => 'hidden', '#value' => $block->info ? $block->info : $block->info); + $form['block_id'] = array('#type' => 'hidden', '#value' => $block->block_id); - return confirm_form($form, t('Are you sure you want to delete the block %name?', array('%name' => $custom_block['info'])), 'admin/structure/block', '', t('Delete'), t('Cancel')); + return confirm_form($form, t('Are you sure you want to delete the block %name?', array('%name' => $block->info)), 'admin/structure/block', '', t('Delete'), t('Cancel')); } /** @@ -521,11 +570,10 @@ function block_custom_block_delete($form, &$form_state, $bid = 0) { */ function block_custom_block_delete_submit($form, &$form_state) { db_delete('block_custom') - ->condition('bid', $form_state['values']['bid']) + ->condition('block_id', $form_state['values']['block_id']) ->execute(); db_delete('block') - ->condition('module', 'block') - ->condition('delta', $form_state['values']['bid']) + ->condition('block_id', $form_state['values']['block_id']) ->execute(); drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['info']))); cache_clear_all(); diff --git a/modules/block/block.install b/modules/block/block.install index 5565e05..70d0c2f 100644 --- a/modules/block/block.install +++ b/modules/block/block.install @@ -11,9 +11,9 @@ */ function block_schema() { $schema['block'] = array( - 'description' => 'Stores block settings, such as region and visibility settings.', + 'description' => 'Stores block settings, such as visibility settings.', 'fields' => array( - 'bid' => array( + 'block_id' => array( 'type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique block ID.', @@ -32,13 +32,6 @@ function block_schema() { 'default' => '0', 'description' => 'Unique ID for block within a module.', ), - 'theme' => array( - 'type' => 'varchar', - 'length' => 64, - 'not null' => TRUE, - 'default' => '', - 'description' => 'The theme under which the block settings apply.', - ), 'status' => array( 'type' => 'int', 'not null' => TRUE, @@ -46,20 +39,6 @@ function block_schema() { 'size' => 'tiny', 'description' => 'Block enabled status. (1 = enabled, 0 = disabled)', ), - 'weight' => array( - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'tiny', - 'description' => 'Block weight within region.', - ), - 'region' => array( - 'type' => 'varchar', - 'length' => 64, - 'not null' => TRUE, - 'default' => '', - 'description' => 'Theme region within which the block is set.', - ), 'custom' => array( 'type' => 'int', 'not null' => TRUE, @@ -94,29 +73,66 @@ function block_schema() { 'description' => 'Binary flag to indicate block cache mode. (-1: Do not cache, 1: Cache per role, 2: Cache per user, 4: Cache per page, 8: Block cache global) See BLOCK_CACHE_* constants in block.module for more detailed information.', ), ), - 'primary key' => array('bid'), + 'primary key' => array('block_id'), 'unique keys' => array( - 'tmd' => array('theme', 'module', 'delta'), + 'bmd' => array('block_id', 'module', 'delta'), ), 'indexes' => array( - 'list' => array('theme', 'status', 'region', 'weight', 'module'), + 'list' => array('status', 'module'), ), ); - $schema['block_role'] = array( - 'description' => 'Sets up access permissions for blocks based on user roles', + $schema['block_instance'] = array( + 'description' => 'Stores block intance information for themes and regions.', 'fields' => array( - 'module' => array( + 'block_instance_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary key: Unique block instance ID', + ), + 'block_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Block ID from {blocks}', + ), + 'theme' => array( 'type' => 'varchar', 'length' => 64, 'not null' => TRUE, - 'description' => "The block's origin module, from {block}.module.", + 'default' => '', + 'description' => 'The theme under which the block settings apply.', ), - 'delta' => array( + 'region' => array( 'type' => 'varchar', - 'length' => 32, + 'length' => 64, 'not null' => TRUE, - 'description' => "The block's unique delta within module, from {block}.delta.", + 'default' => '', + 'description' => 'Theme region within which the block is set.', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Block weight within region.', + ), + ), + 'primary key' => array('block_instance_id'), + 'indexes' => array( + 'list' => array('theme', 'region', 'weight'), + ), + ); + + $schema['block_role'] = array( + 'description' => 'Sets up access permissions for blocks based on user roles', + 'fields' => array( + 'block_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Block ID from {blocks}', ), 'rid' => array( 'type' => 'int', @@ -125,7 +141,7 @@ function block_schema() { 'description' => "The user's role ID from {users_roles}.rid.", ), ), - 'primary key' => array('module', 'delta', 'rid'), + 'primary key' => array('block_id', 'rid'), 'indexes' => array( 'rid' => array('rid'), ), @@ -134,17 +150,11 @@ function block_schema() { $schema['block_node_type'] = array( 'description' => 'Sets up display criteria for blocks based on content types', '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, + 'block_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, - 'description' => "The block's unique delta within module, from {block}.delta.", + 'description' => 'Block ID from {blocks}', ), 'type' => array( 'type' => 'varchar', @@ -153,7 +163,7 @@ function block_schema() { 'description' => "The machine-readable name of this type from {node_type}.type.", ), ), - 'primary key' => array('module', 'delta', 'type'), + 'primary key' => array('block_id', 'type'), 'indexes' => array( 'type' => array('type'), ), @@ -162,18 +172,12 @@ function block_schema() { $schema['block_custom'] = array( 'description' => 'Stores contents of custom-made blocks.', 'fields' => array( - 'bid' => array( - 'type' => 'serial', + 'block_id' => array( + 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => "The block's {block}.bid.", ), - 'body' => array( - 'type' => 'text', - 'not null' => FALSE, - 'size' => 'big', - 'description' => 'Block contents.', - ), 'info' => array( 'type' => 'varchar', 'length' => 128, @@ -181,18 +185,10 @@ function block_schema() { 'default' => '', 'description' => 'Block description.', ), - 'format' => array( - 'type' => 'int', - 'size' => 'small', - 'not null' => TRUE, - 'default' => 0, - 'description' => "Block body's {filter_format}.format; for example, 1 = Filtered HTML.", - ) ), 'unique keys' => array( - 'info' => array('info'), + 'bi' => array('block_id', 'info'), ), - 'primary key' => array('bid'), ); $schema['cache_block'] = drupal_get_schema_unprocessed('system', 'cache'); @@ -264,3 +260,216 @@ function block_update_7001() { db_create_table('block_node_type', $schema['block_node_type']); } + +/** + * Refactor {block} table to {block} and {block_instance} and add a unique ID + * for every block. + */ +function block_update_7002() { + // Create the new {block_instance} table. + $schema['block_instance'] = array( + 'description' => 'Stores block intance information for themes and regions.', + 'fields' => array( + 'block_instance_id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary key: Unique block instance ID', + ), + 'block_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Block ID from {blocks}', + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The theme under which the block settings apply.', + ), + 'region' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Theme region within which the block is set.', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Block weight within region.', + ), + ), + 'primary key' => array('block_instance_id'), + 'unique keys' => array( + 'btr' => array('block_id', 'theme', 'region'), + ), + 'indexes' => array( + 'list' => array('theme', 'region', 'weight'), + ), + ); + db_create_table('block_instance', $schema['block_instance']); + + // Rename {block}.bid to {block}.block_id and adjust keys and indexes. + db_drop_unique_key('block', 'tmd'); + db_drop_index('block', 'list'); + db_change_field('block', 'bid', 'block_id', + array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary Key: Unique block ID.' + ), + array( + 'unique keys' => array( + 'bmd' => array('block_id', 'module', 'delta'), + ), + 'indexes' => array( + 'list' => array('status', 'module'), + ), + ) + ); + + $result = db_query('SELECT * FROM {block}'); + $blocks = array(); + // Copy data from {block} table to new {block_instance} table. Re-use + // block_id for blocks with the same module/delta and keep track of used + // block_id so we can remove the old rows. + foreach ($result as $block) { + if (isset($blocks[$block->module . ':' . $block->delta])) { + $block->block_id = $blocks[$block->module . ':' . $block->delta]; + } + db_query("INSERT INTO {block_instance} (block_id, theme, region, weight) VALUES (" . $block->block_id . ", '" . $block->theme . "', '" . $block->region . "', " . $block->weight . ")"); + $blocks[$block->module . ':' . $block->delta] = $block->block_id; + } + + // Replace module/delta columns with block_id column on {block_role} and + // {block_node_type} tables. + db_drop_primary_key('block_role'); + db_add_field('block_role', 'block_id', + array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Block ID from {blocks}', + 'initial' => 0 + ), + array( + 'primary key' => array('block_id', 'rid'), + ) + ); + db_drop_primary_key('block_node_type'); + db_add_field('block_node_type', 'block_id', + array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Block ID from {blocks}', + 'initial' => 0 + ), + array( + 'primary key' => array('block_id', 'type'), + ) + ); + foreach ($blocks as $key => $block_id) { + list($module, $delta) = explode(':', $key); + db_query("UPDATE {block_role} SET block_id = " . $block_id . " WHERE module = '" . $module . "' AND delta = '" . $delta . "'"); + db_query("UPDATE {block_node_type} SET block_id = " . $block_id . " WHERE module = '" . $module . "' AND delta = '" . $delta . "'"); + } + + // Remove unused columns from {block_role} and {block_node_type}. + db_drop_field('block_role', 'module'); + db_drop_field('block_role', 'delta'); + db_drop_field('block_node_type', 'module'); + db_drop_field('block_node_type', 'delta'); + + // Replace {block_custom}.bid with {block_custom}.block_id. + db_add_field('block_custom', 'block_id', + array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => "The block's {block}.bid.", + 'default' => 0, + ) + ); + $result = db_query("SELECT * FROM {block} WHERE module='block'"); + foreach ($result as $block) { + if ($blocks[$block->module . ':' . $block->delta] == $block->block_id) { + db_query("UPDATE {block_custom} SET block_id = " . $block->block_id . " WHERE bid = " . $block->delta); + db_query("UPDATE {block} SET delta = " . $block->block_id . " WHERE block_id = " . $block->block_id); + } + } + db_drop_field('block_custom', 'bid'); + + // Any block that is not in $blocks is now a duplicate and can be removed. + db_delete('block') + ->condition('block_id', $blocks, 'NOT IN') + ->execute(); + + // Remove columns from {block} that are now in {block_instance}. + db_drop_field('block', 'theme'); + db_drop_field('block', 'region'); + db_drop_field('block', 'weight'); +} + +/** + * Convert custom block body to a field. + */ +function block_update_7003() { + // Create the 'block_body' field if it doesn't already exist. + $field = field_info_field('block_body'); + if (empty($field)) { + $field = array( + 'field_name' => 'block_body', + 'type' => 'text_long', + ); + field_create_field($field); + } + + // Attach an instance of the 'block_body' field to all custom blocks. + $custom_blocks = db_query('SELECT * FROM {block_custom}'); + foreach ($custom_blocks as $block) { + // Create a new bundle for the block + field_attach_create_bundle('block:' . $block->block_id); + + $instance = array( + 'field_name' => 'block_body', + 'bundle' => 'block:' . $block->block_id, + 'label' => t('Block body'), + 'description' => t('The content of the block as shown to the user.'), + 'required' => TRUE, + 'settings' => array('text_processing' => 1), + 'widget' => array( + 'type' => 'text_textarea', + 'label' => t('Block body'), + ), + 'display' => array( + 'full' => array('label' => 'hidden'), + ), + ); + field_create_instance($instance); + + // Move the block body to the new 'block_body' field. + $block->block_machine_name = 'block:' . $block->block_id; + $block->block_body = array( + FIELD_LANGUAGE_NONE => array( + 0 => array( + 'value' => $block->body, + 'format' => $block->format, + ), + ), + ); + // This is a core update and no contrib modules are enabled yet, so we can + // assume default field storage for a faster update. + field_sql_storage_field_storage_write('block', $block, FIELD_STORAGE_INSERT, array()); + } + + // Remove the now-obsolete body info from block_custom. + db_drop_field('block_custom', 'body'); + db_drop_field('block_custom', 'format'); +} diff --git a/modules/block/block.module b/modules/block/block.module index 2cf4749..f188886 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -95,18 +95,18 @@ function block_menu() { 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); - $items['admin/structure/block/configure'] = array( + $items['admin/structure/block/configure/%block'] = array( 'title' => 'Configure block', 'page callback' => 'drupal_get_form', - 'page arguments' => array('block_admin_configure'), + 'page arguments' => array('block_admin_configure', 4), 'access arguments' => array('administer blocks'), 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); - $items['admin/structure/block/delete'] = array( + $items['admin/structure/block/delete/%block'] = array( 'title' => 'Delete block', 'page callback' => 'drupal_get_form', - 'page arguments' => array('block_custom_block_delete'), + 'page arguments' => array('block_custom_block_delete', 4), 'access arguments' => array('administer blocks'), 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', @@ -168,16 +168,116 @@ function _block_custom_theme($theme = NULL) { } /** + * Implement hook_entity_info(). + */ +function block_entity_info() { + $return = array( + 'block' => array( + 'label' => t('Block'), + 'controller class' => 'BlockController', + 'base table' => 'block', + 'fieldable' => TRUE, + 'object keys' => array( + 'id' => 'block_id', + 'bundle' => 'block_machine_name', + ), + 'bundle keys' => array( + 'bundle' => 'block_machine_name', + ), + 'bundles' => array(), + ), + ); + + $blocks = db_query('SELECT block_id, module, delta FROM {block}'); + foreach ($blocks as $block) { + $return['block']['bundles'][$block->module . ':' . $block->delta] = array( + 'label' => $block->module . '_' . $block->delta, + 'admin' => array( + 'path' => 'admin/structure/block/configure/%block', + 'real path' => 'admin/structure/block/configure/' . $block->block_id, + 'access arguments' => array('administer blocks'), + 'bundle argument' => 4, + ), + ); + } + + return $return; +} + +/** + * Implement hook_field_build_modes(). + */ +function block_field_build_modules($obj_type) { + $modes = array(); + if ($obj_type == 'block') { + $modes = array( + 'full' => t('Block'), + ); + } + return $modes; +} + +/** + * Implement hook_field_extra_fields(). + */ +function block_field_extra_fields($bundle) { + $args = explode(':', $bundle); + $module = (isset($args[0])) ? $args[0] : NULL; + $delta = (isset($args[1])) ? $args[1] : NULL; + $is_block = block_entity_id($module, $delta); + + if ($is_block) { + $extra = array(); + $extra['block_settings'] = array( + 'label' => t('Block settings'), + 'description' => t('Block specific settings'), + 'weight' => -10, + ); + $extra['module_content'] = array( + 'label' => t('Module content'), + 'description' => t('Any content provided by the implementing module'), + 'weight' => 0, + ); + $extra['instances'] = array( + 'label' => t('Region settings'), + 'description' => t('Specify in which region this block is displayed.'), + 'weight' => 30, + ); + $extra['page_vis_settings'] = array( + 'label' => t('Page visibility settings'), + 'description' => t('Page specific visibility settings'), + 'weight' => 31, + ); + $extra['role_vis_settings'] = array( + 'label' => t('Role visibility settings'), + 'description' => t('Role specific visibility settings'), + 'weight' => 32, + ); + $extra['content_type_vis_settings'] = array( + 'label' => t('Content type visibility settings'), + 'description' => t('Show block for specific content types'), + 'weight' => 33, + ); + $extra['user_vis_settings'] = array( + 'label' => t('User visibility settings'), + 'description' => t('User specific visibility settings'), + 'weight' => 34, + ); + return $extra; + } +} + +/** * Implement hook_block_info(). */ function block_block_info() { $blocks = array(); - $result = db_query('SELECT bid, info FROM {block_custom} ORDER BY info'); + $result = db_query('SELECT block_id, info FROM {block_custom} ORDER BY info'); foreach ($result as $block) { - $blocks[$block->bid]['info'] = $block->info; + $blocks[$block->block_id]['info'] = $block->info; // Not worth caching. - $blocks[$block->bid]['cache'] = DRUPAL_NO_CACHE; + $blocks[$block->block_id]['cache'] = DRUPAL_NO_CACHE; } return $blocks; } @@ -186,7 +286,7 @@ function block_block_info() { * Implement hook_block_configure(). */ function block_block_configure($delta = 0) { - $custom_block = array('format' => filter_default_format()); + $custom_block = array(); if ($delta) { $custom_block = block_custom_block_get($delta); } @@ -194,6 +294,17 @@ function block_block_configure($delta = 0) { } /** + * Implement hook_block_load(). + */ +function block_block_load($blocks) { + foreach ($blocks as $block_id => $block) { + if ($block->module == 'block' && $custom_block = block_custom_block_get($block_id)) { + $blocks[$block_id]->info = $custom_block['info']; + } + } +} + +/** * Implement hook_block_save(). */ function block_block_save($delta = 0, $edit = array()) { @@ -202,13 +313,11 @@ function block_block_save($delta = 0, $edit = array()) { /** * Implement hook_block_view(). - * - * Generates the administrator-defined blocks for display. */ -function block_block_view($delta = 0, $edit = array()) { - $block = db_query('SELECT body, format FROM {block_custom} WHERE bid = :bid', array(':bid' => $delta))->fetchObject(); - $data['content'] = check_markup($block->body, $block->format, '', TRUE); - return $data; +function block_block_view($delta = 0) { + // All custom block content is handled by the field API, so return an empty + // array here. + return array('content' => array()); } /** @@ -253,6 +362,21 @@ function block_page_build(&$page) { } /** + * Implement hook_modules_enabled(). + */ +function block_modules_enabled($modules) { + // When a new module that provides a block is enabled we need to make sure + // that the block is saved to the database and given a block_id. + foreach ($modules as $module) { + if (function_exists($module . '_block_info')) { + _block_rehash(); + drupal_flush_all_caches(); + break; + } + } +} + +/** * Get a renderable array of a region containing all enabled blocks. * * @param $region @@ -310,14 +434,18 @@ function _block_rehash($theme = NULL) { $theme = $theme_key; } + $block_ids = db_select('block', 'b') + ->fields('b', array('block_id')) + ->execute() + ->fetchCol(); + $existing_blocks = block_load_multiple($block_ids); + $old_blocks = array(); - $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $theme)); - foreach ($result as $old_block) { - $old_block = is_object($old_block) ? get_object_vars($old_block) : $old_block; - $old_blocks[$old_block['module']][$old_block['delta']] = $old_block; + foreach ($existing_blocks as $block) { + $old_blocks[$block->module][$block->delta] = $block; } - $blocks = array(); + // Valid region names for the theme. $regions = system_region_list($theme); @@ -328,39 +456,54 @@ function _block_rehash($theme = NULL) { if (empty($old_blocks[$module][$delta])) { // If it's a new block, add identifiers. $block['module'] = $module; - $block['delta'] = $delta; - $block['theme'] = $theme; + $block['delta'] = $delta; + $block['instances'] = array( + $theme => array( + 'theme' => $theme, + 'region' => isset($block['region']) ? $block['region'] : BLOCK_REGION_NONE, + 'weight' => isset($block['weight']) ? $block['weight'] : NULL, + ) + ); if (!isset($block['pages'])) { // {block}.pages is type 'text', so it cannot have a // default value, and not null, so we need to provide // value if the module did not. $block['pages'] = ''; } - // Add defaults and save it into the database. - drupal_write_record('block', $block); - // Set region to none if not enabled. - $block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE; + + block_save($block); + // Add to the list of blocks we return. - $blocks[] = $block; + $blocks[$block['block_id']] = $block; } else { // If it's an existing block, database settings should overwrite - // the code. But aside from 'info' everything that's definable in - // code is stored in the database and we do not store 'info', so we - // do not need to update the database here. + // the code. + // If the block does not have an instance for the selected theme create one now. + if (!isset($old_blocks[$module][$delta]->instances[$theme])) { + $instance = array( + 'block_id' => $old_blocks[$module][$delta]->block_id, + 'theme' => $theme, + 'region' => BLOCK_REGION_NONE, + ); + drupal_write_record('block_instance', $instance); + $old_blocks[$module][$delta]->instances[$theme] = $instance; + } // Add 'info' to this block. - $old_blocks[$module][$delta]['info'] = $block['info']; + $old_blocks[$module][$delta]->info = $block['info']; // If the region name does not exist, disable the block and assign it to none. - if (!empty($old_blocks[$module][$delta]['region']) && !isset($regions[$old_blocks[$module][$delta]['region']])) { - drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $old_blocks[$module][$delta]['info'], '%region' => $old_blocks[$module][$delta]['region'])), 'warning'); - $old_blocks[$module][$delta]['status'] = 0; - $old_blocks[$module][$delta]['region'] = BLOCK_REGION_NONE; + if ($old_blocks[$module][$delta]->instances[$theme]['region'] != BLOCK_REGION_NONE && !isset($regions[$old_blocks[$module][$delta]->instances[$theme]['region']])) { + drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $old_blocks[$module][$delta]->info, '%region' => $old_blocks[$module][$delta]->instances[$theme]['region'])), 'warning'); + $old_blocks[$module][$delta]->status = 0; + $old_blocks[$module][$delta]->instances[$theme]['region'] = BLOCK_REGION_NONE; + $block = (array) $old_blocks[$module][$delta]; + block_save($block); } else { - $old_blocks[$module][$delta]['region'] = $old_blocks[$module][$delta]['status'] ? $old_blocks[$module][$delta]['region'] : BLOCK_REGION_NONE; + $old_blocks[$module][$delta]->instances[$theme]['region'] = $old_blocks[$module][$delta]->instances[$theme]['region'] ? $old_blocks[$module][$delta]->instances[$theme]['region'] : BLOCK_REGION_NONE; } // Add this block to the list of blocks we return. - $blocks[] = $old_blocks[$module][$delta]; + $blocks[$old_blocks[$module][$delta]->block_id] = (array) $old_blocks[$module][$delta]; // Remove this block from the list of blocks to be deleted. unset($old_blocks[$module][$delta]); } @@ -372,9 +515,7 @@ function _block_rehash($theme = NULL) { foreach ($old_blocks as $module => $old_module_blocks) { foreach ($old_module_blocks as $delta => $block) { db_delete('block') - ->condition('module', $module) - ->condition('delta', $delta) - ->condition('theme', $theme) + ->condition('block_id', $block->block_id) ->execute(); } } @@ -394,39 +535,23 @@ function _block_rehash($theme = NULL) { * - body: Block contents. * - format: Filter ID of the filter format for the body. */ -function block_custom_block_get($bid) { - return db_query("SELECT * FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchAssoc(); +function block_custom_block_get($block_id) { + return db_query("SELECT * FROM {block_custom} WHERE block_id = :block_id", array(':block_id' => $block_id))->fetchAssoc(); } /** * Define the custom block form. */ function block_custom_block_form($edit = array()) { - $edit += array( - 'info' => '', - 'body' => '', - ); $form['info'] = array( '#type' => 'textfield', '#title' => t('Block description'), - '#default_value' => $edit['info'], + '#default_value' => isset($edit['info']) ? $edit['info'] : '', '#maxlength' => 64, '#description' => t('A brief description of your block. Used on the blocks administration page.', array('@overview' => url('admin/structure/block'))), '#required' => TRUE, '#weight' => -19, ); - $form['body_field']['#weight'] = -17; - $form['body_field']['body'] = array( - '#type' => 'textarea', - '#title' => t('Block body'), - '#default_value' => $edit['body'], - '#text_format' => isset($edit['format']) ? $edit['format'] : filter_default_format(), - '#rows' => 15, - '#description' => t('The content of the block as shown to the user.'), - '#required' => TRUE, - '#weight' => -17, - '#access' => filter_access(filter_format_load($edit['format'])), - ); return $form; } @@ -447,11 +572,9 @@ function block_custom_block_form($edit = array()) { function block_custom_block_save($edit, $delta) { db_update('block_custom') ->fields(array( - 'body' => $edit['body'], 'info' => $edit['info'], - 'format' => $edit['body_format'], )) - ->condition('bid', $delta) + ->condition('block_id', $edit['block_id']) ->execute(); return TRUE; } @@ -463,16 +586,16 @@ function block_form_user_profile_form_alter(&$form, &$form_state) { if ($form['#user_category'] == 'account') { $account = $form['#user']; $rids = array_keys($account->roles); - $result = db_query("SELECT DISTINCT b.* FROM {block} b LEFT JOIN {block_role} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom <> 0 AND (r.rid IN (:rids) OR r.rid IS NULL) ORDER BY b.weight, b.module", array(':rids' => $rids)); + $block_ids = db_query('SELECT b.block_id FROM {block} b LEFT JOIN {block_role} r ON b.block_id = r.block_id WHERE b.status = 1 AND b.custom <> 0 AND (r.rid IN (:rids) OR r.rid IS NULL) ORDER BY b.module', array(':rids' => $rids))->fetchAssoc(); + $blocks = block_load_multiple($block_ids); - $blocks = array(); - foreach ($result as $block) { + foreach ($blocks as $block) { $data = module_invoke($block->module, 'block_info'); if ($data[$block->delta]['info']) { - $blocks[$block->module][$block->delta] = array( + $form['block'][$block->block_id] = array( '#type' => 'checkbox', '#title' => check_plain($data[$block->delta]['info']), - '#default_value' => isset($account->block[$block->module][$block->delta]) ? $account->block[$block->module][$block->delta] : ($block->custom == 1), + '#default_value' => isset($account->block[$block->block_id]) ? $account->block[$block->block_id] : ($block->custom == 1), ); } } @@ -515,7 +638,7 @@ function block_system_themes_form_submit(&$form, &$form_state) { } if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] !== variable_get('admin_theme', 0)) { // If we're changing themes, make sure the theme has its blocks initialized. - $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $form_state['values']['admin_theme']))->fetchField(); + $has_blocks = (bool) db_query_range('SELECT 1 FROM {block_instance} WHERE theme = :theme', 0, 1, array(':theme' => $form_state['values']['admin_theme']))->fetchField(); if (!$has_blocks) { block_theme_initialize($form_state['values']['admin_theme']); } @@ -536,19 +659,23 @@ function block_system_themes_form_submit(&$form, &$form_state) { */ function block_theme_initialize($theme) { // Initialize theme's blocks if none already registered. - $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $theme))->fetchField(); + $has_blocks = (bool) db_query_range('SELECT 1 FROM {block_instance} WHERE theme = :theme', 0, 1, array(':theme' => $theme))->fetchField(); if (!$has_blocks) { $default_theme = variable_get('theme_default', 'garland'); $regions = system_region_list($theme); - $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC)); - foreach ($result as $block) { + $block_ids = db_select('block', 'b') + ->fields('b', array('block_id')) + ->execute() + ->fetchCol(); + $blocks = block_load_multiple($block_ids); + foreach ($blocks as $block) { // If the region isn't supported by the theme, assign the block to the theme's default region. - if (!array_key_exists($block['region'], $regions)) { - $block['region'] = system_default_region($theme); + if (!array_key_exists($block->instances[$default_theme]['region'], $regions)) { + $block->instances[$theme]['region'] = system_default_region($theme); } - $block['theme'] = $theme; - unset($block['bid']); - drupal_write_record('block', $block); + $block->instances[$theme]['theme'] = $theme; + $block = (array) $block; + block_save($block); } } } @@ -587,30 +714,207 @@ function block_list($region) { } /** + * Given a module and delta return the unique ID for a block. + * + * @param $module + * Name of the module that implements the block. + * @param $delta + * Unique ID of the block within the defining module. + * + * @return + * The numeric entity ID of the block or FALSE if there is no matching block. + * Resuts are statically cached. + */ +function block_entity_id($module, $delta) { + $block_ids = &drupal_static(__FUNCTION__); + + if (empty($block_ids)) { + $block_ids = array(); + $blocks = db_query('SELECT block_id, module, delta FROM {block}'); + foreach ($blocks as $block) { + $block_ids[$block->module . ':' . $block->delta] = $block->block_id; + } + } + + return (isset($block_ids[$module . ':' . $delta])) ? $block_ids[$module . ':' . $delta] : NULL; +} + +/** + * Load a block object from the database. + * + * @param $block_id + * The block_id of the block to load. + * @param $reset + * Whether to reset the block_load_multiple cache. + * @return + * A block object. + */ +function block_load($block_id, $reset = FALSE) { + $block = block_load_multiple(array($block_id), array(), $reset); + return $block ? $block[$block_id] : FALSE; +} + +/** + * Load block entities from the database. + * + * This function should be used whenever you need to load more than one block + * from the database. Blocks are loaded into memory and will not require + * database access if loaded again during the same page request. + * + * @see entity_load() + * + * @param $block_ids + * An array of block IDs. + * @param $conditions + * An array of conditions on the {block} table in the form 'field' => $value. + * @param $reset + * Whether to reset the internal block_load cache. + * + * @return + * An array of block objects indexed by block_id. + */ +function block_load_multiple($block_ids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('block', $block_ids, $conditions, $reset); +} + +/** + * Save changes to a block or add a new block and related instances. + * + * @param $block + * The $block object to be saved. If $block->block_id is omitted a new node + * will be added. + */ +function block_save(&$block) { + // Check for an existing block with the same module and delta. + $block['block_id'] = isset($block['block_id']) ? $block['block_id'] : block_entity_id($block['module'], $block['delta']); + + // Add defaults and save it into the database. + $update = (isset($block['block_id'])) ? 'block_id' : FALSE; + drupal_write_record('block', $block, $update); + + // Add defaults and save each instance into the database. + if (isset($block['instances']) && is_array($block['instances'])) { + foreach ($block['instances'] as $key => $instance) { + $instance['block_id'] = $block['block_id']; + block_instance_save($instance); + $block['instances'][$key] = $instance; + } + } + + // Create a Field API bundle for the block. + if (!$update) { + field_attach_create_bundle($block['module'] . ':' . $block['delta']); + } + + // Clear the page and block caches. + cache_clear_all(); + // Flush the static cache mapping module:delta to a block_id. + drupal_static_reset('block_entity_id'); +} + +/** + * Save changes to an instances or add a new one. + * + * @param $instances + * A block instance array. + */ +function block_instance_save(&$instance) { + $instance['theme'] = (isset($instance['theme'])) ? $instance['theme'] : variable_get('theme_default', 'garland'); + $instance['region'] = (isset($instance['region'])) ? $instance['region'] : BLOCK_REGION_NONE; + $update = (isset($instance['block_instance_id'])) ? 'block_instance_id' : NULL; + drupal_write_record('block_instance', $instance, $update); +} + +/** + * Controller class for blocks. + * + * This extends the DrupalDefaultEntityController class, adding required + * special handling for block objects. + */ +class BlockController extends DrupalDefaultEntityController { + protected function buildQuery() { + parent::buildQuery(); + $this->query->leftJoin('block_role', 'br', 'base.block_id = br.block_id'); + $this->query->addField('br', 'rid'); + $this->query->leftJoin('block_node_type', 'bnt', 'base.block_id = bnt.block_id'); + $this->query->addField('bnt', 'type'); + } + + protected function attachLoad(&$records) { + // Combine multiple rows that represent the same block from the leftJoin + // into a single record. + foreach ($records as $record) { + if (isset($record->rid)) { + $roles[$record->block_id][$record->rid] = $record->rid; + unset($record->rid); + $record->roles = $roles[$record->block_id]; + } + elseif (!isset($record->roles)) { + $record->roles = array(); + } + + if (isset($record->type)) { + $node_types[$record->block_id][$record->type] = $record->type; + unset($record->type); + $record->node_types = $node_types[$record->block_id]; + } + elseif (!isset($record->nodes)) { + $record->node_types = array(); + } + $queried_blocks[$record->block_id] = $record; + } + $records = $queried_blocks; + + // Load instance information for each block and assign a + // block_machine_name. + foreach ($records as $record) { + $instances = db_query('SELECT block_instance_id, theme, region, weight FROM {block_instance} WHERE block_id = :block_id', array(':block_id' => $record->block_id), array('fetch' => PDO::FETCH_ASSOC)); + if ($instances) { + foreach ($instances as $instance) { + $record->instances[$instance['theme']] = $instance; + } + } + $record->block_machine_name = $record->module . ':' . $record->delta; + } + + parent::attachLoad($records); + } +} + +/** * Load blocks information from the database. */ function _block_load_blocks() { global $theme_key; $query = db_select('block', 'b'); + $query->join('block_instance', 'bi', 'bi.block_id = b.block_id'); $result = $query ->fields('b') - ->condition('b.theme', $theme_key) + ->fields('bi') + ->condition('bi.theme', $theme_key) ->condition('b.status', 1) - ->orderBy('b.region') - ->orderBy('b.weight') + ->orderBy('bi.region') + ->orderBy('bi.weight') ->orderBy('b.module') ->addTag('block_load') - ->execute(); + ->execute() + ->fetchCol(); + + $block_info = block_load_multiple($result); - $block_info = $result->fetchAllAssoc('bid'); // Allow modules to modify the block list. drupal_alter('block_info', $block_info); $blocks = array(); foreach ($block_info as $block) { + // Simplify the block structure for theming. + $block->theme = $block->instances[$theme_key]['theme']; + $block->region = $block->instances[$theme_key]['region']; + $block->weight = $block->instances[$theme_key]['weight']; $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block; } + return $blocks; } @@ -623,22 +927,8 @@ function _block_load_blocks() { function block_block_info_alter(&$blocks) { global $user, $theme_key; - // Build an array of roles for each block. - $block_roles = array(); - $result = db_query('SELECT module, delta, rid FROM {block_role}'); - foreach ($result as $record) { - $block_roles[$record->module][$record->delta][] = $record->rid; - } - - // Build an array of node types for each block. - $block_node_types = array(); - $result = db_query('SELECT module, delta, type FROM {block_node_type}'); - foreach ($result as $record) { - $block_node_types[$record->module][$record->delta][] = $record->type; - } - foreach ($blocks as $key => $block) { - if ($block->theme != $theme_key || $block->status != 1) { + if (!isset($block->instances[$theme_key]) || $block->status != 1) { // This block was added by a contrib module, leave it in the list. continue; } @@ -646,7 +936,7 @@ function block_block_info_alter(&$blocks) { // If a block has no roles associated, it is displayed for every role. // For blocks with roles associated, if none of the user's roles matches // the settings from this block, remove it from the block list. - if (isset($block_roles[$block->module][$block->delta]) && !array_intersect($block_roles[$block->module][$block->delta], array_keys($user->roles))) { + if (count($block->roles) && !array_intersect($block->roles, array_keys($user->roles))) { // No match. unset($blocks[$key]); continue; @@ -655,11 +945,11 @@ function block_block_info_alter(&$blocks) { // If a block has no node types associated, it is displayed for every type. // For blocks with node types associated, if the node type does not match // the settings from this block, remove it from the block list. - if (isset($block_node_types[$block->module][$block->delta])) { + if (count($block->node_types)) { $node = menu_get_object(); if (!empty($node)) { // This is a node or node edit page. - if (!in_array($node->type, $block_node_types[$block->module][$block->delta])) { + if (!in_array($node->type, $block->node_types)) { // This block should not be displayed for this node type. unset($blocks[$key]); continue; @@ -667,7 +957,7 @@ function block_block_info_alter(&$blocks) { } elseif (arg(0) == 'node' && arg(1) == 'add' && in_array(arg(2), array_keys(node_type_get_types()))) { // This is a node creation page - if (!in_array(arg(2), $block_node_types[$block->module][$block->delta])) { + if (!in_array(arg(2), $block->node_types)) { // This block should not be displayed for this node type. unset($blocks[$key]); continue; @@ -682,8 +972,8 @@ function block_block_info_alter(&$blocks) { // Use the user's block visibility setting, if necessary. if ($block->custom != 0) { - if ($user->uid && isset($user->block[$block->module][$block->delta])) { - $enabled = $user->block[$block->module][$block->delta]; + if ($user->uid && isset($user->block[$block->block_id])) { + $enabled = $user->block[$block->block_id]; } else { $enabled = ($block->custom == 1); @@ -762,11 +1052,25 @@ function _block_render_blocks($region_blocks) { $block->$k = $v; } } - if (isset($block->content) && $block->content) { - // Normalize to the drupal_render() structure. - if (is_string($block->content)) { - $block->content = array('#markup' => $block->content); - } + + // Normalize to the drupal_render() structure. + if (!empty($block->content) && is_string($block->content)) { + $block->content = array('#markup' => $block->content); + } + else if (!empty($block->content) && is_array($block->content)) { + // If a module provides the conent in a drupal_render() structure move + // the module provided content into a child array so it does not get + // overwritten when we attach the Fields API content. + $block->content = array('module_content' => $block->content); + $block->content['module_content']['#weight'] = field_attach_extra_weight($block->block_machine_name, 'module_content'); + } + else { + $block->content = array(); + } + + $block->content += field_attach_view('block', $block, 'full'); + + if (!empty($block->content)) { // Override default block title if a custom display title is present. if ($block->title) { // Check plain here to allow module generated titles to keep any @@ -872,26 +1176,18 @@ function block_user_role_delete($role) { } /** - * Implement hook_filter_format_delete(). - */ -function block_filter_format_delete($format, $fallback) { - db_update('block_custom') - ->fields(array('format' => $fallback->format)) - ->condition('format', $format->format) - ->execute(); -} - -/** * Implement hook_menu_delete(). */ function block_menu_delete($menu) { + $block_id = block_entity_id('menu', $menu['menu_name']); db_delete('block') - ->condition('module', 'menu') - ->condition('delta', $menu['menu_name']) + ->condition('block_id', $block_id) + ->execute(); + db_delete('block_instance') + ->condition('block_id', $block_id) ->execute(); db_delete('block_role') - ->condition('module', 'menu') - ->condition('delta', $menu['menu_name']) + ->condition('block_id', $block_id) ->execute(); } diff --git a/modules/block/block.test b/modules/block/block.test index 3b5d8bd..728a9ea 100644 --- a/modules/block/block.test +++ b/modules/block/block.test @@ -41,19 +41,20 @@ class BlockTestCase extends DrupalWebTestCase { $custom_block = array(); $custom_block['info'] = $this->randomName(8); $custom_block['title'] = $this->randomName(8); - $custom_block['body'] = $this->randomName(32); + $custom_block['block_body'] = $this->randomName(32); $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); // Confirm that the custom block has been created, and then query the created bid. $this->assertText(t('The block has been created.'), t('Custom block successfully created.')); - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $block_id = db_query("SELECT block_id FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); // Check to see if the custom block was created by checking that it's in the database.. - $this->assertNotNull($bid, t('Custom block found in database')); + $this->assertNotNull($block_id, t('Custom block found in database')); // Check if the block can be moved to all availble regions. + $custom_block['block_id'] = $block_id; $custom_block['module'] = 'block'; - $custom_block['delta'] = $bid; + $custom_block['delta'] = $block_id; foreach ($this->regions as $region) { $this->moveBlockToRegion($custom_block, $region); } @@ -61,7 +62,7 @@ class BlockTestCase extends DrupalWebTestCase { // Delete the created custom block & verify that it's been deleted and no longer appearing on the page. $this->drupalGet('admin/structure/block'); $this->clickLink(t('delete')); - $this->drupalPost('admin/structure/block/delete/' . $bid, array(), t('Delete')); + $this->drupalPost('admin/structure/block/delete/' . $block_id, array(), t('Delete')); $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['info'])), t('Custom block successfully deleted.')); $this->assertNoText(t($custom_block['title']), t('Custom block no longer appears on page.')); } @@ -74,14 +75,14 @@ class BlockTestCase extends DrupalWebTestCase { $custom_block = array(); $custom_block['info'] = $this->randomName(8); $custom_block['title'] = $this->randomName(8); - $custom_block['body'] = '

Full HTML

'; - $custom_block['body_format'] = 2; + $custom_block['block_body'] = '

Full HTML

'; + $custom_block['block_body_format'] = 2; $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); // Set the created custom block to a specific region. - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $block_id = db_query("SELECT block_id FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); $edit = array(); - $edit['block_' . $bid . '[region]'] = $this->regions[1]['name']; + $edit[$block_id . '[region]'] = $this->regions[1]['name']; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Confirm that the custom block is being displayed using configured text format. @@ -92,9 +93,9 @@ class BlockTestCase extends DrupalWebTestCase { // but can still submit the form without errors. $block_admin = $this->drupalCreateUser(array('administer blocks')); $this->drupalLogin($block_admin); - $this->drupalGet('admin/structure/block/configure/block/' . $bid); + $this->drupalGet('admin/structure/block/configure/block/' . $block_id); $this->assertNoText(t('Block body')); - $this->drupalPost('admin/structure/block/configure/block/' . $bid, array(), t('Save block')); + $this->drupalPost('admin/structure/block/configure/block/' . $block_id, array(), t('Save block')); $this->assertNoText(t('Please ensure that each block description is unique.')); // Confirm that the custom block is still being displayed using configured text format. @@ -107,20 +108,20 @@ class BlockTestCase extends DrupalWebTestCase { */ function testBlockVisibility() { $block = array(); - + // Create a random title for the block $title = $this->randomName(8); - + // Create the custom block $custom_block = array(); $custom_block['info'] = $this->randomName(8); $custom_block['title'] = $title; - $custom_block['body'] = $this->randomName(32); + $custom_block['block_body'] = $this->randomName(32); $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + + $block_id = db_query("SELECT block_id FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); $block['module'] = 'block'; - $block['delta'] = $bid; + $block['delta'] = $block_id; $block['title'] = $title; // Set the block to be hidden on any user path, and to be shown only to @@ -128,9 +129,10 @@ class BlockTestCase extends DrupalWebTestCase { $edit = array(); $edit['pages'] = 'user*'; $edit['roles[2]'] = TRUE; - $this->drupalPost('admin/structure/block/configure/' . $block['module'] . '/' . $block['delta'], $edit, t('Save block')); + $this->drupalPost('admin/structure/block/configure/' . $block_id, $edit, t('Save block')); // Move block to the first sidebar. + $block['block_id'] = $block_id; $this->moveBlockToRegion($block, $this->regions[1]); $this->drupalGet(''); @@ -156,24 +158,26 @@ class BlockTestCase extends DrupalWebTestCase { $block['title'] = $this->randomName(8); // Set block title to confirm that interface works and override any custom titles. - $this->drupalPost('admin/structure/block/configure/' . $block['module'] . '/' . $block['delta'], array('title' => $block['title']), t('Save block')); + $block_id = block_entity_id($block['module'], $block['delta']); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $block['title']), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); - $bid = db_query("SELECT bid FROM {block} WHERE module = :module AND delta = :delta", array( + $block_id = db_query("SELECT block_id FROM {block} WHERE module = :module AND delta = :delta", array( ':module' => $block['module'], ':delta' => $block['delta'], ))->fetchField(); // Check to see if the block was created by checking that it's in the database. - $this->assertNotNull($bid, t('Block found in database')); + $this->assertNotNull($block_id, t('Block found in database')); // Check if the block can be moved to all availble regions. + $block['block_id'] = $block_id; foreach ($this->regions as $region) { $this->moveBlockToRegion($block, $region); } // Set the block to the disabled region. $edit = array(); - $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = '-1'; + $edit[$block_id . '[region]'] = '-1'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Confirm that the block was moved to the proper region. @@ -181,16 +185,16 @@ class BlockTestCase extends DrupalWebTestCase { $this->assertNoText(t($block['title']), t('Block no longer appears on page.')); // Confirm that the regions xpath is not availble - $xpath = '//div[@id="block-block-' . $bid . '"]/*'; + $xpath = '//div[@id="block-block-' . $block_id . '"]/*'; $this->assertNoFieldByXPath($xpath, FALSE, t('Custom block found in no regions.')); // For convenience of developers, put the navigation block back. $edit = array(); - $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = $this->regions[1]['name']; + $edit[$block_id . '[region]'] = $this->regions[1]['name']; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to first sidebar region.')); - $this->drupalPost('admin/structure/block/configure/' . $block['module'] . '/' . $block['delta'], array('title' => 'Navigation'), t('Save block')); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => 'Navigation'), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); } @@ -202,7 +206,7 @@ class BlockTestCase extends DrupalWebTestCase { // Set the created block to a specific region. $edit = array(); - $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = $region['name']; + $edit[$block['block_id'] . '[region]'] = $region['name']; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Confirm that the block was moved to the proper region. @@ -259,25 +263,22 @@ class NewDefaultThemeBlocks extends DrupalWebTestCase { $this->drupalLogin($admin_user); // Ensure no other theme's blocks are in the block table yet. - $count = db_query_range("SELECT 1 FROM {block} WHERE theme NOT IN ('garland', 'seven')", 0, 1)->fetchField(); + $count = db_query_range("SELECT 1 FROM {block_instance} WHERE theme NOT IN ('garland', 'seven')", 0, 1)->fetchField(); $this->assertFalse($count, t('Only Garland and Seven have blocks.')); // Populate list of all blocks for matching against new theme. $blocks = array(); - $result = db_query("SELECT * FROM {block} WHERE theme = 'garland'"); + $result = db_query("SELECT b.* FROM {block} b INNER JOIN {block_instance} bi ON bi.block_id = b.block_id WHERE bi.theme = 'garland'"); foreach ($result as $block) { - // $block->theme and $block->bid will not match, so remove them. - unset($block->theme, $block->bid); - $blocks[$block->module][$block->delta] = $block; + $blocks[$block->block_id] = $block; } // Turn on the Stark theme and ensure that it contains all of the blocks // that Garland did. $this->drupalPost('admin/appearance', array('theme_default' => 'stark'), t('Save configuration')); - $result = db_query("SELECT * FROM {block} WHERE theme='stark'"); + $result = db_query("SELECT b.* FROM {block} b INNER JOIN {block_instance} bi ON bi.block_id = b.block_id WHERE bi.theme='stark'"); foreach ($result as $block) { - unset($block->theme, $block->bid); - $this->assertEqual($blocks[$block->module][$block->delta], $block, t('Block %name matched', array('%name' => $block->module . '-' . $block->delta))); + $this->assertEqual($blocks[$block->block_id], $block, t('Block %name matched', array('%name' => $block->module . '-' . $block->delta))); } } } diff --git a/modules/blog/blog.test b/modules/blog/blog.test index 7d2aa41..f2cec08 100644 --- a/modules/blog/blog.test +++ b/modules/blog/blog.test @@ -62,7 +62,8 @@ class BlogTestCase extends DrupalWebTestCase { $this->drupalLogin($this->big_user); // Enable the recent blog block. $edit = array(); - $edit['blog_recent[region]'] = 'sidebar_second'; + $block_id = block_entity_id('blog', 'recent'); + $edit[$block_id . '[region]'] = 'sidebar_second'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertResponse(200); diff --git a/modules/book/book.test b/modules/book/book.test index 861c04c..366b27f 100644 --- a/modules/book/book.test +++ b/modules/book/book.test @@ -215,12 +215,13 @@ class BookBlockTestCase extends DrupalWebTestCase { function testBookNavigationBlock() { // Set block title to confirm that the interface is availble. - $this->drupalPost('admin/structure/block/configure/book/navigation', array('title' => $this->randomName(8)), t('Save block')); + $block_id = block_entity_id('book', 'navigation'); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $this->randomName(8)), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); // Set the block to a region to confirm block is availble. $edit = array(); - $edit['book_navigation[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); } diff --git a/modules/comment/comment.test b/modules/comment/comment.test index aed0043..da89041 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -720,8 +720,9 @@ class CommentBlockFunctionalTest extends CommentHelperCase { $this->drupalLogin($this->admin_user); // Set the block to a region to confirm block is available. + $block_id = block_entity_id('comment', 'recent'); $edit = array( - 'comment_recent[region]' => 'sidebar_first', + $block_id . '[region]' => 'sidebar_first', ); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block saved to first sidebar region.')); @@ -731,7 +732,7 @@ class CommentBlockFunctionalTest extends CommentHelperCase { 'title' => $this->randomName(), 'comment_block_count' => 2, ); - $this->drupalPost('admin/structure/block/configure/comment/recent', $block, t('Save block')); + $this->drupalPost('admin/structure/block/configure/' . $block_id, $block, t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); // Add some test comments, one without a subject. @@ -760,7 +761,7 @@ class CommentBlockFunctionalTest extends CommentHelperCase { $block = array( 'comment_block_count' => 10, ); - $this->drupalPost('admin/structure/block/configure/comment/recent', $block, t('Save block')); + $this->drupalPost('admin/structure/block/configure/' . $block_id, $block, t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); // Post an additional comment. diff --git a/modules/dashboard/dashboard.module b/modules/dashboard/dashboard.module index f4c6495..452fdf0 100644 --- a/modules/dashboard/dashboard.module +++ b/modules/dashboard/dashboard.module @@ -49,8 +49,10 @@ function dashboard_menu() { function dashboard_block_info_alter(&$blocks) { if (!dashboard_is_visible()) { foreach ($blocks as $key => $block) { - if (in_array($block->region, dashboard_regions())) { - unset($blocks[$key]); + foreach($block->instances as $block_instance) { + if (in_array($block_instance['region'], dashboard_regions())) { + unset($blocks[$key]); + } } } } diff --git a/modules/filter/filter.test b/modules/filter/filter.test index d72a144..a49cdfc 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -1130,8 +1130,8 @@ class FilterHooksTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp('block', 'filter_test'); - $admin_user = $this->drupalCreateUser(array('administer filters', 'administer blocks')); + parent::setUp('filter_test'); + $admin_user = $this->drupalCreateUser(array('administer filters', 'administer nodes', 'administer comments')); $this->drupalLogin($admin_user); } @@ -1157,29 +1157,30 @@ class FilterHooksTestCase extends DrupalWebTestCase { $this->assertRaw(t('The text format %format has been updated.', array('%format' => $name)), t('Format successfully updated.')); $this->assertText('hook_filter_format_update invoked.', t('hook_filter_format_update() was invoked.')); - // Add a new custom block. - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $this->randomName(8); - $custom_block['body'] = $this->randomName(32); - // Use the format created. - $custom_block['body_format'] = $format_id; - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - $this->assertText(t('The block has been created.'), t('New block successfully created.')); + // Create a node and post a comment to it using the format created. + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); + $this->assertTrue($node, t('Article node created.')); + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment'] = $this->randomName(32); + $edit['comment_format'] = $format; + $this->drupalPost('node/' . $node->nid, $edit, t('Save')); + $this->assertText($edit['subject'], t('Comment created.')); + + // Verify the new comment is in the database. + $cid = db_query("SELECT cid FROM {comment} WHERE nid = :nid AND subject = :subject", array(':nid' => $node->nid, ':subject' => $edit['subject']))->fetchField(); + $this->assertNotNull($cid, t('New comment found in database')); - // Verify the new block is in the database. - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $this->assertNotNull($bid, t('New block found in database')); // Delete the text format. $this->drupalPost('admin/config/content/formats/' . $format_id . '/delete', array(), t('Delete')); $this->assertRaw(t('Deleted text format %format.', array('%format' => $name)), t('Format successfully deleted.')); $this->assertText('hook_filter_format_delete invoked.', t('hook_filter_format_delete() was invoked.')); - // Verify that the deleted format was replaced with the fallback format. - $current_format = db_select('block_custom', 'b') - ->fields('b', array('format')) - ->condition('bid', $bid) + // Verify that the deleted format was replaced with the default format. + $current_format = db_select('comment', 'c') + ->fields('c', array('format')) + ->condition('cid', $cid) ->execute() ->fetchField(); $this->assertEqual($current_format, filter_fallback_format(), t('Deleted text format replaced with the fallback format.')); diff --git a/modules/forum/forum.test b/modules/forum/forum.test index 1e3f082..3d55b90 100644 --- a/modules/forum/forum.test +++ b/modules/forum/forum.test @@ -81,14 +81,14 @@ class ForumTestCase extends DrupalWebTestCase { // Enable the active forum block. $edit = array(); - $edit['forum_active[region]'] = 'sidebar_second'; + $edit[block_entity_id('forum', 'active') . '[region]'] = 'sidebar_second'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertResponse(200); $this->assertText(t('The block settings have been updated.'), t('Active forum topics forum block was enabled')); // Enable the new forum block. $edit = array(); - $edit['forum_new[region]'] = 'sidebar_second'; + $edit[block_entity_id('forum', 'new') . '[region]'] = 'sidebar_second'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertResponse(200); $this->assertText(t('The block settings have been updated.'), t('[New forum topics] Forum block was enabled')); diff --git a/modules/locale/locale.test b/modules/locale/locale.test index ade91bc..b254328 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -1069,7 +1069,7 @@ class LanguageSwitchingFunctionalTest extends DrupalWebTestCase { function testLanguageBlock() { // Enable the language switching block. $edit = array( - 'locale_language[region]' => 'sidebar_first', + block_entity_id('locale', 'language') . '[region]' => 'sidebar_first', ); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); diff --git a/modules/locale/locale.test.orig b/modules/locale/locale.test.orig new file mode 100644 index 0000000..ade91bc --- /dev/null +++ b/modules/locale/locale.test.orig @@ -0,0 +1,1760 @@ + 'Language configuration', + 'description' => 'Adds a new locale and tests changing its status and the default language.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Functional tests for adding, editing and deleting languages. + */ + function testLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('fr', t('Language added successfully.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + + // Add custom language. + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText($langcode, t('Language code found.')); + $this->assertText($name, t('Name found.')); + $this->assertText($native, t('Native found.')); + $this->assertText($native, t('Test language added.')); + + // Check if we can change the default language. + $path = 'admin/config/regional/language'; + $this->drupalGet($path); + $this->assertFieldChecked('edit-site-default-en', t('English is the default language.')); + // Change the default language. + $edit = array( + 'site_default' => $langcode, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + $this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + + // Ensure we can't delete the default language. + $path = 'admin/config/regional/language/delete/' . $langcode; + $this->drupalGet($path); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('The default language cannot be deleted.'), t('Failed to delete the default language.')); + + // Check if we can disable a language. + $edit = array( + 'enabled[en]' => FALSE, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + $this->assertNoFieldChecked('edit-enabled-en', t('Language disabled.')); + + // Set disabled language to be the default and ensure it is re-enabled. + $edit = array( + 'site_default' => 'en', + ); + $this->drupalPost($path, $edit, t('Save configuration')); + $this->assertFieldChecked('edit-enabled-en', t('Default language re-enabled.')); + + // Ensure 'edit' link works. + $this->clickLink(t('edit')); + $this->assertTitle(t('Edit language | Drupal'), t('Page title is "Edit language".')); + // Edit a language. + $path = 'admin/config/regional/language/edit/' . $langcode; + $name = $this->randomName(16); + $edit = array( + 'name' => $name, + ); + $this->drupalPost($path, $edit, t('Save language')); + $this->assertRaw($name, t('The language has been updated.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + + // Ensure 'delete' link works. + $path = 'admin/config/regional/language'; + $this->drupalGet($path); + $this->clickLink(t('delete')); + $this->assertText(t('Are you sure you want to delete the language'), t('"delete" link is correct.')); + // Delete the language. + $path = 'admin/config/regional/language/delete/' . $langcode; + $this->drupalGet($path); + // First test the 'cancel' link. + $this->clickLink(t('Cancel')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertRaw($name, t('The language was not deleted.')); + // Delete the language for real. This a confirm form, we do not need any + // fields changed. + $this->drupalPost($path, array(), t('Delete')); + // We need raw here because %locale will add HTML. + $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), t('The test language has been removed.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + // Reload to remove $name. + $this->drupalGet($path); + $this->assertNoText($langcode, t('Language code not found.')); + $this->assertNoText($name, t('Name not found.')); + $this->assertNoText($native, t('Native not found.')); + + // Ensure we can't delete the English language. + $path = 'admin/config/regional/language/delete/en'; + $this->drupalGet($path); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('The English language cannot be deleted.'), t('Failed to delete English language.')); + + $this->drupalLogout(); + } + +} + +/** + * Functional test for string translation and validation. + */ +class LocaleTranslationFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'String translate, search and validate', + 'description' => 'Adds a new locale and translates its name. Checks the validation of translation strings and search results.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Adds a language and tests string translation by users with the appropriate permissions. + */ + function testStringTranslation() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + // User to translate and delete string. + $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. This will be translated. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + // This is the language indicator on the translation search screen for + // untranslated strings. Copied straight from locale.inc. + $language_indicator = "$langcode "; + // This will be the translation of $name. + $translation = $this->randomName(16); + + // Add custom language. + $this->drupalLogin($admin_user); + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Add string. + t($name, array(), array('langcode' => $langcode)); + // Reset locale cache. + locale(NULL, NULL, NULL, TRUE); + $this->assertText($langcode, t('Language code found.')); + $this->assertText($name, t('Name found.')); + $this->assertText($native, t('Native found.')); + // No t() here, we do not want to add this string to the database and it's + // surely not translated yet. + $this->assertText($native, t('Test language added.')); + $this->drupalLogout(); + + // Search for the name and translate it. + $this->drupalLogin($translate_user); + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + // assertText() seems to remove the input field where $name always could be + // found, so this is not a false assert. See how assertNoText succeeds + // later. + $this->assertText($name, t('Search found the name.')); + $this->assertRaw($language_indicator, t('Name is untranslated.')); + // Assume this is the only result, given the random name. + $this->clickLink(t('edit')); + // We save the lid from the path. + $matches = array(); + preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches); + $lid = $matches[1]; + // No t() here, it's surely not translated yet. + $this->assertText($name, t('name found on edit screen.')); + $edit = array( + "translations[$langcode]" => $translation, + ); + $this->drupalPost(NULL, $edit, t('Save translations')); + $this->assertText(t('The string has been saved.'), t('The string has been saved.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, t('t() works.')); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + // The indicator should not be here. + $this->assertNoRaw($language_indicator, t('String is translated.')); + + // Try to edit a non-existent string and ensure we're redirected correctly. + // Assuming we don't have 999,999 strings already. + $random_lid = 999999; + $this->drupalGet('admin/config/regional/translate/edit/' . $random_lid); + $this->assertText(t('String not found'), t('String not found.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->drupalLogout(); + + // Delete the language. + $this->drupalLogin($admin_user); + $path = 'admin/config/regional/language/delete/' . $langcode; + // This a confirm form, we do not need any fields changed. + $this->drupalPost($path, array(), t('Delete')); + // We need raw here because %locale will add HTML. + $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), t('The test language has been removed.')); + // Reload to remove $name. + $this->drupalGet($path); + $this->assertNoText($langcode, t('Language code not found.')); + $this->assertNoText($name, t('Name not found.')); + $this->assertNoText($native, t('Native not found.')); + $this->drupalLogout(); + + // Delete the string. + $this->drupalLogin($translate_user); + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + // Assume this is the only result, given the random name. + $this->clickLink(t('delete')); + $this->assertText(t('Are you sure you want to delete the string'), t('"delete" link is correct.')); + // Delete the string. + $path = 'admin/config/regional/translate/delete/' . $lid; + $this->drupalGet($path); + // First test the 'cancel' link. + $this->clickLink(t('Cancel')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertRaw($name, t('The string was not deleted.')); + // Delete the name string. + $this->drupalPost('admin/config/regional/translate/delete/' . $lid, array(), t('Delete')); + $this->assertText(t('The string has been removed.'), t('The string has been removed message.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText($name, t('Search now can not find the name.')); + } + + /** + * Tests the validation of the translation input. + */ + function testStringValidation() { + global $base_url; + + // User to add language and strings. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface')); + $this->drupalLogin($admin_user); + $langcode = 'xx'; + // The English name for the language. This will be translated. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + // This is the language indicator on the translation search screen for + // untranslated strings. Copied straight from locale.inc. + $language_indicator = "$langcode "; + // These will be the invalid translations of $name. + $key = $this->randomName(16); + $bad_translations[$key] = "" . $key; + $key = $this->randomName(16); + $bad_translations[$key] = '' . $key; + $key = $this->randomName(16); + $bad_translations[$key] = '<' . $key; + $key = $this->randomName(16); + $bad_translations[$key] ="" . $key; + + // Add custom language. + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Add string. + t($name, array(), array('langcode' => $langcode)); + // Reset locale cache. + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + // Find the edit path. + $content = $this->drupalGetContent(); + $this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.')); + $path = $matches[0]; + foreach ($bad_translations as $key => $translation) { + $edit = array( + "translations[$langcode]" => $translation, + ); + $this->drupalPost($path, $edit, t('Save translations')); + // Check for a form error on the textarea. + $form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class'); + $this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), t('The string was rejected as unsafe.')); + $this->assertNoText(t('The string has been saved.'), t('The string was not saved.')); + } + } + + /** + * Tests translation search form. + */ + function testStringSearch() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + // User to translate and delete string. + $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); + + // Code for the language. + $langcode = 'xx'; + // The English name for the language. This will be translated. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + // This is the language indicator on the translation search screen for + // untranslated strings. Copied straight from locale.inc. + $language_indicator = "$langcode "; + // This will be the translation of $name. + $translation = $this->randomName(16); + + // Add custom language. + $this->drupalLogin($admin_user); + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Add string. + t($name, array(), array('langcode' => $langcode)); + // Reset locale cache. + locale(NULL, NULL, NULL, TRUE); + $this->drupalLogout(); + + // Search for the name. + $this->drupalLogin($translate_user); + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + // assertText() seems to remove the input field where $name always could be + // found, so this is not a false assert. See how assertNoText succeeds + // later. + $this->assertText($name, t('Search found the string.')); + + // Ensure untranslated string doesn't appear if searching on 'only + // translated strings'. + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'translated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t("Search didn't find the string.")); + + // Ensure untranslated string appears if searching on 'only untranslated + // strings'. + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'untranslated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings found for your search.'), t('Search found the string.')); + + // Add translation. + // Assume this is the only result, given the random name. + $this->clickLink(t('edit')); + // We save the lid from the path. + $matches = array(); + preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches); + $lid = $matches[1]; + $edit = array( + "translations[$langcode]" => $translation, + ); + $this->drupalPost(NULL, $edit, t('Save translations')); + + // Ensure translated string does appear if searching on 'only + // translated strings'. + $search = array( + 'string' => $translation, + 'language' => 'all', + 'translation' => 'translated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings found for your search.'), t('Search found the translation.')); + + // Ensure translated source string doesn't appear if searching on 'only + // untranslated strings'. + $search = array( + 'string' => $name, + 'language' => 'all', + 'translation' => 'untranslated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t("Search didn't find the source string.")); + + // Ensure translated string doesn't appear if searching on 'only + // untranslated strings'. + $search = array( + 'string' => $translation, + 'language' => 'all', + 'translation' => 'untranslated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t("Search didn't find the translation.")); + + // Ensure translated string does appear if searching on the custom language. + $search = array( + 'string' => $translation, + 'language' => $langcode, + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings found for your search.'), t('Search found the translation.')); + + // Ensure translated string doesn't appear if searching on English. + $search = array( + 'string' => $translation, + 'language' => 'en', + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t("Search didn't find the translation.")); + + // Search for a string that isn't in the system. + $unavailable_string = $this->randomName(16); + $search = array( + 'string' => $unavailable_string, + 'language' => 'all', + 'translation' => 'all', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t("Search didn't find the invalid string.")); + } +} + +/** + * Functional tests for the import of translation files. + */ +class LocaleImportFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Translation import', + 'description' => 'Tests the importation of locale files.', + 'group' => 'Locale', + ); + } + + /** + * A user able to create languages and import translations. + */ + protected $admin_user = NULL; + + function setUp() { + parent::setUp('locale', 'locale_test'); + + $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test importation of standalone .po files. + */ + function testStandalonePoFile() { + // Try importing a .po file. + $this->importPoFile($this->getPoFile(), array( + 'langcode' => 'fr', + )); + + // The import should automatically create the corresponding language. + $this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.')); + + // The import should have created 7 strings. + $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 7, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); + + // Ensure we were redirected correctly. + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + + + // Try importing a .po file with invalid tags in the default text group. + $this->importPoFile($this->getBadPoFile(), array( + 'langcode' => 'fr', + )); + + // The import should have created 1 string and rejected 2. + $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); + $skip_message = format_plural(2, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.'); + $this->assertRaw($skip_message, t('Unsafe strings were skipped.')); + + + // Try importing a .po file with invalid tags in a non default text group. + $this->importPoFile($this->getBadPoFile(), array( + 'langcode' => 'fr', + 'group' => 'custom', + )); + + // The import should have created 3 strings. + $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 3, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); + + + // Try importing a .po file which doesn't exist. + $name = $this->randomName(16); + $this->drupalPost('admin/config/regional/translate/import', array( + 'langcode' => 'fr', + 'files[file]' => $name, + 'group' => 'custom', + ), t('Import')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('File to import not found.'), t('File to import not found message.')); + + + // Try importing a .po file with overriding strings, and ensure existing + // strings are kept. + $this->importPoFile($this->getOverwritePoFile(), array( + 'langcode' => 'fr', + 'mode' => 1, // Existing strings are kept, only new strings are added. + )); + + // The import should have created 1 string. + $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); + // Ensure string wasn't overwritten. + $search = array( + 'string' => 'Montag', + 'language' => 'fr', + 'translation' => 'translated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t('String not overwritten by imported string.')); + + + // Try importing a .po file with overriding strings, and ensure existing + // strings are overwritten. + $this->importPoFile($this->getOverwritePoFile(), array( + 'langcode' => 'fr', + 'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added. + )); + + // The import should have updated 2 strings. + $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The translation file was successfully imported.')); + // Ensure string was overwritten. + $search = array( + 'string' => 'Montag', + 'language' => 'fr', + 'translation' => 'translated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings found for your search.'), t('String overwritten by imported string.')); + } + + /** + * Test automatic importation of a module's translation files when a language + * is enabled. + */ + function testAutomaticModuleTranslationImportLanguageEnable() { + // Code for the language - manually set to match the test translation file. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + + // Create a custom language. + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Ensure the translation file was automatically imported when language was + // added. + $this->assertText(t('One translation file imported for the enabled modules.'), t('Language file automatically imported.')); + + // Ensure strings were successfully imported. + $search = array( + 'string' => 'lundi', + 'language' => $langcode, + 'translation' => 'translated', + 'group' => 'all', + ); + $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter')); + $this->assertNoText(t('No strings found for your search.'), t('String successfully imported.')); + } + + /** + * Test automatic importation of a module's translation files when a language + * is enabled. + */ + function testLanguageContext() { + // Try importing a .po file. + $this->importPoFile($this->getPoFileWithContext(), array( + 'langcode' => 'hr', + )); + + $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', t('Long month name context is working.')); + $this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', t('Default context is working.')); + } + + /** + * Helper function: import a standalone .po file in a given language. + * + * @param $contents + * Contents of the .po file to import. + * @param $options + * Additional options to pass to the translation import form. + */ + function importPoFile($contents, array $options = array()) { + $name = tempnam(file_directory_path('temporary'), "po_"); + file_put_contents($name, $contents); + $options['files[file]'] = $name; + $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); + unlink($name); + } + + /** + * Helper function that returns a proper .po file. + */ + function getPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 6\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Monday" +msgstr "lundi" + +msgid "Tuesday" +msgstr "mardi" + +msgid "Wednesday" +msgstr "mercredi" + +msgid "Thursday" +msgstr "jeudi" + +msgid "Friday" +msgstr "vendredi" + +msgid "Saturday" +msgstr "samedi" + +msgid "Sunday" +msgstr "dimanche" +EOF; + } + + /** + * Helper function that returns a bad .po file. + */ + function getBadPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 6\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Save configuration" +msgstr "Enregistrer la configuration" + +msgid "edit" +msgstr "modifier" + +msgid "delete" +msgstr "supprimer" + +EOF; + } + + /** + * Helper function that returns a proper .po file, for testing overwriting + * existing translations. + */ + function getOverwritePoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 6\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Monday" +msgstr "Montag" + +msgid "Day" +msgstr "Jour" +EOF; + } + + /** + * Helper function that returns a .po file with context. + */ + function getPoFileWithContext() { + // Croatian (code hr) is one the the languages that have a different + // form for the full name and the abbreviated name for the month May. + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 6\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" + +msgctxt "Long month name" +msgid "May" +msgstr "Svibanj" + +msgid "May" +msgstr "Svi." +EOF; + } + + +} + +/** + * Functional tests for the export of translation files. + */ +class LocaleExportFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Translation export', + 'description' => 'Tests the exportation of locale files.', + 'group' => 'Locale', + ); + } + + /** + * A user able to create languages and export translations. + */ + protected $admin_user = NULL; + + function setUp() { + parent::setUp('locale', 'locale_test'); + + $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test exportation of translations. + */ + function testExportTranslation() { + // First import some known translations. + // This will also automatically enable the 'fr' language. + $name = tempnam(file_directory_path('temporary'), "po_"); + file_put_contents($name, $this->getPoFile()); + $this->drupalPost('admin/config/regional/translate/import', array( + 'langcode' => 'fr', + 'files[file]' => $name, + ), t('Import')); + unlink($name); + + // Get the French translations. + $this->drupalPost('admin/config/regional/translate/export', array( + 'langcode' => 'fr', + ), t('Export')); + + // Ensure we have a translation file. + $this->assertRaw('# French translation of Drupal', t('Exported French translation file.')); + // Ensure our imported translations exist in the file. + $this->assertRaw('msgstr "lundi"', t('French translations present in exported file.')); + } + + /** + * Test exportation of translation template file. + */ + function testExportTranslationTemplateFile() { + // Get the translation template file. + // There are two 'Export' buttons on this page, but it somehow works. It'd + // be better if we could use the submit button id like documented but that + // doesn't work. + $this->drupalPost('admin/config/regional/translate/export', array(), t('Export')); + // Ensure we have a translation file. + $this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.')); + } + + /** + * Helper function that returns a proper .po file. + */ + function getPoFile() { + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 6\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\\n" + +msgid "Monday" +msgstr "lundi" +EOF; + } + +} + +/** + * Locale uninstall with English UI functional test. + */ +class LocaleUninstallFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Locale uninstall (EN)', + 'description' => 'Tests the uninstall process using the built-in UI language.', + 'group' => 'Locale', + ); + } + + /** + * The default language set for the UI before uninstall. + */ + protected $language_interface; + + function setUp() { + parent::setUp('locale'); + $this->language_interface = 'en'; + } + + /** + * Check if the values of the Locale variables are correct after uninstall. + */ + function testUninstallProcess() { + $locale_module = array('locale'); + + // Add a new language and optionally set it as default. + require_once DRUPAL_ROOT . '/includes/locale.inc'; + locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->language_interface == 'fr'); + + // Check the UI language. + drupal_language_initialize(); + global $language_interface; + $this->assertEqual($language_interface->language, $this->language_interface, t('Current language: %lang', array('%lang' => $language_interface->language))); + + // Enable multilingual workflow option for articles. + variable_set('language_content_type_article', 1); + + // Change JavaScript translations directory. + variable_set('locale_js_directory', 'js_translations'); + + // Build the JavaScript translation file for French. + $user = $this->drupalCreateUser(array('translate interface', 'access administration pages')); + $this->drupalLogin($user); + $this->drupalGet('admin/config/regional/translate/translate'); + $string = db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE :location AND textgroup = :textgroup', array( + ':location' => '%.js%', + ':textgroup' => 'default', + ))->fetchObject(); + $edit = array('translations[fr]' => 'french translation'); + $this->drupalPost('admin/config/regional/translate/edit/' . $string->lid, $edit, t('Save translations')); + _locale_rebuild_js('fr'); + $file = db_query('SELECT javascript FROM {languages} WHERE language = :language', array(':language' => 'fr'))->fetchObject(); + $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/fr_' . $file->javascript . '.js'; + $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('none')))); + + // Disable string caching. + variable_set('locale_cache_strings', 0); + + // Change language negotiation options. + drupal_load('module', 'locale'); + variable_set('language_types', drupal_language_types() + array('language_custom' => TRUE)); + variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, locale_language_negotiation_info()); + + // Change language providers settings. + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX); + variable_set('locale_language_negotiation_session_param', TRUE); + + // Uninstall Locale. + module_disable($locale_module); + drupal_uninstall_modules($locale_module); + + // Visit the front page. + $this->drupalGet(''); + + // Check the init language logic. + drupal_language_initialize(); + $this->assertEqual($language_interface->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language_interface->language))); + + // Check JavaScript files deletion. + $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found')))); + + // Check language count. + $language_count = variable_get('language_count', 1); + $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count))); + + // Check language negotiation. + require_once DRUPAL_ROOT . '/includes/language.inc'; + $this->assertTrue(count(language_types()) == count(drupal_language_types()), t('Language types reset')); + $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + + // Check language providers settings. + $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE), t('URL language provider indicator settings cleared.')); + $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE), t('Visit language provider settings cleared.')); + + // Check JavaScript parsed. + $javascript_parsed_count = count(variable_get('javascript_parsed', array())); + $this->assertEqual($javascript_parsed_count, 0, t('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count))); + + // Check multilingual workflow option for articles. + $multilingual = variable_get('language_content_type_article', 0); + $this->assertEqual($multilingual, 0, t('Multilingual workflow option: %status', array('%status' => t($multilingual ? 'enabled': 'disabled')))); + + // Check JavaScript translations directory. + $locale_js_directory = variable_get('locale_js_directory', 'languages'); + $this->assertEqual($locale_js_directory, 'languages', t('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory))); + + // Check string caching. + $locale_cache_strings = variable_get('locale_cache_strings', 1); + $this->assertEqual($locale_cache_strings, 1, t('String caching: %status', array('%status' => t($locale_cache_strings ? 'enabled': 'disabled')))); + } +} + +/** + * Locale uninstall with French UI functional test. + * + * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new + * test of its own. Rather, it switches the default UI language in setUp and then + * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest) + * to test with this new language. + */ +class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest { + public static function getInfo() { + return array( + 'name' => 'Locale uninstall (FR)', + 'description' => 'Tests the uninstall process using French as interface language.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp(); + $this->language_interface = 'fr'; + } +} + + +/** + * Functional tests for the language switching feature. + */ +class LanguageSwitchingFunctionalTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Language switching', + 'description' => 'Tests for the language switching feature.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($admin_user); + } + + /** + * Functional tests for the language switcher block. + */ + function testLanguageBlock() { + // Enable the language switching block. + $edit = array( + 'locale_language[region]' => 'sidebar_first', + ); + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Add language. + $edit = array( + 'langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set language negotiation. + drupal_load('module', 'locale'); + include_once DRUPAL_ROOT . '/includes/language.inc'; + language_negotiation_set(LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); + + // Assert that the language switching block is displayed on the frontpage. + $this->drupalGet(''); + $this->assertText(t('Languages'), t('Language switcher block found.')); + + // Assert that only the current language is marked as active. + list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]'); + $links = array( + 'active' => array(), + 'inactive' => array(), + ); + $anchors = array( + 'active' => array(), + 'inactive' => array(), + ); + foreach ($language_switcher->div->ul->li as $link) { + $classes = explode(" ", (string) $link['class']); + list($language) = array_intersect($classes, array('en', 'fr')); + if (in_array('active', $classes)) { + $links['active'][] = $language; + } + else { + $links['inactive'][] = $language; + } + $anchor_classes = explode(" ", (string) $link->a['class']); + if (in_array('active', $anchor_classes)) { + $anchors['active'][] = $language; + } + else { + $anchors['inactive'][] = $language; + } + } + $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language list item is marked as active on the language switcher block.')); + $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language anchor is marked as active on the language switcher block.')); + } +} + +/** + * Functional tests for a user's ability to change their default language. + */ +class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'User language settings', + 'description' => "Tests user's ability to change their default language.", + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Test if user can change their default language. + */ + function testUserLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + // User to change their default language. + $web_user = $this->drupalCreateUser(); + + // Add custom language. + $this->drupalLogin($admin_user); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = 'xx'; + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Add custom language and disable it. + // Code for the language. + $langcode_disabled = 'xx-yy'; + // The English name for the language. This will be translated. + $name_disabled = $this->randomName(16); + // The native name for the language. + $native_disabled = $this->randomName(16); + // The domain prefix. + $prefix_disabled = $langcode_disabled; + $edit = array( + 'langcode' => $langcode_disabled, + 'name' => $name_disabled, + 'native' => $native_disabled, + 'prefix' => $prefix_disabled, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Disable the language. + $edit = array( + 'enabled[' . $langcode_disabled . ']' => FALSE, + ); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); + $this->drupalLogout(); + + // Login as normal user and edit account settings. + $this->drupalLogin($web_user); + $path = 'user/' . $web_user->uid . '/edit'; + $this->drupalGet($path); + // Ensure language settings fieldset is available. + $this->assertText(t('Language settings'), t('Language settings available.')); + // Ensure custom language is present. + $this->assertText($name, t('Language present on form.')); + // Ensure disabled language isn't present. + $this->assertNoText($name_disabled, t('Disabled language not present on form.')); + // Switch to our custom language. + $edit = array( + 'language' => $langcode, + ); + $this->drupalPost($path, $edit, t('Save')); + // Ensure form was submitted successfully. + $this->assertText(t('The changes have been saved.'), t('Changes were saved.')); + // Check if language was changed. + $elements = $this->xpath('//input[@id="edit-language-' . $langcode . '"]'); + $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language successfully updated.')); + + $this->drupalLogout(); + } +} + +/** + * Functional tests for configuring a different path alias per language. + */ +class LocalePathFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Path language settings', + 'description' => 'Checks you can configure a language for individual url aliases.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale', 'path'); + } + + /** + * Test if a language can be associated with a path alias. + */ + function testPathLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages')); + + // Add custom language. + $this->drupalLogin($admin_user); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Set language negotiation. + drupal_load('module', 'locale'); + variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); + + // Create a node. + $node = $this->drupalCreateNode(array('type' => 'page')); + + // Create a path alias in default language (English). + $path = 'admin/config/search/path/add'; + $english_path = $this->randomName(8); + $edit = array( + 'source' => 'node/' . $node->nid, + 'alias' => $english_path, + 'language' => 'en', + ); + $this->drupalPost($path, $edit, t('Create new alias')); + + // Create a path alias in new custom language. + $custom_language_path = $this->randomName(8); + $edit = array( + 'source' => 'node/' . $node->nid, + 'alias' => $custom_language_path, + 'language' => $langcode, + ); + $this->drupalPost($path, $edit, t('Create new alias')); + + // Confirm English language path alias works. + $this->drupalGet($english_path); + $this->assertText($node->title[FIELD_LANGUAGE_NONE][0]['value'], t('English alias works.')); + + // Confirm custom language path alias works. + $this->drupalGet($prefix . '/' . $custom_language_path); + $this->assertText($node->title[FIELD_LANGUAGE_NONE][0]['value'], t('Custom language alias works.')); + + $this->drupalLogout(); + } +} +/** + * Functional tests for multilingual support on nodes. + */ +class LocaleContentFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Content language settings', + 'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + /** + * Test if a content type can be set to multilingual and language setting is + * present on node add and edit forms. + */ + function testContentTypeLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); + // User to create a node. + $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content')); + + // Add custom language. + $this->drupalLogin($admin_user); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + // The native name for the language. + $native = $this->randomName(16); + // The domain prefix. + $prefix = $langcode; + $edit = array( + 'langcode' => $langcode, + 'name' => $name, + 'native' => $native, + 'prefix' => $prefix, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + + // Add disabled custom language. + // Code for the language. + $langcode_disabled = 'xx-yy'; + // The English name for the language. + $name_disabled = $this->randomName(16); + // The native name for the language. + $native_disabled = $this->randomName(16); + // The domain prefix. + $prefix_disabled = $langcode_disabled; + $edit = array( + 'langcode' => $langcode_disabled, + 'name' => $name_disabled, + 'native' => $native_disabled, + 'prefix' => $prefix_disabled, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + // Disable second custom language. + $path = 'admin/config/regional/language'; + $edit = array( + 'enabled[' . $langcode_disabled . ']' => FALSE, + ); + $this->drupalPost($path, $edit, t('Save configuration')); + + // Set page content type to use multilingual support. + $this->drupalGet('admin/structure/types/manage/page'); + $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.')); + $edit = array( + 'language_content_type' => 1, + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Page')), t('Page content type has been updated.')); + $this->drupalLogout(); + + // Verify language selection is not present on add article form. + $this->drupalLogin($web_user); + $this->drupalGet('node/add/article'); + // Verify language select list is not present. + $this->assertNoRaw('', t('Language select present on add page form.')); + // Ensure enabled language appears. + $this->assertText($name, t('Enabled language present.')); + // Ensure disabled language doesn't appear. + $this->assertNoText($name_disabled, t('Disabled language not present.')); + + // Create page content. + $node_title = $this->randomName(); + $node_body = $this->randomName(); + $edit = array( + 'type' => 'page', + 'title' => array(FIELD_LANGUAGE_NONE => array(array('value' => $node_title))), + 'body' => array($langcode => array(array('value' => $node_body))), + 'language' => $langcode, + ); + $node = $this->drupalCreateNode($edit); + // Edit the page content and ensure correct language is selected. + $path = 'node/' . $node->nid . '/edit'; + $this->drupalGet($path); + $this->assertRaw('', t('Correct language selected.')); + // Ensure we can change the node language. + $edit = array( + 'language' => 'en', + ); + $this->drupalPost($path, $edit, t('Save')); + $this->assertRaw(t('Page %title has been updated.', array('%title' => $node_title)), t('Page updated.')); + + $this->drupalLogout(); + } +} + + + +/** + * Test UI language negotiation + * 1. URL (PATH) > DEFAULT + * UI Language base on URL prefix, browser language preference has no + * influence: + * admin/config + * UI in site default language + * zh-hans/admin/config + * UI in Chinese + * blah-blah/admin/config + * 404 + * 2. URL (PATH) > BROWSER > DEFAULT + * admin/config + * UI in user's browser language preference if the site has that + * language enabled, if not, the default language + * zh-hans/admin/config + * UI in Chinese + * blah-blah/admin/config + * 404 + * 3. URL (DOMAIN) > DEFAULT + * http://example.com/admin/config + * UI language in site default + * http://example.cn/admin/config + * UI language in Chinese + */ +class UILanguageNegotiationTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'UI language negotiation', + 'description' => 'Test UI language switching by url path prefix and domain.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale', 'locale_test'); + require_once DRUPAL_ROOT . '/includes/language.inc'; + drupal_load('module', 'locale'); + } + + /** + * Tests for language switching by URL path. + */ + function testUILanguageNegotiation() { + $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages')); + $this->drupalLogin($admin_user); + + // A few languages to switch to. + // This one is unknown, should get the default lang version. + $language_unknown = 'blah-blah'; + // For testing browser lang preference. + $language_browser_fallback = 'vi'; + // For testing path prefix. + $language = 'zh-hans'; + // For setting browser language preference to 'vi'. + $http_header_browser_fallback = array("Accept-Language: $language_browser_fallback;q=1"); + // For setting browser language preference to some unknown. + $http_header_blah = array("Accept-Language: blah;q=1"); + + // This domain should switch the UI to Chinese. + $language_domain = 'example.cn'; + + // Setup the site languages by installing two languages. + require_once('includes/locale.inc'); + locale_add_language($language_browser_fallback); + locale_add_language($language); + + // We will look for this string in the admin/config screen to see if the + // corresponding translated string is shown. + $default_string = 'Configure languages for content and the user interface'; + + // Set the default language in order for the translated string to be registered + // into database when seen by t(). Without doing this, our target string + // is for some reason not found when doing translate search. This might + // be some bug. + drupal_static_reset('language_list'); + $languages = language_list('enabled'); + variable_set('language_default', $languages[1]['vi']); + // First visit this page to make sure our target string is searchable. + $this->drupalGet('admin/config'); + // Now the t()'ed string is in db so switch the language back to default. + variable_del('language_default'); + + // Translate the string. + $language_browser_fallback_string = "In $language_browser_fallback In $language_browser_fallback In $language_browser_fallback"; + $language_string = "In $language In $language In $language"; + // Do a translate search of our target string. + $edit = array( 'string' => $default_string); + $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter')); + // Should find the string and now click edit to post translated string. + $this->clickLink('edit'); + $edit = array( + "translations[$language_browser_fallback]" => $language_browser_fallback_string, + "translations[$language]" => $language_string, + ); + $this->drupalPost(NULL, $edit, t('Save translations')); + + // Configure URL language rewrite. + variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE); + + $tests = array( + // Default, browser preference should have no influence. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'path' => 'admin/config', + 'expect' => $default_string, + 'http_header' => $http_header_browser_fallback, + 'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.', + ), + // Language prefix. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'path' => "$language/admin/config", + 'expect' => $language_string, + 'http_header' => $http_header_browser_fallback, + 'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix', + ), + // Default, go by browser preference. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER), + 'path' => 'admin/config', + 'expect' => $language_browser_fallback_string, + 'http_header' => $http_header_browser_fallback, + 'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference', + ), + // Prefix, switch to the language. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER), + 'path' => "$language/admin/config", + 'expect' => $language_string, + 'http_header' => $http_header_browser_fallback, + 'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix', + ), + // Default, browser language preference is not one of site's lang. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT), + 'path' => 'admin/config', + 'expect' => $default_string, + 'http_header' => $http_header_blah, + 'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language', + ), + ); + + foreach ($tests as $test) { + $this->runTest($test); + } + + // Unknown language prefix should return 404. + variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info()); + $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback); + $this->assertResponse(404, "Unknown language path prefix should return 404"); + + // Setup for domain negotiation, first configure the language to have domain + // URL. + $edit = array('prefix' => '', 'domain' => "http://$language_domain"); + $this->drupalPost("admin/config/regional/language/edit/$language", $edit, t('Save language')); + // Set the site to use domain language negotiation. + + $tests = array( + // Default domain, browser preference should have no influence. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN, + 'path' => 'admin/config', + 'expect' => $default_string, + 'http_header' => $http_header_browser_fallback, + 'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language', + ), + // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in + // locale_test.module hook_boot() to simulate this. + array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN, + 'locale_test_domain' => $language_domain, + 'path' => 'admin/config', + 'expect' => $language_string, + 'http_header' => $http_header_browser_fallback, + 'message' => 'URL (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese', + ), + ); + + foreach ($tests as $test) { + $this->runTest($test); + } + } + + private function runTest($test) { + if (!empty($test['language_negotiation'])) { + $negotiation = array_flip($test['language_negotiation']); + language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation); + } + if (!empty($test['locale_language_negotiation_url_part'])) { + variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']); + } + if (!empty($test['locale_test_domain'])) { + variable_set('locale_test_domain', $test['locale_test_domain']); + } + $this->drupalGet($test['path'], array(), $test['http_header']); + $this->assertText($test['expect'], $test['message']); + } +} + +class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Multilingual fields', + 'description' => 'Test multilingual support for fields.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + } + + function testMultilingualNodeForm() { + // Setup users. + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content')); + $this->drupalLogin($admin_user); + + // Add a new language. + require_once DRUPAL_ROOT . '/includes/locale.inc'; + locale_add_language('it', 'Italian', 'Italiano', LANGUAGE_LTR, '', '', TRUE, FALSE); + + // Set page content type to use multilingual support. + $this->drupalGet('admin/structure/types/manage/page'); + $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.')); + $edit = array( + 'language_content_type' => 1, + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Page')), t('Page content type has been updated.')); + + // Create page content. + $langcode = FIELD_LANGUAGE_NONE; + $title_key = "title[$langcode][0][value]"; + $title_value = $this->randomName(8); + $body_key = "body[$langcode][0][value]"; + $body_value = $this->randomName(16); + + // Create node to edit. + $edit = array(); + $edit[$title_key] = $title_value; + $edit[$body_key] = $body_value; + $edit['language'] = 'en'; + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit[$title_key]); + $this->assertTrue($node, t('Node found in database.')); + + $assert = isset($node->body['en']) && !isset($node->body[FIELD_LANGUAGE_NONE]) && $node->body['en'][0]['value'] == $body_value; + $this->assertTrue($assert, t('Field language correctly set.')); + + // Change node language. + $this->drupalGet("node/$node->nid/edit"); + $edit = array( + $title_key => $this->randomName(8), + 'language' => 'it' + ); + $this->drupalPost(NULL, $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit[$title_key]); + $this->assertTrue($node, t('Node found in database.')); + + $assert = isset($node->body['it']) && !isset($node->body['en']) && $node->body['it'][0]['value'] == $body_value; + $this->assertTrue($assert, t('Field language correctly changed.')); + } +} + +/** + * Functional tests for localizing date formats. + */ +class LocalizeDateFormatsFunctionalTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Localize date formats', + 'description' => 'Tests for the localization of date formats.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content')); + $this->drupalLogin($admin_user); + } + + /** + * Functional tests for localizing date formats. + */ + function testLocalizeDateFormats() { + // Add language. + $edit = array( + 'langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set language negotiation. + $edit = array( + 'language[enabled][locale-url]' => TRUE, + 'language_interface[enabled][locale-url]' => TRUE, + ); + $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); + + // Configure date formats. + $this->drupalGet('admin/config/regional/date-time/locale'); + $this->assertText('Français', 'Configured languages appear.'); + $edit = array( + 'date_format_long' => 'd.m.Y - H:i', + 'date_format_medium' => 'd.m.Y - H:i', + 'date_format_short' => 'd.m.Y - H:i', + ); + $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration')); + $this->assertText(t('Configuration saved.'), 'French date formats updated.'); + $edit = array( + 'date_format_long' => 'j M Y - g:ia', + 'date_format_medium' => 'j M Y - g:ia', + 'date_format_short' => 'j M Y - g:ia', + ); + $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration')); + $this->assertText(t('Configuration saved.'), 'English date formats updated.'); + + // Create node content. + $node = $this->drupalCreateNode(array('type' => 'article')); + + // Configure format for the node posted date changes with the language. + $this->drupalGet('node/' . $node->nid); + $english_date = format_date($node->created, 'custom', 'j M Y'); + $this->assertText($english_date, t('English date format appears')); + $this->drupalGet('fr/node/' . $node->nid); + $french_date = format_date($node->created, 'custom', 'd.m.Y'); + $this->assertText($french_date, t('French date format appears')); + } +} + + diff --git a/modules/menu/menu.test b/modules/menu/menu.test index 6bcf56e..182e7aa 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -154,10 +154,18 @@ class MenuTestCase extends DrupalWebTestCase { $this->drupalGet('admin/structure/menu'); $this->assertText($title, 'Menu created'); + // After creating a custom menu there is also a new block provided by menu + // that has not been saved to the {block} table yet. Save the new block and + // flush the block_entity_id cache so that the remainder of the tests can + // use the new block. + _block_rehash(); + drupal_static_reset('block_entity_id'); + // Enable the custom menu block. $menu_name = 'menu-' . $menu_name; // Drupal prepends the name with 'menu-'. $edit = array(); - $edit['menu_' . $menu_name . '[region]'] = 'sidebar_first'; + $block_id = block_entity_id('menu', $menu_name); + $edit[$block_id . '[region]'] = 'sidebar_first'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertResponse(200); $this->assertText(t('The block settings have been updated.'), t('Custom menu block was enabled')); diff --git a/modules/node/node.test b/modules/node/node.test index 26e9e09..3c3cb54 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -490,12 +490,13 @@ class NodeBlockTestCase extends DrupalWebTestCase { function testSearchFormBlock() { // Set block title to confirm that the interface is availble. - $this->drupalPost('admin/structure/block/configure/node/syndicate', array('title' => $this->randomName(8)), t('Save block')); + $block_id = block_entity_id('node', 'syndicate'); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $this->randomName(8)), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); // Set the block to a region to confirm block is availble. $edit = array(); - $edit['node_syndicate[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); } diff --git a/modules/poll/poll.test b/modules/poll/poll.test index afe10db..c68160f 100644 --- a/modules/poll/poll.test +++ b/modules/poll/poll.test @@ -260,12 +260,13 @@ class PollBlockTestCase extends PollTestCase { function testRecentBlock() { // Set block title to confirm that the interface is available. - $this->drupalPost('admin/structure/block/configure/poll/recent', array('title' => $this->randomName(8)), t('Save block')); + $block_id = block_entity_id('poll', 'recent'); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $this->randomName(8)), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); // Set the block to a region to confirm block is available. $edit = array(); - $edit['poll_recent[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); diff --git a/modules/profile/profile.test b/modules/profile/profile.test index 0dae49a..dcb9b8b 100644 --- a/modules/profile/profile.test +++ b/modules/profile/profile.test @@ -314,12 +314,13 @@ class ProfileBlockTestCase extends DrupalWebTestCase { function testAuthorInformationBlock() { // Set block title to confirm that the interface is availble. - $this->drupalPost('admin/structure/block/configure/profile/author-information', array('title' => $this->randomName(8)), t('Save block')); + $block_id = block_entity_id('profile', 'author-information'); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $this->randomName(8)), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); // Set the block to a region to confirm block is availble. $edit = array(); - $edit['profile_author-information[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); } diff --git a/modules/search/search.test b/modules/search/search.test index 2ded22f..b7b7aab 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -404,12 +404,13 @@ class SearchBlockTestCase extends DrupalWebTestCase { function testSearchFormBlock() { // Set block title to confirm that the interface is availble. - $this->drupalPost('admin/structure/block/configure/search/form', array('title' => $this->randomName(8)), t('Save block')); + $block_id = block_entity_id('search', 'form'); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $this->randomName(8)), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); // Set the block to a region to confirm block is availble. $edit = array(); - $edit['search_form[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); } @@ -420,7 +421,8 @@ class SearchBlockTestCase extends DrupalWebTestCase { function testBlock() { // Enable the block, and place it in the 'content' region so that it isn't // hidden on 404 pages. - $edit = array('search_form[region]' => 'content'); + $block_id = block_entity_id('search', 'form'); + $edit = array($block_id . '[region]' => 'content'); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Test a normal search via the block form, from the front page. @@ -434,7 +436,7 @@ class SearchBlockTestCase extends DrupalWebTestCase { // Test a search from the block when it doesn't appear on the search page. $edit = array('pages' => 'search'); - $this->drupalPost('admin/structure/block/configure/search/form', $edit, t('Save block')); + $this->drupalPost('admin/structure/block/configure/' . $block_id, $edit, t('Save block')); $this->drupalPost('node', $terms, t('Search')); $this->assertText('Your search yielded no results'); } diff --git a/modules/system/system.test b/modules/system/system.test index e7c75b8..170c75e 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -562,7 +562,7 @@ class AccessDeniedTestCase extends DrupalWebTestCase { // Log back in, set the custom 403 page to /user and remove the block $this->drupalLogin($this->admin_user); variable_set('site_403', 'user'); - $this->drupalPost('admin/structure/block', array('user_login[region]' => '-1'), t('Save blocks')); + $this->drupalPost('admin/structure/block', array(block_entity_id('user', 'login') . '[region]' => '-1'), t('Save blocks')); // Check that we can log in from the 403 page. $this->drupalLogout(); @@ -972,12 +972,13 @@ class SystemBlockTestCase extends DrupalWebTestCase { */ function testPoweredByBlock() { // Set block title and some settings to confirm that the interface is availble. - $this->drupalPost('admin/structure/block/configure/system/powered-by', array('title' => $this->randomName(8), 'color' => 'powered-black', 'size' => '135x42'), t('Save block')); + $block_id = block_entity_id('system', 'powered-by'); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => $this->randomName(8), 'color' => 'powered-black', 'size' => '135x42'), t('Save block')); $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); // Set the powered-by block to the footer region. $edit = array(); - $edit['system_powered-by[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to footer region.')); @@ -987,7 +988,7 @@ class SystemBlockTestCase extends DrupalWebTestCase { // Set the block to the disabled region. $edit = array(); - $edit['system_powered-by[region]'] = '-1'; + $edit[$block_id . '[region]'] = '-1'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Confirm that the block is hidden. @@ -995,9 +996,9 @@ class SystemBlockTestCase extends DrupalWebTestCase { // For convenience of developers, set the block to it's default settings. $edit = array(); - $edit['system_powered-by[region]'] = 'footer'; + $edit[$block_id . '[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->drupalPost('admin/structure/block/configure/system/powered-by', array('title' => '', 'color' => 'powered-blue', 'size' => '80x15'), t('Save block')); + $this->drupalPost('admin/structure/block/configure/' . $block_id, array('title' => '', 'color' => 'powered-blue', 'size' => '80x15'), t('Save block')); } } diff --git a/profiles/default/default.install b/profiles/default/default.install index 59cc545..2a51b1f 100644 --- a/profiles/default/default.install +++ b/profiles/default/default.install @@ -9,133 +9,142 @@ function default_install() { // Enable some standard blocks. - $values = array( + $blocks = array( array( 'module' => 'system', 'delta' => 'main', - 'theme' => 'garland', 'status' => 1, - 'weight' => 0, - 'region' => 'content', - 'pages' => '', - 'cache' => -1, - ), - array( - 'module' => 'search', - 'delta' => 'form', - 'theme' => 'garland', - 'status' => 1, - 'weight' => -1, - 'region' => 'sidebar_first', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'content', + 'weight' => 0, + ), + array( + 'theme' => 'seven', + 'region' => 'content', + 'weight' => 0, + ), + ), ), array( 'module' => 'user', 'delta' => 'login', - 'theme' => 'garland', 'status' => 1, - 'weight' => 0, - 'region' => 'sidebar_first', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'sidebar_first', + 'weight' => 0, + ), + array( + 'theme' => 'seven', + 'region' => 'content', + 'weight' => 10, + ), + ), ), array( 'module' => 'system', 'delta' => 'navigation', - 'theme' => 'garland', 'status' => 1, - 'weight' => 0, - 'region' => 'sidebar_first', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'sidebar_first', + 'weight' => 0, + ), + ), ), array( 'module' => 'system', 'delta' => 'management', - 'theme' => 'garland', - 'status' => 1, - 'weight' => 1, - 'region' => 'sidebar_first', - 'pages' => '', - 'cache' => -1, - ), - array( - 'module' => 'system', - 'delta' => 'powered-by', - 'theme' => 'garland', 'status' => 1, - 'weight' => 10, - 'region' => 'footer', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'sidebar_first', + 'weight' => 1, + ), + array( + 'theme' => 'seven', + 'region' => 'dashboard_main', + 'weight' => 0, + ), + ), ), array( - 'module' => 'system', - 'delta' => 'help', - 'theme' => 'garland', + 'module' => 'search', + 'delta' => 'form', 'status' => 1, - 'weight' => 0, - 'region' => 'help', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'sidebar_first', + 'weight' => -1, + ) + ), ), array( 'module' => 'system', - 'delta' => 'main', - 'theme' => 'seven', + 'delta' => 'powered-by', 'status' => 1, - 'weight' => 0, - 'region' => 'content', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'footer', + 'weight' => 10, + ), + ), ), array( 'module' => 'system', 'delta' => 'help', - 'theme' => 'seven', 'status' => 1, - 'weight' => 0, - 'region' => 'help', - 'pages' => '', - 'cache' => -1, - ), - array( - 'module' => 'user', - 'delta' => 'login', - 'theme' => 'seven', - 'status' => 1, - 'weight' => 10, - 'region' => 'content', - 'pages' => '', - 'cache' => -1, - ), - array( - 'module' => 'system', - 'delta' => 'management', - 'theme' => 'seven', - 'status' => 1, - 'weight' => 0, - 'region' => 'dashboard_main', 'pages' => '', 'cache' => -1, + 'instances' => array( + array( + 'theme' => 'garland', + 'region' => 'help', + 'weight' => 0, + ), + array( + 'theme' => 'seven', + 'region' => 'help', + 'weight' => 0, + ), + ), ), array( 'module' => 'user', 'delta' => 'new', - 'theme' => 'seven', 'status' => 1, - 'weight' => 0, - 'region' => 'dashboard_sidebar', 'pages' => '', 'cache' => -1, + 'instances' => array( + 'theme' => 'seven', + 'region' => 'dashboard_sidebar', + 'weight' => 0, + ), ), ); - $query = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache')); - foreach ($values as $record) { - $query->values($record); + + foreach ($blocks as $block) { + block_save($block); } - $query->execute(); // Insert default user-defined node types into the database. For a complete // list of available node type attributes, refer to the node type API diff --git a/sites/default/settings.php b/sites/default/settings.php new file mode 100644 index 0000000..68c8d63 --- /dev/null +++ b/sites/default/settings.php @@ -0,0 +1,358 @@ + 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * 'port' => 3306, + * ); + * + * The "driver" property indicates what Drupal database driver the + * connection should use. This is usually the same as the name of the + * database type, such as mysql or sqlite, but not always. The other + * properties will vary depending on the driver. For SQLite, you must + * specify a database. For most other drivers, you must specify a username, + * password, host, and database name. + * + * Some database engines support transactions. In order to enable + * transaction support for a given database, set the 'transactions' key + * to TRUE. To disable it, set it to FALSE. Note that the default value + * varies by driver. For MySQL, the default is FALSE since MyISAM tables + * do not support transactions. + * + * For each database, you may optionally specify multiple "target" databases. + * A target database allows Drupal to try to send certain queries to a + * different database if it can but fall back to the default connection if not. + * That is useful for master/slave replication, as Drupal may try to connect + * to a slave server when appropriate and if one is not available will simply + * fall back to the single master server. + * + * The general format for the $databases array is as follows: + * + * $databases['default']['default'] = $info_array; + * $databases['default']['slave'][] = $info_array; + * $databases['default']['slave'][] = $info_array; + * $databases['extra']['default'] = $info_array; + * + * In the above example, $info_array is an array of settings described above. + * The first line sets a "default" database that has one master database + * (the second level default). The second and third lines create an array + * of potential slave databases. Drupal will select one at random for a given + * request as needed. The fourth line creates a new database with a name of + * "extra". + * + * For a single database configuration, the following is sufficient: + * + * $databases['default']['default'] = array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * + * You can optionally set prefixes for some or all database table names + * by using the $db_prefix setting. If a prefix is specified, the table + * name will be prepended with its value. Be sure to use valid database + * characters only, usually alphanumeric and underscore. If no prefixes + * are desired, leave it as an empty string ''. + * + * To have all database names prefixed, set $db_prefix as a string: + * + * $db_prefix = 'main_'; + * + * To provide prefixes for specific tables, set $db_prefix as an array. + * The array's keys are the table names and the values are the prefixes. + * The 'default' element holds the prefix for any tables not specified + * elsewhere in the array. Example: + * + * $db_prefix = array( + * 'default' => 'main_', + * 'users' => 'shared_', + * 'sessions' => 'shared_', + * 'role' => 'shared_', + * 'authmap' => 'shared_', + * ); + * + * Database configuration format: + * $databases['default']['default'] = array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * $databases['default']['default'] = array( + * 'driver' => 'pgsql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * $databases['default']['default'] = array( + * 'driver' => 'sqlite', + * 'database' => 'databasefilename', + * ); + */ +$databases = array ( + 'default' => + array ( + 'default' => + array ( + 'driver' => 'mysql', + 'database' => 'd7_dev2', + 'username' => 'root', + 'password' => 'root', + 'host' => 'localhost', + 'port' => '', + ), + ), +); +$db_prefix = ''; + +/** + * Access control for update.php script + * + * If you are updating your Drupal installation using the update.php script but + * are not logged in using either an account with the "Administer software + * updates" permission or the site maintenance account (the account that was + * created during installation), you will need to modify the access check + * statement below. Change the FALSE to a TRUE to disable the access check. + * After finishing the upgrade, be sure to open this file again and change the + * TRUE back to a FALSE! + */ +$update_free_access = FALSE; + +/** + * Base URL (optional). + * + * If you are experiencing issues with different site domains, + * uncomment the Base URL statement below (remove the leading hash sign) + * and fill in the absolute URL to your Drupal installation. + * + * You might also want to force users to use a given domain. + * See the .htaccess file for more information. + * + * Examples: + * $base_url = 'http://www.example.com'; + * $base_url = 'http://www.example.com:8888'; + * $base_url = 'http://www.example.com/drupal'; + * $base_url = 'https://www.example.com:8888/drupal'; + * + * It is not allowed to have a trailing slash; Drupal will add it + * for you. + */ +# $base_url = 'http://www.example.com'; // NO trailing slash! + +/** + * PHP settings: + * + * To see what PHP settings are possible, including whether they can be set at + * runtime (by using ini_set()), read the PHP documentation: + * http://www.php.net/manual/en/ini.php#ini.list + * See drupal_initialize_variables() in includes/bootstrap.inc for required + * runtime settings and the .htaccess file for non-runtime settings. Settings + * defined there should not be duplicated here so as to avoid conflict issues. + */ + +/** + * Some distributions of Linux (most notably Debian) ship their PHP + * installations with garbage collection (gc) disabled. Since Drupal depends on + * PHP's garbage collection for clearing sessions, ensure that garbage + * collection occurs by using the most common settings. + */ +ini_set('session.gc_probability', 1); +ini_set('session.gc_divisor', 100); + +/** + * Set session lifetime (in seconds), i.e. the time from the user's last visit + * to the active session may be deleted by the session garbage collector. When + * a session is deleted, authenticated users are logged out, and the contents + * of the user's $_SESSION variable is discarded. + */ +ini_set('session.gc_maxlifetime', 200000); + +/** + * Set session cookie lifetime (in seconds), i.e. the time from the session is + * created to the cookie expires, i.e. when the browser is expected to discard + * the cookie. The value 0 means "until the browser is closed". + */ +ini_set('session.cookie_lifetime', 2000000); + +/** + * Drupal automatically generates a unique session cookie name for each site + * based on on its full domain name. If you have multiple domains pointing at + * the same Drupal site, you can either redirect them all to a single domain + * (see comment in .htaccess), or uncomment the line below and specify their + * shared base domain. Doing so assures that users remain logged in as they + * cross between your various domains. + */ +# $cookie_domain = 'example.com'; + +/** + * Variable overrides: + * + * To override specific entries in the 'variable' table for this site, + * set them here. You usually don't need to use this feature. This is + * useful in a configuration file for a vhost or directory, rather than + * the default settings.php. Any configuration setting from the 'variable' + * table can be given a new value. Note that any values you provide in + * these variable overrides will not be modifiable from the Drupal + * administration interface. + * + * Remove the leading hash signs to enable. + */ +$conf = array( +# 'site_name' => 'My Drupal site', +# 'theme_default' => 'minnelli', +# 'anonymous' => 'Visitor', +/** + * A custom theme can be set for the offline page. This applies when the site + * is explicitly set to maintenance mode through the administration page or when + * the database is inactive due to an error. It can be set through the + * 'maintenance_theme' key. The template file should also be copied into the + * theme. It is located inside 'modules/system/maintenance-page.tpl.php'. + * Note: This setting does not apply to installation and update pages. + */ +# 'maintenance_theme' => 'minnelli', // Leave the comma here. +/** + * reverse_proxy accepts a boolean value. + * + * Enable this setting to determine the correct IP address of the remote + * client by examining information stored in the X-Forwarded-For headers. + * X-Forwarded-For headers are a standard mechanism for identifying client + * systems connecting through a reverse proxy server, such as Squid or + * Pound. Reverse proxy servers are often used to enhance the performance + * of heavily visited sites and may also provide other site caching, + * security or encryption benefits. If this Drupal installation operates + * behind a reverse proxy, this setting should be enabled so that correct + * IP address information is captured in Drupal's session management, + * logging, statistics and access management systems; if you are unsure + * about this setting, do not have a reverse proxy, or Drupal operates in + * a shared hosting environment, this setting should remain commented out. + */ +# 'reverse_proxy' => TRUE, // Leave the comma here. +/** + * reverse_proxy accepts an array of IP addresses. + * + * Each element of this array is the IP address of any of your reverse + * proxies. Filling this array Drupal will trust the information stored + * in the X-Forwarded-For headers only if Remote IP address is one of + * these, that is the request reaches the web server from one of your + * reverse proxies. Otherwise, the client could directly connect to + * your web server spoofing the X-Forwarded-For headers. + */ +# 'reverse_proxy_addresses' => array('a.b.c.d', ...), // Leave the comma here. +); + +/** + * Page caching: + * + * By default, Drupal sends a "Vary: Cookie" HTTP header for anonymous page + * views. This tells a HTTP proxy that it may return a page from its local + * cache without contacting the web server, if the user sends the same Cookie + * header as the user who originally requested the cached page. Without "Vary: + * Cookie", authenticated users would also be served the anonymous page from + * the cache. If the site has mostly anonymous users except a few known + * editors/administrators, the Vary header can be omitted. This allows for + * better caching in HTTP proxies (including reverse proxies), i.e. even if + * clients send different cookies, they still get content served from the cache + * if aggressive caching is enabled and the minimum cache time is non-zero. + * However, authenticated users should access the site directly (i.e. not use an + * HTTP proxy, and bypass the reverse proxy if one is used) in order to avoid + * getting cached pages from the proxy. + */ +# $conf['omit_vary_cookie'] = TRUE; + +/** + * String overrides: + * + * To override specific strings on your site with or without enabling locale + * module, add an entry to this list. This functionality allows you to change + * a small number of your site's default English language interface strings. + * + * Remove the leading hash signs to enable. + */ +# $conf['locale_custom_strings_en'][''] = array( +# 'forum' => 'Discussion board', +# '@count min' => '@count minutes', +# ); + +/** + * + * IP blocking: + * + * To bypass database queries for denied IP addresses, use this setting. + * Drupal queries the {blocked_ips} table by default on every page request + * for both authenticated and anonymous users. This allows the system to + * block IP addresses from within the administrative interface and before any + * modules are loaded. However on high traffic websites you may want to avoid + * this query, allowing you to bypass database access altogether for anonymous + * users under certain caching configurations. + * + * If using this setting, you will need to add back any IP addresses which + * you may have blocked via the administrative interface. Each element of this + * array represents a blocked IP address. Uncommenting the array and leaving it + * empty will have the effect of disabling IP blocking on your site. + * + * Remove the leading hash signs to enable. + */ +# $conf['blocked_ips'] = array( +# 'a.b.c.d', +# );