Commit 516d24d1 authored by Dries's avatar Dries
Browse files

- Patch #558666 by sun, dropcube: revamp text format/filter configuration for better usability.

parent 5db74af7
......@@ -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);
......
......@@ -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.
......
......@@ -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',
......
......@@ -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,61 +122,131 @@ 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'));
return $form;
}
/**
* 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.
*/
......@@ -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);
}
// $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();