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

merge-requests/26/head
Dries Buytaert 2009-08-13 19:53:20 +00:00
parent ecfa5477e7
commit 2abbabbcc2
5 changed files with 122 additions and 200 deletions

View File

@ -151,10 +151,11 @@ function filter_admin_format_form(&$form_state, $format) {
'#tree' => TRUE, '#tree' => TRUE,
); );
foreach ($all as $id => $filter) { foreach ($all as $id => $filter) {
$filter_info = module_invoke($filter->module, 'filter_info');
$form['filters'][$id] = array('#type' => 'checkbox', $form['filters'][$id] = array('#type' => 'checkbox',
'#title' => $filter->name, '#title' => $filter->name,
'#default_value' => isset($enabled[$id]), '#default_value' => isset($enabled[$id]),
'#description' => module_invoke($filter->module, 'filter', 'description', $filter->delta), '#description' => $filter_info[$filter->delta]['description'],
); );
} }
if (!empty($format->format)) { if (!empty($format->format)) {
@ -349,7 +350,10 @@ function filter_admin_configure(&$form_state, $format) {
$list = filter_list_format($format->format); $list = filter_list_format($format->format);
$form = array(); $form = array();
foreach ($list as $filter) { 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)) { if (isset($form_module) && is_array($form_module)) {
$form = array_merge($form, $form_module); $form = array_merge($form, $form_module);
} }

View File

@ -18,31 +18,27 @@
* output. This lets a module modify content to the site administrator's * output. This lets a module modify content to the site administrator's
* liking. * liking.
* *
* This hook contains all that is needed for having a module provide filtering * This hook allows modules to declare input filters they provide.
* functionality.
*
* Depending on $op, different tasks are performed.
* *
* A module can contain as many filters as it wants. The 'list' operation tells * 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 * the filter system which filters are available.
* 'delta' which is used to refer to it in every operation.
* *
* Filtering is a two-step process. First, the content is 'prepared' by calling * 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 * the 'prepare callback' function for every filter. The purpose of the 'prepare callback'
* escape HTML-like structures. For example, imagine a filter which allows the * 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 * user to paste entire chunks of programming code without requiring manual
* escaping of special HTML characters like @< or @&. If the programming code * escaping of special HTML characters like @< or @&. If the programming code
* were left untouched, then other filters could think it was HTML and change * 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 * it. For most filters however, the prepare-step is not necessary, and they can
* just return the input without changes. * 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 * because that would short-circuits the control the user has over the order
* in which filters are applied. * in which filters are applied.
* *
* The second step is the actual processing step. The result from the * 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 * 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 * the content: transforming URLs into hyperlinks, converting smileys into
* images, etc. * images, etc.
* *
@ -69,93 +65,42 @@
* if it's not needed. You can clear the cache by running the SQL query 'DELETE * if it's not needed. You can clear the cache by running the SQL query 'DELETE
* FROM cache_filter'; * 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 * @return
* The return value depends on $op. The filter hook is designed so that a * An array of filter items. Each filter item has a numeric key corresponding to the
* module can return $text for operations it does not use/need. * filter delta in the module. The item is an associative array that may
* * contain the following key-value pairs:
* For a detailed usage example, see filter_example.module. For an example of * - "name": Required. The name of the filter.
* using multiple filters in one module, see filter_filter() and * - "description": Short description of what this filter does.
* filter_filter_tips(). * - "prepare callback": The callback function to call in the 'prepare' step of
*/ * the filtering.
function hook_filter($op, $delta = 0, $format = -1, $text = '', $langcode = '', $cache_id = 0) { * - "process callback": Required. The callback function to call in the 'process' step of
switch ($op) { * the filtering.
case 'list': * - "settings callback": The callback function that provides form controls for
return array(0 => t('Code filter')); * 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
case 'description': * and control names to store settings per text format (e.g. "mymodule_setting_$format").
return t('Allows users to post code verbatim using &lt;code&gt; and &lt;?php ?&gt; tags.'); * - "tips callback": The callback function that provide tips for using filters.
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 * A module's tips should be informative and to the point. Short tips are
* preferably one-liners. * preferably one-liners.
* - "cache": Specify if the filter result can be cached. TRUE by default.
* *
* @param $delta * For a detailed usage example, see filter_example.module. For an example of
* Which of this module's filters to use. Modules which only implement one * using multiple filters in one module, see filter_filter_info().
* 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.
*
*
*/ */
function hook_filter_tips($delta, $format, $long = FALSE) { function hook_filter_info() {
if ($long) { $filters[0] = array(
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.'); '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.'),
else { 'process callback' => '_filter_html',
return t('You may post code using &lt;code&gt;...&lt;/code&gt; (generic) or &lt;?php ... ?&gt; (highlighted PHP) tags.'); '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'
);
} }
/** /**

View File

@ -329,12 +329,15 @@ function filter_formats($index = NULL) {
function filter_list_all() { function filter_list_all() {
$filters = array(); $filters = array();
foreach (module_implements('filter') as $module) { foreach (module_implements('filter_info') as $module) {
$function = $module . '_filter'; $function = $module . '_filter_info';
$list = $function('list'); $info = $function('list');
if (isset($list) && is_array($list)) { if (isset($info) && is_array($info)) {
foreach ($list as $delta => $name) { foreach ($info as $delta => $filter) {
$filters[$module . '/' . $delta] = (object)array('module' => $module, 'delta' => $delta, 'name' => $name); $filters[$module . '/' . $delta] = (object)($filter + array(
'module' => $module,
'delta' => $delta,
));
} }
} }
} }
@ -379,9 +382,9 @@ function filter_list_format($format) {
$filters[$format] = array(); $filters[$format] = array();
$result = db_query("SELECT * FROM {filter} WHERE format = :format ORDER BY weight, module, delta", array(':format' => (int) $format)); $result = db_query("SELECT * FROM {filter} WHERE format = :format ORDER BY weight, module, delta", array(':format' => (int) $format));
foreach ($result as $filter) { foreach ($result as $filter) {
$list = module_invoke($filter->module, 'filter', 'list'); $info = module_invoke($filter->module, 'filter_info');
if (isset($list) && is_array($list) && isset($list[$filter->delta])) { if (isset($info) && is_array($info) && isset($info[$filter->delta])) {
$filter->name = $list[$filter->delta]; $filter->name = $info[$filter->delta]['name'];
$filters[$format][$filter->module . '/' . $filter->delta] = $filter; $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. // Give filters the chance to escape HTML-like data such as code or formulas.
foreach ($filters as $filter) { 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. // Perform filtering.
foreach ($filters as $filter) { 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. // Store in cache with a minimum expiration time of 1 day.
@ -641,72 +650,39 @@ function theme_filter_guidelines($format) {
* Filters implemented by the filter.module. * Filters implemented by the filter.module.
*/ */
/** function filter_filter_info() {
* Implement hook_filter(). $filters[0] = array(
* 'name' => t('Limit allowed HTML tags'),
* Set up a basic set of essential filters: '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.'),
* - Limit allowed HTML tags: 'process callback' => '_filter_html',
* Restricts user-supplied HTML to certain tags, and removes dangerous 'settings callback' => '_filter_html_settings',
* components in allowed tags. 'tips callback' => '_filter_html_tips'
* - Convert line breaks: );
* Converts newlines into paragraph and break tags. $filters[1] = array(
* - Convert URLs into links: 'name' => t('Convert line breaks'),
* Converts URLs and e-mail addresses into links. 'description' => t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.'),
* - Correct broken HTML: 'process callback' => '_filter_autop',
* Fixes faulty HTML. 'tips callback' => '_filter_autop_tips'
* - Escape all HTML: );
* Converts all HTML tags into visible text. $filters[2] = array(
*/ 'name' => t('Convert URLs into links'),
function filter_filter($op, $delta = 0, $format = -1, $text = '') { 'description' => t('Turns web and e-mail addresses into clickable links.'),
switch ($op) { 'process callback' => '_filter_url',
case 'list': 'settings callback' => '_filter_url_settings',
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')); 'tips callback' => '_filter_url_tips'
);
case 'description': $filters[3] = array(
switch ($delta) { 'name' => t('Correct broken HTML'),
case 0: 'description' => t('Corrects faulty and chopped off HTML in postings.'),
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.'); 'process callback' => '_filter_htmlcorrector',
case 1: );
return t('Converts line breaks into HTML (i.e. &lt;br&gt; and &lt;p&gt;) tags.'); $filters[4] = array(
case 2: 'name' => t('Escape all HTML'),
return t('Turns web and e-mail addresses into clickable links.'); 'description' => t('Escapes all HTML tags, so they will be visible instead of being effective.'),
case 3: 'process callback' => '_filter_html_escape',
return t('Corrects faulty and chopped off HTML in postings.'); 'tips callback' => '_filter_html_escape_tips'
case 4: );
return t('Escapes all HTML tags, so they will be visible instead of being effective.'); return $filters;
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;
}
} }
/** /**

View File

@ -421,35 +421,35 @@ class FilterUnitTest extends DrupalWebTestCase {
* or better a whitelist approach should be used for that too. * or better a whitelist approach should be used for that too.
*/ */
function testFilter() { function testFilter() {
// Check that access restriction really works. $format = "fake format";
// HTML filter is not able to secure some tags, these should never be // HTML filter is not able to secure some tags, these should never be
// allowed. // 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.')); $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.')); $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.')); $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.')); $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. // 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.')); $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.')); $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.'));
// Filtering content of some attributes is infeasible, these shouldn't be // Filtering content of some attributes is infeasible, these shouldn't be
// allowed too. // 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.')); $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.')); $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.'));
} }

View File

@ -121,24 +121,21 @@ else {
} }
/** /**
* Implement hook_filter(). Contains a basic PHP evaluator. * Implement hook_filter_info().
*
* Contains a basic PHP evaluator.
* *
* Executes PHP code. Use with care. * Executes PHP code. Use with care.
*/ */
function php_filter($op, $delta = 0, $format = -1, $text = '') { function php_filter_info() {
switch ($op) { return array(
case 'list': array(
return array(0 => t('PHP evaluator')); 'name' => t('PHP evaluator'),
case 'no cache': 'description' => t('Executes a piece of PHP code. The usage of this filter should be restricted to administrators only!'),
// No caching for the PHP evaluator. 'cache' => FALSE,
return $delta == 0; 'process callback' => 'php_eval'
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;
}
} }