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('