Commit 2abbabbc authored by Dries's avatar Dries

- Patch #546336 by dropcube: transform hook_filter from having a paramater to hook_filter_().

parent ecfa5477
......@@ -151,10 +151,11 @@ function filter_admin_format_form(&$form_state, $format) {
'#tree' => TRUE,
);
foreach ($all as $id => $filter) {
$filter_info = module_invoke($filter->module, 'filter_info');
$form['filters'][$id] = array('#type' => 'checkbox',
'#title' => $filter->name,
'#default_value' => isset($enabled[$id]),
'#description' => module_invoke($filter->module, 'filter', 'description', $filter->delta),
'#description' => $filter_info[$filter->delta]['description'],
);
}
if (!empty($format->format)) {
......@@ -349,7 +350,10 @@ function filter_admin_configure(&$form_state, $format) {
$list = filter_list_format($format->format);
$form = array();
foreach ($list as $filter) {
$form_module = module_invoke($filter->module, 'filter', 'settings', $filter->delta, $format->format);
$filter_info = module_invoke($filter->module, 'filter_info');
if (isset($filter_info[$filter->delta]['settings callback']) && drupal_function_exists($filter_info[$filter->delta]['settings callback'])) {
$form_module = call_user_func($filter_info[$filter->delta]['settings callback'], $format->format);
}
if (isset($form_module) && is_array($form_module)) {
$form = array_merge($form, $form_module);
}
......
......@@ -13,36 +13,32 @@
/**
* Define content filters.
*
*
* Content in Drupal is passed through all enabled filters before it is
* output. This lets a module modify content to the site administrator's
* liking.
*
* This hook contains all that is needed for having a module provide filtering
* functionality.
*
* Depending on $op, different tasks are performed.
* This hook allows modules to declare input filters they provide.
*
* A module can contain as many filters as it wants. The 'list' operation tells
* the filter system which filters are available. Every filter has a numerical
* 'delta' which is used to refer to it in every operation.
* the filter system which filters are available.
*
* Filtering is a two-step process. First, the content is 'prepared' by calling
* the 'prepare' operation for every filter. The purpose of 'prepare' is to
* escape HTML-like structures. For example, imagine a filter which allows the
* 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 most filters however, the prepare-step is not necessary, and they can
* just return the input without changes.
*
* Filters should not use the 'prepare' step for anything other than escaping,
* Filters should not use the 'prepare callback' step for anything other than escaping,
* because that would short-circuits the control the user has over the order
* in which filters are applied.
*
* The second step is the actual processing step. The result from the
* prepare-step gets passed to all the filters again, this time with the
* 'process' operation. It's here that filters should perform actual changing of
* 'process callback' function. It's here that filters should perform actual changing of
* the content: transforming URLs into hyperlinks, converting smileys into
* images, etc.
*
......@@ -69,93 +65,42 @@
* if it's not needed. You can clear the cache by running the SQL query 'DELETE
* FROM cache_filter';
*
* @param $op
* Which filtering operation to perform. Possible values:
* - list: provide a list of available filters.
* Returns an associative array of filter names with numerical keys.
* These keys are used for subsequent operations and passed back through
* the $delta parameter.
* - no cache: Return true if caching should be disabled for this filter.
* - description: Return a short description of what this filter does.
* - prepare: Return the prepared version of the content in $text.
* - process: Return the processed version of the content in $text.
* - settings: Return HTML form controls for the filter's settings. These
* settings are stored with variable_set() when the form is submitted.
* Remember to use the $format identifier in the variable and control names
* to store settings per text format (e.g. "mymodule_setting_$format").
* @param $delta
* Which of the module's filters to use (applies to every operation except
* 'list'). Modules that only contain one filter can ignore this parameter.
* @param $format
* Which text format the filter is being used in (applies to 'prepare',
* 'process' and 'settings').
* @param $text
* The content to filter (applies to 'prepare' and 'process').
* @param $langcode
* The language code associated with the content, e.g. 'en' for English. This
* enables filters to be language aware and can be used to implement language
* specific text replacements.
* @param $cache_id
* The cache id of the content.
* @return
* The return value depends on $op. The filter hook is designed so that a
* module can return $text for operations it does not use/need.
* @return
* An array of filter items. Each filter item has a numeric key corresponding to the
* filter delta in the module. The item is an associative array that may
* contain the following key-value pairs:
* - "name": Required. The name of the filter.
* - "description": Short description of what this filter does.
* - "prepare callback": The callback function to call in the 'prepare' step of
* the filtering.
* - "process callback": Required. The callback function to call in the 'process' step of
* the filtering.
* - "settings callback": The callback function that provides form controls for
* the filter's settings. These settings are stored with variable_set() when
* the form is submitted. Remember to use the $format identifier in the variable
* and control names to store settings per text format (e.g. "mymodule_setting_$format").
* - "tips callback": The callback function that provide tips for using filters.
* A module's tips should be informative and to the point. Short tips are
* preferably one-liners.
* - "cache": Specify if the filter result can be cached. TRUE by default.
*
* For a detailed usage example, see filter_example.module. For an example of
* using multiple filters in one module, see filter_filter() and
* filter_filter_tips().
*/
function hook_filter($op, $delta = 0, $format = -1, $text = '', $langcode = '', $cache_id = 0) {
switch ($op) {
case 'list':
return array(0 => t('Code filter'));
case 'description':
return t('Allows users to post code verbatim using &lt;code&gt; and &lt;?php ?&gt; tags.');
case 'prepare':
// Note: we use [ and ] to replace < > during the filtering process.
// For more information, see "Temporary placeholders and
// delimiters" at http://drupal.org/node/209715.
$text = preg_replace('@<code>(.+?)</code>@se', "'[codefilter-code]' . codefilter_escape('\\1') . '[/codefilter-code]'", $text);
$text = preg_replace('@<(\?(php)?|%)(.+?)(\?|%)>@se', "'[codefilter-php]' . codefilter_escape('\\3') . '[/codefilter-php]'", $text);
return $text;
case "process":
$text = preg_replace('@[codefilter-code](.+?)[/codefilter-code]@se', "codefilter_process_code('$1')", $text);
$text = preg_replace('@[codefilter-php](.+?)[/codefilter-php]@se', "codefilter_process_php('$1')", $text);
return $text;
default:
return $text;
}
}
/**
* Provide tips for using filters.
*
* A module's tips should be informative and to the point. Short tips are
* preferably one-liners.
*
* @param $delta
* Which of this module's filters to use. Modules which only implement one
* filter can ignore this parameter.
* @param $format
* Which format we are providing tips for.
* @param $long
* If set to true, long tips are requested, otherwise short tips are needed.
* @return
* The text of the filter tip.
*
*
* using multiple filters in one module, see filter_filter_info().
*/
function hook_filter_tips($delta, $format, $long = FALSE) {
if ($long) {
return t('To post pieces of code, surround them with &lt;code&gt;...&lt;/code&gt; tags. For PHP code, you can use &lt;?php ... ?&gt;, which will also colour it based on syntax.');
}
else {
return t('You may post code using &lt;code&gt;...&lt;/code&gt; (generic) or &lt;?php ... ?&gt; (highlighted PHP) tags.');
}
function hook_filter_info() {
$filters[0] = array(
'name' => 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',
'tips callback' => '_filter_html_tips'
);
$filters[1] = array(
'name' => t('Convert line breaks'),
'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'
);
}
/**
......
......@@ -329,12 +329,15 @@ function filter_formats($index = NULL) {
function filter_list_all() {
$filters = array();
foreach (module_implements('filter') as $module) {
$function = $module . '_filter';
$list = $function('list');
if (isset($list) && is_array($list)) {
foreach ($list as $delta => $name) {
$filters[$module . '/' . $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name);
foreach (module_implements('filter_info') as $module) {
$function = $module . '_filter_info';
$info = $function('list');
if (isset($info) && is_array($info)) {
foreach ($info as $delta => $filter) {
$filters[$module . '/' . $delta] = (object)($filter + array(
'module' => $module,
'delta' => $delta,
));
}
}
}
......@@ -379,9 +382,9 @@ function filter_list_format($format) {
$filters[$format] = array();
$result = db_query("SELECT * FROM {filter} WHERE format = :format ORDER BY weight, module, delta", array(':format' => (int) $format));
foreach ($result as $filter) {
$list = module_invoke($filter->module, 'filter', 'list');
if (isset($list) && is_array($list) && isset($list[$filter->delta])) {
$filter->name = $list[$filter->delta];
$info = module_invoke($filter->module, 'filter_info');
if (isset($info) && is_array($info) && isset($info[$filter->delta])) {
$filter->name = $info[$filter->delta]['name'];
$filters[$format][$filter->module . '/' . $filter->delta] = $filter;
}
}
......@@ -448,12 +451,18 @@ function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $c
// Give filters the chance to escape HTML-like data such as code or formulas.
foreach ($filters as $filter) {
$text = module_invoke($filter->module, 'filter', 'prepare', $filter->delta, $format, $text, $langcode, $cache_id);
$filter_info = module_invoke($filter->module, 'filter_info');
if (isset($filter_info[$filter->delta]['prepare callback']) && drupal_function_exists($filter_info[$filter->delta]['prepare callback'])) {
$text = call_user_func($filter_info[$filter->delta]['prepare callback'], $text, $format, $langcode, $cache_id);
}
}
// Perform filtering.
foreach ($filters as $filter) {
$text = module_invoke($filter->module, 'filter', 'process', $filter->delta, $format, $text, $langcode, $cache_id);
$filter_info = module_invoke($filter->module, 'filter_info');
if (isset($filter_info[$filter->delta]['process callback']) && drupal_function_exists($filter_info[$filter->delta]['process callback'])) {
$text = call_user_func($filter_info[$filter->delta]['process callback'], $text, $format, $langcode, $cache_id);
}
}
// Store in cache with a minimum expiration time of 1 day.
......@@ -578,7 +587,7 @@ function _filter_tips($format, $long = FALSE) {
* and returns a full DOMDocument object that represents this document.
* You can use filter_dom_serialize() to serialize this DOMDocument
* back to a XHTML snippet.
*
*
* @param $text
* The partial (X)HTML snippet to load. Invalid mark-up
* will be corrected on import.
......@@ -600,7 +609,7 @@ function filter_dom_load($text) {
*
* The resulting XHTML snippet will be properly formatted
* to be compatible with HTML user agents.
*
*
* @param $dom_document
* A DOMDocument object to serialize, only the tags below
* the first <body> node will be converted.
......@@ -641,72 +650,39 @@ function theme_filter_guidelines($format) {
* Filters implemented by the filter.module.
*/
/**
* Implement hook_filter().
*
* Set up a basic set of essential filters:
* - Limit allowed HTML tags:
* Restricts user-supplied HTML to certain tags, and removes dangerous
* components in allowed tags.
* - Convert line breaks:
* Converts newlines into paragraph and break tags.
* - Convert URLs into links:
* Converts URLs and e-mail addresses into links.
* - Correct broken HTML:
* Fixes faulty HTML.
* - Escape all HTML:
* Converts all HTML tags into visible text.
*/
function filter_filter($op, $delta = 0, $format = -1, $text = '') {
switch ($op) {
case 'list':
return array(0 => t('Limit allowed HTML tags'), 1 => t('Convert line breaks'), 2 => t('Convert URLs into links'), 3 => t('Correct broken HTML'), 4 => t('Escape all HTML'));
case 'description':
switch ($delta) {
case 0:
return 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.');
case 1:
return t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.');
case 2:
return t('Turns web and e-mail addresses into clickable links.');
case 3:
return t('Corrects faulty and chopped off HTML in postings.');
case 4:
return t('Escapes all HTML tags, so they will be visible instead of being effective.');
default:
return;
}
case 'process':
switch ($delta) {
case 0:
return _filter_html($text, $format);
case 1:
return _filter_autop($text);
case 2:
return _filter_url($text, $format);
case 3:
return _filter_htmlcorrector($text);
case 4:
return trim(check_plain($text));
default:
return $text;
}
case 'settings':
switch ($delta) {
case 0:
return _filter_html_settings($format);
case 2:
return _filter_url_settings($format);
default:
return;
}
default:
return $text;
}
function filter_filter_info() {
$filters[0] = array(
'name' => 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',
'tips callback' => '_filter_html_tips'
);
$filters[1] = array(
'name' => t('Convert line breaks'),
'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'
);
$filters[2] = array(
'name' => 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',
'tips callback' => '_filter_url_tips'
);
$filters[3] = array(
'name' => t('Correct broken HTML'),
'description' => t('Corrects faulty and chopped off HTML in postings.'),
'process callback' => '_filter_htmlcorrector',
);
$filters[4] = array(
'name' => t('Escape all HTML'),
'description' => t('Escapes all HTML tags, so they will be visible instead of being effective.'),
'process callback' => '_filter_html_escape',
'tips callback' => '_filter_html_escape_tips'
);
return $filters;
}
/**
......
......@@ -421,35 +421,35 @@ class FilterUnitTest extends DrupalWebTestCase {
* or better a whitelist approach should be used for that too.
*/
function testFilter() {
// Check that access restriction really works.
$format = "fake format";
// HTML filter is not able to secure some tags, these should never be
// allowed.
$f = filter_filter('process', 0, 'no_such_format', '<script />');
$f = _filter_html('<script />', $format);
$this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.'));
$f = filter_filter('process', 0, 'no_such_format', '<iframe />');
$f = _filter_html('<iframe />', $format);
$this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.'));
$f = filter_filter('process', 0, 'no_such_format', '<object />');
$f = _filter_html('<object />', $format);
$this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.'));
$f = filter_filter('process', 0, 'no_such_format', '<style />');
$f = _filter_html('<style />', $format);
$this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.'));
// Some tags make CSRF attacks easier, let the user take the risk herself.
$f = filter_filter('process', 0, 'no_such_format', '<img />');
$f = _filter_html('<img />', $format);
$this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.'));
$f = filter_filter('process', 0, 'no_such_format', '<input />');
$f = _filter_html('<input />', $format);
$this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.'));
// Filtering content of some attributes is infeasible, these shouldn't be
// allowed too.
$f = filter_filter('process', 0, 'no_such_format', '<p style="display: none;" />');
$f = _filter_html('<p style="display: none;" />', $format);
$this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.'));
$f = filter_filter('process', 0, 'no_such_format', '<p onerror="alert(0);" />');
$f = _filter_html('<p onerror="alert(0);" />', $format);
$this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.'));
}
......
......@@ -121,24 +121,21 @@ function php_filter_tips($delta, $format, $long = FALSE) {
}
/**
* Implement hook_filter(). Contains a basic PHP evaluator.
* Implement hook_filter_info().
*
* Contains a basic PHP evaluator.
*
* Executes PHP code. Use with care.
*/
function php_filter($op, $delta = 0, $format = -1, $text = '') {
switch ($op) {
case 'list':
return array(0 => t('PHP evaluator'));
case 'no cache':
// No caching for the PHP evaluator.
return $delta == 0;
case 'description':
return t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!');
case 'process':
return php_eval($text);
default:
return $text;
}
function php_filter_info() {
return array(
array(
'name' => t('PHP evaluator'),
'description' => t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!'),
'cache' => FALSE,
'process callback' => 'php_eval'
)
);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment