Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.435
diff -u -r1.435 block.module
--- modules/block/block.module 23 Nov 2010 16:12:15 -0000 1.435
+++ modules/block/block.module 22 Dec 2010 20:59:00 -0000
@@ -243,7 +243,7 @@
function block_block_view($delta = '') {
$block = db_query('SELECT body, format FROM {block_custom} WHERE bid = :bid', array(':bid' => $delta))->fetchObject();
$data['subject'] = NULL;
- $data['content'] = check_markup($block->body, $block->format, '', TRUE);
+ $data['content'] = check_markup($block->body, $block->format, array('cache' => TRUE, 'type' => 'block', 'object' => $block));
return $data;
}
Index: modules/book/book.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.test,v
retrieving revision 1.28
diff -u -r1.28 book.test
--- modules/book/book.test 18 Oct 2010 05:53:34 -0000 1.28
+++ modules/book/book.test 22 Dec 2010 18:26:21 -0000
@@ -161,7 +161,7 @@
// Check printer friendly version.
$this->drupalGet('book/export/html/' . $node->nid);
$this->assertText($node->title, t('Printer friendly title found.'));
- $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Printer friendly body found.'));
+ $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format'], array('type' => 'node', 'object' => $node)), t('Printer friendly body found.'));
$number++;
}
@@ -230,7 +230,7 @@
// Make sure each part of the book is there.
foreach ($nodes as $node) {
$this->assertText($node->title, t('Node title found in printer friendly version.'));
- $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Node body found in printer friendly version.'));
+ $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format'], array('type' => 'node', 'object' => $node)), t('Node body found in printer friendly version.'));
}
// Make sure we can't export an unsupported format.
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.929
diff -u -r1.929 comment.module
--- modules/comment/comment.module 18 Dec 2010 01:50:15 -0000 1.929
+++ modules/comment/comment.module 22 Dec 2010 18:26:21 -0000
@@ -2182,7 +2182,7 @@
// 1) Filter it into HTML
// 2) Strip out all HTML tags
// 3) Convert entities back to plain-text.
- $comment->subject = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment->comment_body[LANGUAGE_NONE][0]['value'], $comment->comment_body[LANGUAGE_NONE][0]['format'])))), 29, TRUE);
+ $comment->subject = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment->comment_body[LANGUAGE_NONE][0]['value'], $comment->comment_body[LANGUAGE_NONE][0]['format'], array('type' => 'comment', 'object' => $comment))))), 29, TRUE);
// Edge cases where the comment body is populated only by HTML tags will
// require a default subject.
if ($comment->subject == '') {
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.68
diff -u -r1.68 text.module
--- modules/field/modules/text/text.module 13 Nov 2010 07:39:35 -0000 1.68
+++ modules/field/modules/text/text.module 22 Dec 2010 18:26:22 -0000
@@ -157,9 +157,9 @@
// Only process items with a cacheable format, the rest will be handled
// by formatters if needed.
if (empty($instances[$id]['settings']['text_processing']) || filter_format_allowcache($item['format'])) {
- $items[$id][$delta]['safe_value'] = isset($item['value']) ? _text_sanitize($instances[$id], $langcode, $item, 'value') : '';
+ $items[$id][$delta]['safe_value'] = isset($item['value']) ? _text_sanitize($instances[$id], $langcode, $entity_type, $entity, $item, 'value') : '';
if ($field['type'] == 'text_with_summary') {
- $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? _text_sanitize($instances[$id], $langcode, $item, 'summary') : '';
+ $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? _text_sanitize($instances[$id], $langcode, $entity_type, $entity, $item, 'summary') : '';
}
}
}
@@ -262,7 +262,7 @@
case 'text_default':
case 'text_trimmed':
foreach ($items as $delta => $item) {
- $output = _text_sanitize($instance, $langcode, $item, 'value');
+ $output = _text_sanitize($instance, $langcode, $entity_type, $entity, $item, 'value');
if ($display['type'] == 'text_trimmed') {
$output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
}
@@ -273,10 +273,10 @@
case 'text_summary_or_trimmed':
foreach ($items as $delta => $item) {
if (!empty($item['summary'])) {
- $output = _text_sanitize($instance, $langcode, $item, 'summary');
+ $output = _text_sanitize($instance, $langcode, $entity_type, $entity, $item, 'summary');
}
else {
- $output = _text_sanitize($instance, $langcode, $item, 'value');
+ $output = _text_sanitize($instance, $langcode, $entity_type, $entity, $item, 'value');
$output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
}
$element[$delta] = array('#markup' => $output);
@@ -302,7 +302,11 @@
* @param $instance
* The instance definition.
* @param $langcode
- * The language associated to $item.
+ * The language associated to $item.
+ * @param $entity_type
+ * The entity type to sanitize, used for contextual data.
+ * @param $entity
+ * The entity to sanitize, used for contextual data.
* @param $item
* The field value to sanitize.
* @param $column
@@ -311,13 +315,13 @@
* @return
* The sanitized string.
*/
-function _text_sanitize($instance, $langcode, $item, $column) {
+function _text_sanitize($instance, $langcode, $entity_type, $entity, $item, $column) {
// If the value uses a cacheable text format, text_field_load() precomputes
// the sanitized string.
if (isset($item["safe_$column"])) {
return $item["safe_$column"];
}
- return $instance['settings']['text_processing'] ? check_markup($item[$column], $item['format'], $langcode) : check_plain($item[$column]);
+ return $instance['settings']['text_processing'] ? check_markup($item[$column], $item['format'], array('langcode' => $langcode, 'type' => $type, 'object' => $entity)) : check_plain($item[$column]);
}
/**
Index: modules/filter/filter.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.api.php,v
retrieving revision 1.22
diff -u -r1.22 filter.api.php
--- modules/filter/filter.api.php 9 Dec 2010 02:04:16 -0000 1.22
+++ modules/filter/filter.api.php 22 Dec 2010 20:21:34 -0000
@@ -66,6 +66,11 @@
* details.
* - process callback: (required) The name the function that performs the
* actual filtering. See hook_filter_FILTER_process() for details.
+ * - 'context callback': The name of a function that extracts contextual data
+ * from the object of the filtering. For example, a filter that uses the
+ * name of the author of a node would use this to provide the name of the
+ * author as context to the filter. This function will provide additional
+ * contextual data to the prepare and process callbacks.
* - 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.
@@ -190,8 +195,9 @@
* 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 $context
+ * The contextual data for the text to be filtered, including the
+ * language code and any context provided in a 'context callback'.
* @param $cache
* A Boolean indicating whether the filtered text is going to be cached in
* {cache_filter}.
@@ -201,7 +207,7 @@
* @return
* The prepared, escaped text.
*/
-function hook_filter_FILTER_prepare($text, $filter, $format, $langcode, $cache, $cache_id) {
+function hook_filter_FILTER_prepare($text, $filter, $format, $context, $cache, $cache_id) {
// Escape and tags.
$text = preg_replace('|(.+?)|se', "[codefilter_code]$1[/codefilter_code]", $text);
return $text;
@@ -223,8 +229,9 @@
* 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 $context
+ * The contextual data for the text to be filtered, including the
+ * language code and any context provided in a 'context callback'.
* @param $cache
* A Boolean indicating whether the filtered text is going to be cached in
* {cache_filter}.
@@ -234,13 +241,52 @@
* @return
* The filtered text.
*/
-function hook_filter_FILTER_process($text, $filter, $format, $langcode, $cache, $cache_id) {
+function hook_filter_FILTER_process($text, $filter, $format, $context, $cache, $cache_id) {
$text = preg_replace('|\[codefilter_code\](.+?)\[/codefilter_code\]|se', "
$1", $text); return $text; } /** + * Context callback for hook_filter_info(). + * + * Note: This is not really a hook. The function name is manually specified via + * 'context 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 contextual data is generated for the filter. + * + * @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 $type + * The type of object being filtered. + * @param $object + * The object variable of the object being filtered. + * + * @return + * An associative array of contextual data from the object that, upon + * changing, will require the filter to be rerun. + */ +function hook_filter_FILTER_context($text, $filter, $format, $type, $object) { + switch ($type) { + case 'node': + case 'comment': + $account = user_load($object->uid); + // Filter will have to recache when the account name changes, either when + // the author of the comment/node changes or when the user changes their + // name. + return array( + 'user_name' => $account->name, + ); + } +} + +/** * Tips callback for hook_filter_info(). * * Note: This is not really a hook. The function name is manually specified via Index: modules/filter/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v retrieving revision 1.361 diff -u -r1.361 filter.module --- modules/filter/filter.module 9 Dec 2010 02:04:16 -0000 1.361 +++ modules/filter/filter.module 22 Dec 2010 20:34:21 -0000 @@ -705,18 +705,31 @@ * @param $format_id * The format id of the text to be filtered. If no format is assigned, the * fallback format will be used. - * @param $langcode - * Optional: the language code of the text to be filtered, e.g. 'en' for - * English. This allows filters to be language aware so language specific - * text replacement can be implemented. - * @param $cache - * Boolean whether to cache the filtered output in the {cache_filter} table. - * The caller may set this to FALSE when the output is already cached - * elsewhere to avoid duplicate cache lookups and storage. + * @param $options + * An associative array of options, used to override the defaults. Possible + * values include: + * - cache: Boolean whether to cache the filtered output in the + * {cache_filter} table. The caller may set this to TRUE when the output + * is not being cached elsewhere. Otherwise, this should be left as FALSE + * to avoid duplicate cache lookups and storage. Defaults to FALSE. + * - langcode: the language code of the text to be filtered, e.g. 'en' for + * English. This allows filters to be language aware so language specific + * text replacement can be implemented. Defaults to ''. + * - object: an object from which contextual data can be pulled, such as a + * node object, a user object, or a block. Defaults to NULL for no context. + * - type: a string that identifies the type of object, such as 'node', or + * 'user', or 'block'. Defaults to '' for no context. * * @ingroup sanitization */ -function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) { +function check_markup($text, $format_id = NULL, $options = array()) { + $options += array( + 'cache' => FALSE, + 'langcode' => '', + 'object' => NULL, + 'type' => '', + ); + if (!isset($format_id)) { $format_id = filter_fallback_format(); } @@ -727,10 +740,28 @@ } // Check for a cached version of this piece of text. - $cache = $cache && !empty($format->cache); + $cache = $options['cache'] && !empty($format->cache); $cache_id = ''; + + // Get a complete list of filters, ordered properly. + $filters = filter_list_format($format->format); + $filter_info = filter_get_filters(); + + $context = array( + 'langcode' => $options['langcode'], + ); + foreach ($filters as $name => $filter) { + if ($filter->status && isset($filter_info[$name]['context callback']) && function_exists($filter_info[$name]['context callback'])) { + $function = $filter_info[$name]['context callback']; + $result = $function($text, $filter, $format, $options['type'], $options['object']); + if (is_array($result)) { + $context += $result; + } + } + } + if ($cache) { - $cache_id = $format->format . ':' . $langcode . ':' . hash('sha256', $text); + $cache_id = $format->format . ':' . hash('sha256', serialize($context)) . ':' . hash('sha256', $text); if ($cached = cache_get($cache_id, 'cache_filter')) { return $cached->data; } @@ -740,15 +771,11 @@ // need to deal with one possibility. $text = str_replace(array("\r\n", "\r"), "\n", $text); - // Get a complete list of filters, ordered properly. - $filters = filter_list_format($format->format); - $filter_info = filter_get_filters(); - // Give filters the chance to escape HTML-like data such as code or formulas. foreach ($filters as $name => $filter) { if ($filter->status && isset($filter_info[$name]['prepare callback']) && function_exists($filter_info[$name]['prepare callback'])) { $function = $filter_info[$name]['prepare callback']; - $text = $function($text, $filter, $format, $langcode, $cache, $cache_id); + $text = $function($text, $filter, $format, $context, $cache, $cache_id); } } @@ -756,7 +783,7 @@ foreach ($filters as $name => $filter) { if ($filter->status && isset($filter_info[$name]['process callback']) && function_exists($filter_info[$name]['process callback'])) { $function = $filter_info[$name]['process callback']; - $text = $function($text, $filter, $format, $langcode, $cache, $cache_id); + $text = $function($text, $filter, $format, $context, $cache, $cache_id); } } Index: modules/filter/filter.test =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.test,v retrieving revision 1.82 diff -u -r1.82 filter.test --- modules/filter/filter.test 1 Dec 2010 00:00:21 -0000 1.82 +++ modules/filter/filter.test 22 Dec 2010 20:55:15 -0000 @@ -1725,3 +1725,80 @@ } } +/** + * Tests the filter system's contextual data handling. + */ +class FilterContextTestCase extends DrupalWebTestCase { + protected $format_id; + + public static function getInfo() { + return array( + 'name' => 'Filter context', + 'description' => 'Tests the ability of the filtering system to incorporate textual data.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp('filter_test_2'); + $admin_user = $this->drupalCreateUser(array('administer filters')); + $this->drupalLogin($admin_user); + $format_name = $this->randomName(); + $this->drupalPost('admin/config/content/formats/add', array('name' => $format_name, 'format' => strtolower($format_name), 'filters[filter_test_2_context][status]' => TRUE), t('Save configuration')); + $this->drupalLogout(); + $this->format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $format_name))->fetchField(); + filter_formats_reset(); + } + + /** + * Test to make sure that filters are able to use contextual data, and that + * the caching system works properly with contextual data changes. + */ + function testFilterContext() { + $text1 = $this->randomName(); + $text2 = $this->randomName(); + $var1 = (object)array('one' => $this->randomName()); + $var2 = (object)array('one' => $this->randomName()); + + // Text 1 with object 1. + $result1 = check_markup($text1, $this->format_id, array('cache' => TRUE, 'type' => 'test', 'object' => $var1)); + list($success, $text, $var, $rand1) = explode('/', $result1); + $this->assertEqual($success, 'Success', 'Context successfully passed.'); + $this->assertEqual($text, $text1, 'Text matched.'); + $this->assertEqual($var, $var1->one, 'Contextual variable matched.'); + $rand_store[] = $rand1; + + // Text 1 with object 1 again - make sure random variable stays the same. + $result2 = check_markup($text1, $this->format_id, array('cache' => TRUE, 'type' => 'test', 'object' => $var1)); + list($success, $text, $var, $rand2) = explode('/', $result2); + $this->assertEqual($success, 'Success', 'Context successfully passed.'); + $this->assertEqual($text, $text1, 'Text matched.'); + $this->assertEqual($var, $var1->one, 'Contextual variable matched.'); + $this->assertEqual($rand1, $rand2, 'Filter with contextual data was successfully cached.'); + + // Text 1 with object 2 - make sure cache invalidates. + $result3 = check_markup($text1, $this->format_id, array('cache' => TRUE, 'type' => 'test', 'object' => $var2)); + list($success, $text, $var, $rand3) = explode('/', $result3); + $this->assertEqual($success, 'Success', 'Context successfully passed.'); + $this->assertEqual($text, $text1, 'Text matched.'); + $this->assertEqual($var, $var2->one, 'Contextual variable matched.'); + $this->assertNotEqual($rand1, $rand3, 'Filter with contextual data successfully invalidated cache.'); + + // Back to text 1 with object 1 - cache should still be valid. + $result4 = check_markup($text1, $this->format_id, array('cache' => TRUE, 'type' => 'test', 'object' => $var1)); + list($success, $text, $var, $rand4) = explode('/', $result4); + $this->assertEqual($success, 'Success', 'Context successfully passed.'); + $this->assertEqual($text, $text1, 'Text matched.'); + $this->assertEqual($var, $var1->one, 'Contextual variable matched.'); + $this->assertEqual($rand1, $rand4, 'Filter with contextual data did not have cache invalidated.'); + + // Text 2 with object 2 - should be entirely new data. + $result5 = check_markup($text2, $this->format_id, array('cache' => TRUE, 'type' => 'test', 'object' => $var2)); + list($success, $text, $var, $rand5) = explode('/', $result5); + $this->assertEqual($success, 'Success', 'Context successfully passed.'); + $this->assertEqual($text, $text2, 'Text matched.'); + $this->assertEqual($var, $var2->one, 'Contextual variable matched.'); + $this->assertNotEqual($rand1, $rand5, 'No cache mix-up.'); + $this->assertNotEqual($rand3, $rand5, 'No cache mix-up.'); + } +} Index: modules/node/node.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v retrieving revision 1.82 diff -u -r1.82 node.api.php --- modules/node/node.api.php 20 Nov 2010 04:33:56 -0000 1.82 +++ modules/node/node.api.php 22 Dec 2010 18:26:22 -0000 @@ -703,7 +703,7 @@ $text = ''; $comments = db_query('SELECT subject, comment, format FROM {comment} WHERE nid = :nid AND status = :status', array(':nid' => $node->nid, ':status' => COMMENT_PUBLISHED)); foreach ($comments as $comment) { - $text .= '