Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.301 diff -u -p -r1.301 form.inc --- includes/form.inc 3 Nov 2008 09:54:43 -0000 1.301 +++ includes/form.inc 4 Nov 2008 23:44:30 -0000 @@ -1790,7 +1790,7 @@ function form_process_radios($element) { * $form['body'] = array( * '#type' => 'textarea', * '#title' => t('Body'), - * '#input_format' => isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT, + * '#input_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.312 diff -u -p -r1.312 block.module --- modules/block/block.module 9 Oct 2008 15:15:50 -0000 1.312 +++ modules/block/block.module 4 Nov 2008 23:44:30 -0000 @@ -198,7 +198,7 @@ function block_block($op = 'list', $delt return $blocks; case 'configure': - $box = array('format' => FILTER_FORMAT_DEFAULT); + $box = array('format' => filter_default_format()); if ($delta) { $box = block_box_get($delta); } @@ -321,7 +321,7 @@ function block_box_form($edit = array()) '#type' => 'textarea', '#title' => t('Block body'), '#default_value' => $edit['body'], - '#input_format' => isset($edit['format']) ? $edit['format'] : FILTER_FORMAT_DEFAULT, + '#input_format' => isset($edit['format']) ? $edit['format'] : filter_default_format(), '#rows' => 15, '#description' => t('The content of the block as shown to the user.'), '#weight' => -17, @@ -332,7 +332,7 @@ function block_box_form($edit = array()) function block_box_save($edit, $delta) { if (!filter_access($edit['body_format'])) { - $edit['body_format'] = FILTER_FORMAT_DEFAULT; + $edit['body_format'] = filter_default_format(); } db_query("UPDATE {boxes} SET body = '%s', info = '%s', format = %d WHERE bid = %d", $edit['body'], $edit['info'], $edit['body_format'], $delta); Index: modules/blogapi/blogapi.module =================================================================== RCS file: /cvs/drupal/drupal/modules/blogapi/blogapi.module,v retrieving revision 1.132 diff -u -p -r1.132 blogapi.module --- modules/blogapi/blogapi.module 12 Oct 2008 02:58:23 -0000 1.132 +++ modules/blogapi/blogapi.module 4 Nov 2008 23:44:30 -0000 @@ -202,7 +202,7 @@ function blogapi_blogger_new_post($appke $edit['promote'] = in_array('promote', $node_type_default); $edit['comment'] = variable_get('comment_' . $edit['type'], 2); $edit['revision'] = in_array('revision', $node_type_default); - $edit['format'] = FILTER_FORMAT_DEFAULT; + $edit['format'] = filter_default_format($user); $edit['status'] = $publish; // Check for bloggerAPI vs. metaWeblogAPI. @@ -622,7 +622,7 @@ function blogapi_mt_validate_terms($node function blogapi_mt_supported_text_filters() { // NOTE: we're only using anonymous' formats because the MT spec // does not allow for per-user formats. - $formats = filter_formats(); + $formats = filter_formats(drupal_anonymous_user()); $filters = array(); foreach ($formats as $format) { Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.660 diff -u -p -r1.660 comment.module --- modules/comment/comment.module 1 Nov 2008 19:51:06 -0000 1.660 +++ modules/comment/comment.module 4 Nov 2008 23:44:30 -0000 @@ -1371,7 +1371,7 @@ function comment_form(&$form_state, $edi '#title' => t('Comment'), '#rows' => 15, '#default_value' => $default, - '#input_format' => isset($edit['format']) ? $edit['format'] : FILTER_FORMAT_DEFAULT, + '#input_format' => isset($edit['format']) ? $edit['format'] : filter_default_format(), '#required' => TRUE, ); Index: modules/filter/filter.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v retrieving revision 1.16 diff -u -p -r1.16 filter.admin.inc --- modules/filter/filter.admin.inc 13 Oct 2008 00:33:02 -0000 1.16 +++ modules/filter/filter.admin.inc 4 Nov 2008 23:44:30 -0000 @@ -21,32 +21,20 @@ function filter_admin_overview() { $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 (strstr($format->roles, ",$rid,")) { - $roles[] = $name; - } - } - $default = ($id == variable_get('filter_default_format', 1)); + $roles = filter_format_roles($format); + $fallback = ($id == filter_fallback_format()); $options[$id] = ''; $form[$id]['name'] = array('#markup' => $format->name); - $form[$id]['roles'] = array('#markup' => $default ? t('All roles may use default format') : ($roles ? implode(', ', $roles) : t('No roles may use this format'))); - $form[$id]['configure'] = array('#markup' => l(t('configure'), 'admin/settings/filters/' . $id)); - $form[$id]['delete'] = array('#markup' => $default ? '' : l(t('delete'), 'admin/settings/filters/delete/' . $id)); + $form[$id]['roles'] = array('#markup' => $fallback ? theme('placeholder', t('All roles may use this format')) : ($roles ? implode(', ', $roles) : t('No roles may use this format'))); + $form[$id]['configure'] = array('#markup' => $fallback ? '' : l(t('configure'), 'admin/settings/filters/' . $id)); + $form[$id]['delete'] = array('#markup' => $fallback ? '' : l(t('delete'), 'admin/settings/filters/delete/' . $id)); $form[$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) { if (is_array($data) && isset($data['weight'])) { // Only update if this is a form element with weight. @@ -70,7 +58,6 @@ function theme_filter_admin_overview($fo 'data' => array( check_plain($element['name']['#markup']), drupal_render($element['roles']), - drupal_render($form['default'][$id]), drupal_render($element['weight']), drupal_render($element['configure']), drupal_render($element['delete']), @@ -80,7 +67,7 @@ function theme_filter_admin_overview($fo 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' => 'input-format-order')); $output .= drupal_render($form); @@ -95,7 +82,7 @@ function theme_filter_admin_overview($fo function filter_admin_format_page($format = NULL) { if (!isset($format->name)) { drupal_set_title(t('Add input format'), PASS_THROUGH); - $format = (object)array('name' => '', 'roles' => '', 'format' => ''); + $format = (object)array('name' => '', 'format' => ''); } return drupal_get_form('filter_admin_format_form', $format); } @@ -108,12 +95,6 @@ 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); - } - $form['name'] = array('#type' => 'textfield', '#title' => t('Name'), '#default_value' => $format->name, @@ -121,23 +102,6 @@ function filter_admin_format_form(&$form '#required' => TRUE, ); - // 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 filter format. Note that roles with the "administer filters" permission can always use all the filter formats.'), - '#tree' => TRUE, - ); - - foreach (user_roles() as $rid => $name) { - $checked = strstr($format->roles, ",$rid,"); - $form['roles'][$rid] = array('#type' => 'checkbox', - '#title' => $name, - '#default_value' => ($default || $checked), - ); - if ($default) { - $form['roles'][$rid]['#disabled'] = TRUE; - } - } // Table with filters $all = filter_list_all(); $enabled = filter_list_format($format->format); @@ -195,7 +159,7 @@ function filter_admin_format_form_submit $name = trim($form_state['values']['name']); $cache = TRUE; - // Add a new filter format. + // Add a new input format. if (!$format) { $new = TRUE; db_query("INSERT INTO {filter_formats} (name) VALUES ('%s')", $name); @@ -219,25 +183,7 @@ function filter_admin_format_form_submit } } - // We store the roles as a string for ease of use. - // We should always set all roles to TRUE when saving a default role. - // We use leading and trailing comma's to allow easy substring matching. - $roles = array(); - if (isset($form_state['values']['roles'])) { - foreach ($form_state['values']['roles'] as $id => $checked) { - if ($checked) { - $roles[] = $id; - } - } - } - if (!empty($form_state['values']['default_format'])) { - $roles = ',' . implode(',', array_keys(user_roles())) . ','; - } - else { - $roles = ',' . implode(',', $roles) . ','; - } - - db_query("UPDATE {filter_formats} SET cache = %d, name='%s', roles = '%s' WHERE format = %d", $cache, $name, $roles, $format); + db_query("UPDATE {filter_formats} SET cache = %d, name='%s' WHERE format = %d", $cache, $name, $format); cache_clear_all($format . ':', 'cache_filter', TRUE); @@ -261,19 +207,19 @@ function filter_admin_delete() { $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format)); if ($format) { - if ($format->format != variable_get('filter_default_format', 1)) { + if ($format->format != filter_fallback_format()) { $form['format'] = array('#type' => 'hidden', '#value' => $format->format); $form['name'] = array('#type' => 'hidden', '#value' => $format->name); - return confirm_form($form, t('Are you sure you want to delete the input format %format?', array('%format' => $format->name)), 'admin/settings/filters', t('If you have any content left in this input format, it will be switched to the default input format. This action cannot be undone.'), t('Delete'), t('Cancel')); + return confirm_form($form, t('Are you sure you want to delete the input format %format?', array('%format' => $format->name)), 'admin/settings/filters', t('If you have any content left in this input format, it will be converted to plain text. This action cannot be undone.'), t('Delete'), t('Cancel')); } else { - drupal_set_message(t('The default format cannot be deleted.')); + drupal_set_message(t('The %format format cannot be deleted.', array('%format' => $format->name))); drupal_goto('admin/settings/filters'); } } else { - drupal_not_found(); + return drupal_not_found(); } } @@ -284,11 +230,12 @@ function filter_admin_delete_submit($for db_query("DELETE FROM {filter_formats} WHERE format = %d", $form_state['values']['format']); db_query("DELETE FROM {filters} WHERE format = %d", $form_state['values']['format']); - $default = variable_get('filter_default_format', 1); - // Replace existing instances of the deleted format with the default format. - db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $default, $form_state['values']['format']); - db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $form_state['values']['format']); - db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $form_state['values']['format']); + $fallback = filter_fallback_format(); + // Replace existing instances of the deleted format with the fallback + // (plain text) format. + db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $fallback, $form_state['values']['format']); + db_query("UPDATE {comments} SET format = %d WHERE format = %d", $fallback, $form_state['values']['format']); + db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $fallback, $form_state['values']['format']); cache_clear_all($form_state['values']['format'] . ':', 'cache_filter', TRUE); drupal_set_message(t('Deleted input format %format.', array('%format' => $form_state['values']['name']))); Index: modules/filter/filter.install =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.install,v retrieving revision 1.9 diff -u -p -r1.9 filter.install --- modules/filter/filter.install 14 Apr 2008 17:48:37 -0000 1.9 +++ modules/filter/filter.install 4 Nov 2008 23:44:30 -0000 @@ -64,13 +64,6 @@ function filter_schema() { 'default' => '', 'description' => t('Name of the input format (Filtered HTML).'), ), - 'roles' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => t('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, @@ -99,6 +92,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() { @@ -122,3 +120,42 @@ function filter_update_7001() { } return $ret; } + +/** + * Move filter format access to the user permissions handler, and create a + * fallback plain text input format. + */ +function filter_update_7002() { + $ret = array(); + + // Move role data from filter_formats to user permissions. + $all_roles = array_keys(user_roles()); + $result = db_query("SELECT format, roles FROM {filter_formats}"); + while ($format = db_fetch_object($result)) { + $format_roles = explode(',', $format->roles); + foreach ($format_roles as $format_role) { + if (in_array($format_role, $all_roles)) { + $ret[] = update_sql("INSERT INTO {role_permission} (rid, permission) VALUES (" . $format_role . ", " . filter_permission_name($format) . ")"); + } + } + } + // Drop the roles field from filter_formats. + db_drop_field($ret, 'filter_formats', 'roles'); + + // Add a fallback plain text input format (and make sure it sinks to the + // bottom). + $weight = db_result(db_query("SELECT MAX(weight) FROM {filter_formats}")) + 1; + $ret[] = update_sql("INSERT INTO {filter_formats} (name, cache, weight) VALUES ('Plain text', 1, " . $weight . ")"); + $fallback_format = db_last_insert_id('filter_formats', 'format'); + variable_set('filter_fallback_format', $fallback_format); + // Use the line break filter only (check_plain is hardcoded separately + // for this input format. + $ret[] = update_sql("INSERT INTO {filters} (format, module, delta, weight) VALUES (" . $fallback_format . ", 'filter', 1, 0)"); + + 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.231 diff -u -p -r1.231 filter.module --- modules/filter/filter.module 1 Nov 2008 19:51:06 -0000 1.231 +++ modules/filter/filter.module 4 Nov 2008 23:44:30 -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); - -/** * Implementation of 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 input formats for use on your site. An input 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 input 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 input formats may be created by an administrator.") . '

'; $output .= '

' . t('Each input format uses filters to manipulate text, and most input 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 input format can use the Input format fieldset to choose between available input formats when creating or editing multi-line content. Administrators determine the input formats available to each user role, select a default input format, and control the order of formats listed in the Input format fieldset.') . '

'; - $output .= '

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

'; + $output .= '

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

'; + $output .= '

' . t('The special %plain_text input format is available to all users. Content with this format will be displayed with line and paragraph breaks preserved but otherwise without any visible formatting.', array('%plain_text' => filter_admin_format_title(filter_format_load(filter_fallback_format())))) . '

'; return $output; case 'admin/settings/filters': - $output = '

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

'; - $output .= '

' . t('Since input 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. To change the order of an input format, grab a drag-and-drop handle under the Name column and drag to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) 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 input formats available to each user role and to control the order of formats listed in the Input format fieldset. (The Input format fieldset is displayed below textareas when users with access to more than one input format create multi-line content.) All input formats are available to users in roles with the "Administer filters" permission.') . '

'; + $output .= '

' . t('It may be helpful to arrange the formats in descending order of your preference for their use, since the default input format for each user is the first one on the list which that user has access to. To change the order of an input format, grab a drag-and-drop handle under the Name column and drag to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) 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/settings/filters/%': 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 input format. If you notice some filters are causing conflicts in the output, you can rearrange them.', array('@rearrange' => url('admin/settings/filters/' . $arg[3] . '/order'))) . '

'; @@ -104,7 +96,8 @@ function filter_menu() { 'title arguments' => array(3), 'page callback' => 'filter_admin_format_page', 'page arguments' => array(3), - 'access arguments' => array('administer filters'), + 'access callback' => 'filter_edit_format_access', + 'access arguments' => array(3), ); $items['admin/settings/filters/%filter_format/edit'] = array( 'title' => 'Edit', @@ -115,7 +108,8 @@ function filter_menu() { 'title' => 'Configure', 'page callback' => 'filter_admin_configure_page', 'page arguments' => array(3), - 'access arguments' => array('administer filters'), + 'access callback' => 'filter_edit_format_access', + 'access arguments' => array(3), 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); @@ -123,7 +117,8 @@ function filter_menu() { 'title' => 'Rearrange', 'page callback' => 'filter_admin_order_page', 'page arguments' => array(3), - 'access arguments' => array('administer filters'), + 'access callback' => 'filter_edit_format_access', + 'access arguments' => array(3), 'type' => MENU_LOCAL_TASK, 'weight' => 2, ); @@ -131,7 +126,19 @@ function filter_menu() { } function filter_format_load($arg) { - return filter_formats($arg); + $formats = filter_formats(); + if (isset($formats[$arg])) { + return $formats[$arg]; + } + return FALSE; +} + +/** + * Determine access for editing input formats. + */ +function filter_edit_format_access($format) { + // The fallback plain text format can never be edited. + return user_access('administer filters') && ($format->format != filter_fallback_format()); } /** @@ -145,12 +152,44 @@ function filter_admin_format_title($form * Implementation of hook_perm(). */ function filter_perm() { - return array( - 'administer filters' => array( - 'title' => t('Administer filters'), - 'description' => t('Manage input 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 input 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 input format. Warn the administrator + // that any of them are potentially unsafe. + foreach (filter_formats() as $format) { + $name = filter_permission_name($format); + if ($name) { + // Only link to the input format configuration page if the user who + // is viewing this will have access to that page. + $format_name_replacement = array('!input_format' => (user_access('administer filters') ? l($format->name, 'admin/settings/filters/' . $format->format) : theme('placeholder', $format->name))); + $perms[$name] = array( + 'title' => t("Use the '@input_format' input format", array('@input_format' => $format->name)), + 'description' => t('Use !input_format in forms when entering or editing content. %warning', array_merge($format_name_replacement, array('%warning' => t('Warning: This permission may have security implications depending on how the input format is configured.')))), + ); + } + } + return $perms; +} + +/** + * Returns the machine-readable permission name for the provided input + * format. + * + * @param $format + * An object representing the input format. + * @return + * The machine-readable permission name, or FALSE if the provided input + * 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 input format '. $format->format; + } + return FALSE; } /** @@ -283,41 +322,68 @@ function filter_filter_tips($delta, $for } /** - * Retrieve a list of input formats. - */ -function filter_formats($index = NULL) { - global $user; - static $formats; - - // Administrators can always use all input formats. - $all = user_access('administer filters'); - - if (!isset($formats)) { - $formats = array(); + * Retrieve a list of input 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. + */ +function filter_formats($account = NULL) { + static $formats = array(); + + // Check if we've already loaded format data before doing a query. + if (!isset($formats['all'])) { + $formats['all'] = array(); + $result = db_query('SELECT * FROM {filter_formats} ORDER BY weight'); + while ($format = db_fetch_object($result)) { + $formats['all'][$format->format] = $format; + } + } - $query = db_select('filter_formats', 'f'); - $query->addField('f', 'format', 'format'); - $query->addField('f', 'name', 'name'); - $query->addField('f', 'roles', 'roles'); - $query->addField('f', 'cache', 'cache'); - $query->addField('f', 'weight', 'weight'); - $query->orderBy('weight'); - - // Build query for selecting the format(s) based on the user's roles. - if (!$all) { - $or = db_or()->condition('format', variable_get('filter_default_format', 1)); - foreach ($user->roles as $rid => $role) { - $or->condition('roles', '%'. (int)$rid .'%', 'LIKE'); + // Build a list of user-specific formats if 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); } + } + + return isset($account) ? $formats['user'][$account->uid] : $formats['all']; +} - $formats = $query->execute()->fetchAllAssoc('format'); +/** + * Return the ID of the default input format for a particular user. + * + * @param $account + * Optional. The user account to check. Defaults to the current logged-in + * user. + */ +function filter_default_format($account = NULL) { + global $user; + if (!isset($account)) { + $account = $user; } - if (isset($index)) { - return isset($formats[$index]) ? $formats[$index] : FALSE; + // Get a list of formats for this user, ordered by weight. The first one + // available is that user's default format. + $formats = filter_formats($account); + $first_format = array_shift($formats); + if (isset($first_format) && isset($first_format->format)) { + return $first_format->format; } - return $formats; + // All users have access to the fallback format, so we fall back on it + // here too (however, under normal circumstances this code should never + // be reached). + return filter_fallback_format(); +} + +/** + * Return the ID of the fallback ("plain text") input format that all users + * have access to. + */ +function filter_fallback_format() { + return variable_get('filter_fallback_format', 3); } /** @@ -348,17 +414,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 input 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_result(db_query('SELECT cache FROM {filter_formats} WHERE format = %d', $format)); } @@ -407,8 +466,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 provided, the + * fallback "plain text" format will be used. * @param $check * Whether to check the $format with filter_access() first. Defaults to TRUE. * Note that this will check the permissions of the current user, so you @@ -416,11 +475,12 @@ function filter_list_format($format) { * showing content that is not (yet) stored in the database (eg. upon preview), * set to TRUE so the user's permissions are checked. */ -function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $check = TRUE) { +function check_markup($text, $format, $check = TRUE) { + // Use the fallback format if none was provided. + $format = !isset($format) ? filter_fallback_format() : $format; + // When $check = TRUE, do an access check on $format. if (isset($text) && (!$check || filter_access($format))) { - $format = filter_resolve_format($format); - // Check for a cached version of this piece of text. $cache_id = $format . ':' . md5($text); if ($cached = cache_get($cache_id, 'cache_filter')) { @@ -434,6 +494,22 @@ function check_markup($text, $format = F // so filters only need to deal with one possibility. $text = str_replace(array("\r\n", "\r"), "\n", $text); + // The fallback "plain text" format is available to any user who has + // permission to create content and is therefore an inherent security + // risk if configured incorrectly. To mitigate this risk, we make sure + // to escape all HTML tags here, before allowing any filters to run, + // and thereby attempt to enforce the "plain" nature of this text via a + // hardcoded method. This means that even if a contributed module were + // to allow the site administrator to configure additional filters for + // this input format (Drupal core does not provide a method to do this + // at all), it would still be difficult to make this input format + // unsafe; e.g., if the "PHP evaluator" filter were enabled for this + // input format, that filter (by itself) would not be able to evaluate + // PHP commands, due to the code here. + if ($format == filter_fallback_format()) { + $text = trim(check_plain($text)); + } + // Get a complete list of filters, ordered properly. $filters = filter_list_format($format); @@ -473,9 +549,14 @@ function check_markup($text, $format = F * @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; + + // Get a list of filters that the current user has access to. + $formats = filter_formats($user); + + // Use the default format for this user if none was selected. + $value = !isset($value) ? filter_default_format($user) : $value; $extra = theme('filter_tips_more_info'); @@ -508,7 +589,7 @@ function filter_form($value = FILTER_FOR // Only one format available: use a hidden form item and only show tips. $format = array_shift($formats); $form[$format->format] = array('#type' => 'value', '#value' => $format->format, '#parents' => $parents); - $tips = _filter_tips(variable_get('filter_default_format', 1), FALSE); + $tips = _filter_tips($format->format, FALSE); $form['format']['guidelines'] = array( '#title' => t('Formatting guidelines'), '#markup' => theme('filter_tips', $tips, FALSE, $extra), @@ -529,17 +610,71 @@ function filter_form_validate($form) { } /** - * Returns TRUE if the user is allowed to access this format. + * Check if a user has access to a particular input 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 filter. */ -function filter_access($format) { - $format = filter_resolve_format($format); - if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) { +function filter_access($format, $account = NULL) { + global $user; + if (!isset($account)) { + $account = $user; + } + // 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 one was not 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 $permission && user_access($permission, $account); +} + +/** + * Retreive a list of a roles that are allowed to use a particular format. + * + * @param $format + * The format object that will be checked for access. + * + * @return + * An array of roles structured $rid => $role_name. + */ +function filter_format_roles($format) { + $name = filter_permission_name($format); + if ($name) { + return user_roles(FALSE, $name); + } + // Don't list any roles if the permission doesn't exist, unless it's for + // the fallback format (which all roles have access to). else { - $formats = filter_formats(); - return isset($formats[$format]); + return $format->format == filter_fallback_format() ? user_roles() : array(); + } +} + +/** + * Retreive a list of formats that are allowed for a particular role. + */ +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->name; + } } + return $formats; } /** @@ -551,8 +686,10 @@ function filter_access($format) { * Helper function for fetching filter tips. */ function _filter_tips($format, $long = FALSE) { + global $user; + if ($format == -1) { - $formats = filter_formats(); + $formats = filter_formats($user); } else { $formats = array(db_fetch_object(db_query("SELECT * FROM {filter_formats} WHERE format = %d", $format))); Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.993 diff -u -p -r1.993 node.module --- modules/node/node.module 3 Nov 2008 05:55:56 -0000 1.993 +++ modules/node/node.module 4 Nov 2008 23:44:31 -0000 @@ -870,7 +870,7 @@ function node_submit($node) { // module-provided 'teaser' form item). if (!isset($node->teaser)) { if (isset($node->body)) { - $node->format = (!empty($node->body_format) ? $node->body_format : FILTER_FORMAT_DEFAULT); + $node->format = (!empty($node->body_format) ? $node->body_format : filter_default_format()); $node->teaser = node_teaser($node->body, isset($node->format) ? $node->format : NULL); // Chop off the teaser from the body if needed. The teaser_include // property might not be set (eg. in Blog API postings), so only act on Index: modules/node/node.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v retrieving revision 1.45 diff -u -p -r1.45 node.pages.inc --- modules/node/node.pages.inc 13 Oct 2008 00:33:03 -0000 1.45 +++ modules/node/node.pages.inc 4 Nov 2008 23:44:31 -0000 @@ -297,7 +297,7 @@ function node_body_field(&$node, $label, '#default_value' => $include ? $node->body : ($node->teaser . $node->body), '#rows' => 20, '#required' => ($word_count > 0), - '#input_format' => isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT, + '#input_format' => isset($node->format) ? $node->format : filter_default_format(), ); return $form; Index: modules/profile/profile.module =================================================================== RCS file: /cvs/drupal/drupal/modules/profile/profile.module,v retrieving revision 1.246 diff -u -p -r1.246 profile.module --- modules/profile/profile.module 12 Oct 2008 04:30:08 -0000 1.246 +++ modules/profile/profile.module 4 Nov 2008 23:44:31 -0000 @@ -289,7 +289,7 @@ function profile_view_field($user, $fiel if (isset($user->{$field->name}) && $value = $user->{$field->name}) { switch ($field->type) { case 'textarea': - return check_markup($value); + return check_markup($value, filter_default_format($user), FALSE); case 'textfield': case 'selection': return $browse ? l($value, 'profile/' . $field->name . '/' . $value) : check_plain($value); Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.53 diff -u -p -r1.53 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 1 Nov 2008 21:21:35 -0000 1.53 +++ modules/simpletest/drupal_web_test_case.php 4 Nov 2008 23:44:31 -0000 @@ -371,7 +371,7 @@ class DrupalWebTestCase { 'title' => $this->randomName(8), 'comment' => 2, 'changed' => REQUEST_TIME, - 'format' => FILTER_FORMAT_DEFAULT, + 'format' => filter_default_format(), 'moderate' => 0, 'promote' => 0, 'revision' => 1, Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.277 diff -u -p -r1.277 system.install --- modules/system/system.install 3 Nov 2008 06:33:21 -0000 1.277 +++ modules/system/system.install 4 Nov 2008 23:44:31 -0000 @@ -385,12 +385,14 @@ function system_install() { // Anonymous role permissions. db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 1, 'access content'); + db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 1, 'use input format 1'); // Filtered HTML // Authenticated role permissions. db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'access comments'); db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'access content'); db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'post comments'); db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'post comments without approval'); + db_query("INSERT INTO {role_permission} (rid, permission) VALUES (%d, '%s')", 2, 'use input format 1'); // Filtered HTML db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'theme_default', 's:7:"garland";'); db_query("UPDATE {system} SET status = %d WHERE type = '%s' AND name = '%s'", 1, 'theme', 'garland'); @@ -401,8 +403,13 @@ function system_install() { db_query("INSERT INTO {node_access} (nid, gid, realm, grant_view, grant_update, grant_delete) VALUES (%d, %d, '%s', %d, %d, %d)", 0, 0, 'all', 1, 0, 0); // Add input formats. - db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('%s', '%s', %d)", 'Filtered HTML', ',1,2,', 1); - db_query("INSERT INTO {filter_formats} (name, roles, cache) VALUES ('%s', '%s', %d)", 'Full HTML', '', 1); + db_query("INSERT INTO {filter_formats} (name, cache, weight) VALUES ('%s', %d, %d)", 'Filtered HTML', 1, 0); + db_query("INSERT INTO {filter_formats} (name, cache, weight) VALUES ('%s', %d, %d)", 'Full HTML', 1, 0); + db_query("INSERT INTO {filter_formats} (name, cache, weight) VALUES ('%s', %d, %d)", 'Plain text', 1, 1); + $fallback_format = db_last_insert_id('filter_formats', 'format'); + if ($fallback_format != 3) { + db_query("INSERT INTO {variable} (name, value) VALUES ('%s','%s')", 'filter_fallback_format', serialize($fallback_format)); + } // Enable filters for each input format. @@ -424,6 +431,10 @@ function system_install() { // HTML corrector filter. db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 2, 'filter', 3, 10); + // Plain text: + // Line break filter only (check_plain is hardcoded for this input format). + db_query("INSERT INTO {filters} (format, module, delta, weight) VALUES (%d, '%s', %d, %d)", 3, 'filter', 1, 0); + db_query("INSERT INTO {variable} (name, value) VALUES ('%s','%s')", 'filter_html_1', 'i:1;'); db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'node_options_forum', 'a:1:{i:0;s:6:"status";}');