Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.368
diff -u -r1.368 form.inc
--- includes/form.inc 29 Aug 2009 16:30:14 -0000 1.368
+++ includes/form.inc 30 Aug 2009 00:19:52 -0000
@@ -1893,7 +1893,7 @@
* $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: profiles/default/default.install
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.install,v
retrieving revision 1.2
diff -u -r1.2 default.install
--- profiles/default/default.install 27 Aug 2009 20:25:29 -0000 1.2
+++ profiles/default/default.install 30 Aug 2009 00:21:50 -0000
@@ -184,9 +184,8 @@
db_insert('taxonomy_vocabulary_node_type')->fields(array('vid' => $vid, 'type' => 'article'))->execute();
// Enable default permissions for system roles.
- user_role_set_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
- user_role_set_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'post comments without approval'));
-
+ user_role_set_permissions(DRUPAL_ANONYMOUS_RID, array('access content', 'use text format 1'));
+ user_role_set_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'post comments without approval', 'use text format 1'));
// Create a default role for site administrators, with all available permissions assigned.
$admin_role = new stdClass();
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.371
diff -u -r1.371 block.module
--- modules/block/block.module 29 Aug 2009 05:46:02 -0000 1.371
+++ modules/block/block.module 30 Aug 2009 00:19:57 -0000
@@ -205,7 +205,7 @@
* 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);
}
@@ -398,7 +398,7 @@
'#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,
@@ -867,9 +867,9 @@
/**
* 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/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.25
diff -u -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 30 Aug 2009 00:20:15 -0000
@@ -558,14 +558,14 @@
'#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 @@
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 @@
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 @@
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 @@
// 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 -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 30 Aug 2009 00:20:20 -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 @@
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 @@
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 @@
$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/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.145
diff -u -r1.145 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php 27 Aug 2009 20:25:28 -0000 1.145
+++ modules/simpletest/drupal_web_test_case.php 30 Aug 2009 00:21:17 -0000
@@ -728,7 +728,7 @@
// Merge body field value and format separately.
$body = array(
'value' => $this->randomName(32),
- 'format' => FILTER_FORMAT_DEFAULT
+ 'format' => filter_default_format(),
);
$settings['body'][FIELD_LANGUAGE_NONE][0] += $body;
Index: modules/filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.286
diff -u -r1.286 filter.module
--- modules/filter/filter.module 29 Aug 2009 03:55:44 -0000 1.286
+++ modules/filter/filter.module 30 Aug 2009 00:20:38 -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 @@
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 @@ '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 @@ } /** + * 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 @@ * 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 @@ * 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 @@ 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 @@ ->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,44 @@ * 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 = array(); + $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 the provided text format. + * + * @param $format + * An object representing the text format. + * @return + * The machine-readable permission name, or FALSE if the provided text format + * is either (a) malformed, or (b) the fallback format available to all users + * (and therefore not controlled by the permission system). + */ +function filter_permission_name($format) { + if (isset($format->format) && $format->format != filter_fallback_format()) { + return 'use text format ' . $format->format; + } + return FALSE; } /** @@ -386,45 +417,151 @@ /** * @} 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; - - // Administrators can always use all text formats. - $all = user_access('administer filters'); +function filter_formats($account = NULL) { + $formats = &drupal_static(__FUNCTION__, array()); - 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 + * Either the format ID or format object that will be checked for access. + * @return + * An array of role names, keyed by role ID. + */ +function filter_format_roles($format) { + // Handle the fallback format up front (all roles have access to this + // format). + $format_id = isset($format->format) ? $format->format : $format; + if ($format_id == filter_fallback_format()) { + return user_roles(); } - if (isset($index)) { - return isset($formats[$index]) ? $formats[$index] : FALSE; + // Otherwise, retrieve the full format object if one was not provided. + if (!isset($format->format)) { + $format = filter_format_load($format); + } + // 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 ID of the role to check. + * @return + * An array of text format objects that are allowed for the role, keyed by + * the format ID and ordered by weight. + */ +function filter_role_formats($rid) { + $formats = array(); + foreach (filter_formats() as $format) { + $roles = filter_format_roles($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 usually 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 as their + * 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 current 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. + $formats = filter_formats($account); + $first_format = array_shift($formats); + return $first_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 +591,10 @@ } /** - * 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 +666,8 @@ * @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 +677,10 @@ * 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 +731,16 @@ * @return * HTML for the form element. */ -function filter_form($value = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) { - $value = filter_resolve_format($value); - $formats = filter_formats(); +function filter_form($value = NULL, $weight = NULL, $parents = array('format')) { + global $user; + + // Use the default format for this user if none was selected. + if (empty($value)) { + $value = 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 +783,35 @@ } /** - * Returns TRUE if the user is allowed to access this format. + * Checks if a user has access to a particular text format. + * + * @param $format + * Either the format ID or format object that will be checked for access. + * @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. + $format_id = isset($format->format) ? $format->format : $format; + if (user_access('administer filters', $account) || $format_id == filter_fallback_format()) { + return TRUE; } + // Otherwise, retrieve the full format object if a format ID was provided. + if (!isset($format->format)) { + $format = filter_format_load($format); + } + // Check the permission if one exists; otherwise, we have a nonexistent + // format so we return FALSE. + $permission = filter_permission_name($format); + return !empty($permission) && user_access($permission, $account); } /** @@ -666,7 +823,9 @@ * 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 +1161,3 @@ /** * @} 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.install =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.install,v retrieving revision 1.19 diff -u -r1.19 filter.install --- modules/filter/filter.install 27 Aug 2009 21:18:19 -0000 1.19 +++ modules/filter/filter.install 30 Aug 2009 00:20:26 -0000 @@ -77,13 +77,6 @@ '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 @@ } /** + * @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,91 @@ 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, start with 'Plain text'. + $start_name = 'Plain text'; + $id = ''; + do { + $format_name = $start_name . $id; + $format = db_query('SELECT format FROM {filter_format} WHERE name = ' . $format_name)->fetchField(); + $id = empty($id) ? 1 : $id++; + } while (!$format); + + $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(); + } + } + + // Note: We do not delete the 'filter_default_format' variable, since other + // modules may need it in their update functions. + // @TODO: This variable can probably 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.test =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v retrieving revision 1.39 diff -u -r1.39 filter.test --- modules/filter/filter.test 29 Aug 2009 03:55:44 -0000 1.39 +++ modules/filter/filter.test 30 Aug 2009 00:20:49 -0000 @@ -21,18 +21,17 @@ // 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 @@ // 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 @@ $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 @@ $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 . '