Commit 0c49d579 authored by webchick's avatar webchick
Browse files

#358437 follow-up by David_Rothstein, sun, chx: Disallow invalid text format...

#358437 follow-up by David_Rothstein, sun, chx: Disallow invalid text format IDs; force 0 and non-existant formats to NULL.
parent 782eb4c7
......@@ -156,9 +156,8 @@ function block_schema() {
),
'format' => array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The {filter_format}.format of the block body.',
),
),
......@@ -188,6 +187,19 @@ function block_install() {
->execute();
}
/**
* Implements hook_update_dependencies().
*/
function block_update_dependencies() {
// Block update 7005 needs to query the list of existing text formats and
// therefore must run after filter_update_7000().
$dependencies['block'][7005] = array(
'filter' => 7000,
);
return $dependencies;
}
/**
* @defgroup updates-6.x-to-7.x Block updates from 6.x to 7.x
* @{
......@@ -397,13 +409,25 @@ function block_update_7004() {
* Update the {block_custom}.format column.
*/
function block_update_7005() {
// It was previously possible for a value of "0" to be stored in database
// tables to indicate that a particular piece of text should be filtered
// using the default text format.
// For an explanation of these updates, see the code comments in
// user_update_7010().
db_change_field('block_custom', 'format', 'format', array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The {filter_format}.format of the block body.',
));
db_update('block_custom')
->fields(array('format' => NULL))
->condition('body', '')
->condition('format', 0)
->execute();
$existing_formats = db_query("SELECT format FROM {filter_format}")->fetchCol();
$default_format = variable_get('filter_default_format', 1);
db_update('block_custom')
->fields(array('format' => $default_format))
->condition('format', 0)
->isNotNull('format')
->condition('format', $existing_formats, 'NOT IN')
->execute();
}
......
......@@ -219,10 +219,12 @@ function block_block_info() {
* Implements hook_block_configure().
*/
function block_block_configure($delta = 0) {
$custom_block = array('format' => filter_default_format());
if ($delta) {
$custom_block = block_custom_block_get($delta);
}
else {
$custom_block = array();
}
return block_custom_block_form($custom_block);
}
......
......@@ -87,6 +87,12 @@ function comment_update_dependencies() {
'system' => 7021,
);
// Comment update 7006 needs to query the list of existing text formats and
// therefore must run after filter_update_7000().
$dependencies['comment'][7006] = array(
'filter' => 7000,
);
return $dependencies;
}
......@@ -300,24 +306,31 @@ function comment_update_7006(&$sandbox) {
$query->addField('c', 'comment', 'comment_body_value');
$query->addField('c', 'format', 'comment_body_format');
$comment_body_table = 'field_data_comment_body';
db_insert($comment_body_table)
db_insert('field_data_comment_body')
->from($query)
->execute();
// Update the comment body format in a similar manner as is done for other
// modules in filter_update_7005(), but we do this one here since we are
// already migrating the data.
db_update($comment_body_table)
->fields(array('comment_body_format' => variable_get('filter_default_format', 1)))
->condition('comment_body_format', 0)
->execute();
$sandbox['#finished'] = 1 - count($sandbox['types']) / $sandbox['total'];
}
// On the last pass of the update, $sandbox['types'] will be empty.
if (empty($sandbox['types'])) {
// Update the comment body text formats. For an explanation of these
// updates, see the code comments in user_update_7010().
db_update('field_data_comment_body')
->fields(array('comment_body_format' => NULL))
->condition('comment_body_value', '')
->condition('comment_body_format', 0)
->execute();
$existing_formats = db_query("SELECT format FROM {filter_format}")->fetchCol();
$default_format = variable_get('filter_default_format', 1);
db_update('field_data_comment_body')
->fields(array('comment_body_format' => $default_format))
->isNotNull('comment_body_format')
->condition('comment_body_format', $existing_formats, 'NOT IN')
->execute();
// Finally, remove the old comment data.
db_drop_field('comment', 'comment');
db_drop_field('comment', 'format');
}
......
......@@ -435,7 +435,7 @@ function filter_update_7005() {
->execute();
// We do not delete the 'filter_default_format' variable, since other modules
// may need it in their update functions.
// need it in their update functions; for an example, see user_update_7010().
// @todo This variable can be deleted in Drupal 8.
}
......
......@@ -751,8 +751,8 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE)
* The form element to process. Properties used:
* - #base_type: The form element #type to use for the 'value' element.
* 'textarea' by default.
* - #format: (optional) The text format id to preselect. If 0, NULL, or not
* set, the default format for the current user will be used.
* - #format: (optional) The text format id to preselect. If NULL or not set,
* the default format for the current user will be used.
*
* @return
* The expanded element.
......@@ -820,9 +820,10 @@ function filter_process_format($element) {
}
// Use the default format for this user if none was selected.
if (empty($element['#format'])) {
if (!isset($element['#format'])) {
$element['#format'] = filter_default_format($user);
}
$element['format']['format'] = array(
'#type' => 'select',
'#title' => t('Text format'),
......@@ -841,20 +842,30 @@ function filter_process_format($element) {
'#weight' => 0,
);
// Lastly, disallow editing of this field if the user is not allowed to use
// the stored and preselected text format. But only, if that format actually
// exists.
$all_formats = filter_formats();
if (!isset($formats[$element['#format']]) && isset($all_formats[$element['#format']])) {
$format_exists = isset($all_formats[$element['#format']]);
$user_has_access = isset($formats[$element['#format']]);
$user_is_admin = user_access('administer filters');
// If the stored format does not exist, administrators have to assign a new
// format.
if (!$format_exists && $user_is_admin) {
$element['format']['format']['#default_value'] = NULL;
// Force access to the format selector (it may have been denied above if
// the user only has access to a single format).
$element['format']['format']['#access'] = TRUE;
}
// Disable this widget, if the user is not allowed to use the stored format,
// or if the stored format does not exist. The 'administer filters' permission
// only grants access to the filter administration, not to all formats.
elseif (!$user_has_access || !$format_exists) {
// Overload default values into #value to make them unalterable.
$element['value']['#value'] = $element['value']['#default_value'];
$element['format']['format']['#value'] = $element['format']['format']['#default_value'];
// Prepend #pre_render callback to replace field value with user notice
// prior to rendering.
if (!isset($element['value']['#pre_render'])) {
$element['value']['#pre_render'] = array();
}
$element['value'] += array('#pre_render' => array());
array_unshift($element['value']['#pre_render'], 'filter_form_access_denied');
// Cosmetic adjustments.
......
......@@ -396,6 +396,7 @@ class FilterAdminTestCase extends DrupalWebTestCase {
class FilterFormatAccessTestCase extends DrupalWebTestCase {
protected $admin_user;
protected $filter_admin_user;
protected $web_user;
protected $allowed_format;
protected $disallowed_format;
......@@ -411,16 +412,16 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase {
function setUp() {
parent::setUp();
$this->full_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject();
// Create two text formats and grant a regular user access to one of them.
$this->admin_user = $this->drupalCreateUser(array(
// Create a user who can administer text formats, but does not have
// specific permission to use any of them.
$this->filter_admin_user = $this->drupalCreateUser(array(
'administer filters',
'create page content',
'edit any page content',
filter_permission_name($this->full_html_format),
));
$this->drupalLogin($this->admin_user);
// Create two text formats.
$this->drupalLogin($this->filter_admin_user);
$formats = array();
for ($i = 0; $i < 2; $i++) {
$edit = array('name' => $this->randomName());
......@@ -430,11 +431,23 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase {
$formats[] = filter_format_load($format_id);
}
list($this->allowed_format, $this->disallowed_format) = $formats;
$this->drupalLogout();
// Create a regular user with access to one of the formats.
$this->web_user = $this->drupalCreateUser(array(
'create page content',
'edit any page content',
filter_permission_name($this->allowed_format),
));
// Create an administrative user who has access to use both formats.
$this->admin_user = $this->drupalCreateUser(array(
'administer filters',
'create page content',
'edit any page content',
filter_permission_name($this->allowed_format),
filter_permission_name($this->disallowed_format),
));
}
function testFormatPermissions() {
......@@ -487,8 +500,11 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase {
/**
* Test editing a page using a disallowed text format.
*
* Verifies that a regular user is able to edit a page, but is not allowed to
* change the fields which use an inaccessible text format.
* Verifies that regular users and administrators are able to edit a page,
* but not allowed to change the fields which use an inaccessible text
* format. Also verifies that fields which use a text format that does not
* exist can be edited by administrators only, but that the administrator is
* forced to choose a new format before saving the page.
*/
function testFormatWidgetPermissions() {
$langcode = LANGUAGE_NONE;
......@@ -501,16 +517,12 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase {
$edit = array();
$edit['title'] = $this->randomName(8);
$edit[$body_value_key] = $this->randomName(16);
$edit[$body_format_key] = $this->full_html_format->format;
$edit[$body_format_key] = $this->disallowed_format->format;
$this->drupalPost('node/add/page', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit['title']);
// Try to edit with a less privileged user.
$this->moderator = $this->drupalCreateUser(array(
'edit any page content',
'create page content',
));
$this->drupalLogin($this->moderator);
$this->drupalLogin($this->web_user);
$this->drupalGet('node/' . $node->nid);
$this->clickLink(t('Edit'));
......@@ -529,14 +541,82 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase {
$this->assertText($new_edit['title'], t('New title found.'));
$this->assertText($edit[$body_value_key], t('Old body found.'));
// Disable the Full HTML text format.
filter_format_disable($this->full_html_format);
// Check that even an administrator with "administer filters" permission
// cannot edit the body field if they do not have specific permission to
// use its stored format. (This must be disallowed so that the
// administrator is never forced to switch the text format to something
// else.)
$this->drupalLogin($this->filter_admin_user);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
// Disable the text format used above.
filter_format_disable($this->disallowed_format);
$this->resetFilterCaches();
// Verify that body field can be edited and a new format can be selected.
// Log back in as the less privileged user and verify that the body field
// is still disabled, since the less privileged user should not be able to
// edit content that does not have an assigned format.
$this->drupalLogin($this->web_user);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.'));
// Log back in as the filter administrator and verify that the body field
// can be edited.
$this->drupalLogin($this->filter_admin_user);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, t('Text format access denied message not found.'));
$this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, t('Text format selector found.'));
// Verify that trying to save the node without selecting a new text format
// produces an error message, and does not result in the node being saved.
$old_title = $new_edit['title'];
$new_title = $this->randomName(8);
$edit = array('title' => $new_title);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
$this->drupalGet('node/' . $node->nid);
$this->assertText($old_title, t('Old title found.'));
$this->assertNoText($new_title, t('New title not found.'));
// Now select a new text format and make sure the node can be saved.
$edit[$body_format_key] = filter_fallback_format();
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
$this->assertText($new_title, t('New title found.'));
$this->assertNoText($old_title, t('Old title not found.'));
// Switch the text format to a new one, then disable that format and all
// other formats on the site (leaving only the fallback format).
$this->drupalLogin($this->admin_user);
$edit = array($body_format_key => $this->allowed_format->format);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
foreach (filter_formats() as $format) {
if ($format->format != filter_fallback_format()) {
filter_format_disable($format);
}
}
// Since there is now only one available text format, the widget for
// selecting a text format would normally not display when the content is
// edited. However, we need to verify that the filter administrator still
// is forced to make a conscious choice to reassign the text to a different
// format.
$this->drupalLogin($this->filter_admin_user);
$old_title = $new_title;
$new_title = $this->randomName(8);
$edit = array('title' => $new_title);
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.'));
$this->drupalGet('node/' . $node->nid);
$this->assertText($old_title, t('Old title found.'));
$this->assertNoText($new_title, t('New title not found.'));
$edit[$body_format_key] = filter_fallback_format();
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertUrl('node/' . $node->nid);
$this->assertText($new_title, t('New title found.'));
$this->assertNoText($old_title, t('Old title not found.'));
}
/**
......
......@@ -411,6 +411,9 @@ function node_update_dependencies() {
// the Field module has been enabled, but before upgrading field data.
$dependencies['node'][7006] = array(
'system' => 7049,
// It must also run after filter_update_7000() because it needs to query
// the list of existing text formats.
'filter' => 7000,
);
$dependencies['system'][7050] = array(
'node' => 7006,
......@@ -567,6 +570,9 @@ function node_update_7006(&$sandbox) {
);
}
// Used below when updating the stored text format of each node body.
$sandbox['existing_text_formats'] = db_query("SELECT format FROM {filter_format}")->fetchCol();
// Initialize state for future calls.
$sandbox['last'] = 0;
$sandbox['count'] = 0;
......@@ -625,11 +631,20 @@ function node_update_7006(&$sandbox) {
$revision->body = substr($revision->body, strlen($break));
}
$node->body[$langcode][0]['value'] = $revision->body;
// Explicitly store the current default text format if the revision
// did not have its own text format. Similar conversions for other
// core modules are performed in filter_update_7005(), but we do this
// one here since we are already migrating the data.
$node->body[$langcode][0]['format'] = !empty($revision->format) ? $revision->format : variable_get('filter_default_format', 1);
// Update the revision's text format for the changes to the Drupal 7
// filter system. This uses the same kind of logic that occurs, for
// example, in user_update_7010(), but we do this here rather than
// via a separate set of database queries, since we are already
// migrating the data.
if (empty($revision->body) && empty($revision->format)) {
$node->body[$langcode][0]['format'] = NULL;
}
elseif (!in_array($revision->format, $sandbox['existing_text_formats'])) {
$node->body[$langcode][0]['format'] = variable_get('filter_default_format', 1);
}
else {
$node->body[$langcode][0]['format'] = $revision->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('node', $node, FIELD_STORAGE_INSERT, array($body_field_id));
......
......@@ -2517,6 +2517,31 @@ protected function drupalSetSettings($settings) {
$this->drupalSettings = $settings;
}
/**
* Pass if the internal browser's URL matches the given path.
*
* @param $path
* The expected system path.
* @param $options
* (optional) Any additional options to pass for $path to url().
* @param $message
* Message to display.
* @param $group
* The group this message belongs to, defaults to 'Other'.
*
* @return
* TRUE on pass, FALSE on fail.
*/
protected function assertUrl($path, array $options = array(), $message = '', $group = 'Other') {
if (!$message) {
$message = t('Current URL is @url.', array(
'@url' => var_export(url($path, $options), TRUE),
));
}
$options['absolute'] = TRUE;
return $this->assertEqual($this->getUrl(), url($path, $options), $message, $group);
}
/**
* Pass if the raw text IS found on the loaded page, fail otherwise. Raw text
* refers to the raw HTML that the page generated.
......
......@@ -656,7 +656,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary =
$defaults = array(
'name' => '',
'description' => '',
'format' => filter_default_format(),
'format' => NULL,
'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
'tid' => NULL,
'weight' => 0,
......
......@@ -52,9 +52,8 @@ function taxonomy_schema() {
),
'format' => array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The {filter_format}.format of the description.',
),
'weight' => array(
......@@ -736,9 +735,8 @@ function taxonomy_update_7005(&$sandbox) {
function taxonomy_update_7006() {
db_add_field('taxonomy_term_data', 'format', array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The {filter_format}.format of the description.',
));
}
......
......@@ -34,6 +34,8 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
$term = new stdClass();
$term->name = $this->randomName();
$term->description = $this->randomName();
// Use the first available text format.
$term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
$term->vid = $vocabulary->vid;
taxonomy_term_save($term);
return $term;
......
......@@ -168,9 +168,8 @@ function user_schema() {
),
'signature_format' => array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The {filter_format}.format of the signature.',
),
'created' => array(
......@@ -346,8 +345,9 @@ function user_update_dependencies() {
$dependencies['system'][7000] = array(
'user' => 7008,
);
// user_update_7006 relies on filter_update_7002.
// TODO: move user_update_7006 down below in the upgrade process.
// Both user_update_7006() and user_update_7010() need to query the list of
// existing text formats and therefore must run after filter_update_7003().
// @todo: move user_update_7006 down below in the upgrade process.
$dependencies['user'][7006] = array(
'filter' => 7003,
);
......@@ -633,17 +633,51 @@ function user_update_7009() {
* Update the {user}.signature_format column.
*/
function user_update_7010() {
// It was previously possible for a value of "0" to be stored in database
// tables to indicate that a particular piece of text should be filtered
// using the default text format.
// Update the database column to allow NULL values.
db_change_field('users', 'signature_format', 'signature_format', array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'description' => 'The {filter_format}.format of the signature.',
));
// Replace the signature format with NULL if the signature is empty and does
// not already have a stored text format.
//
// In Drupal 6, "0" (the former FILTER_FORMAT_DEFAULT constant) could be used
// to indicate this situation, but in Drupal 7, only NULL is supported. This
// update therefore preserves the ability of user accounts which were never
// given a signature (for example, if the site did not have user signatures
// enabled, or if the user never edited their account information) to not
// have a particular text format assumed for them the first time the
// signature is edited.
db_update('users')
->fields(array('signature_format' => NULL))
->condition('signature', '')
->condition('signature_format', 0)
->execute();
// There are a number of situations in which a Drupal 6 site could store
// content with a nonexistent text format. This includes text formats that
// had later been deleted, or non-empty content stored with a value of "0"
// (the former FILTER_FORMAT_DEFAULT constant). Drupal 6 would filter this
// content using whatever the site-wide default text format was at the moment
// the text was being displayed.
//
// In Drupal 7, this behavior is no longer supported, and all content must be
// stored with an explicit text format (or it will not be displayed when it
// is filtered). Therefore, to preserve the behavior of the site after the
// upgrade, we must replace all instances described above with the current
// value of the (old) site-wide default format at the moment of the upgrade.
$existing_formats = db_query("SELECT format FROM {filter_format}")->fetchCol();
$default_format = variable_get('filter_default_format', 1);
db_update('users')
->fields(array('signature_format' => $default_format))
->condition('signature_format', 0)
->isNotNull('signature_format')
->condition('signature_format', $existing_formats, 'NOT IN')
->execute();
}
/**
* Updates email templates to use new tokens.
*
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment