Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v retrieving revision 1.340 diff -u -p -r1.340 CHANGELOG.txt --- CHANGELOG.txt 5 Sep 2009 06:03:29 -0000 1.340 +++ CHANGELOG.txt 8 Sep 2009 04:09:59 -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.370 diff -u -p -r1.370 form.inc --- includes/form.inc 5 Sep 2009 15:05:01 -0000 1.370 +++ includes/form.inc 8 Sep 2009 04:10:00 -0000 @@ -1901,7 +1901,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.373 diff -u -p -r1.373 block.module --- modules/block/block.module 31 Aug 2009 17:06:08 -0000 1.373 +++ modules/block/block.module 8 Sep 2009 04:10:00 -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); } @@ -349,12 +349,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; @@ -798,9 +798,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.767 diff -u -p -r1.767 comment.module --- modules/comment/comment.module 5 Sep 2009 15:05:02 -0000 1.767 +++ modules/comment/comment.module 8 Sep 2009 04:10:00 -0000 @@ -1810,7 +1810,7 @@ function comment_form(&$form_state, $com '#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, ); @@ -2436,9 +2436,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.25 diff -u -p -r1.25 text.module --- modules/field/modules/text/text.module 27 Aug 2009 00:33:52 -0000 1.25 +++ modules/field/modules/text/text.module 8 Sep 2009 04:10:00 -0000 @@ -558,14 +558,14 @@ function text_elements() { '#columns' => array('value', 'format'), '#delta' => 0, '#process' => array('text_textarea_elements_process'), '#theme_wrappers' => array('text_textarea'), - '#filter_value' => FILTER_FORMAT_DEFAULT, + '#filter_value' => filter_default_format(), ), 'text_textarea_with_summary' => array( '#input' => TRUE, '#columns' => array('value', 'format', 'summary'), '#delta' => 0, '#process' => array('text_textarea_with_summary_process'), '#theme_wrappers' => array('text_textarea'), - '#filter_value' => FILTER_FORMAT_DEFAULT, + '#filter_value' => filter_default_format(), ), ); } @@ -667,7 +667,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; } @@ -700,7 +700,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; } @@ -748,7 +748,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; } @@ -769,7 +769,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 8 Sep 2009 04:10:00 -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.43 diff -u -p -r1.43 filter.admin.inc --- modules/filter/filter.admin.inc 5 Sep 2009 13:22:13 -0000 1.43 +++ modules/filter/filter.admin.inc 8 Sep 2009 04:10:00 -0000 @@ -7,47 +7,42 @@ */ /** - * 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_format_form_validate() * @see filter_admin_overview_submit() */ function filter_admin_overview() { - // Overview of all formats. $formats = filter_formats(); - $error = FALSE; + $fallback_format = filter_fallback_format(); $form = array('#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') @@ -56,35 +51,34 @@ function filter_admin_overview_submit($f ->execute(); } } + filter_format_reset_cache(); 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]; + foreach (element_children($form['formats']) as $id) { + $element = &$form['formats'][$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['name']), 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]); } } - $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); @@ -99,7 +93,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); } @@ -112,10 +106,9 @@ function filter_admin_format_page($forma * @see filter_admin_format_form_submit() */ function filter_admin_format_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('#type' => 'textfield', @@ -128,17 +121,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,6 +193,22 @@ 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)) { + $grant = array(); + foreach ($format->roles as $rid => $enabled) { + // @todo http://drupal.org/node/300993 + if ($enabled) { + user_role_set_permissions($rid, array($permission), TRUE); + } + else { + db_delete('role_permission') + ->condition('rid', $rid) + ->condition('permission', $permission) + ->execute(); + } + } + } + switch ($status) { case SAVED_NEW: drupal_set_message(t('Added text format %format.', array('%format' => $format->name))); @@ -219,26 +227,15 @@ function filter_admin_format_form_submit * @see filter_admin_delete_submit() */ function filter_admin_delete(&$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.13 diff -u -p -r1.13 filter.api.php --- modules/filter/filter.api.php 27 Aug 2009 21:18:19 -0000 1.13 +++ modules/filter/filter.api.php 8 Sep 2009 04:10:00 -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 8 Sep 2009 04:10:00 -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,89 @@ 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_set_permissions($format_role, array(filter_permission_name($format)), TRUE); + } + } + } + + // Drop the roles field from the {filter_format} table. + db_drop_field($ret, 'filter_format', 'roles'); + + // Add a fallback text format which appears last on the list for all users. + // This format should output plain text, so we escape all HTML and apply the + // line break filter only. + // Generate a unique name for the fallback text format, 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; + $fallback_format->filters = array( + 'filter_html_escape' => 1, + 'filter_autop' => 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.287 diff -u -p -r1.287 filter.module --- modules/filter/filter.module 30 Aug 2009 06:04:09 -0000 1.287 +++ modules/filter/filter.module 8 Sep 2009 04:10:00 -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. @@ -211,6 +208,7 @@ function filter_format_save($format) { module_invoke_all('filter_format_update', $format); } + filter_format_reset_cache(); cache_clear_all($format->format . ':', 'cache_filter', TRUE); return $return; @@ -231,9 +229,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_format_reset_cache(); cache_clear_all($format->format . ':', 'cache_filter', TRUE); } @@ -248,12 +247,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; } /** @@ -386,45 +415,144 @@ function _filter_html_escape_tips($filte /** * @} End of "Tips callback for filters". */ + /** - * Retrieve a list of text formats. + * Retrieve a list of text formats, ordered by weight. + * + * @param $account + * (optional) If provided, only those formats that are allowed for this user + * account will be returned. All formats will be returned otherwise. + * @return + * An array of text format objects, keyed by the format ID and ordered by + * weight. + * + * @see filter_format_reset_cache() */ -function filter_formats($index = NULL) { - global $user; - static $formats; +function filter_formats($account = NULL) { + $formats = &drupal_static(__FUNCTION__, array()); - // Administrators can always use all text formats. - $all = user_access('administer filters'); - - if (!isset($formats)) { - $formats = array(); - - $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'); + // Check if we've already loaded format data before doing a query. + if (!isset($formats['all'])) { + $formats['all'] = db_query('SELECT * FROM {filter_format} ORDER BY weight')->fetchAllAssoc('format'); + } + + // Build a list of user-specific formats if not already set. + 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); } + } - $formats = $query->execute()->fetchAllAssoc('format'); + return isset($account) ? $formats['user'][$account->uid] : $formats['all']; +} + +/** + * Resets the static cache of all text formats. + * + * @see filter_formats() + */ +function filter_format_reset_cache() { + drupal_static_reset('filter_formats'); +} + +/** + * Retrieves a list of roles that are allowed to use a particular 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 up front (all roles have access to this + // format). + if ($format->format == filter_fallback_format()) { + return user_roles(); } - if (isset($index)) { - return isset($formats[$index]) ? $formats[$index] : FALSE; + // Don't list any roles if the permission doesn't exist. + $permission = filter_permission_name($format); + return !empty($permission) ? user_roles(FALSE, $permission) : array(); +} + +/** + * Retrieves a list of text formats that are allowed for a particular 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; + } } 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; + } + // 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); +} + +/** * Return a list of all filters provided by modules. */ function filter_get_filters() { @@ -454,17 +582,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(); } @@ -536,8 +657,8 @@ function filter_list_format($format) { * @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 @@ -547,8 +668,10 @@ function filter_list_format($format) { * 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); @@ -599,9 +722,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'); @@ -644,17 +774,30 @@ function filter_form($selected_format = } /** - * Returns TRUE if the user is allowed to access this format. + * Checks if a user has access to a particular text format. + * + * @param $format + * An object representing the text format. + * @param $account + * The user object that access will be checked against. Defaults to the + * current logged-in user. + * @return + * Boolean TRUE if the user has access to the requested format. */ -function filter_access($format) { - $format = filter_resolve_format($format); - if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) { - return TRUE; +function filter_access($format, $account = NULL) { + global $user; + if (!isset($account)) { + $account = $user; } - else { - $formats = filter_formats(); - return isset($formats[$format]); + // 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); } /** @@ -666,7 +809,9 @@ function filter_access($format) { * 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(); @@ -1002,15 +1147,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.39 diff -u -p -r1.39 filter.test --- modules/filter/filter.test 29 Aug 2009 03:55:44 -0000 1.39 +++ modules/filter/filter.test 8 Sep 2009 04:10:01 -0000 @@ -21,18 +21,17 @@ 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(); - // 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.')); + // 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.')); // Add an additional tag. $edit = array(); @@ -64,7 +63,6 @@ class FilterAdminTestCase extends Drupal // Add filter. $edit = array(); $edit['name'] = $this->randomName(); - $edit['roles[2]'] = 1; $edit['filters[' . $second_filter . ']'] = TRUE; $edit['filters[' . $first_filter . ']'] = TRUE; $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); @@ -74,7 +72,6 @@ class FilterAdminTestCase extends Drupal $this->assertNotNull($format, t('Format found in database.')); if ($format !== NULL) { - $this->assertFieldByName('roles[2]', '', t('Role found.')); $this->assertFieldByName('filters[' . $second_filter . ']', '', t('Line break filter found.')); $this->assertFieldByName('filters[' . $first_filter . ']', '', t('Url filter found.')); @@ -83,36 +80,19 @@ 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. - $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.')); - // Switch user. $this->drupalLogout(); $this->drupalLogin($web_user); - $this->drupalGet('node/add/page'); - $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.')); @@ -121,7 +101,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(); @@ -134,12 +121,6 @@ class FilterAdminTestCase extends Drupal $this->drupalPost('admin/config/content/formats/' . $filtered . '/configure', $edit, t('Save configuration')); $this->assertText(t('The configuration options have been saved.'), t('Changes reverted.')); - // Full HTML. - $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.')); - // Filter order. $edit = array(); $edit['weights[' . $second_filter . ']'] = 2; @@ -149,16 +130,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; @@ -166,9 +148,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); } /** @@ -184,6 +169,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_format_reset_cache(); + $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_format_reset_cache(); + $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. */ @@ -790,7 +953,6 @@ class FilterHooksTestCase extends Drupal $name = $this->randomName(); $edit = array(); $edit['name'] = $name; - $edit['roles[1]'] = 1; $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); $this->assertRaw(t('Added text format %format.', array('%format' => $name)), t('New format created.')); $this->assertText('hook_filter_format_insert invoked.', t('hook_filter_format_insert was invoked.')); @@ -798,10 +960,11 @@ class FilterHooksTestCase extends Drupal $format = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $name))->fetchField(); // Update text format. + $name .= 'new name'; $edit = array(); - $edit['roles[2]'] = 1; + $edit['name'] = $name; $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 settings have been updated.'), t('Format successfully updated.')); $this->assertText('hook_filter_format_update invoked.', t('hook_filter_format_update() was invoked.')); // Add a new custom block. @@ -823,14 +986,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.29 diff -u -p -r1.29 node.install --- modules/node/node.install 31 Aug 2009 16:24:15 -0000 1.29 +++ modules/node/node.install 8 Sep 2009 04:10:01 -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.12 diff -u -p -r1.12 php.install --- modules/php/php.install 28 Aug 2009 16:23:04 -0000 1.12 +++ modules/php/php.install 8 Sep 2009 04:10:01 -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 8 Sep 2009 04:10:01 -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('