Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v retrieving revision 1.342 diff -u -p -r1.342 CHANGELOG.txt --- CHANGELOG.txt 19 Sep 2009 11:07:36 -0000 1.342 +++ CHANGELOG.txt 19 Sep 2009 21:34:05 -0000 @@ -43,6 +43,7 @@ Drupal 7.0, xxxx-xx-xx (development vers * Redesigned the add content type screen. * Highlight duplicate URL aliases. * Renamed "input formats" to "text formats". + * Moved text format permissions to the main permissions page. * Added configurable ability for users to cancel their own accounts. * Added "vertical tabs", a reusable interface component that features automatic summaries and increases usability. @@ -80,6 +81,7 @@ Drupal 7.0, xxxx-xx-xx (development vers fallback to the system time zone and will have to be reconfigured by each user. - Filter system: * Revamped the filter API and text format storage. + * Added support for default text formats to be assigned on a per-role basis. * Refactored the HTML corrector to take advantage of PHP 5 features. - Removed ping module: * Contributed modules with similar functionality are available. Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.373 diff -u -p -r1.373 form.inc --- includes/form.inc 18 Sep 2009 00:12:45 -0000 1.373 +++ includes/form.inc 19 Sep 2009 21:34:05 -0000 @@ -1917,7 +1917,7 @@ function form_process_radios($element) { * $form['body'] = array( * '#type' => 'textarea', * '#title' => t('Body'), - * '#text_format' => isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT, + * '#text_format' => isset($node->format) ? $node->format : filter_default_format(), * ); * @endcode * Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.377 diff -u -p -r1.377 block.module --- modules/block/block.module 18 Sep 2009 00:04:22 -0000 1.377 +++ modules/block/block.module 19 Sep 2009 21:34:05 -0000 @@ -156,7 +156,7 @@ function block_block_info() { * Implement hook_block_configure(). */ function block_block_configure($delta = 0) { - $custom_block = array('format' => FILTER_FORMAT_DEFAULT); + $custom_block = array('format' => filter_default_format()); if ($delta) { $custom_block = block_custom_block_get($delta); } @@ -350,12 +350,12 @@ function block_custom_block_form($edit = '#type' => 'textarea', '#title' => t('Block body'), '#default_value' => $edit['body'], - '#text_format' => isset($edit['format']) ? $edit['format'] : FILTER_FORMAT_DEFAULT, + '#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($edit['format']), + '#access' => filter_access(filter_format_load($edit['format'])), ); return $form; @@ -799,9 +799,9 @@ function block_user_role_delete($role) { /** * Implement hook_filter_format_delete(). */ -function block_filter_format_delete($format, $default) { +function block_filter_format_delete($format, $fallback) { db_update('block_custom') - ->fields(array('format' => $default->format)) + ->fields(array('format' => $fallback->format)) ->condition('format', $format->format) ->execute(); } Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.772 diff -u -p -r1.772 comment.module --- modules/comment/comment.module 19 Sep 2009 11:07:36 -0000 1.772 +++ modules/comment/comment.module 19 Sep 2009 21:34:05 -0000 @@ -1814,7 +1814,7 @@ function comment_form($form, &$form_stat '#title' => t('Comment'), '#rows' => 15, '#default_value' => $default, - '#text_format' => isset($comment->format) ? $comment->format : FILTER_FORMAT_DEFAULT, + '#text_format' => isset($comment->format) ? $comment->format : filter_default_format(), '#required' => TRUE, ); @@ -2418,9 +2418,9 @@ function comment_menu_alter(&$items) { /** * Implement hook_filter_format_delete(). */ -function comment_filter_format_delete($format, $default) { +function comment_filter_format_delete($format, $fallback) { db_update('comment') - ->fields(array('format' => $default->format)) + ->fields(array('format' => $fallback->format)) ->condition('format', $format->format) ->execute(); } Index: modules/field/modules/text/text.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v retrieving revision 1.28 diff -u -p -r1.28 text.module --- modules/field/modules/text/text.module 11 Sep 2009 13:30:49 -0000 1.28 +++ modules/field/modules/text/text.module 19 Sep 2009 21:34:05 -0000 @@ -541,7 +541,7 @@ function text_element_info() { '#delta' => 0, '#process' => array('text_textarea_elements_process'), '#theme_wrappers' => array('text_textarea'), - '#filter_value' => FILTER_FORMAT_DEFAULT, + '#filter_value' => filter_default_format(), ); $types['text_textarea_with_summary'] = array( '#input' => TRUE, @@ -549,7 +549,7 @@ function text_element_info() { '#delta' => 0, '#process' => array('text_textarea_with_summary_process'), '#theme_wrappers' => array('text_textarea'), - '#filter_value' => FILTER_FORMAT_DEFAULT, + '#filter_value' => filter_default_format(), ); return $types; } @@ -651,7 +651,7 @@ function text_textfield_elements_process if (!empty($instance['settings']['text_processing'])) { $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); $element[$field_key]['#text_format'] = $format; } @@ -684,7 +684,7 @@ function text_textarea_elements_process( if (!empty($instance['settings']['text_processing'])) { $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); $element[$field_key]['#text_format'] = $format; } @@ -739,7 +739,7 @@ function text_textarea_with_summary_proc if (!empty($instance['settings']['text_processing'])) { $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); $element[$field_key]['#text_format'] = $format; } @@ -760,7 +760,7 @@ function text_field_widget_formatted_tex // The format selector uses #access = FALSE if only one format is // available. In this case, we don't receive its value, and need to // manually set it. - $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_resolve_format(FILTER_FORMAT_DEFAULT); + $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_default_format(); unset($edit[$default_key]); return $edit; } Index: modules/field/modules/text/text.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.test,v retrieving revision 1.10 diff -u -p -r1.10 text.test --- modules/field/modules/text/text.test 22 Aug 2009 00:58:53 -0000 1.10 +++ modules/field/modules/text/text.test 19 Sep 2009 21:34:05 -0000 @@ -3,6 +3,8 @@ class TextFieldTestCase extends DrupalWebTestCase { protected $instance; + protected $admin_user; + protected $web_user; public static function getInfo() { return array( @@ -15,8 +17,9 @@ class TextFieldTestCase extends DrupalWe function setUp() { parent::setUp('field_test'); - $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($web_user); + $this->admin_user = $this->drupalCreateUser(array('administer filters')); + $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->drupalLogin($this->web_user); } // Test fields. @@ -147,15 +150,23 @@ class TextFieldTestCase extends DrupalWe field_create_instance($this->instance); $langcode = FIELD_LANGUAGE_NONE; - // Display creation form. - // By default, the user only has access to 'Filtered HTML', and no format - // selector is displayed + // Delete all text formats besides the plain text fallback format. + $this->drupalLogin($this->admin_user); + foreach (filter_formats() as $format) { + if ($format->format != filter_fallback_format()) { + $this->drupalPost('admin/config/content/formats/' . $format->format . '/delete', array(), t('Delete')); + } + } + $this->drupalLogin($this->web_user); + + // Display the creation form. Since the user only has access to one format, + // no format selector will be displayed. $this->drupalGet('test-entity/add/test-bundle'); $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertNoFieldByName("{$this->field_name}[$langcode][0][value_format]", '1', t('Format selector is not displayed')); + $this->assertNoFieldByName("{$this->field_name}[$langcode][0][value_format]", '', t('Format selector is not displayed')); // Submit with data that should be filtered. - $value = $this->randomName() . '
' . $this->randomName(); + $value = '' . $this->randomName() . ''; $edit = array( "{$this->field_name}[$langcode][0][value]" => $value, ); @@ -168,21 +179,31 @@ class TextFieldTestCase extends DrupalWe $entity = field_test_entity_load($id); $entity->content = field_attach_view($entity_type, $entity); $this->content = drupal_render($entity->content); - $this->assertNoRaw($value, 'Filtered tags are not displayed'); - $this->assertRaw(str_replace('
', '', $value), t('Filtered value is displayed correctly')); + $this->assertNoRaw($value, t('HTML tags are not displayed.')); + $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.')); - // Allow the user to use the 'Full HTML' format. - db_update('filter_format')->fields(array('roles' => ',2,'))->condition('format', 2)->execute(); + // Create a new text format that does not escape HTML, and grant the user + // access to it. + $this->drupalLogin($this->admin_user); + $edit = array('name' => $this->randomName()); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + filter_format_reset_cache(); + $this->checkPermissions(array(), TRUE); + $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField(); + $permission = filter_permission_name(filter_format_load($format_id)); + $rid = max(array_keys($this->web_user->roles)); + user_role_set_permissions($rid, array($permission), TRUE); + $this->drupalLogin($this->web_user); // Display edition form. // We should now have a 'text format' selector. $this->drupalGet('test-entity/' . $id . '/edit'); $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value_format]", '1', t('Format selector is displayed')); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value_format]", '', t('Format selector is displayed')); - // Edit and change the format to 'Full HTML'. + // Edit and change the text format to the new one that was created. $edit = array( - "{$this->field_name}[$langcode][0][value_format]" => 2, + "{$this->field_name}[$langcode][0][value_format]" => $format_id, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated')); Index: modules/filter/filter.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v retrieving revision 1.45 diff -u -p -r1.45 filter.admin.inc --- modules/filter/filter.admin.inc 18 Sep 2009 00:12:46 -0000 1.45 +++ modules/filter/filter.admin.inc 19 Sep 2009 22:29:56 -0000 @@ -7,8 +7,7 @@ */ /** - * Menu callback; Displays a list of all text formats and which - * one is the default. + * Menu callback; Displays a list of all text formats and allows them to be rearranged. * * @ingroup forms * @see filter_admin_overview_submit() @@ -16,37 +15,33 @@ function filter_admin_overview($form) { // Overview of all formats. $formats = filter_formats(); - $error = FALSE; + $fallback_format = filter_fallback_format(); $form['#tree'] = TRUE; foreach ($formats as $id => $format) { - $roles = array(); - foreach (user_roles() as $rid => $name) { - // Prepare a roles array with roles that may access the filter. - if (strpos($format->roles, ",$rid,") !== FALSE) { - $roles[] = $name; - } + // Check whether this is the fallback text format. This format is available + // to all roles and cannot be deleted via the admin interface. + $form['formats'][$id]['#is_fallback'] = ($id == $fallback_format); + if ($form['formats'][$id]['#is_fallback']) { + $form['formats'][$id]['name'] = array('#markup' => theme('placeholder', $format->name)); + $roles_markup = theme('placeholder', t('All roles may use this format')); } - $default = ($id == variable_get('filter_default_format', 1)); - $options[$id] = ''; - $form[$id]['name'] = array('#markup' => $format->name); - $form[$id]['roles'] = array('#markup' => $default ? t('All roles may use the default format') : ($roles ? implode(', ', $roles) : t('No roles may use this format'))); - $form[$id]['configure'] = array('#markup' => l(t('configure'), 'admin/config/content/formats/' . $id)); - $form[$id]['delete'] = array('#markup' => $default ? '' : l(t('delete'), 'admin/config/content/formats/delete/' . $id)); - $form[$id]['weight'] = array('#type' => 'weight', '#default_value' => $format->weight); + else { + $form['formats'][$id]['name'] = array('#markup' => check_plain($format->name)); + $roles = filter_get_roles_by_format($format); + $roles_markup = $roles ? implode(', ', $roles) : t('No roles may use this format'); + } + $form['formats'][$id]['roles'] = array('#markup' => $roles_markup); + $form['formats'][$id]['configure'] = array('#markup' => l(t('configure'), 'admin/config/content/formats/' . $id)); + $form['formats'][$id]['delete'] = array('#markup' => $form['formats'][$id]['#is_fallback'] ? '' : l(t('delete'), 'admin/config/content/formats/delete/' . $id)); + $form['formats'][$id]['weight'] = array('#type' => 'weight', '#default_value' => $format->weight); } - $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => variable_get('filter_default_format', 1)); $form['submit'] = array('#type' => 'submit', '#value' => t('Save changes')); return $form; } function filter_admin_overview_submit($form, &$form_state) { - // Process form submission to set the default format. - if (is_numeric($form_state['values']['default'])) { - drupal_set_message(t('Default format updated.')); - variable_set('filter_default_format', $form_state['values']['default']); - } - foreach ($form_state['values'] as $id => $data) { + foreach ($form_state['values']['formats'] as $id => $data) { if (is_array($data) && isset($data['weight'])) { // Only update if this is a form element with weight. db_update('filter_format') @@ -55,35 +50,31 @@ function filter_admin_overview_submit($f ->execute(); } } + filter_formats_reset(); drupal_set_message(t('The text format ordering has been saved.')); } /** - * Theme the admin overview form. + * Theme the text format administration overview form. * * @ingroup themeable */ function theme_filter_admin_overview($form) { $rows = array(); - foreach (element_children($form) as $id) { - $element = $form[$id]; - if (isset($element['roles']) && is_array($element['roles'])) { - $element['weight']['#attributes']['class'] = array('text-format-order-weight'); - $rows[] = array( - 'data' => array( - check_plain($element['name']['#markup']), - drupal_render($element['roles']), - drupal_render($form['default'][$id]), - drupal_render($element['weight']), - drupal_render($element['configure']), - drupal_render($element['delete']), - ), - 'class' => array('draggable'), - ); - unset($form[$id]); - } + foreach (element_children($form['formats']) as $id) { + $form['formats'][$id]['weight']['#attributes']['class'] = array('text-format-order-weight'); + $rows[] = array( + 'data' => array( + drupal_render($form['formats'][$id]['name']), + drupal_render($form['formats'][$id]['roles']), + drupal_render($form['formats'][$id]['weight']), + drupal_render($form['formats'][$id]['configure']), + drupal_render($form['formats'][$id]['delete']), + ), + 'class' => array('draggable'), + ); } - $header = array(t('Name'), t('Roles'), t('Default'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2)); + $header = array(t('Name'), t('Roles'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2)); $output = theme('table', $header, $rows, array('id' => 'text-format-order')); $output .= drupal_render_children($form); @@ -98,7 +89,7 @@ function theme_filter_admin_overview($fo function filter_admin_format_page($format = NULL) { if (!isset($format->name)) { drupal_set_title(t('Add text format'), PASS_THROUGH); - $format = (object)array('name' => '', 'roles' => '', 'format' => FILTER_FORMAT_DEFAULT); + $format = (object)array('name' => '', 'format' => 0); } return drupal_get_form('filter_admin_format_form', $format); } @@ -111,10 +102,9 @@ function filter_admin_format_page($forma * @see filter_admin_format_form_submit() */ function filter_admin_format_form($form, &$form_state, $format) { - $default = ($format->format == variable_get('filter_default_format', 1)); - if ($default) { - $help = t('All roles for the default format must be enabled and cannot be changed.'); - $form['default_format'] = array('#type' => 'hidden', '#value' => 1); + $is_fallback = ($format->format == filter_fallback_format()); + if ($is_fallback) { + $help = t('All roles for this text format must be enabled and cannot be changed.'); } $form['name'] = array( @@ -128,17 +118,16 @@ function filter_admin_format_form($form, // Add a row of checkboxes for form group. $form['roles'] = array('#type' => 'fieldset', '#title' => t('Roles'), - '#description' => $default ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'), + '#description' => $is_fallback ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'), '#tree' => TRUE, ); - + $checked = filter_get_roles_by_format($format); foreach (user_roles() as $rid => $name) { - $checked = strpos($format->roles, ",$rid,") !== FALSE; $form['roles'][$rid] = array('#type' => 'checkbox', '#title' => $name, - '#default_value' => ($default || $checked), + '#default_value' => ($is_fallback || isset($checked[$rid])), ); - if ($default) { + if ($is_fallback) { $form['roles'][$rid]['#disabled'] = TRUE; } } @@ -201,13 +190,19 @@ function filter_admin_format_form_submit $format->format = isset($form_state['values']['format']) ? $form_state['values']['format'] : NULL; $status = filter_format_save($format); + if ($permission = filter_permission_name($format)) { + foreach ($format->roles as $rid => $enabled) { + user_role_change_permissions($rid, array($permission => $enabled)); + } + } + switch ($status) { case SAVED_NEW: drupal_set_message(t('Added text format %format.', array('%format' => $format->name))); break; case SAVED_UPDATED: - drupal_set_message(t('The text format settings have been updated.')); + drupal_set_message(t('The text format %format has been updated.', array('%format' => $format->name))); break; } } @@ -219,26 +214,15 @@ function filter_admin_format_form_submit * @see filter_admin_delete_submit() */ function filter_admin_delete($form, &$form_state, $format) { - if ($format) { - if ($format->format != variable_get('filter_default_format', 1)) { - $form['#format'] = $format; - - return confirm_form($form, - t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), - 'admin/config/content/formats', - t('If you have any content left in this text format, it will be switched to the default text format. This action cannot be undone.'), - t('Delete'), - t('Cancel') - ); - } - else { - drupal_set_message(t('The default format cannot be deleted.')); - drupal_goto('admin/config/content/formats'); - } - } - else { - drupal_not_found(); - } + $form['#format'] = $format; + + return confirm_form($form, + t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), + 'admin/config/content/formats', + t('If you have any content left in this text format, it will be switched to the %fallback text format. This action cannot be undone.', array('%fallback' => filter_fallback_format_title())), + t('Delete'), + t('Cancel') + ); } /** Index: modules/filter/filter.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.api.php,v retrieving revision 1.14 diff -u -p -r1.14 filter.api.php --- modules/filter/filter.api.php 18 Sep 2009 00:12:46 -0000 1.14 +++ modules/filter/filter.api.php 19 Sep 2009 21:34:05 -0000 @@ -236,20 +236,21 @@ function hook_filter_format_update($form * * It is recommended for modules to implement this hook, when they store * references to text formats to replace existing references to the deleted - * text format with the default format. + * text format with the fallback format. * * @param $format * The format object of the format being deleted. - * @param $default - * The format object of the site's default format. + * @param $fallback + * The format object of the site's fallback format, which is always available + * to all users. * * @see hook_filter_format_update(). * @see hook_filter_format_delete(). */ -function hook_filter_format_delete($format, $default) { - // Replace the deleted format with the default format. +function hook_filter_format_delete($format, $fallback) { + // Replace the deleted format with the fallback format. db_update('my_module_table') - ->fields(array('format' => $default->format)) + ->fields(array('format' => $fallback->format)) ->condition('format', $format->format) ->execute(); } Index: modules/filter/filter.install =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.install,v retrieving revision 1.19 diff -u -p -r1.19 filter.install --- modules/filter/filter.install 27 Aug 2009 21:18:19 -0000 1.19 +++ modules/filter/filter.install 19 Sep 2009 21:41:13 -0000 @@ -77,13 +77,6 @@ function filter_schema() { 'default' => '', 'description' => 'Name of the text format (Filtered HTML).', ), - 'roles' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'A comma-separated string of roles; references {role}.rid.', // This is bad since you can't use joins, nor index. - ), 'cache' => array( 'type' => 'int', 'not null' => TRUE, @@ -112,6 +105,11 @@ function filter_schema() { } /** + * @defgroup updates-6.x-to-7.x Filter updates from 6.x to 7.x + * @{ + */ + +/** * Add a weight column to the filter formats table. */ function filter_update_7000() { @@ -260,3 +258,88 @@ function filter_update_7004() { return $ret; } +/** + * Integrate text formats with the user permissions system. + * + * This function converts text format role assignments to use the new text + * format permissions introduced in Drupal 7, creates a fallback (plain text) + * format that is available to all users, and explicitly sets the text format + * in cases that used to rely on a single site-wide default. + */ +function filter_update_7005() { + $ret = array(); + + // Move role data from the filter system to the user permission system. + $all_roles = array_keys(user_roles()); + $default_format = variable_get('filter_default_format', 1); + $result = db_query("SELECT * FROM {filter_format}"); + foreach ($result as $format) { + // We need to assign the default format to all roles (regardless of what + // was stored in the database) to preserve the behavior of the site at the + // moment of the upgrade. + $format_roles = ($format->format == $default_format ? $all_roles : explode(',', $format->roles)); + foreach ($format_roles as $format_role) { + if (in_array($format_role, $all_roles)) { + user_role_grant_permissions($format_role, array(filter_permission_name($format))); + } + } + } + + // Drop the roles field from the {filter_format} table. + db_drop_field($ret, 'filter_format', 'roles'); + + // Add a fallback text format which outputs plain text and appears last on + // the list for all users. Generate a unique name for it, starting with + // "Plain text". + $start_name = 'Plain text'; + $format_name = $start_name; + while ($format = db_query('SELECT format FROM {filter_format} WHERE name = :name', array(':name' => $format_name))->fetchField()) { + $id = empty($id) ? 1 : $id + 1; + $format_name = $start_name . ' ' . $id; + } + $fallback_format = new stdClass(); + $fallback_format->name = $format_name; + $fallback_format->cache = 1; + $fallback_format->weight = 1; + // This format should output plain text, so we escape all HTML and apply the + // line break filter only. + $fallback_format->filters = array( + 'filter_html_escape' => array('status' => 1), + 'filter_autop' => array('status' => 1), + ); + filter_format_save($fallback_format); + variable_set('filter_fallback_format', $fallback_format->format); + drupal_set_message('A new Plain text format has been created which will be available to all users. You can configure this text format on the text format configuration page.'); + + // Move the former site-wide default text format to the top of the list, so + // that it continues to be the default text format for all users. + db_update('filter_format') + ->fields(array('weight' => -1)) + ->condition('format', $default_format) + ->execute(); + + // 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. Therefore, we have to convert all such + // instances (in Drupal core) to explicitly use the appropriate format. + // Note that the update of the node body field is handled separately, in + // node_update_7006(). + foreach (array('block_custom', 'comment') as $table) { + if (db_table_exists($table)) { + db_update($table) + ->fields(array('format' => $default_format)) + ->condition('format', 0) + ->execute(); + } + } + + // We do not delete the 'filter_default_format' variable, since other modules + // may need it in their update functions. + // @todo This variable can be deleted in Drupal 8. + return $ret; +} + +/** + * @} End of "defgroup updates-6.x-to-7.x" + * The next series of updates should start at 8000. + */ Index: modules/filter/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v retrieving revision 1.290 diff -u -p -r1.290 filter.module --- modules/filter/filter.module 18 Sep 2009 00:12:46 -0000 1.290 +++ modules/filter/filter.module 19 Sep 2009 22:15:41 -0000 @@ -7,14 +7,6 @@ */ /** - * Special format ID which means "use the default format". - * - * This value can be passed to the filter APIs as a format ID: this is - * equivalent to not passing an explicit format at all. - */ -define('FILTER_FORMAT_DEFAULT', 0); - -/** * Implement hook_help(). */ function filter_help($path, $arg) { @@ -22,12 +14,12 @@ function filter_help($path, $arg) { case 'admin/help#filter': $output = '

' . t("The filter module allows administrators to configure text formats for use on your site. A text format defines the HTML tags, codes, and other input allowed in both content and comments, and is a key feature in guarding against potentially damaging input from malicious users. Two formats included by default are Filtered HTML (which allows only an administrator-approved subset of HTML tags) and Full HTML (which allows the full set of HTML tags). Additional formats may be created by an administrator.") . '

'; $output .= '

' . t('Each text format uses filters to manipulate text, and most formats apply several different filters to text in a specific order. Each filter is designed for a specific purpose, and generally either adds, removes or transforms elements within user-entered text before it is displayed. A filter does not change the actual content of a post, but instead, modifies it temporarily before it is displayed. A filter may remove unapproved HTML tags, for instance, while another automatically adds HTML to make links referenced in text clickable.') . '

'; - $output .= '

' . t('Users with access to more than one text format can use the Text format fieldset to choose between available text formats when creating or editing multi-line content. Administrators determine the text formats available to each user role, select a default text format, and control the order of formats listed in the Text format fieldset.') . '

'; + $output .= '

' . t('Users with access to more than one text format can use the Text format fieldset to choose between available text formats when creating or editing multi-line content. Administrators determine the text formats available to each user role and control the order of formats listed in the Text format fieldset.') . '

'; $output .= '

' . t('For more information, see the online handbook entry for Filter module.', array('@filter' => 'http://drupal.org/handbook/modules/filter/')) . '

'; return $output; case 'admin/config/content/formats': - $output = '

' . t('Use the list below to review the text formats available to each user role, to select a default text format, and to control the order of formats listed in the Text format fieldset. (The Text format fieldset is displayed below textareas when users with access to more than one text format create multi-line content.) The text format selected as Default is available to all users and, unless another format is selected, is applied to all content. All text formats are available to users in roles with the "administer filters" permission.') . '

'; - $output .= '

' . t('Since text formats, if available, are presented in the same order as the list below, it may be helpful to arrange the formats in descending order of your preference for their use. Remember that your changes will not be saved until you click the Save changes button at the bottom of the page.') . '

'; + $output = '

' . t('Use the list below to review the text formats available to each user role and to control the order of formats listed in the Text format fieldset. (The Text format fieldset is displayed below textareas when users with access to more than one text format create multi-line content.) All text formats are available to users in roles with the "administer filters" permission, and the special %fallback format is available to all users. You can configure access to other text formats on the permissions page.', array('%fallback' => filter_fallback_format_title(), '@url' => url('admin/config/people/permissions', array('fragment' => 'module-filter')))) . '

'; + $output .= '

' . t('Since text formats, if available, are presented in the same order as the list below, and the default format for each user is the first one on the list for which that user has access, it may be helpful to arrange the formats in descending order of your preference for their use. Remember that your changes will not be saved until you click the Save changes button at the bottom of the page.') . '

'; return $output; case 'admin/config/content/formats/%': return '

' . t('Every filter performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this format. If you notice some filters are causing conflicts in the output, you can rearrange them.', array('@rearrange' => url('admin/config/content/formats/' . $arg[4] . '/order'))) . '

'; @@ -133,7 +125,8 @@ function filter_menu() { 'title' => 'Delete text format', 'page callback' => 'drupal_get_form', 'page arguments' => array('filter_admin_delete', 4), - 'access arguments' => array('administer filters'), + 'access callback' => '_filter_delete_format_access', + 'access arguments' => array(4), 'type' => MENU_CALLBACK, 'file' => 'filter.admin.inc', ); @@ -141,6 +134,20 @@ function filter_menu() { } /** + * Access callback for deleting text formats. + * + * @param $format + * A text format object. + * @return + * TRUE if the text format can be deleted by the current user, FALSE + * otherwise. + */ +function _filter_delete_format_access($format) { + // The fallback format can never be deleted. + return user_access('administer filters') && ($format->format != filter_fallback_format()); +} + +/** * Load a text format object from the database. * * @param $format @@ -150,7 +157,8 @@ function filter_menu() { * A fully-populated text format object. */ function filter_format_load($format) { - return filter_formats($format); + $formats = filter_formats(); + return isset($formats[$format]) ? $formats[$format] : FALSE; } /** @@ -160,17 +168,6 @@ function filter_format_load($format) { * A format object. */ function filter_format_save($format) { - // We store the roles as a string for ease of use. - // We should always set all roles to TRUE when saving the default format. - // We use leading and trailing comma's to allow easy substring matching. - $roles = array_filter($format->roles); - if (!empty($format->format) && $format->format == variable_get('filter_default_format', 1)) { - $roles = ',' . implode(',', array_keys(user_roles())) . ','; - } - else { - $roles = ',' . implode(',', array_keys($roles)) . ','; - } - $format->roles = $roles; $format->name = trim($format->name); // Add a new text format. @@ -208,11 +205,17 @@ function filter_format_save($format) { } else { module_invoke_all('filter_format_update', $format); + // Explicitly indicate that the format was updated. We need to do this + // since if the filters were updated but the format object itself was not, + // the call to drupal_write_record() above would not return an indication + // that anything had changed. + $return = SAVED_UPDATED; + + // Clear the filter cache whenever a text format is updated. + cache_clear_all($format->format . ':', 'cache_filter', TRUE); } - // Clear the filter cache whenever a text format is saved. - drupal_static_reset('filter_list_format'); - cache_clear_all($format->format . ':', 'cache_filter', TRUE); + filter_formats_reset(); return $return; } @@ -232,9 +235,10 @@ function filter_format_delete($format) { ->execute(); // Allow modules to react on text format deletion. - $default = filter_format_load(variable_get('filter_default_format', 1)); - module_invoke_all('filter_format_delete', $format, $default); + $fallback = filter_format_load(filter_fallback_format()); + module_invoke_all('filter_format_delete', $format, $fallback); + filter_formats_reset(); cache_clear_all($format->format . ':', 'cache_filter', TRUE); } @@ -249,12 +253,42 @@ function filter_admin_format_title($form * Implement hook_permission(). */ function filter_permission() { - return array( - 'administer filters' => array( - 'title' => t('Administer filters'), - 'description' => t('Manage text formats and filters, and select which roles may use them. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))), - ), - ); + $perms['administer filters'] = array( + 'title' => t('Administer filters'), + 'description' => t('Manage text formats and filters, and use any of them, without restriction, when entering or editing content. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))), + ); + + // Generate permissions for each text format. Warn the administrator that any + // of them are potentially unsafe. + foreach (filter_formats() as $format) { + $permission = filter_permission_name($format); + if (!empty($permission)) { + // Only link to the text format configuration page if the user who is + // viewing this will have access to that page. + $format_name_replacement = user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : theme('placeholder', $format->name); + $perms[$permission] = array( + 'title' => t("Use the '@text_format' text format", array('@text_format' => $format->name)), + 'description' => t('Use !text_format in forms when entering or editing content. %warning', array('!text_format' => $format_name_replacement, '%warning' => t('Warning: This permission may have security implications depending on how the text format is configured.'))), + ); + } + } + return $perms; +} + +/** + * Returns the machine-readable permission name for a provided text format. + * + * @param $format + * An object representing a text format. + * @return + * The machine-readable permission name, or FALSE if the provided text format + * is malformed or is the fallback format (which is available to all users). + */ +function filter_permission_name($format) { + if (isset($format->format) && $format->format != filter_fallback_format()) { + return 'use text format ' . $format->format; + } + return FALSE; } /** @@ -389,55 +423,139 @@ function _filter_html_escape_tips($filte */ /** - * Retrieve a list of text formats. + * Retrieve a list of text formats, ordered by weight. * - * @param $format - * (optional) The text format to retrieve; if omitted or NULL, retrieve an - * array of accessible text formats. * @param $account - * (optional) The user account to retrieve accessible text formats for; if - * omitted, the currently logged-in user is used. - * + * (optional) If provided, only those formats that are allowed for this user + * account will be returned. All formats will be returned otherwise. * @return - * Either one text format object or a list of text format objects, depending - * on the $format parameter. FALSE if the user does not have access to the - * given text $format. + * An array of text format objects, keyed by the format ID and ordered by + * weight. + * + * @see filter_formats_reset() */ -function filter_formats($format = NULL, $account = NULL) { - global $user; +function filter_formats($account = NULL) { $formats = &drupal_static(__FUNCTION__, array()); - if (!isset($account)) { - $account = $user; + // Statically cache all existing formats upfront. + if (!isset($formats['all'])) { + $formats['all'] = db_query('SELECT * FROM {filter_format} ORDER BY weight')->fetchAllAssoc('format'); } - // Administrators can always use all text formats. - $all = user_access('administer filters', $account); - - if (!isset($formats[$account->uid])) { - $query = db_select('filter_format', 'f'); - $query->addField('f', 'format', 'format'); - $query->addField('f', 'name', 'name'); - $query->addField('f', 'roles', 'roles'); - $query->addField('f', 'cache', 'cache'); - $query->addField('f', 'weight', 'weight'); - $query->orderBy('weight'); - - // Build query for selecting the format(s) based on the user's roles. - if (!$all) { - $or = db_or()->condition('format', variable_get('filter_default_format', 1)); - foreach ($user->roles as $rid => $role) { - $or->condition('roles', '%' . (int)$rid . '%', 'LIKE'); + // Build a list of user-specific formats. + if (isset($account) && !isset($formats['user'][$account->uid])) { + $formats['user'][$account->uid] = array(); + foreach ($formats['all'] as $format) { + if (filter_access($format, $account)) { + $formats['user'][$account->uid][$format->format] = $format; } - $query->condition($or); } + } + + return isset($account) ? $formats['user'][$account->uid] : $formats['all']; +} + +/** + * Resets the static cache of all text formats. + * + * @see filter_formats() + */ +function filter_formats_reset() { + drupal_static_reset('filter_list_format'); + drupal_static_reset('filter_formats'); +} + +/** + * Retrieves a list of roles that are allowed to use a given text format. + * + * @param $format + * An object representing the text format. + * @return + * An array of role names, keyed by role ID. + */ +function filter_get_roles_by_format($format) { + // Handle the fallback format upfront (all roles have access to this format). + if ($format->format == filter_fallback_format()) { + return user_roles(); + } + // Do not list any roles if the permission does not exist. + $permission = filter_permission_name($format); + return !empty($permission) ? user_roles(FALSE, $permission) : array(); +} - $formats[$account->uid] = $query->execute()->fetchAllAssoc('format'); +/** + * Retrieves a list of text formats that are allowed for a given role. + * + * @param $rid + * The user role ID to retrieve text formats for. + * @return + * An array of text format objects that are allowed for the role, keyed by + * the text format ID and ordered by weight. + */ +function filter_get_formats_by_role($rid) { + $formats = array(); + foreach (filter_formats() as $format) { + $roles = filter_get_roles_by_format($format); + if (isset($roles[$rid])) { + $formats[$format->format] = $format; + } } - if (isset($format)) { - return isset($formats[$account->uid][$format]) ? $formats[$account->uid][$format] : FALSE; + return $formats; +} + +/** + * Returns the ID of the default text format for a particular user. + * + * The default text format is the first available format that the user is + * allowed to access, when the formats are ordered by weight. It should + * generally be used as a default choice when presenting the user with a list + * of possible text formats (for example, in a node creation form). + * + * Conversely, when existing content that does not have an assigned text format + * needs to be filtered for display, the default text format is the wrong + * choice, because it is not guaranteed to be consistent from user to user, and + * some trusted users may have an unsafe text format set by default, which + * should not be used on text of unknown origin. Instead, the fallback format + * returned by filter_fallback_format() should be used, since that is intended + * to be a safe, consistent format that is always available to all users. + * + * @param $account + * (optional) The user account to check. Defaults to the currently logged-in + * user. + * @return + * The ID of the user's default text format. + * + * @see filter_fallback_format() + */ +function filter_default_format($account = NULL) { + global $user; + if (!isset($account)) { + $account = $user; } - return $formats[$account->uid]; + // Get a list of formats for this user, ordered by weight. The first one + // available is the user's default format. + $format = array_shift(filter_formats($account)); + return $format->format; +} + +/** + * Returns the ID of the fallback text format that all users have access to. + */ +function filter_fallback_format() { + // This variable is automatically set in the database for all installations + // of Drupal. In the event that it gets deleted somehow, there is no safe + // default to return, since we do not want to risk making an existing (and + // potentially unsafe) text format on the site automatically available to all + // users. Returning NULL at least guarantees that this cannot happen. + return variable_get('filter_fallback_format'); +} + +/** + * Returns the title of the fallback text format. + */ +function filter_fallback_format_title() { + $fallback_format = filter_format_load(filter_fallback_format()); + return filter_admin_format_title($fallback_format); } /** @@ -470,17 +588,10 @@ function _filter_list_cmp($a, $b) { } /** - * Resolve a format id, including the default format. - */ -function filter_resolve_format($format) { - return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format; -} -/** * Check if text in a certain text format is allowed to be cached. */ function filter_format_allowcache($format) { static $cache = array(); - $format = filter_resolve_format($format); if (!isset($cache[$format])) { $cache[$format] = db_query('SELECT cache FROM {filter_format} WHERE format = :format', array(':format' => $format))->fetchField(); } @@ -557,8 +668,8 @@ function filter_list_format($format, $in * @param $text * The text to be filtered. * @param $format - * The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for - * the default format. + * The format of the text to be filtered. If no format is assigned, the + * fallback format will be used. * @param $langcode * Optional: the language code of the text to be filtered, e.g. 'en' for * English. This allows filters to be language aware so language specific @@ -568,8 +679,10 @@ function filter_list_format($format, $in * The caller may set this to FALSE when the output is already cached * elsewhere to avoid duplicate cache lookups and storage. */ -function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $cache = TRUE) { - $format = filter_resolve_format($format); +function check_markup($text, $format = NULL, $langcode = '', $cache = TRUE) { + if (empty($format)) { + $format = filter_fallback_format(); + } // Check for a cached version of this piece of text. $cache_id = $format . ':' . $langcode . ':' . md5($text); @@ -620,9 +733,16 @@ function check_markup($text, $format = F * @return * HTML for the form element. */ -function filter_form($selected_format = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) { - $selected_format = filter_resolve_format($selected_format); - $formats = filter_formats(); +function filter_form($selected_format = NULL, $weight = NULL, $parents = array('format')) { + global $user; + + // Use the default format for this user if none was selected. + if (empty($selected_format)) { + $selected_format = filter_default_format($user); + } + + // Get a list of formats that the current user has access to. + $formats = filter_formats($user); drupal_add_js('misc/form.js'); drupal_add_css(drupal_get_path('module', 'filter') . '/filter.css'); @@ -665,28 +785,31 @@ function filter_form($selected_format = } /** - * Returns whether a user is allowed to access a given text format. + * Checks if a user has access to a particular text format. * * @param $format - * The format of a text to be filtered. Specify FILTER_FORMAT_DEFAULT for - * the site's default text format. + * An object representing the text format. * @param $account * (optional) The user account to check access for; if omitted, the currently * logged-in user is used. * * @return * Boolean TRUE if the user is allowed to access the given format. - * - * @see filter_formats() */ function filter_access($format, $account = NULL) { - $format = filter_resolve_format($format); - if (user_access('administer filters', $account) || ($format == variable_get('filter_default_format', 1))) { - return TRUE; + global $user; + if (!isset($account)) { + $account = $user; } - else { - return (bool) filter_formats($format, $account); + // Handle special cases up front. All users have access to the fallback + // format, and administrators have access to all formats. + if (user_access('administer filters', $account) || $format->format == filter_fallback_format()) { + return TRUE; } + // Check the permission if one exists; otherwise, we have a non-existent + // format so we return FALSE. + $permission = filter_permission_name($format); + return !empty($permission) && user_access($permission, $account); } /** @@ -698,7 +821,9 @@ function filter_access($format, $account * Helper function for fetching filter tips. */ function _filter_tips($format, $long = FALSE) { - $formats = filter_formats(); + global $user; + + $formats = filter_formats($user); $filter_info = filter_get_filters(); $tips = array(); @@ -1034,15 +1159,3 @@ function _filter_html_escape($text) { /** * @} End of "Standard filters". */ - -/** - * Implement hook_user_role_delete(). - * - * Remove deleted role from formats that use it. - */ -function filter_user_role_delete($role) { - db_update('filter_format') - ->expression('roles', 'REPLACE(roles, :rid, :replacement)', array(':rid' => ',' . $role->rid, ':replacement' => '')) - ->condition('roles', '%,' . $role->rid . '%', 'LIKE') - ->execute(); -} Index: modules/filter/filter.test =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v retrieving revision 1.41 diff -u -p -r1.41 filter.test --- modules/filter/filter.test 12 Sep 2009 06:09:45 -0000 1.41 +++ modules/filter/filter.test 19 Sep 2009 22:42:23 -0000 @@ -21,22 +21,21 @@ class FilterAdminTestCase extends Drupal // Create users. $admin_user = $this->drupalCreateUser(array('administer filters')); - $web_user = $this->drupalCreateUser(array('create page content')); + $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content')); $this->drupalLogin($admin_user); - list($filtered, $full) = $this->checkFilterFormats(); + list($filtered, $full, $plain) = $this->checkFilterFormats(); - // Verify access permissions to Full HTML format. - $this->assertTrue(filter_access($full, $admin_user), t('Admin user may use Full HTML.')); - $this->assertFalse(filter_access($full, $web_user), t('Web user may not use Full HTML.')); + // Check that the fallback format exists and cannot be deleted. + $this->assertTrue(!empty($plain) && $plain == filter_fallback_format(), t('The fallback format is set to plain text.')); + $this->drupalGet('admin/config/content/formats'); + $this->assertNoRaw('admin/config/content/formats/' . $plain . '/delete', t('Delete link for the fallback format not found.')); + $this->drupalGet('admin/config/content/formats/' . $plain . '/delete'); + $this->assertResponse(403, t('The fallback format cannot be deleted.')); - // Change default filter. - $edit = array(); - $edit['default'] = $full; - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->assertText(t('Default format updated.'), t('Default filter updated successfully.')); - - $this->assertNoRaw('admin/config/content/formats/' . $full . '/delete', t('Delete link not found.')); + // Verify access permissions to Full HTML format. + $this->assertTrue(filter_access(filter_format_load($full), $admin_user), t('Admin user may use Full HTML.')); + $this->assertFalse(filter_access(filter_format_load($full), $web_user), t('Web user may not use Full HTML.')); // Add an additional tag. $edit = array(); @@ -87,20 +86,13 @@ class FilterAdminTestCase extends Drupal $this->assertRaw(t('Deleted text format %format.', array('%format' => $edit['name'])), t('Format successfully deleted.')); } - // Change default filter back. - $edit = array(); - $edit['default'] = $filtered; - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->assertText(t('Default format updated.'), t('Default filter updated successfully.')); - - $this->assertNoRaw('admin/config/content/formats/' . $filtered . '/delete', t('Delete link not found.')); - // Allow authenticated users on full HTML. + $format = filter_format_load($full); $edit = array(); $edit['roles[1]'] = 0; $edit['roles[2]'] = 1; $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); - $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully updated.')); + $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.')); // Switch user. $this->drupalLogout(); @@ -110,13 +102,14 @@ class FilterAdminTestCase extends Drupal $this->assertRaw('', t('Full HTML filter accessible.')); // Use filtered HTML and see if it removes tags that are not allowed. - $body = $this->randomName(); + $body = '' . $this->randomName() . ''; $extra_text = 'text'; + $text = $body . '' . $extra_text . ''; $edit = array(); $edit['title'] = $this->randomName(); $langcode = FIELD_LANGUAGE_NONE; - $edit["body[$langcode][0][value]"] = $body . '' . $extra_text . ''; + $edit["body[$langcode][0][value]"] = $text; $edit["body[$langcode][0][value_format]"] = $filtered; $this->drupalPost('node/add/page', $edit, t('Save')); $this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.')); @@ -125,7 +118,14 @@ class FilterAdminTestCase extends Drupal $this->assertTrue($node, t('Node found in database.')); $this->drupalGet('node/' . $node->nid); - $this->assertText($body . $extra_text, t('Filter removed invalid tag.')); + $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.')); + + // Use plain text and see if it escapes all tags, whether allowed or not. + $edit = array(); + $edit["body[$langcode][0][value_format]"] = $plain; + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->drupalGet('node/' . $node->nid); + $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.')); // Switch user. $this->drupalLogout(); @@ -142,7 +142,7 @@ class FilterAdminTestCase extends Drupal $edit = array(); $edit['roles[2]'] = FALSE; $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); - $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully reverted.')); + $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.')); // Filter order. $edit = array(); @@ -153,16 +153,17 @@ class FilterAdminTestCase extends Drupal } /** - * Query the database to get the two basic formats. + * Query the database to get the three basic formats. * * @return - * An array containing filtered and full filter ids. + * An array containing filtered, full, and plain text format ids. */ function checkFilterFormats() { $result = db_query('SELECT format, name FROM {filter_format}'); $filtered = -1; $full = -1; + $plain = -1; foreach ($result as $format) { if ($format->name == 'Filtered HTML') { $filtered = $format->format; @@ -170,9 +171,12 @@ class FilterAdminTestCase extends Drupal elseif ($format->name == 'Full HTML') { $full = $format->format; } + elseif ($format->name == 'Plain text') { + $plain = $format->format; + } } - return array($filtered, $full); + return array($filtered, $full, $plain); } /** @@ -188,6 +192,184 @@ class FilterAdminTestCase extends Drupal } } +class FilterAccessTestCase extends DrupalWebTestCase { + protected $admin_user; + protected $web_user; + protected $allowed_format; + protected $disallowed_format; + + public static function getInfo() { + return array( + 'name' => 'Filter access functionality', + 'description' => 'Test the filter access system.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp(); + + // Create two text formats and grant a regular user access to one of them. + $this->admin_user = $this->drupalCreateUser(array('administer filters')); + $this->drupalLogin($this->admin_user); + $formats = array(); + for ($i = 0; $i < 2; $i++) { + $edit = array('name' => $this->randomName()); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->resetFilterCaches(); + $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField(); + $formats[] = filter_format_load($format_id); + } + list($this->allowed_format, $this->disallowed_format) = $formats; + $this->web_user = $this->drupalCreateUser(array('create page content', filter_permission_name($this->allowed_format))); + } + + function testFormatPermissions() { + // Make sure that a regular user only has access to the text format they + // were granted access to, as well to the fallback format. + $this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.')); + $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.')); + $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.')); + + // Perform similar checks as above, but now against the entire list of + // available formats for this user. + $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.')); + $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.')); + $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.')); + + // Make sure that a regular user only has permission to use the format + // they were granted access to. + $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.')); + $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.')); + + // Make sure that the allowed format appears on the node form and that + // the disallowed format does not. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/add/page'); + $this->assertRaw($this->formatSelectorHTML($this->allowed_format), t('The allowed text format appears as an option when adding a new node.')); + $this->assertNoRaw($this->formatSelectorHTML($this->disallowed_format), t('The disallowed text format does not appear as an option when adding a new node.')); + $this->assertRaw($this->formatSelectorHTML(filter_format_load(filter_fallback_format())), t('The fallback format appears as an option when adding a new node.')); + } + + function testFormatRoles() { + // Get the role ID assigned to the regular user; it must be the maximum. + $rid = max(array_keys($this->web_user->roles)); + + // Check that this role appears in the list of roles that have access to an + // allowed text format, but does not appear in the list of roles that have + // access to a disallowed text format. + $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.')); + $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.')); + + // Check that the correct text format appears in the list of formats + // available to that role. + $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.')); + $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.')); + + // Check that the fallback format is always allowed. + $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.')); + $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.')); + } + + /** + * Returns the expected HTML for a particular text format selector. + * + * @param $format + * An object representing the text format for which to return HTML. + * @return + * The expected HTML for that text format's selector. + */ + function formatSelectorHTML($format) { + return ''; + } + + /** + * Rebuild text format and permission caches in the thread running the tests. + */ + protected function resetFilterCaches() { + filter_formats_reset(); + $this->checkPermissions(array(), TRUE); + } +} + +class FilterDefaultFormatTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Default text format functionality', + 'description' => 'Test the default text formats for different users.', + 'group' => 'Filter', + ); + } + + function testDefaultTextFormats() { + // Create two text formats, and two users. The first user has access to + // both formats, but the second user only has access to the second one. + $admin_user = $this->drupalCreateUser(array('administer filters')); + $this->drupalLogin($admin_user); + $formats = array(); + for ($i = 0; $i < 2; $i++) { + $edit = array('name' => $this->randomName()); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->resetFilterCaches(); + $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField(); + $formats[] = filter_format_load($format_id); + } + list($first_format, $second_format) = $formats; + $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format))); + $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format))); + + // Adjust the weights so that the first and second formats (in that order) + // are the two lowest weighted formats available to any user. + $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField(); + $edit = array(); + $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2; + $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1; + $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); + $this->resetFilterCaches(); + + // Check that each user's default format is the lowest weighted format that + // the user has access to. + $this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to.")); + $this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's.")); + + // Reorder the two formats, and check that both users now have the same + // default. + $edit = array(); + $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3; + $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); + $this->resetFilterCaches(); + $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.')); + } + + /** + * Rebuild text format and permission caches in the thread running the tests. + */ + protected function resetFilterCaches() { + filter_formats_reset(); + $this->checkPermissions(array(), TRUE); + } +} + +class FilterNoFormatTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Unassigned text format functionality', + 'description' => 'Test the behavior of check_markup() when it is called without a text format.', + 'group' => 'Filter', + ); + } + + function testCheckMarkupNoFormat() { + // Create some text. Include some HTML and line breaks, so we get a good + // test of the filtering that is applied to it. + $text = "" . $this->randomName(32) . "\n\n
" . $this->randomName(32) . "
"; + + // Make sure that when this text is run through check_markup() with no text + // format, it is filtered as though it is in the fallback format. + $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.')); + } +} + /** * Unit tests for core filters. */ @@ -805,7 +987,7 @@ class FilterHooksTestCase extends Drupal $edit = array(); $edit['roles[2]'] = 1; $this->drupalPost('admin/config/content/formats/' . $format, $edit, t('Save configuration')); - $this->assertRaw(t('The text format settings have been updated.'), t('Full HTML format successfully updated.')); + $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. @@ -827,14 +1009,13 @@ class FilterHooksTestCase extends Drupal $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 default format. + // 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) ->execute() ->fetchField(); - $default = variable_get('filter_default_format', 1); - $this->assertEqual($current_format, $default, t('Deleted text format replaced with the default format.')); + $this->assertEqual($current_format, filter_fallback_format(), t('Deleted text format replaced with the fallback format.')); } } Index: modules/node/node.install =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.install,v retrieving revision 1.30 diff -u -p -r1.30 node.install --- modules/node/node.install 17 Sep 2009 03:12:40 -0000 1.30 +++ modules/node/node.install 19 Sep 2009 21:34:05 -0000 @@ -506,7 +506,11 @@ function node_update_7006(&$context) { $revision->body = substr($revision->body, strlen($break)); } $node->body[FIELD_LANGUAGE_NONE][0]['value'] = $revision->body; - $node->body[FIELD_LANGUAGE_NONE][0]['format'] = $revision->format; + // 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[FIELD_LANGUAGE_NONE][0]['format'] = !empty($revision->format) ? $revision->format : variable_get('filter_default_format', 1); // 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()); Index: modules/php/php.install =================================================================== RCS file: /cvs/drupal/drupal/modules/php/php.install,v retrieving revision 1.13 diff -u -p -r1.13 php.install --- modules/php/php.install 18 Sep 2009 00:04:23 -0000 1.13 +++ modules/php/php.install 19 Sep 2009 21:34:05 -0000 @@ -19,7 +19,6 @@ function php_install() { $format = db_insert('filter_format') ->fields(array( 'name' => 'PHP code', - 'roles' => '', 'cache' => 0, )) ->execute(); Index: modules/php/php.test =================================================================== RCS file: /cvs/drupal/drupal/modules/php/php.test,v retrieving revision 1.16 diff -u -p -r1.16 php.test --- modules/php/php.test 28 Aug 2009 16:23:04 -0000 1.16 +++ modules/php/php.test 19 Sep 2009 21:34:05 -0000 @@ -5,6 +5,8 @@ * Base PHP test case class. */ class PHPTestCase extends DrupalWebTestCase { + protected $php_code_format; + function setUp() { parent::setUp('php'); @@ -12,9 +14,14 @@ class PHPTestCase extends DrupalWebTestC $admin_user = $this->drupalCreateUser(array('administer filters')); $this->drupalLogin($admin_user); - // Confirm that the PHP filter is #3. - $this->drupalGet('admin/config/content/formats/3'); - $this->assertText('PHP code', t('On PHP code filter page.')); + // Confirm that the PHP code text format was inserted as the newest format + // on the site. + $newest_format_id = db_query("SELECT MAX(format) FROM {filter_format}")->fetchField(); + $newest_format = filter_format_load($newest_format_id); + $this->assertEqual($newest_format->name, 'PHP code', t('PHP code text format was created.')); + + // Store the format ID of the PHP code text format for later use. + $this->php_code_format = $newest_format_id; } /** @@ -43,15 +50,12 @@ class PHPFilterTestCase extends PHPTestC * Make sure that the PHP filter evaluates PHP code when used. */ function testPHPFilter() { - // Setup PHP filter. - $edit = array(); - $edit['roles[2]'] = TRUE; // Set authenticated users to have permission to use filter. - $this->drupalPost(NULL, $edit, 'Save configuration'); - $this->assertText(t('The text format settings have been updated.'), t('PHP format available to authenticated users.')); - - // Create node with PHP filter enabled. - $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content')); + // Log in as a user with permission to use the PHP code text format. + $php_code_permission = filter_permission_name(filter_format_load($this->php_code_format)); + $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission)); $this->drupalLogin($web_user); + + // Create a node with PHP code in it. $node = $this->createNodeWithCode(); // Make sure that the PHP code shows up as text. @@ -61,7 +65,7 @@ class PHPFilterTestCase extends PHPTestC // Change filter to PHP filter and see that PHP code is evaluated. $edit = array(); $langcode = FIELD_LANGUAGE_NONE; - $edit["body[$langcode][0][value_format]"] = 3; + $edit["body[$langcode][0][value_format]"] = $this->php_code_format; $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.')); @@ -98,6 +102,6 @@ class PHPAccessTestCase extends PHPTestC // Make sure that user doesn't have access to filter. $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertNoFieldByName('body_format', '3', t('Format not available.')); + $this->assertNoRaw('