summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDries Buytaert2009-12-14 13:32:53 (GMT)
committerDries Buytaert2009-12-14 13:32:53 (GMT)
commit516d24d1cd880c9ee6ccab195fd50d90c6f74e13 (patch)
tree86a09c9f2367d6babe359d5268cf8bada0b41bb8
parent5db74af7934009d5b032d822e4eb360f716a735e (diff)
- Patch #558666 by sun, dropcube: revamp text format/filter configuration for better usability.
-rw-r--r--includes/common.inc7
-rw-r--r--includes/form.inc105
-rw-r--r--modules/block/block.admin.inc8
-rw-r--r--modules/filter/filter.admin.inc278
-rw-r--r--modules/filter/filter.admin.js85
-rw-r--r--modules/filter/filter.css11
-rw-r--r--modules/filter/filter.module84
-rw-r--r--modules/filter/filter.test49
8 files changed, 296 insertions, 331 deletions
diff --git a/includes/common.inc b/includes/common.inc
index fdc0b40..6646496 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -4984,7 +4984,7 @@ function drupal_render(&$elements) {
}
// Do not print elements twice.
- if (isset($elements['#printed']) && $elements['#printed']) {
+ if (!empty($elements['#printed'])) {
return;
}
@@ -5016,6 +5016,11 @@ function drupal_render(&$elements) {
}
}
+ // Allow #pre_render to abort rendering.
+ if (!empty($elements['#printed'])) {
+ return;
+ }
+
// Get the children of the element, sorted by weight.
$children = element_children($elements, TRUE);
diff --git a/includes/form.inc b/includes/form.inc
index e042643..394345e 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -2463,9 +2463,10 @@ function form_process_tableselect($element) {
* Adds fieldsets to the specified group or adds group members to this
* fieldset.
*
- * @param $element
+ * @param &$element
* An associative array containing the properties and children of the
- * fieldset.
+ * fieldset. Note that $element must be taken by reference here, so processed
+ * child elements are taken over into $form_state.
* @param $form_state
* The $form_state array for the form this fieldset belongs to.
* @return
@@ -2474,39 +2475,17 @@ function form_process_tableselect($element) {
function form_process_fieldset(&$element, &$form_state) {
$parents = implode('][', $element['#parents']);
- // Add this fieldset to a group if one is set and if it's not being
- // added to itself.
- if (isset($element['#group']) && $element['#group'] != $parents) {
- if (isset($form_state['groups'][$element['#group']]) && !empty($form_state['groups'][$element['#group']]['#group_exists'])) {
- // Trick drupal_render() into believing this has already been output.
- // The group widget will rerender this later. This only happens when the
- // group is actually defined ('#group_exists' is TRUE). This prevents
- // fieldsets from disappearing when the group they are associated to
- // does not exist.
- // If the group does not exist yet, the element's #printed value is left
- // as is. As soon as the group is processed (fieldsets are also groups;
- // see below), this element's #printed value is set to TRUE to prevent
- // rendering in the original context.
- $element['#printed'] = TRUE;
- }
-
- // Store a reference to this fieldset for the vertical tabs processing
- // function.
- $form_state['groups'][$element['#group']][] = &$element;
- }
-
- // Each fieldset can be a group itself and gets a reference to all
- // elements in its group.
+ // Each fieldset forms a new group. The #type 'vertical_tabs' basically only
+ // injects a new fieldset.
$form_state['groups'][$parents]['#group_exists'] = TRUE;
- // There might already be elements associated with this group. Since the
- // group did not exist yet at the time they were added to this group, they
- // couldn't set #printed to TRUE (see above). We now know that this group
- // does in fact exist and set #printed to TRUE to prevent rendering in the
- // original context.
- foreach (element_children($form_state['groups'][$parents]) as $key) {
- $form_state['groups'][$parents][$key]['#printed'] = TRUE;
+ $element['#groups'] = &$form_state['groups'];
+
+ // Process vertical tabs group member fieldsets.
+ if (isset($element['#group'])) {
+ // Add this fieldset to the defined group (by reference).
+ $group = $element['#group'];
+ $form_state['groups'][$group][] = &$element;
}
- $element['#group_members'] = &$form_state['groups'][$parents];
// Contains form element summary functionalities.
$element['#attached']['js']['misc/form.js'] = array('weight' => JS_LIBRARY + 1);
@@ -2534,22 +2513,47 @@ function form_process_fieldset(&$element, &$form_state) {
* @param $element
* An associative array containing the properties and children of the
* fieldset.
+ *
* @return
* The modified element with all group members.
*/
function form_pre_render_fieldset($element) {
- if (!empty($element['#group_members'])) {
- // Add the group members to this fieldset for rendering purposes only.
- foreach (element_children($element['#group_members']) as $key) {
- // This was set in form_process_fieldset so that fieldsets which are
- // added to groups are not rendered at their original location.
- // drupal_render_children() will set this back to TRUE.
- unset($element['#group_members'][$key]['#printed']);
- $element[] = &$element['#group_members'][$key];
+ // Inject group member elements belonging to this group.
+ $parents = implode('][', $element['#parents']);
+ $children = element_children($element['#groups'][$parents]);
+ if (!empty($children)) {
+ foreach ($children as $key) {
+ // Break references and indicate that the element should be rendered as
+ // group member.
+ $child = (array) $element['#groups'][$parents][$key];
+ $child['#group_fieldset'] = TRUE;
+ // Inject the element as new child element.
+ $element[] = $child;
+
+ $sort = TRUE;
+ }
+ // Re-sort the element's children if we injected group member elements.
+ if (isset($sort)) {
+ $element['#sorted'] = FALSE;
+ }
+ }
+
+ if (isset($element['#group'])) {
+ $group = $element['#group'];
+ // If this element belongs to a group, but the group-holding element does
+ // not exist, we need to render it (at its original location).
+ if (!isset($element['#groups'][$group]['#group_exists'])) {
+ // Intentionally empty to clarify the flow; we simply return $element.
+ }
+ // If we injected this element into the group, then we want to render it.
+ elseif (!empty($element['#group_fieldset'])) {
+ // Intentionally empty to clarify the flow; we simply return $element.
+ }
+ // Otherwise, this element belongs to a group and the group exists, so we do
+ // not render it.
+ elseif (element_children($element['#groups'][$group])) {
+ $element['#printed'] = TRUE;
}
-
- // Resort the element's children after the group members have been added.
- $element['#sorted'] = FALSE;
}
return $element;
@@ -2567,10 +2571,8 @@ function form_pre_render_fieldset($element) {
* The processed element.
*/
function form_process_vertical_tabs($element, &$form_state) {
- // To save us from modifying the existing element and changing its #type,
- // a new form element is created as a child. The default #process hooks
- // are called automatically by the form renderer and we don't have to do
- // that manually.
+ // Inject a new fieldset as child, so that form_process_fieldset() processes
+ // this fieldset like any other fieldset.
$element['group'] = array(
'#type' => 'fieldset',
'#theme_wrappers' => array(),
@@ -2599,11 +2601,8 @@ function form_process_vertical_tabs($element, &$form_state) {
*
* @param $variables
* An associative array containing:
- *
- * - element
- * An associative array containing the properties and children of the
- * fieldset.
- * Properties used: #children.
+ * - element: An associative array containing the properties and children of the
+ * fieldset. Properties used: #children.
*
* @return
* A themed HTML string representing the form element.
diff --git a/modules/block/block.admin.inc b/modules/block/block.admin.inc
index e5270c9..c25cbdc 100644
--- a/modules/block/block.admin.inc
+++ b/modules/block/block.admin.inc
@@ -259,7 +259,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) {
'#title' => t('Pages'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
- '#group' => 'visibility_settings',
+ '#group' => 'visibility',
);
$access = user_access('use PHP for settings');
@@ -312,7 +312,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) {
'#title' => t('Content types'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
- '#group' => 'visibility_settings',
+ '#group' => 'visibility',
);
$form['visibility']['node_type']['types'] = array(
'#type' => 'checkboxes',
@@ -333,7 +333,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) {
'#title' => t('Roles'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
- '#group' => 'visibility_settings',
+ '#group' => 'visibility',
);
$form['visibility']['role']['roles'] = array(
'#type' => 'checkboxes',
@@ -349,7 +349,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) {
'#title' => t('Users'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
- '#group' => 'visibility_settings',
+ '#group' => 'visibility',
);
$form['visibility']['user']['custom'] = array(
'#type' => 'radios',
diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc
index b8ab186..d7b9e52 100644
--- a/modules/filter/filter.admin.inc
+++ b/modules/filter/filter.admin.inc
@@ -105,18 +105,16 @@ function filter_admin_format_page($format = NULL) {
*/
function filter_admin_format_form($form, &$form_state, $format) {
$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['#format'] = $format;
$form['#tree'] = TRUE;
+ $form['#attached']['js'][] = drupal_get_path('module', 'filter') . '/filter.admin.js';
+ $form['#attached']['css'][] = drupal_get_path('module', 'filter') . '/filter.css';
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#default_value' => $format->name,
- '#description' => t('Specify a unique name for this text format.'),
'#required' => TRUE,
);
@@ -124,55 +122,98 @@ function filter_admin_format_form($form, &$form_state, $format) {
$form['roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Roles'),
- '#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.'),
'#options' => user_roles(),
'#default_value' => array_keys(filter_get_roles_by_format($format)),
'#disabled' => $is_fallback,
);
+ if ($is_fallback) {
+ $form['roles']['#description'] = t('All roles for this text format must be enabled and cannot be changed.');
+ }
- // Table with filters
+ // Retrieve available filters and load all configured filters for existing
+ // text formats.
$filter_info = filter_get_filters();
- // Load all configured filters for existing text formats.
$filters = !empty($format->format) ? filter_list_format($format->format) : array();
- $form['filters'] = array(
- '#type' => 'fieldset',
- '#title' => t('Filters'),
- '#description' => t('Choose the filters that will be used in this text format.'),
- );
-
+ // Prepare filters for form sections.
foreach ($filter_info as $name => $filter) {
// Create an empty filter object for new/unconfigured filters.
if (!isset($filters[$name])) {
$filters[$name] = new stdClass;
$filters[$name]->status = 0;
- $filters[$name]->weight = 0;
+ $filters[$name]->weight = $filter['weight'];
$filters[$name]->settings = array();
}
- $form['filters'][$name]['#filter'] = $filters[$name];
- $form['filters'][$name]['status'] = array(
+ }
+ $form['#filters'] = $filters;
+
+ // Filter status.
+ $form['filters']['status'] = array(
+ '#type' => 'item',
+ '#title' => t('Enabled filters'),
+ '#prefix' => '<div id="filters-status-wrapper">',
+ '#suffix' => '</div>',
+ );
+ foreach ($filter_info as $name => $filter) {
+ $form['filters']['status'][$name] = array(
'#type' => 'checkbox',
'#title' => $filter['title'],
'#default_value' => $filters[$name]->status,
+ '#parents' => array('filters', $name, 'status'),
'#description' => $filter['description'],
+ '#weight' => $filter['weight'],
);
}
- if (!empty($format->format)) {
- $form['format'] = array('#type' => 'value', '#value' => $format->format);
+ // Filter order (tabledrag).
+ $form['filters']['order'] = array(
+ '#type' => 'item',
+ '#title' => t('Filter processing order'),
+ '#theme' => 'filter_admin_format_filter_order',
+ );
+ foreach ($filter_info as $name => $filter) {
+ $form['filters']['order'][$name]['filter'] = array(
+ '#markup' => $filter['title'],
+ );
+ $form['filters']['order'][$name]['weight'] = array(
+ '#type' => 'weight',
+ '#delta' => 50,
+ '#default_value' => $filters[$name]->weight,
+ '#parents' => array('filters', $name, 'weight'),
+ );
+ }
- // Composition tips (guidelines)
- $tips = _filter_tips($format->format, FALSE);
- $tiplist = theme('filter_tips', array('tips' => $tips, 'long' => FALSE));
- if (!$tiplist) {
- $tiplist = '<p>' . t('No guidelines available.') . '</p>';
- }
- else {
- $tiplist .= theme('filter_tips_more_info');
+ // Filter settings.
+ $form['filter_settings_title'] = array(
+ '#type' => 'item',
+ '#title' => t('Filter settings'),
+ );
+ $form['filter_settings'] = array(
+ '#type' => 'vertical_tabs',
+ );
+
+ foreach ($filter_info as $name => $filter) {
+ if (isset($filter['settings callback']) && function_exists($filter['settings callback'])) {
+ $function = $filter['settings callback'];
+ // Pass along stored filter settings and default settings, but also the
+ // format object and all filters to allow for complex implementations.
+ $defaults = (isset($filter['default settings']) ? $filter['default settings'] : array());
+ $settings_form = $function($form, $form_state, $filters[$name], $format, $defaults, $filters);
+ if (!empty($settings_form)) {
+ $form['filters']['settings'][$name] = array(
+ '#type' => 'fieldset',
+ '#title' => $filter['title'],
+ '#parents' => array('filters', $name, 'settings'),
+ '#weight' => $filter['weight'],
+ '#group' => 'filter_settings',
+ );
+ $form['filters']['settings'][$name] += $settings_form;
+ }
}
- $group = '<p>' . t('These are the guidelines that users will see for posting in this text format. They are automatically generated from the filter settings.') . '</p>';
- $group .= $tiplist;
- $form['tips'] = array('#markup' => '<h2>' . t('Formatting guidelines') . '</h2>' . $group);
+ }
+
+ if (!empty($format->format)) {
+ $form['format'] = array('#type' => 'value', '#value' => $format->format);
}
$form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
@@ -180,6 +221,33 @@ function filter_admin_format_form($form, &$form_state, $format) {
}
/**
+ * Theme text format filter order form elements as tabledrag.
+ *
+ * @ingroup themeable
+ */
+function theme_filter_admin_format_filter_order($variables) {
+ $element = $variables['element'];
+
+ // Filter order (tabledrag).
+ $rows = array();
+ foreach (element_children($element, TRUE) as $name) {
+ $element[$name]['weight']['#attributes']['class'][] = 'filter-order-weight';
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($element[$name]['filter']),
+ drupal_render($element[$name]['weight']),
+ ),
+ 'class' => array('draggable'),
+ );
+ }
+ $output = drupal_render_children($element);
+ $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'filter-order')));
+ drupal_add_tabledrag('filter-order', 'order', 'sibling', 'filter-order-weight', NULL, NULL, TRUE);
+
+ return $output;
+}
+
+/**
* Validate text format form submissions.
*/
function filter_admin_format_form_validate($form, &$form_state) {
@@ -254,155 +322,3 @@ function filter_admin_delete_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/config/content/formats';
}
-/**
- * Menu callback; display settings defined by a format's filters.
- */
-function filter_admin_configure_page($format) {
- drupal_set_title(t("Configure %format", array('%format' => $format->name)), PASS_THROUGH);
- return drupal_get_form('filter_admin_configure', $format);
-}
-
-/**
- * Build a form to change the settings for filters in a text format.
- *
- * The form is built by merging the results of 'settings callback' for each
- * enabled filter in the given format.
- *
- * @ingroup forms
- */
-function filter_admin_configure($form, &$form_state, $format) {
- $filters = filter_list_format($format->format);
- $filter_info = filter_get_filters();
-
- $form['#format'] = $format;
- foreach ($filters as $name => $filter) {
- if ($filter->status && isset($filter_info[$name]['settings callback']) && function_exists($filter_info[$name]['settings callback'])) {
- $function = $filter_info[$name]['settings callback'];
- // Pass along stored filter settings and default settings, but also the
- // format object and all filters to allow for complex implementations.
- $defaults = (isset($filter_info[$name]['default settings']) ? $filter_info[$name]['default settings'] : array());
- $settings_form = $function($form, $form_state, $filters[$name], $format, $defaults, $filters);
- if (!empty($settings_form)) {
- $form['settings'][$name] = array(
- '#type' => 'fieldset',
- '#title' => check_plain($filter->title),
- );
- $form['settings'][$name] += $settings_form;
- }
- }
- }
-
- if (empty($form['settings'])) {
- $form['error'] = array('#markup' => t('No settings are available.'));
- return $form;
- }
- $form['settings']['#tree'] = TRUE;
- $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
-
- return $form;
-}
-
-/**
- * Form submit handler for text format filter configuration form.
- *
- * @see filter_admin_configure()
- */
-function filter_admin_configure_submit($form, &$form_state) {
- $format = $form['#format'];
-
- foreach ($form_state['values']['settings'] as $name => $settings) {
- db_update('filter')
- ->fields(array(
- 'settings' => serialize($settings),
- ))
- ->condition('format', $format->format)
- ->condition('name', $name)
- ->execute();
- }
-
- // Clear the filter's cache when configuration settings are saved.
- cache_clear_all($format->format . ':', 'cache_filter', TRUE);
-
- drupal_set_message(t('The configuration options have been saved.'));
-}
-
-/**
- * Menu callback; display form for ordering filters for a format.
- */
-function filter_admin_order_page($format) {
- drupal_set_title(t("Rearrange %format", array('%format' => $format->name)), PASS_THROUGH);
- return drupal_get_form('filter_admin_order', $format);
-}
-
-/**
- * Build the form for ordering filters for a format.
- *
- * @ingroup forms
- * @see theme_filter_admin_order()
- * @see filter_admin_order_submit()
- */
-function filter_admin_order($form, &$form_state, $format = NULL) {
- // Get list (with forced refresh).
- $filters = filter_list_format($format->format);
-
- $form['weights'] = array('#tree' => TRUE);
- foreach ($filters as $id => $filter) {
- if ($filter->status) {
- $form['names'][$id] = array('#markup' => $filter->title);
- $form['weights'][$id] = array('#type' => 'weight', '#default_value' => $filter->weight);
- }
- }
- $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
- $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
-
- return $form;
-}
-
-/**
- * Theme filter order configuration form.
- *
- * @ingroup themeable
- */
-function theme_filter_admin_order($variables) {
- $form = $variables['form'];
-
- $header = array(t('Name'), t('Weight'));
- $rows = array();
- foreach (element_children($form['names']) as $id) {
- // Don't take form control structures.
- if (is_array($form['names'][$id])) {
- $form['weights'][$id]['#attributes']['class'] = array('filter-order-weight');
- $rows[] = array(
- 'data' => array(drupal_render($form['names'][$id]), drupal_render($form['weights'][$id])),
- 'class' => array('draggable'),
- );
- }
- }
-
- $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'filter-order')));
- $output .= drupal_render_children($form);
-
- drupal_add_tabledrag('filter-order', 'order', 'sibling', 'filter-order-weight', NULL, NULL, FALSE);
-
- return $output;
-}
-
-/**
- * Process filter order configuration form submission.
- */
-function filter_admin_order_submit($form, &$form_state) {
- foreach ($form_state['values']['weights'] as $name => $weight) {
- db_merge('filter')
- ->key(array(
- 'format' => $form_state['values']['format'],
- 'name' => $name,
- ))
- ->fields(array(
- 'weight' => $weight,
- ))
- ->execute();
- }
- drupal_set_message(t('The filter ordering has been saved.'));
-
- cache_clear_all($form_state['values']['format'] . ':', 'cache_filter', TRUE);
-}
diff --git a/modules/filter/filter.admin.js b/modules/filter/filter.admin.js
new file mode 100644
index 0000000..f882088
--- /dev/null
+++ b/modules/filter/filter.admin.js
@@ -0,0 +1,85 @@
+// $Id$
+
+(function ($) {
+
+/**
+ * Shows the vertical tab pane.
+ */
+Drupal.verticalTab.prototype.tabShow = function () {
+ // Display the tab.
+ this.item.show();
+ // Update .first marker for items. We need recurse from parent to retain the
+ // actual DOM element order as jQuery implements sortOrder, but not as public
+ // method.
+ this.item.parent().children('.vertical-tab-button').removeClass('first')
+ .filter(':visible:first').addClass('first');
+ // Display the fieldset.
+ this.fieldset.removeClass('filter-settings-hidden').show();
+ // Focus this tab.
+ this.focus();
+ return this;
+};
+
+/**
+ * Hides the vertical tab pane.
+ */
+Drupal.verticalTab.prototype.tabHide = function () {
+ // Hide this tab.
+ this.item.hide();
+ // Update .first marker for items. We need recurse from parent to retain the
+ // actual DOM element order as jQuery implements sortOrder, but not as public
+ // method.
+ this.item.parent().children('.vertical-tab-button').removeClass('first')
+ .filter(':visible:first').addClass('first');
+ // Hide the fieldset.
+ this.fieldset.addClass('filter-settings-hidden').hide();
+ // Focus the first visible tab (if there is one).
+ var $firstTab = this.fieldset.siblings('.vertical-tabs-pane:not(.filter-settings-hidden):first');
+ if ($firstTab.length) {
+ $firstTab.data('verticalTab').focus();
+ }
+ return this;
+};
+
+Drupal.behaviors.filterStatus = {
+ attach: function (context, settings) {
+ $('#filters-status-wrapper input.form-checkbox', context).once('filter-status', function () {
+ var $checkbox = $(this);
+ // Retrieve the tabledrag row belonging to this filter.
+ var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
+ // Retrieve the vertical tab belonging to this filter.
+ var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
+
+ // Bind click handler to this checkbox to conditionally show and hide the
+ // filter's tableDrag row and vertical tab pane.
+ $checkbox.bind('click.filterUpdate', function () {
+ if ($checkbox.is(':checked')) {
+ $row.show();
+ if (tab) {
+ tab.tabShow().updateSummary();
+ }
+ }
+ else {
+ $row.hide();
+ if (tab) {
+ tab.tabHide().updateSummary();
+ }
+ }
+ // Restripe table after toggling visibility of table row.
+ Drupal.tableDrag['filter-order'].restripeTable();
+ });
+
+ // Attach summary for configurable filters (only for screen-readers).
+ if (tab) {
+ tab.fieldset.setSummary(function (tabContext) {
+ return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
+ });
+ }
+
+ // Trigger our bound click handler to update elements to initial state.
+ $checkbox.trigger('click.filterUpdate');
+ });
+ }
+};
+
+})(jQuery);
diff --git a/modules/filter/filter.css b/modules/filter/filter.css
index a26ddae..76be059 100644
--- a/modules/filter/filter.css
+++ b/modules/filter/filter.css
@@ -35,3 +35,14 @@
.text-format-wrapper .description {
margin-top: 0.5em;
}
+
+#filter-order tr .form-item {
+ padding: 0.5em 0 0 3em;
+ white-space: normal;
+}
+#filter-order tr .form-type-checkbox .description {
+ padding: 0 0 0 2.5em;
+}
+input#edit-filters-filter-html-settings-allowed-html {
+ width: 100%;
+}
diff --git a/modules/filter/filter.module b/modules/filter/filter.module
index b552b7c..b2ae5dc 100644
--- a/modules/filter/filter.module
+++ b/modules/filter/filter.module
@@ -25,17 +25,14 @@ function filter_help($path, $arg) {
$output .= '<dd>' . t('Users with access to more than one text format can use the <em>Text format</em> fieldset to choose between available text formats when creating or editing multi-line content. Administrators can determine the text formats available to each user role, and control the order of formats listed in the <em>Text format</em> fieldset on the <a href="@text-formats">Text formats page</a>.', array('@text-formats' => url('admin/config/content/formats'))) . '</dd>';
$output .= '</dl>';
return $output;
+
case 'admin/config/content/formats':
$output = '<p>' . t("Control which HTML tags and other formatting can be used for text input. Don't allow too much formatting for untrusted users. This can be a serious security risk.") . '</p>';
$output .= '<p>' . t('Text formats are presented in the order you arrange them below. Configure a text format to change its behavior.') . '</p>';
return $output;
+
case 'admin/config/content/formats/%':
- return '<p>' . t('Every <em>filter</em> 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 <a href="@rearrange">rearrange them</a>.', array('@rearrange' => url('admin/config/content/formats/' . $arg[4] . '/order'))) . '</p>';
- case 'admin/config/content/formats/%/configure':
- return '<p>' . t('If you cannot find the settings for a certain filter, make sure you have enabled it on the <a href="@url">edit tab</a> first.', array('@url' => url('admin/config/content/formats/' . $arg[4]))) . '</p>';
- case 'admin/config/content/formats/%/order':
- $output = '<p>' . t('Because of the flexible filtering system, you might encounter a situation where one filter prevents another from doing its job. For example: a word in an URL gets converted into a glossary term, before the URL can be converted to a clickable link. When this happens, rearrange the order of the filters.') . '</p>';
- $output .= '<p>' . t("Filters are executed from top-to-bottom. To change the order of the filters, modify the values in the <em>Weight</em> column or grab a drag-and-drop handle under the <em>Name</em> column and drag filters to new locations 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 <em>Save configuration</em> button at the bottom of the page.") . '</p>';
+ $output = '<p>' . t('A text format contains filters that change the user input, for example stripping out malicious HTML or making URLs clickable. Filters are executed from top to bottom and the order is important, since one filter may prevent another filter from doing its job. For example, when URLs are converted into links before disallowed HTML tags are removed, all links may be removed. When this happens, the order of filters may need to be re-arranged.') . '</p>';
return $output;
}
}
@@ -49,8 +46,8 @@ function filter_theme() {
'render element' => 'form',
'file' => 'filter.admin.inc',
),
- 'filter_admin_order' => array(
- 'render element' => 'form',
+ 'filter_admin_format_filter_order' => array(
+ 'render element' => 'element',
'file' => 'filter.admin.inc',
),
'filter_tips' => array(
@@ -106,29 +103,6 @@ function filter_menu() {
'access arguments' => array('administer filters'),
'file' => 'filter.admin.inc',
);
- $items['admin/config/content/formats/%filter_format/edit'] = array(
- 'title' => 'Edit',
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- 'weight' => 0,
- );
- $items['admin/config/content/formats/%filter_format/configure'] = array(
- 'title' => 'Configure',
- 'page callback' => 'filter_admin_configure_page',
- 'page arguments' => array(4),
- 'access arguments' => array('administer filters'),
- 'type' => MENU_LOCAL_TASK,
- 'weight' => 1,
- 'file' => 'filter.admin.inc',
- );
- $items['admin/config/content/formats/%filter_format/order'] = array(
- 'title' => 'Rearrange',
- 'page callback' => 'filter_admin_order_page',
- 'page arguments' => array(4),
- 'access arguments' => array('administer filters'),
- 'type' => MENU_LOCAL_TASK,
- 'weight' => 2,
- 'file' => 'filter.admin.inc',
- );
$items['admin/config/content/formats/%filter_format/delete'] = array(
'title' => 'Delete text format',
'page callback' => 'drupal_get_form',
@@ -200,37 +174,25 @@ function filter_format_save(&$format) {
$return = drupal_write_record('filter_format', $format, 'format');
}
- // Get the current filters in the format, to add new filters to the bottom.
- $current = ($return != SAVED_NEW ? filter_list_format($format->format) : array());
$filter_info = filter_get_filters();
// Programmatic saves may not contain any filters.
if (!isset($format->filters)) {
$format->filters = array();
}
foreach ($filter_info as $name => $filter) {
- // As of now, only programmatic saves may contain weight (see below). If
- // there is no weight, either fall back to the currently stored weight or
- // add new filters to the bottom.
+ // Add new filters without weight to the bottom.
if (!isset($format->filters[$name]['weight'])) {
- $format->filters[$name]['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10;
+ $format->filters[$name]['weight'] = 10;
}
$format->filters[$name]['status'] = isset($format->filters[$name]['status']) ? $format->filters[$name]['status'] : 0;
$format->filters[$name]['module'] = $filter['module'];
- // Since filter configuration/order lives on separate pages, there may be no
- // filter settings contained. In that case, we either fall back to currently
- // stored settings, default settings (if existent), or an empty array.
- // @see http://drupal.org/node/558666
// If settings were passed, only ensure default settings.
if (isset($format->filters[$name]['settings'])) {
if (isset($filter['default settings'])) {
$format->filters[$name]['settings'] = array_merge($filter['default settings'], $format->filters[$name]['settings']);
}
}
- // If we have existing settings, take them over directly.
- elseif (isset($current[$name]->settings)) {
- $format->filters[$name]['settings'] = $current[$name]->settings;
- }
// Otherwise, use default settings or fall back to an empty array.
else {
$format->filters[$name]['settings'] = isset($filter['default settings']) ? $filter['default settings'] : array();
@@ -503,9 +465,14 @@ function filter_get_filters() {
foreach (module_implements('filter_info') as $module) {
$info = module_invoke($module, 'filter_info');
if (isset($info) && is_array($info)) {
- // Assign the name of the module implementing the filters.
+ // Assign the name of the module implementing the filters and ensure
+ // default values.
foreach (array_keys($info) as $name) {
$info[$name]['module'] = $module;
+ $info[$name] += array(
+ 'description' => '',
+ 'weight' => 0,
+ );
}
$filters = array_merge($filters, $info);
}
@@ -895,7 +862,6 @@ function theme_filter_guidelines($variables) {
function filter_filter_info() {
$filters['filter_html'] = array(
'title' => t('Limit allowed HTML tags'),
- 'description' => t('Allows you to restrict the HTML tags the user can use. It will also remove harmful content such as JavaScript events, JavaScript URLs and CSS styles from those tags that are not removed.'),
'process callback' => '_filter_html',
'settings callback' => '_filter_html_settings',
'default settings' => array(
@@ -904,16 +870,15 @@ function filter_filter_info() {
'filter_html_nofollow' => 0,
),
'tips callback' => '_filter_html_tips',
+ 'weight' => -10,
);
$filters['filter_autop'] = array(
- 'title' => t('Convert line breaks'),
- 'description' => t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.'),
+ 'title' => t('Convert line breaks into HTML (i.e. <code>&lt;br&gt;</code> and <code>&lt;p&gt;</code>)'),
'process callback' => '_filter_autop',
'tips callback' => '_filter_autop_tips',
);
$filters['filter_url'] = array(
'title' => t('Convert URLs into links'),
- 'description' => t('Turns web and e-mail addresses into clickable links.'),
'process callback' => '_filter_url',
'settings callback' => '_filter_url_settings',
'default settings' => array(
@@ -922,15 +887,15 @@ function filter_filter_info() {
'tips callback' => '_filter_url_tips',
);
$filters['filter_htmlcorrector'] = array(
- 'title' => t('Correct broken HTML'),
- 'description' => t('Corrects faulty and chopped off HTML in postings.'),
+ 'title' => t('Correct faulty and chopped off HTML'),
'process callback' => '_filter_htmlcorrector',
+ 'weight' => 10,
);
$filters['filter_html_escape'] = array(
- 'title' => t('Escape all HTML'),
- 'description' => t('Escapes all HTML tags, so they will be visible instead of being effective.'),
+ 'title' => t('Display any HTML as plain text'),
'process callback' => '_filter_html_escape',
'tips callback' => '_filter_html_escape_tips',
+ 'weight' => -10,
);
return $filters;
}
@@ -943,21 +908,18 @@ function _filter_html_settings($form, &$form_state, $filter, $format, $defaults)
'#type' => 'textfield',
'#title' => t('Allowed HTML tags'),
'#default_value' => isset($filter->settings['allowed_html']) ? $filter->settings['allowed_html'] : $defaults['allowed_html'],
- '#size' => 64,
'#maxlength' => 1024,
- '#description' => t('Specify a list of tags which should not be stripped. (Note that JavaScript event attributes are always stripped.)'),
+ '#description' => t('A list of HTML tags that can be used. JavaScript event attributes, JavaScript URLs, and CSS are always stripped.'),
);
$settings['filter_html_help'] = array(
'#type' => 'checkbox',
- '#title' => t('Display HTML help'),
+ '#title' => t('Display basic HTML help in long filter tips'),
'#default_value' => isset($filter->settings['filter_html_help']) ? $filter->settings['filter_html_help'] : $defaults['filter_html_help'],
- '#description' => t('If enabled, Drupal will display some basic HTML help in the long filter tips.'),
);
$settings['filter_html_nofollow'] = array(
'#type' => 'checkbox',
- '#title' => t('Spam link deterrent'),
+ '#title' => t('Add rel="nofollow" to all links'),
'#default_value' => isset($filter->settings['filter_html_nofollow']) ? $filter->settings['filter_html_nofollow'] : $defaults['filter_html_nofollow'],
- '#description' => t('If enabled, Drupal will add rel="nofollow" to all links, as a measure to reduce the effectiveness of spam links. Note: this will also prevent valid links from being followed by search engines, therefore it is likely most effective when enabled for anonymous users.'),
);
return $settings;
}
@@ -1089,7 +1051,9 @@ function _filter_url_settings($form, &$form_state, $filter, $format, $defaults)
'#type' => 'textfield',
'#title' => t('Maximum link text length'),
'#default_value' => isset($filter->settings['filter_url_length']) ? $filter->settings['filter_url_length'] : $defaults['filter_url_length'],
+ '#size' => 5,
'#maxlength' => 4,
+ '#field_suffix' => t('characters'),
'#description' => t('URLs longer than this number of characters will be truncated to prevent long strings that break formatting. The link itself will be retained; just the text portion of the link will be truncated.'),
);
return $settings;
diff --git a/modules/filter/filter.test b/modules/filter/filter.test
index fb49a9c..2ef6d80 100644
--- a/modules/filter/filter.test
+++ b/modules/filter/filter.test
@@ -222,39 +222,22 @@ class FilterAdminTestCase extends DrupalWebTestCase {
$this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.'));
$this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.'));
- // Verify that disabled filters are not displayed.
- $edit = array();
- $edit['filters[filter_url][status]'] = FALSE;
- $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
- $this->drupalGet('admin/config/content/formats/' . $filtered . '/configure');
- $this->assertNoText(t('Convert URLs into links'), t('Disabled URL filter cannot be configured.'));
- $this->drupalGet('admin/config/content/formats/' . $filtered . '/order');
- $this->assertNoText(t('Convert URLs into links'), t('Disabled URL filter cannot be re-ordered.'));
- $edit = array();
- $edit['filters[filter_url][status]'] = 1;
- $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
- $this->drupalGet('admin/config/content/formats/' . $filtered . '/configure');
- $this->assertText(t('Convert URLs into links'), t('Enabled URL filter can be configured.'));
- $this->drupalGet('admin/config/content/formats/' . $filtered . '/order');
- $this->assertText(t('Convert URLs into links'), t('Enabled URL filter can be re-ordered.'));
-
// Add an additional tag.
$edit = array();
- $edit['settings[filter_html][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
- $this->drupalPost('admin/config/content/formats/' . $filtered . '/configure', $edit, t('Save configuration'));
- $this->assertText(t('The configuration options have been saved.'), t('Allowed HTML tag added.'));
-
- $this->assertRaw(htmlentities($edit['settings[filter_html][allowed_html]']), t('Tag displayed.'));
+ $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>';
+ $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Allowed HTML tag added.'));
$result = db_query('SELECT * FROM {cache_filter}')->fetchObject();
$this->assertFalse($result, t('Cache cleared.'));
// Reorder filters.
$edit = array();
- $edit['weights[' . $second_filter . ']'] = 1;
- $edit['weights[' . $first_filter . ']'] = 2;
- $this->drupalPost('admin/config/content/formats/' . $filtered . '/order', $edit, t('Save configuration'));
- $this->assertText(t('The filter ordering has been saved.'), t('Order saved successfully.'));
+ $edit['filters[' . $second_filter . '][weight]'] = 1;
+ $edit['filters[' . $first_filter . '][weight]'] = 2;
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, t('Order saved successfully.'));
+ $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, t('Order saved successfully.'));
$result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered));
$filters = array();
@@ -333,22 +316,24 @@ class FilterAdminTestCase extends DrupalWebTestCase {
// Clean up.
// Allowed tags.
$edit = array();
- $edit['settings[filter_html][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
- $this->drupalPost('admin/config/content/formats/' . $filtered . '/configure', $edit, t('Save configuration'));
- $this->assertText(t('The configuration options have been saved.'), t('Changes reverted.'));
+ $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>';
+ $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Changes reverted.'));
// Full HTML.
$edit = array();
$edit['roles[2]'] = FALSE;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
$this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.'));
+ $this->assertFieldByName('roles[2]', $edit['roles[2]'], t('Changes reverted.'));
// Filter order.
$edit = array();
- $edit['weights[' . $second_filter . ']'] = 2;
- $edit['weights[' . $first_filter . ']'] = 1;
- $this->drupalPost('admin/config/content/formats/' . $filtered . '/order', $edit, t('Save configuration'));
- $this->assertText(t('The filter ordering has been saved.'), t('Order successfully reverted.'));
+ $edit['filters[' . $second_filter . '][weight]'] = 2;
+ $edit['filters[' . $first_filter . '][weight]'] = 1;
+ $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration'));
+ $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], t('Changes reverted.'));
+ $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], t('Changes reverted.'));
}
/**