Commit 9f57b6fa authored by alexpott's avatar alexpott

Issue #1868772 by tim.plunkett, sun, heyrocker: Convert filters to plugins.

parent 523d64a4
...@@ -107,7 +107,7 @@ function testGetJSSettings() { ...@@ -107,7 +107,7 @@ function testGetJSSettings() {
// Change the allowed HTML tags; the "format_tags" setting for CKEditor // Change the allowed HTML tags; the "format_tags" setting for CKEditor
// should automatically be updated as well. // should automatically be updated as well.
$format = entity_load('filter_format', 'filtered_html'); $format = entity_load('filter_format', 'filtered_html');
$format->filters['filter_html']['settings']['allowed_html'] .= '<pre> <h3>'; $format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h3>';
$format->save(); $format->save();
$expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre'; $expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre';
$this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.'); $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
......
...@@ -15,12 +15,19 @@ filters: ...@@ -15,12 +15,19 @@ filters:
filter_html_escape: filter_html_escape:
module: filter module: filter
status: '1' status: '1'
weight: '-10'
settings: { }
# Convert URLs into links. # Convert URLs into links.
filter_url: filter_url:
module: filter module: filter
status: '1' status: '1'
weight: '0'
settings:
filter_url_length: '72'
# Convert linebreaks into paragraphs. # Convert linebreaks into paragraphs.
filter_autop: filter_autop:
module: filter module: filter
status: '1' status: '1'
weight: '0'
settings: { }
langcode: und langcode: und
...@@ -197,25 +197,9 @@ function filter_admin_format_form($form, &$form_state, $format) { ...@@ -197,25 +197,9 @@ function filter_admin_format_form($form, &$form_state, $format) {
$form['roles']['#default_value'] = array($admin_role); $form['roles']['#default_value'] = array($admin_role);
} }
// Retrieve available filters and load all configured filters for existing // Create filter plugin instances for all available filters, including both
// text formats. // enabled/configured ones as well as new and not yet unconfigured ones.
$filter_info = filter_get_filters(); $filters = $format->filters()->sort();
$filters = !empty($format->format) ? filter_list_format($format->format) : array();
// 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]->format = $format->format;
$filters[$name]->module = $filter['module'];
$filters[$name]->name = $name;
$filters[$name]->status = 0;
$filters[$name]->weight = $filter['weight'];
$filters[$name]->settings = array();
}
}
$form['#filters'] = $filters;
// Filter status. // Filter status.
$form['filters']['status'] = array( $form['filters']['status'] = array(
...@@ -228,17 +212,6 @@ function filter_admin_format_form($form, &$form_state, $format) { ...@@ -228,17 +212,6 @@ function filter_admin_format_form($form, &$form_state, $format) {
// @see http://drupal.org/node/1829202 // @see http://drupal.org/node/1829202
'#input' => FALSE, '#input' => FALSE,
); );
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'],
);
}
// Filter order (tabledrag). // Filter order (tabledrag).
$form['filters']['order'] = array( $form['filters']['order'] = array(
'#type' => 'table', '#type' => 'table',
...@@ -252,48 +225,54 @@ function filter_admin_format_form($form, &$form_state, $format) { ...@@ -252,48 +225,54 @@ function filter_admin_format_form($form, &$form_state, $format) {
'#input' => FALSE, '#input' => FALSE,
'#theme_wrappers' => array('form_element'), '#theme_wrappers' => array('form_element'),
); );
foreach ($filter_info as $name => $filter) { // Filter settings.
$form['filter_settings'] = array(
'#type' => 'vertical_tabs',
'#title' => t('Filter settings'),
);
foreach ($filters as $name => $filter) {
$form['filters']['status'][$name] = array(
'#type' => 'checkbox',
'#title' => $filter->getLabel(),
'#default_value' => $filter->status,
'#parents' => array('filters', $name, 'status'),
'#description' => $filter->getDescription(),
'#weight' => $filter->weight,
);
$form['filters']['order'][$name]['#attributes']['class'][] = 'draggable'; $form['filters']['order'][$name]['#attributes']['class'][] = 'draggable';
$form['filters']['order'][$name]['#weight'] = $filters[$name]->weight; $form['filters']['order'][$name]['#weight'] = $filter->weight;
$form['filters']['order'][$name]['filter'] = array( $form['filters']['order'][$name]['filter'] = array(
'#markup' => $filter['title'], '#markup' => $filter->getLabel(),
); );
$form['filters']['order'][$name]['weight'] = array( $form['filters']['order'][$name]['weight'] = array(
'#type' => 'weight', '#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $filter['title'])), '#title' => t('Weight for @title', array('@title' => $filter->getLabel())),
'#title_display' => 'invisible', '#title_display' => 'invisible',
'#delta' => 50, '#delta' => 50,
'#default_value' => $filters[$name]->weight, '#default_value' => $filter->weight,
'#parents' => array('filters', $name, 'weight'), '#parents' => array('filters', $name, 'weight'),
'#attributes' => array('class' => array('filter-order-weight')), '#attributes' => array('class' => array('filter-order-weight')),
); );
}
// Make sure filters are in the correct order, since filter_get_filters()
// doesn't return sorted filters.
uasort($form['filters']['order'], 'element_sort');
// Filter settings. // Retrieve the settings form of the filter plugin. The plugin should not be
$form['filter_settings'] = array( // aware of the text format. Therefore, it only receives a set of minimal
'#type' => 'vertical_tabs', // base properties to allow advanced implementations to work.
'#title' => t('Filter settings'), $settings_form = array(
); '#parents' => array('filters', $name, 'settings'),
'#tree' => TRUE,
foreach ($filter_info as $name => $filter) { );
if (isset($filter['settings callback'])) { $settings_form = $filter->settingsForm($settings_form, $form_state);
$function = $filter['settings callback']; if (!empty($settings_form)) {
// Pass along stored filter settings and default settings, but also the $form['filters']['settings'][$name] = array(
// format object and all filters to allow for complex implementations. '#type' => 'details',
$settings_form = $function($form, $form_state, $filters[$name], $format, $filter['default settings'], $filters); '#title' => $filter->getLabel(),
if (!empty($settings_form)) { '#weight' => $filter->weight,
$form['filters']['settings'][$name] = array( '#parents' => array('filters', $name, 'settings'),
'#type' => 'details', '#group' => 'filter_settings',
'#title' => $filter['title'], );
'#parents' => array('filters', $name, 'settings'), $form['filters']['settings'][$name] += $settings_form;
'#weight' => $filter['weight'],
'#group' => 'filter_settings',
);
$form['filters']['settings'][$name] += $settings_form;
}
} }
} }
...@@ -337,7 +316,14 @@ function filter_admin_format_form_submit($form, &$form_state) { ...@@ -337,7 +316,14 @@ function filter_admin_format_form_submit($form, &$form_state) {
// Add the submitted form values to the text format, and save it. // Add the submitted form values to the text format, and save it.
$format = $form['#format']; $format = $form['#format'];
foreach ($form_state['values'] as $key => $value) { foreach ($form_state['values'] as $key => $value) {
$format->set($key, $value); if ($key != 'filters') {
$format->set($key, $value);
}
else {
foreach ($value as $instance_id => $config) {
$format->setFilterConfig($instance_id, $config);
}
}
} }
$status = $format->save(); $status = $format->save();
......
...@@ -10,283 +10,19 @@ ...@@ -10,283 +10,19 @@
* @{ * @{
*/ */
/**
* Define content filters.
*
* User submitted content is passed through a group of filters before it is
* output in HTML, in order to remove insecure or unwanted parts, correct or
* enhance the formatting, transform special keywords, etc. A group of filters
* is referred to as a "text format". Administrators can create as many text
* formats as needed. Individual filters can be enabled and configured
* differently for each text format.
*
* This hook is invoked by filter_get_filters() and allows modules to register
* input filters they provide.
*
* Filtering is a two-step process. First, the content is 'prepared' by calling
* the 'prepare callback' function for every filter. The purpose of the
* 'prepare callback' is to escape HTML-like structures. For example, imagine a
* filter which allows the user to paste entire chunks of programming code
* without requiring manual escaping of special HTML characters like < or &. If
* the programming code were left untouched, then other filters could think it
* was HTML and change it. For many filters, the prepare step is not necessary.
*
* The second step is the actual processing step. The result from passing the
* text through all the filters' prepare steps gets passed to all the filters
* again, this time with the 'process callback' function. The process callbacks
* should then actually change the content: transform URLs into hyperlinks,
* convert smileys into images, etc.
*
* For performance reasons content is only filtered once; the result is stored
* in the cache table and retrieved from the cache the next time the same piece
* of content is displayed. If a filter's output is dynamic, it can override
* the cache mechanism, but obviously this should be used with caution: having
* one filter that does not support caching in a particular text format
* disables caching for the entire format, not just for one filter.
*
* Beware of the filter cache when developing your module: it is advised to set
* your filter to 'cache' => FALSE while developing, but be sure to remove that
* setting if it's not needed, when you are no longer in development mode.
*
* @return
* An associative array of filters, whose keys are internal filter names,
* which should be unique and therefore prefixed with the name of the module.
* Each value is an associative array describing the filter, with the
* following elements (all are optional except as noted):
* - title: (required) An administrative summary of what the filter does.
* - type: (required) A classification of the filter's purpose. This is one
* of the following:
* - FILTER_TYPE_HTML_RESTRICTOR: HTML tag and attribute restricting
* filters.
* - FILTER_TYPE_MARKUP_LANGUAGE: Non-HTML markup language filters that
* generate HTML.
* - FILTER_TYPE_TRANSFORM_IRREVERSIBLE: Irreversible transformation
* filters.
* - FILTER_TYPE_TRANSFORM_REVERSIBLE: Reversible transformation filters.
* - description: Additional administrative information about the filter's
* behavior, if needed for clarification.
* - settings callback: The name of a function that returns configuration
* form elements for the filter. See hook_filter_FILTER_settings() for
* details.
* - default settings: An associative array containing default settings for
* the filter, to be applied when the filter has not been configured yet.
* - prepare callback: The name of a function that escapes the content before
* the actual filtering happens. See hook_filter_FILTER_prepare() for
* details.
* - process callback: (required) The name the function that performs the
* actual filtering. See hook_filter_FILTER_process() for details.
* - cache (default TRUE): Specifies whether the filtered text can be cached.
* Note that setting this to FALSE makes the entire text format not
* cacheable, which may have an impact on the site's overall performance.
* See filter_format_allowcache() for details.
* - tips callback: The name of a function that returns end-user-facing
* filter usage guidelines for the filter. See hook_filter_FILTER_tips()
* for details.
* - weight: A default weight for the filter in new text formats.
*
* @see filter_example.module
* @see hook_filter_info_alter()
*/
function hook_filter_info() {
$filters['filter_html'] = array(
'title' => t('Limit allowed HTML tags'),
'type' => FILTER_TYPE_HTML_RESTRICTOR,
'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(
'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>',
'filter_html_help' => 1,
'filter_html_nofollow' => 0,
),
'tips callback' => '_filter_html_tips',
);
$filters['filter_autop'] = array(
'title' => t('Convert line breaks'),
'type' => FILTER_TYPE_MARKUP_LANGUAGE,
'description' => t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.'),
'process callback' => '_filter_autop',
'tips callback' => '_filter_autop_tips',
);
return $filters;
}
/** /**
* Perform alterations on filter definitions. * Perform alterations on filter definitions.
* *
* @param $info * @param $info
* Array of information on filters exposed by hook_filter_info() * Array of information on filters exposed by filter plugins.
* implementations.
*/ */
function hook_filter_info_alter(&$info) { function hook_filter_info_alter(&$info) {
// Replace the PHP evaluator process callback with an improved
// PHP evaluator provided by a module.
$info['php_code']['process callback'] = 'my_module_php_evaluator';
// Alter the default settings of the URL filter provided by core. // Alter the default settings of the URL filter provided by core.
$info['filter_url']['default settings'] = array( $info['filter_url']['default_settings'] = array(
'filter_url_length' => 100, 'filter_url_length' => 100,
); );
} }
/**
* @} End of "addtogroup hooks".
*/
/**
* Settings callback for hook_filter_info().
*
* Note: This is not really a hook. The function name is manually specified via
* 'settings callback' in hook_filter_info(), with this recommended callback
* name pattern. It is called from filter_admin_format_form().
*
* This callback function is used to provide a settings form for filter
* settings, for filters that need settings on a per-text-format basis. This
* function should return the form elements for the settings; the filter
* module will take care of saving the settings in the database.
*
* If the filter's behavior depends on an extensive list and/or external data
* (e.g. a list of smileys, a list of glossary terms), then the filter module
* can choose to provide a separate, global configuration page rather than
* per-text-format settings. In that case, the settings callback function
* should provide a link to the separate settings page.
*
* @param $form
* The prepopulated form array of the filter administration form.
* @param $form_state
* The state of the (entire) configuration form.
* @param $filter
* The filter object containing the current settings for the given format,
* in $filter->settings.
* @param $format
* The format object being configured.
* @param $defaults
* The default settings for the filter, as defined in 'default settings' in
* hook_filter_info(). These should be combined with $filter->settings to
* define the form element defaults.
* @param $filters
* The complete list of filter objects that are enabled for the given format.
*
* @return
* An array of form elements defining settings for the filter. Array keys
* should match the array keys in $filter->settings and $defaults.
*/
function hook_filter_FILTER_settings($form, &$form_state, $filter, $format, $defaults, $filters) {
$filter->settings += $defaults;
$elements = array();
$elements['nofollow'] = array(
'#type' => 'checkbox',
'#title' => t('Add rel="nofollow" to all links'),
'#default_value' => $filter->settings['nofollow'],
);
return $elements;
}
/**
* Prepare callback for hook_filter_info().
*
* Note: This is not really a hook. The function name is manually specified via
* 'prepare callback' in hook_filter_info(), with this recommended callback
* name pattern. It is called from check_markup().
*
* See hook_filter_info() for a description of the filtering process. Filters
* should not use the 'prepare callback' step for anything other than escaping,
* because that would short-circuit the control the user has over the order in
* which filters are applied.
*
* @param $text
* The text string to be filtered.
* @param $filter
* The filter object containing settings for the given format.
* @param $format
* The text format object assigned to the text to be filtered.
* @param $langcode
* The language code of the text to be filtered.
* @param $cache
* A Boolean indicating whether the filtered text is going to be cached in
* {cache_filter}.
* @param $cache_id
* The ID of the filtered text in {cache_filter}, if $cache is TRUE.
*
* @return
* The prepared, escaped text.
*/
function hook_filter_FILTER_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
// Escape <code> and </code> tags.
$text = preg_replace('|<code>(.+?)</code>|se', "[codefilter_code]$1[/codefilter_code]", $text);
return $text;
}
/**
* Process callback for hook_filter_info().
*
* Note: This is not really a hook. The function name is manually specified via
* 'process callback' in hook_filter_info(), with this recommended callback
* name pattern. It is called from check_markup().
*
* See hook_filter_info() for a description of the filtering process. This step
* is where the filter actually transforms the text.
*
* @param $text
* The text string to be filtered.
* @param $filter
* The filter object containing settings for the given format.
* @param $format
* The text format object assigned to the text to be filtered.
* @param $langcode
* The language code of the text to be filtered.
* @param $cache
* A Boolean indicating whether the filtered text is going to be cached in
* {cache_filter}.
* @param $cache_id
* The ID of the filtered text in {cache_filter}, if $cache is TRUE.
*
* @return
* The filtered text.
*/
function hook_filter_FILTER_process($text, $filter, $format, $langcode, $cache, $cache_id) {
$text = preg_replace('|\[codefilter_code\](.+?)\[/codefilter_code\]|se', "<pre>$1</pre>", $text);
return $text;
}
/**
* Tips callback for hook_filter_info().
*
* Note: This is not really a hook. The function name is manually specified via
* 'tips callback' in hook_filter_info(), with this recommended callback
* name pattern. It is called from _filter_tips().
*
* A filter's tips should be informative and to the point. Short tips are
* preferably one-liners.
*
* @param $filter
* An object representing the filter.
* @param $format
* An object representing the text format the filter is contained in.
* @param $long
* Whether this callback should return a short tip to display in a form
* (FALSE), or whether a more elaborate filter tips should be returned for
* theme_filter_tips() (TRUE).
*
* @return
* Translated text to display as a tip.
*/
function hook_filter_FILTER_tips($filter, $format, $long) {
if ($long) {
return t('Lines and paragraphs are automatically recognized. The &lt;br /&gt; line break, &lt;p&gt; paragraph and &lt;/p&gt; close paragraph tags are inserted automatically. If paragraphs are not recognized simply add a couple blank lines.');
}
else {
return t('Lines and paragraphs break automatically.');
}
}
/**
* @addtogroup hooks
* @{
*/
/** /**
* Perform actions when a text format has been disabled. * Perform actions when a text format has been disabled.
* *
......
...@@ -11,21 +11,29 @@ ...@@ -11,21 +11,29 @@
/** /**
* Non-HTML markup language filters that generate HTML. * Non-HTML markup language filters that generate HTML.
*
* @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface
*/ */
const FILTER_TYPE_MARKUP_LANGUAGE = 0; const FILTER_TYPE_MARKUP_LANGUAGE = 0;
/** /**
* HTML tag and attribute restricting filters. * HTML tag and attribute restricting filters.
*
* @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface
*/ */
const FILTER_TYPE_HTML_RESTRICTOR = 1; const FILTER_TYPE_HTML_RESTRICTOR = 1;
/** /**
* Reversible transformation filters. * Reversible transformation filters.
*
* @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface
*/ */
const FILTER_TYPE_TRANSFORM_REVERSIBLE = 2; const FILTER_TYPE_TRANSFORM_REVERSIBLE = 2;
/** /**
* Irreversible transformation filters. * Irreversible transformation filters.
*
* @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface
*/ */
const FILTER_TYPE_TRANSFORM_IRREVERSIBLE = 3; const FILTER_TYPE_TRANSFORM_IRREVERSIBLE = 3;
...@@ -166,6 +174,8 @@ function filter_menu() { ...@@ -166,6 +174,8 @@ function filter_menu() {
* has been marked as disabled, FALSE is returned. * has been marked as disabled, FALSE is returned.
* *
* @see filter_format_exists() * @see filter_format_exists()
*
* @todo Use entity_load().
*/ */
function filter_format_load($format_id) { function filter_format_load($format_id) {
$formats = filter_formats(); $formats = filter_formats();
...@@ -250,24 +260,6 @@ function filter_permission_name($format) { ...@@ -250,24 +260,6 @@ function filter_permission_name($format) {
return FALSE; return FALSE;
} }
/**
* Implements hook_modules_enabled().
*/
function filter_modules_enabled($modules) {
// Reset the static cache of module-provided filters, in case any of the
// newly enabled modules defines a new filter or alters existing ones.
drupal_static_reset('filter_get_filters');
}
/**
* Implements hook_modules_disabled().
*/
function filter_modules_disabled($modules) {
// Reset the static cache of module-provided filters, in case any of the
// newly disabled modules defined or altered any filters.
drupal_static_reset('filter_get_filters');
}
/** /**
* Retrieves a list of text formats, ordered by weight. * Retrieves a list of text formats, ordered by weight.
* *
...@@ -422,19 +414,10 @@ function filter_get_filter_types_by_format($format_id) { ...@@ -422,19 +414,10 @@ function filter_get_filter_types_by_format($format_id) {
$filter_types = array(); $filter_types = array();
$filters = filter_list_format($format_id); $filters = filter_list_format($format_id);
// Ignore filters that are disabled.
$filters = array_filter($filters, function($filter) {
return $filter->status;
});
$filters_info = filter_get_filters();
foreach ($filters as $filter) { foreach ($filters as $filter) {
if (!isset($filters_info[$filter->name]['type'])) { if ($filter->status) {
throw new Exception(t('Filter %filter has no type specified.', array ('%filter' => $filter->name))); $filter_types[] = $filter->getType();
} }
$filter_types[] = $filters_info[$filter->name]['type'];
} }
return array_unique($filter_types);