diff --git a/core/includes/common.inc b/core/includes/common.inc index 5572e54..edefe1d 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -3468,7 +3468,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { * * @return string * The rendered HTML of all children of the element. - * @see drupal_render() */ function drupal_render_children(&$element, $children_keys = NULL) { @@ -3681,7 +3680,7 @@ function drupal_render_cache_generate_placeholder($callback, array &$context) { 'token' => \Drupal\Component\Utility\Crypt::randomBytesBase64(55), ); - return ''; + return ''; } /** diff --git a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php index 7272357..c0faa78 100644 --- a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php +++ b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php @@ -9,6 +9,7 @@ use Drupal\ckeditor\CKEditorPluginBase; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Cache\Cache; use Drupal\editor\Entity\Editor; use Drupal\filter\Plugin\FilterInterface; @@ -251,22 +252,42 @@ public function getButtons() { * An array containing the "format_tags" configuration. */ protected function generateFormatTagsSetting(Editor $editor) { - // The

tag is always allowed — HTML without

tags is nonsensical. - $format_tags = array('p'); + // When no text format is associated yet, assume no tag is allowed. + // @see \Drupal\Editor\EditorInterface::hasAssociatedFilterFormat() + if (!$editor->hasAssociatedFilterFormat()) { + return array(); + } - // Given the list of possible format tags, automatically determine whether - // the current text format allows this tag, and thus whether it should show - // up in the "Format" dropdown. - $possible_format_tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre'); - foreach ($possible_format_tags as $tag) { - $input = '<' . $tag . '>TEST'; - $output = trim(check_markup($input, $editor->id(), '', TRUE)); - if ($input == $output) { - $format_tags[] = $tag; + $format = $editor->getFilterFormat(); + $cid = 'ckeditor_internal_format_tags:' . $format->id(); + + if ($cached = \Drupal::cache()->get($cid)) { + $format_tags = $cached->data; + } + else { + // The

tag is always allowed — HTML without

tags is nonsensical. + $format_tags = array('p'); + + // Given the list of possible format tags, automatically determine whether + // the current text format allows this tag, and thus whether it should show + // up in the "Format" dropdown. + $possible_format_tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre'); + foreach ($possible_format_tags as $tag) { + $input = '<' . $tag . '>TEST'; + $output = trim(check_markup($input, $editor->id())); + if ($input == $output) { + $format_tags[] = $tag; + } } + $format_tags = implode(';', $format_tags); + + // Cache the "format_tags" configuration. This cache item is infinitely + // valid; it only changes whenever the text format is changed, hence it's + // tagged with the text format's cache tag. + \Drupal::cache()->set($cid, $format_tags, Cache::PERMANENT, $format->getCacheTag()); } - return implode(';', $format_tags); + return $format_tags; } /** diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index b09bfa8..4844daf 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -1369,8 +1369,9 @@ function template_preprocess_comment(&$variables) { $variables['user_picture'] = array(); } - if (\Drupal::config('user.settings')->get('signatures') && $account->getSignature()) { - $variables['signature'] = check_markup($account->getSignature(), $account->getSignatureFormat(), '', TRUE) ; + if (isset($variables['elements']['signature'])) { + $variables['signature'] = $variables['elements']['signature']['#markup']; + unset($variables['elements']['signature']); } else { $variables['signature'] = ''; diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php index e993e7a..7769b5e 100644 --- a/core/modules/comment/src/CommentViewBuilder.php +++ b/core/modules/comment/src/CommentViewBuilder.php @@ -131,6 +131,21 @@ public function buildComponents(array &$build, array $entities, array $displays, '#markup' => $placeholder, ); + $account = comment_prepare_author($entity); + if (\Drupal::config('user.settings')->get('signatures') && $account->getSignature()) { + $build[$id]['signature'] = array( + '#type' => 'processed_text', + '#text' => $account->getSignature(), + '#format' => $account->getSignatureFormat(), + '#langcode' => $entity->language()->getId(), + ); + // The signature will only be rendered in the theme layer, which means + // its associated cache tags will not bubble up. Work around this for + // now by already rendering the signature here. + // @todo remove this work-around, see https://drupal.org/node/2273277 + drupal_render($build[$id]['signature'], TRUE); + } + if (!isset($build[$id]['#attached'])) { $build[$id]['#attached'] = array(); } diff --git a/core/modules/editor/src/EditorController.php b/core/modules/editor/src/EditorController.php index 59c1ef9..e5a768e 100644 --- a/core/modules/editor/src/EditorController.php +++ b/core/modules/editor/src/EditorController.php @@ -47,7 +47,7 @@ public function getUntransformedText(EntityInterface $entity, $field_name, $lang // Direct text editing is only supported for single-valued fields. $field = $entity->getTranslation($langcode)->$field_name; - $editable_text = check_markup($field->value, $field->format, $langcode, FALSE, array(FilterInterface::TYPE_TRANSFORM_REVERSIBLE, FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE)); + $editable_text = check_markup($field->value, $field->format, $langcode, array(FilterInterface::TYPE_TRANSFORM_REVERSIBLE, FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE)); $response->addCommand(new GetUntransformedTextCommand($editable_text)); return $response; diff --git a/core/modules/editor/src/Plugin/Filter/EditorFileReference.php b/core/modules/editor/src/Plugin/Filter/EditorFileReference.php new file mode 100644 index 0000000..37717a0 --- /dev/null +++ b/core/modules/editor/src/Plugin/Filter/EditorFileReference.php @@ -0,0 +1,55 @@ +query('//*[@data-editor-file-uuid]') as $node) { + $uuid = $node->getAttribute('data-editor-file-uuid'); + // Only process the first occurrence of each file UUID. + if (!isset($processed_uuids[$uuid])) { + $processed_uuids[$uuid] = TRUE; + + $file = entity_load_by_uuid('file', $uuid); + if ($file) { + $result->addCacheTags($file->getCacheTag()); + } + } + } + } + + return $result; + } + +} diff --git a/core/modules/editor/src/Tests/EditorFileReferenceFilterTest.php b/core/modules/editor/src/Tests/EditorFileReferenceFilterTest.php new file mode 100644 index 0000000..d0bfbb5 --- /dev/null +++ b/core/modules/editor/src/Tests/EditorFileReferenceFilterTest.php @@ -0,0 +1,120 @@ + 'Editor File Reference filter', + 'description' => "Tests Editor module's file reference filter.", + 'group' => 'Text Editor', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->installConfig(array('system')); + $this->installSchema('file', array('file_managed', 'file_usage')); + + $manager = $this->container->get('plugin.manager.filter'); + $bag = new FilterBag($manager, array()); + $this->filters = $bag->getAll(); + } + + /** + * Tests the editor file reference filter. + */ + function testEditorFileReferenceFilter() { + $filter = $this->filters['editor_file_reference']; + + $test = function($input) use ($filter) { + return $filter->process($input, 'und'); + }; + + file_put_contents('public://llama.jpg', $this->randomName()); + $image = entity_create('file', array('uri' => 'public://llama.jpg')); + $image->save(); + $id = $image->id(); + $uuid = $image->uuid(); + + file_put_contents('public://alpaca.jpg', $this->randomName()); + $image_2 = entity_create('file', array('uri' => 'public://alpaca.jpg')); + $image_2->save(); + $id_2 = $image_2->id(); + $uuid_2 = $image_2->uuid(); + + $this->pass('No data-editor-file-uuid attribute.'); + $input = ''; + $output = $test($input); + $this->assertIdentical($input, $output->getProcessedText()); + + $this->pass('One data-editor-file-uuid attribute.'); + $input = ''; + $output = $test($input); + $this->assertIdentical($input, $output->getProcessedText()); + $this->assertEqual(array('file' => array($id)), $output->getCacheTags()); + + $this->pass('One data-editor-file-uuid attribute with odd capitalization.'); + $input = ''; + $output = $test($input); + $this->assertIdentical($input, $output->getProcessedText()); + $this->assertEqual(array('file' => array($id)), $output->getCacheTags()); + + $this->pass('One data-editor-file-uuid attribute on a non-image tag.'); + $input = '

Body: 
-
+

Hello, world!

'; diff --git a/core/modules/filter/config/install/filter.format.plain_text.yml b/core/modules/filter/config/install/filter.format.plain_text.yml index 70abb88..5de8988 100644 --- a/core/modules/filter/config/install/filter.format.plain_text.yml +++ b/core/modules/filter/config/install/filter.format.plain_text.yml @@ -9,7 +9,6 @@ weight: 10 roles: - anonymous - authenticated -cache: true filters: # Escape all HTML. filter_html_escape: diff --git a/core/modules/filter/config/schema/filter.schema.yml b/core/modules/filter/config/schema/filter.schema.yml index f1df7df..e0f53e3 100644 --- a/core/modules/filter/config/schema/filter.schema.yml +++ b/core/modules/filter/config/schema/filter.schema.yml @@ -36,9 +36,6 @@ filter.format.*: sequence: - type: string label: 'Role' - cache: - type: boolean - label: 'Cache' filters: type: sequence label: 'Enabled filters' @@ -47,6 +44,9 @@ filter.format.*: langcode: type: string label: 'Default language' + dependencies: + type: config_dependencies + label: 'Dependencies' filter_settings.*: type: sequence diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 3efe9ad..5c0a4e2 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -6,6 +6,7 @@ */ use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\String; use Drupal\Component\Utility\Xss; use Drupal\Core\Cache\Cache; @@ -98,10 +99,137 @@ function filter_element_info() { '#base_type' => 'textarea', '#theme_wrappers' => array('text_format_wrapper'), ); + $type['processed_text'] = array( + '#text' => '', + '#format' => NULL, + '#filter_types_to_skip' => array(), + '#langcode' => 'und', + '#pre_render' => array('filter_pre_render_text'), + ); return $type; } /** + * Pre-render callback: Renders a processed text element into #markup. + * + * Runs all the enabled filters on a piece of text. + * + * Note: Because filters can inject JavaScript or execute PHP code, security is + * vital here. When a user supplies a text format, you should validate it using + * $format->access() before accepting/using it. This is normally done in the + * validation stage of the Form API. You should for example never make a + * preview of content in a disallowed format. + * + * @param array $element + * A structured array with the following key-value pairs: + * - #text: containing the text to be filtered + * - #format: containing the machine name of the filter format to be used to + * filter the text. Defaults to the fallback format. See + * filter_fallback_format(). + * - #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 'und'. + * - #filter_types_to_skip: an array of filter types to skip, or an empty + * array (default) to skip no filter types. All of the format's filters will + * be applied, except for filters of the types that are marked to be skipped. + * FilterInterface::TYPE_HTML_RESTRICTOR is the only type that cannot be + * skipped. + * + * @return array + * The passed-in element with the filtered text in '#markup'. + * + * @ingroup sanitization + */ +function filter_pre_render_text(array $element) { + $format_id = $element['#format']; + $filter_types_to_skip = $element['#filter_types_to_skip']; + $text = $element['#text']; + $langcode = $element['#langcode']; + + if (!isset($format_id)) { + $format_id = filter_fallback_format(); + } + // If the requested text format does not exist, the text cannot be filtered. + /** @var \Drupal\filter\Entity\FilterFormat $format **/ + if (!$format = entity_load('filter_format', $format_id)) { + watchdog('filter', 'Missing text format: %format.', array('%format' => $format_id), WATCHDOG_ALERT); + $element['#markup'] = ''; + return $element; + } + + // Prevent FilterInterface::TYPE_HTML_RESTRICTOR from being skipped. + if (in_array(FilterInterface::TYPE_HTML_RESTRICTOR, $filter_types_to_skip)) { + $filter_types_to_skip = array_diff($filter_types_to_skip, array(FilterInterface::TYPE_HTML_RESTRICTOR)); + } + + // Convert all Windows and Mac newlines to a single newline, so filters only + // need to deal with one possibility. + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + // Get a complete list of filters, ordered properly. + /** @var \Drupal\filter\FilterBag $filters **/ + $filters = $format->filters(); + + // Give filters the chance to escape HTML-like data such as code or formulas. + /** @var \Drupal\filter\Plugin\FilterInterface $filter **/ + foreach ($filters as $filter) { + // If necessary, skip filters of a certain type. + if (in_array($filter->getType(), $filter_types_to_skip)) { + continue; + } + if ($filter->status) { + $text = $filter->prepare($text, $langcode); + } + } + + // Perform filtering. + $all_cache_tags = array(); + $all_assets = array(); + $all_post_render_cache_callbacks = array(); + foreach ($filters as $filter) { + // If necessary, skip filters of a certain type. + if (in_array($filter->getType(), $filter_types_to_skip)) { + continue; + } + if ($filter->status) { + $result = $filter->process($text, $langcode); + $all_assets[] = $result->getAssets(); + $all_cache_tags[] = $result->getCacheTags(); + $all_post_render_cache_callbacks[] = $result->getPostRenderCacheCallbacks(); + $text = $result->getProcessedText(); + } + } + + // Filtering done, store in #markup. + $element['#markup'] = $text; + + // Collect all cache tags. + if (isset($element['#cache']) && isset($element['#cache']['tags'])) { + // Prepend the original cache tags array. + array_unshift($all_cache_tags, $element['#cache']['tags']); + } + // Prepend the text format's cache tags array. + array_unshift($all_cache_tags, $format->getCacheTag()); + $element['#cache']['tags'] = NestedArray::mergeDeepArray($all_cache_tags); + + // Collect all attached assets. + if (isset($element['#attached'])) { + // Prepend the original attached assets array. + array_unshift($all_assets, $element['#attached']); + } + $element['#attached'] = NestedArray::mergeDeepArray($all_assets); + + // Collect all #post_render_cache callbacks. + if (isset($element['#post_render_cache'])) { + // Prepend the original attached #post_render_cache array. + array_unshift($all_assets, $element['#post_render_cache']); + } + $element['#post_render_cache'] = NestedArray::mergeDeepArray($all_post_render_cache_callbacks); + + return $element; +} + +/** * Implements hook_permission(). */ function filter_permission() { @@ -301,24 +429,6 @@ function filter_fallback_format() { } /** - * Checks if the text in a certain text format is allowed to be cached. - * - * This function can be used to check whether the result of the filtering - * process can be cached. A text format may allow caching depending on the - * filters enabled. - * - * @param string $format_id - * The text format ID to check. - * - * @return bool - * TRUE if the given text format allows caching, FALSE otherwise. - */ -function filter_format_allowcache($format_id) { - $format = $format_id ? entity_load('filter_format', $format_id) : FALSE; - return !empty($format->cache); -} - -/** * Runs all the enabled filters on a piece of text. * * Note: Because filters can inject JavaScript or execute PHP code, security is @@ -327,99 +437,46 @@ function filter_format_allowcache($format_id) { * validation stage of the Form API. You should for example never make a * preview of content in a disallowed format. * - * @param $text + * Note: this function should only be used when filtering text for use elsewhere + * than on a rendered HTML page. If this is part of a HTML page, then a + * renderable array with a #type 'processed_text' element should be used instead + * of this, because that will allow cache tags to be set and bubbled up, assets + * to be loaded and #post_render_cache callbacks to be associated. In other + * words: if you are presenting the filtered text in a HTML page, the only way + * this will be presented correctly, is by using the 'processed_text' element. + * + * @param string $text * The text to be filtered. - * @param $format_id + * @param string|null $format_id * (optional) The machine name of the filter format to be used to filter the * text. Defaults to the fallback format. See filter_fallback_format(). - * @param $langcode + * @param string $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 + * English. This allows filters to be language-aware so language-specific * text replacement can be implemented. Defaults to an empty string. - * @param $cache - * (optional) A Boolean indicating 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. - * Defaults to FALSE. * @param array $filter_types_to_skip * (optional) An array of filter types to skip, or an empty array (default) * to skip no filter types. All of the format's filters will be applied, * except for filters of the types that are marked to be skipped. - * FilterInterface::TYPE_HTML_RESTRICTOR is the only type that cannot be skipped. + * FilterInterface::TYPE_HTML_RESTRICTOR is the only type that cannot be + * skipped. * - * @return + * @return string * The filtered text. * + * @see filter_process_text() + * * @ingroup sanitization */ -function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE, $filter_types_to_skip = array()) { - if (!isset($format_id)) { - $format_id = filter_fallback_format(); - } - // If the requested text format does not exist, the text cannot be filtered. - if (!$format = entity_load('filter_format', $format_id)) { - watchdog('filter', 'Missing text format: %format.', array('%format' => $format_id), WATCHDOG_ALERT); - return ''; - } - - // Prevent FilterInterface::TYPE_HTML_RESTRICTOR from being skipped. - if (in_array(FilterInterface::TYPE_HTML_RESTRICTOR, $filter_types_to_skip)) { - $filter_types_to_skip = array_diff($filter_types_to_skip, array(FilterInterface::TYPE_HTML_RESTRICTOR)); - } - - // When certain filters should be skipped, don't perform caching. - if ($filter_types_to_skip) { - $cache = FALSE; - } - - // Check for a cached version of this piece of text. - $cache = $cache && !empty($format->cache); - $cache_id = ''; - if ($cache) { - $cache_id = $format->format . ':' . $langcode . ':' . hash('sha256', $text); - if ($cached = \Drupal::cache('filter')->get($cache_id)) { - return $cached->data; - } - } - - // Convert all Windows and Mac newlines to a single newline, so filters only - // need to deal with one possibility. - $text = str_replace(array("\r\n", "\r"), "\n", $text); - - // Get a complete list of filters, ordered properly. - $filters = $format->filters(); - - // Give filters the chance to escape HTML-like data such as code or formulas. - foreach ($filters as $filter) { - // If necessary, skip filters of a certain type. - if (in_array($filter->getType(), $filter_types_to_skip)) { - continue; - } - if ($filter->status) { - $text = $filter->prepare($text, $langcode, $cache, $cache_id); - } - } - - // Perform filtering. - foreach ($filters as $filter) { - // If necessary, skip filters of a certain type. - if (in_array($filter->getType(), $filter_types_to_skip)) { - continue; - } - if ($filter->status) { - $text = $filter->process($text, $langcode, $cache, $cache_id); - } - } - - // Cache the filtered text. This cache is infinitely valid. It becomes - // obsolete when $text changes (which leads to a new $cache_id). It is - // automatically flushed when the text format is updated. - // @see \Drupal\filter\Entity\FilterFormat::save() - if ($cache) { - \Drupal::cache('filter')->set($cache_id, $text, Cache::PERMANENT, array('filter_format' => $format->id())); - } - - return $text; +function check_markup($text, $format_id = NULL, $langcode = 'und', $filter_types_to_skip = array()) { + $build = array( + '#type' => 'processed_text', + '#text' => $text, + '#format' => $format_id, + '#filter_types_to_skip' => $filter_types_to_skip, + '#langcode' => $langcode, + ); + return drupal_render($build); } /** @@ -1144,10 +1201,3 @@ function filter_filter_secure_image_alter(&$image) { /** * @} End of "defgroup standard_filters". */ - -/** - * Implements hook_page_build(). - */ -function filter_page_build(&$page) { - $page['#attached']['library'][] = 'filter/caption'; -} diff --git a/core/modules/filter/filter.services.yml b/core/modules/filter/filter.services.yml index dc93fdd..406161a 100644 --- a/core/modules/filter/filter.services.yml +++ b/core/modules/filter/filter.services.yml @@ -1,11 +1,4 @@ services: - cache.filter: - class: Drupal\Core\Cache\CacheBackendInterface - tags: - - { name: cache.bin } - factory_method: get - factory_service: cache_factory - arguments: [filter] plugin.manager.filter: class: Drupal\filter\FilterPluginManager parent: default_plugin_manager diff --git a/core/modules/filter/src/Annotation/Filter.php b/core/modules/filter/src/Annotation/Filter.php index a14b72f..808357c 100644 --- a/core/modules/filter/src/Annotation/Filter.php +++ b/core/modules/filter/src/Annotation/Filter.php @@ -65,16 +65,6 @@ class Filter extends Plugin { public $status = FALSE; /** - * 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. - * - * @var bool (optional) - */ - public $cache = TRUE; - - /** * The default settings for the filter. * * @var array (optional) diff --git a/core/modules/filter/src/Entity/FilterFormat.php b/core/modules/filter/src/Entity/FilterFormat.php index 6f5f807..9598f77 100644 --- a/core/modules/filter/src/Entity/FilterFormat.php +++ b/core/modules/filter/src/Entity/FilterFormat.php @@ -93,13 +93,6 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En protected $roles; /** - * Whether processed text of this format can be cached. - * - * @var bool - */ - public $cache = FALSE; - - /** * Configured filters for this text format. * * An associative array of filters assigned to the text format, keyed by the @@ -207,15 +200,6 @@ public function preSave(EntityStorageInterface $storage) { // @todo Do not save disabled filters whose properties are identical to // all default properties. - - // Determine whether the format can be cached. - // @todo This is a derived/computed definition, not configuration. - $this->cache = TRUE; - foreach ($this->filters()->getAll() as $filter) { - if ($filter->status && !$filter->cache) { - $this->cache = FALSE; - } - } } /** diff --git a/core/modules/filter/src/FilterProcessResult.php b/core/modules/filter/src/FilterProcessResult.php new file mode 100644 index 0000000..17f0a13 --- /dev/null +++ b/core/modules/filter/src/FilterProcessResult.php @@ -0,0 +1,250 @@ +setAssets(array( + * 'library' => array( + * 'filter/caption', + * ), + * )); + * + * // Associate cache tags to be invalidated by. + * $result->setCacheTags($node->getCacheTag()); + * + * return $result; + * } + * @endcode + */ +class FilterProcessResult { + + /** + * The processed text. + * + * @see \Drupal\filter\Plugin\FilterInterface::process() + * + * @var string + */ + protected $processedText; + + /** + * An array of associated assets to be attached. + * + * @see drupal_process_attached() + * + * @var array + */ + protected $assets; + + /** + * The attached cache tags. + * + * @see drupal_render_collect_cache_tags() + * + * @var array + */ + protected $cacheTags; + + /** + * The associated #post_render_cache callbacks. + * + * @see _drupal_render_process_post_render_cache() + * + * @var array + */ + protected $postRenderCacheCallbacks; + + /** + * Constructs a FilterProcessResult object. + * + * @param string $processed_text + * The text as processed by a text filter. + */ + public function __construct($processed_text) { + $this->processedText = $processed_text; + + $this->assets = array(); + $this->cacheTags = array(); + $this->postRenderCacheCallbacks = array(); + } + + /** + * Gets the processed text. + * + * @return string + */ + public function getProcessedText() { + return $this->processedText; + } + + /** + * Gets the processed text. + * + * @return string + */ + public function __toString() { + return $this->getProcessedText(); + } + + /** + * Sets the processed text. + * + * @param string $processed_text + * The text as processed by a text filter. + * + * @return $this + */ + public function setProcessedText($processed_text) { + $this->processedText = $processed_text; + return $this; + } + + /** + * Gets cache tags associated with the processed text. + * + * @return array + */ + public function getCacheTags() { + return $this->cacheTags; + } + + /** + * Adds cache tags associated with the processed text. + * + * @param array $cache_tags + * The cache tags to be added. + * + * @return $this + */ + public function addCacheTags(array $cache_tags) { + $this->cacheTags = NestedArray::mergeDeep($this->cacheTags, $cache_tags); + return $this; + } + + /** + * Sets cache tags associated with the processed text. + * + * @param array $cache_tags + * The cache tags to be associated. + * + * @return $this + */ + public function setCacheTags(array $cache_tags) { + $this->cacheTags = $cache_tags; + return $this; + } + + /** + * Gets assets associated with the processed text. + * + * @return array + */ + public function getAssets() { + return $this->assets; + } + + /** + * Adds assets associated with the processed text. + * + * @param array $assets + * The associated assets to be attached. + * + * @return $this + */ + public function addAssets(array $assets) { + $this->assets = drupal_merge_attached($this->assets, $assets); + return $this; + } + + /** + * Sets assets associated with the processed text. + * + * @param array $assets + * The associated assets to be attached. + * + * @return $this + */ + public function setAssets(array $assets) { + $this->assets = $assets; + return $this; + } + + /** + * Gets #post_render_cache callbacks associated with the processed text. + * + * @return array + */ + public function getPostRenderCacheCallbacks() { + return $this->postRenderCacheCallbacks; + } + + /** + * Adds #post_render_cache callbacks associated with the processed text. + * + * @param callable $callback + * The #post_render_cache callback that will replace the placeholder with + * its eventual markup. + * @param array $context + * An array providing context for the #post_render_cache callback. + * + * @see drupal_render_cache_generate_placeholder() + * + * @return $this + */ + public function addPostRenderCacheCallback($callback, array $context) { + $this->postRenderCacheCallbacks[$callback][] = $context; + return $this; + } + + /** + * Sets #post_render_cache callbacks associated with the processed text. + * + * @param array $post_render_cache_callbacks + * The associated #post_render_cache callbacks to be executed. + * + * @return $this + */ + public function setPostRenderCacheCallbacks(array $post_render_cache_callbacks) { + $this->postRenderCacheCallbacks = $post_render_cache_callbacks; + return $this; + } + +} diff --git a/core/modules/filter/src/Plugin/Filter/FilterAutoP.php b/core/modules/filter/src/Plugin/Filter/FilterAutoP.php index a77b19e..0627527 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterAutoP.php +++ b/core/modules/filter/src/Plugin/Filter/FilterAutoP.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Plugin\Filter; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -23,8 +24,8 @@ class FilterAutoP extends FilterBase { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return _filter_autop($text); + public function process($text, $langcode) { + return new FilterProcessResult(_filter_autop($text)); } /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterCaption.php b/core/modules/filter/src/Plugin/Filter/FilterCaption.php index 08ad4c8..bccaf02 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterCaption.php +++ b/core/modules/filter/src/Plugin/Filter/FilterCaption.php @@ -11,6 +11,7 @@ use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Xss; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -28,9 +29,11 @@ class FilterCaption extends FilterBase { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { + public function process($text, $langcode) { + $result = new FilterProcessResult($text); if (stristr($text, 'data-caption') !== FALSE || stristr($text, 'data-align') !== FALSE) { + $caption_found = FALSE; $dom = Html::load($text); $xpath = new \DOMXPath($dom); foreach ($xpath->query('//*[@data-caption or @data-align]') as $node) { @@ -71,6 +74,9 @@ public function process($text, $langcode, $cache, $cache_id) { } continue; } + else { + $caption_found = TRUE; + } // Given the updated node, caption and alignment: re-render it with a // caption. @@ -96,10 +102,18 @@ public function process($text, $langcode, $cache, $cache_id) { $node->parentNode->replaceChild($updated_node, $node); } - return Html::serialize($dom); + $result->setProcessedText(Html::serialize($dom)); + + if ($caption_found) { + $result->addAssets(array( + 'library' => array( + 'filter/caption', + ), + )); + } } - return $text; + return $result; } /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtml.php b/core/modules/filter/src/Plugin/Filter/FilterHtml.php index 109d943..543f147 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterHtml.php +++ b/core/modules/filter/src/Plugin/Filter/FilterHtml.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Plugin\Filter; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -58,8 +59,8 @@ public function settingsForm(array $form, array &$form_state) { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return _filter_html($text, $this); + public function process($text, $langcode) { + return new FilterProcessResult(_filter_html($text, $this)); } /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtmlCorrector.php b/core/modules/filter/src/Plugin/Filter/FilterHtmlCorrector.php index bc0a036..0be711c 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterHtmlCorrector.php +++ b/core/modules/filter/src/Plugin/Filter/FilterHtmlCorrector.php @@ -8,6 +8,7 @@ namespace Drupal\filter\Plugin\Filter; use Drupal\Component\Utility\Html; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -25,8 +26,8 @@ class FilterHtmlCorrector extends FilterBase { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return Html::normalize($text); + public function process($text, $langcode) { + return new FilterProcessResult(Html::normalize($text)); } } diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtmlEscape.php b/core/modules/filter/src/Plugin/Filter/FilterHtmlEscape.php index 0825df0..a508298 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterHtmlEscape.php +++ b/core/modules/filter/src/Plugin/Filter/FilterHtmlEscape.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Plugin\Filter; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -24,8 +25,8 @@ class FilterHtmlEscape extends FilterBase { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return _filter_html_escape($text); + public function process($text, $langcode) { + return new FilterProcessResult(_filter_html_escape($text)); } /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtmlImageSecure.php b/core/modules/filter/src/Plugin/Filter/FilterHtmlImageSecure.php index f00b055..f03c75a 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterHtmlImageSecure.php +++ b/core/modules/filter/src/Plugin/Filter/FilterHtmlImageSecure.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Plugin\Filter; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -25,8 +26,8 @@ class FilterHtmlImageSecure extends FilterBase { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return _filter_html_image_secure_process($text); + public function process($text, $langcode) { + return new FilterProcessResult(_filter_html_image_secure_process($text)); } /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterNull.php b/core/modules/filter/src/Plugin/Filter/FilterNull.php index d4fbe62..9c30edc 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterNull.php +++ b/core/modules/filter/src/Plugin/Filter/FilterNull.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Plugin\Filter; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -47,8 +48,8 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return ''; + public function process($text, $langcode) { + return new FilterProcessResult(''); } /** diff --git a/core/modules/filter/src/Plugin/Filter/FilterUrl.php b/core/modules/filter/src/Plugin/Filter/FilterUrl.php index 0918b19..469ff41 100644 --- a/core/modules/filter/src/Plugin/Filter/FilterUrl.php +++ b/core/modules/filter/src/Plugin/Filter/FilterUrl.php @@ -7,6 +7,7 @@ namespace Drupal\filter\Plugin\Filter; +use Drupal\filter\FilterProcessResult; use Drupal\filter\Plugin\FilterBase; /** @@ -41,8 +42,8 @@ public function settingsForm(array $form, array &$form_state) { /** * {@inheritdoc} */ - public function process($text, $langcode, $cache, $cache_id) { - return _filter_url($text, $this); + public function process($text, $langcode) { + return new FilterProcessResult(_filter_url($text, $this)); } /** diff --git a/core/modules/filter/src/Plugin/FilterBase.php b/core/modules/filter/src/Plugin/FilterBase.php index facd818..44421e1 100644 --- a/core/modules/filter/src/Plugin/FilterBase.php +++ b/core/modules/filter/src/Plugin/FilterBase.php @@ -45,13 +45,6 @@ public $weight = 0; /** - * A Boolean indicating whether the text processed by this filter may be cached. - * - * @var bool - */ - public $cache = TRUE; - - /** * An associative array containing the configured settings of this filter. * * @var array @@ -72,7 +65,6 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition parent::__construct($configuration, $plugin_id, $plugin_definition); $this->provider = $this->pluginDefinition['provider']; - $this->cache = $this->pluginDefinition['cache']; $this->setConfiguration($configuration); } @@ -154,7 +146,7 @@ public function settingsForm(array $form, array &$form_state) { /** * {@inheritdoc} */ - public function prepare($text, $langcode, $cache, $cache_id) { + public function prepare($text, $langcode) { return $text; } diff --git a/core/modules/filter/src/Plugin/FilterInterface.php b/core/modules/filter/src/Plugin/FilterInterface.php index f6f7dd2..fea8559 100644 --- a/core/modules/filter/src/Plugin/FilterInterface.php +++ b/core/modules/filter/src/Plugin/FilterInterface.php @@ -36,19 +36,20 @@ * 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 collection of filters disables - * caching for the entire collection, not just for one filter. - * - * Beware of the filter cache when developing your module: it is advised to set - * your filter to 'cache' to FALSE while developing, but be sure to remove that - * setting if it's not needed, when you are no longer in development mode. - * + * @see filter_process_text() * @see check_markup() * + * Typically, only text processing is applied, but in more advanced use cases, + * filters may also: + * - declare asset libraries to be loaded; + * - declare cache tags that the resulting filtered text depends upon, so when + * either of those cache tags is invalidated, the render-cached HTML that the + * filtered text is part of should also be invalidated; + * - declare #post_render_cache callbacks to apply uncacheable filtering, for + * example because it differs per user. + * + * @see \Drupal\filter\Plugin\FilterInterface::process() + * * Filters are discovered through annotations, which may contain the following * definition properties: * - title: (required) An administrative summary of what the filter does. @@ -67,9 +68,6 @@ * - status: The default status for new instances of the filter. Defaults to * FALSE. * - weight: A default weight for new instances of the filter. Defaults to 0. - * - cache: Whether the filtered text can be cached. Defaults to TRUE. - * Note that setting this to FALSE disables caching for an entire text format, - * which can have a negative impact on the site's overall performance. * - settings: An associative array containing default settings for new * instances of the filter. * @@ -151,16 +149,11 @@ public function settingsForm(array $form, array &$form_state); * The text string to be filtered. * @param string $langcode * The language code of the text to be filtered. - * @param bool $cache - * A Boolean indicating whether the filtered text is going to be cached in - * {cache_filter}. - * @param string $cache_id - * The ID of the filtered text in {cache_filter}, if $cache is TRUE. * * @return string * The prepared, escaped text. */ - public function prepare($text, $langcode, $cache, $cache_id); + public function prepare($text, $langcode); /** * Performs the filter processing. @@ -169,16 +162,14 @@ public function prepare($text, $langcode, $cache, $cache_id); * The text string to be filtered. * @param string $langcode * The language code of the text to be filtered. - * @param bool $cache - * A Boolean indicating whether the filtered text is going to be cached in - * {cache_filter}. - * @param string $cache_id - * The ID of the filtered text in {cache_filter}, if $cache is TRUE. * - * @return string - * The filtered text. + * @return \Drupal\filter\FilterProcessResult + * The filtered text, wrapped in a FilterProcessResult object, and possibly + * with associated assets, cache tags and #post_render_cache callbacks. + * + * @see \Drupal\filter\FilterProcessResult */ - public function process($text, $langcode, $cache, $cache_id); + public function process($text, $langcode); /** * Returns HTML allowed by this filter's configuration. diff --git a/core/modules/filter/src/Tests/FilterAPITest.php b/core/modules/filter/src/Tests/FilterAPITest.php index 1c33d74..e0c06d5 100644 --- a/core/modules/filter/src/Tests/FilterAPITest.php +++ b/core/modules/filter/src/Tests/FilterAPITest.php @@ -106,12 +106,12 @@ function testCheckMarkupFilterSubset() { $expected_filter_text_without_html_generators = "Text with evil content and a URL: http://drupal.org!"; $this->assertIdentical( - check_markup($text, 'filtered_html', '', FALSE, array()), + check_markup($text, 'filtered_html', '', array()), $expected_filtered_text, 'Expected filter result.' ); $this->assertIdentical( - check_markup($text, 'filtered_html', '', FALSE, array(FilterInterface::TYPE_MARKUP_LANGUAGE)), + check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_MARKUP_LANGUAGE)), $expected_filter_text_without_html_generators, 'Expected filter result when skipping FilterInterface::TYPE_MARKUP_LANGUAGE filters.' ); @@ -120,7 +120,7 @@ function testCheckMarkupFilterSubset() { // Drupal core only ships with these two types of filters, so this is the // most extensive test possible. $this->assertIdentical( - check_markup($text, 'filtered_html', '', FALSE, array(FilterInterface::TYPE_HTML_RESTRICTOR, FilterInterface::TYPE_MARKUP_LANGUAGE)), + check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_HTML_RESTRICTOR, FilterInterface::TYPE_MARKUP_LANGUAGE)), $expected_filter_text_without_html_generators, 'Expected filter result when skipping FilterInterface::TYPE_MARKUP_LANGUAGE filters, even when trying to disable filters of the FilterInterface::TYPE_HTML_RESTRICTOR type.' ); @@ -224,6 +224,73 @@ function testFilterFormatAPI() { } /** + * Tests the 'processed_text' element. + * + * check_markup() is a wrapper for the 'processed_text' element, for use in + * simple scenarios; the 'processed_text' element has more advanced features: + * it lets filters attach assets, associate cache tags and define + * #post_render_cache callbacks. + * This test focuses solely on those advanced features. + */ + function testProcessedTextElement() { + entity_create('filter_format', array( + 'format' => 'element_test', + 'name' => 'processed_text element test format', + 'filters' => array( + 'filter_test_assets' => array( + 'weight' => -1, + 'status' => TRUE, + ), + 'filter_test_cache_tags' => array( + 'weight' => 0, + 'status' => TRUE, + ), + 'filter_test_post_render_cache' => array( + 'weight' => 1, + 'status' => TRUE, + ), + // Run the HTML corrector filter last, because it has the potential to + // break the render cache placeholders added by the + // filter_test_post_render_cache filter. + 'filter_htmlcorrector' => array( + 'weight' => 10, + 'status' => TRUE, + ), + ), + ))->save(); + + $build = array( + '#type' => 'processed_text', + '#text' => '

Hello, world!

', + '#format' => 'element_test', + ); + drupal_render($build); + + // Verify the assets, cache tags and #post_render_cache callbacks. + $expected_assets = array( + // The assets attached by the filter_test_assets filter. + 'library' => array( + 'filter/caption', + ), + ); + $this->assertEqual($expected_assets, $build['#attached'], 'Expected assets present'); + $expected_cache_tags = array( + // The cache tag set by the processed_text element itself. + 'filter_format' => array( + 'element_test' => 'element_test', + ), + // The cache tags set by the filter_test_cache_tags filter. + 'foo' => array( + 'bar' => 'bar', + 'baz' => 'baz', + ), + ); + $this->assertEqual($expected_cache_tags, $build['#cache']['tags'], 'Expected cache tags present.'); + $expected_markup = '

Hello, world!

This is a dynamic llama.

'; + $this->assertEqual($expected_markup, $build['#markup'], 'Expected #post_render_cache callback has been applied.'); + } + + /** * Tests the function of the typed data type. */ function testTypedDataAPI() { diff --git a/core/modules/filter/src/Tests/FilterAdminTest.php b/core/modules/filter/src/Tests/FilterAdminTest.php index 7aa30a8..9e6d74b 100644 --- a/core/modules/filter/src/Tests/FilterAdminTest.php +++ b/core/modules/filter/src/Tests/FilterAdminTest.php @@ -8,7 +8,6 @@ namespace Drupal\filter\Tests; use Drupal\simpletest\WebTestBase; -use Drupal\filter\Plugin\FilterInterface; /** * Tests the administrative functionality of the Filter module. @@ -354,73 +353,4 @@ function testUrlFilterAdmin() { $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Basic HTML'))); } - /** - * Tests that changing filter properties clears the filter cache. - */ - public function testFilterAdminClearsFilterCache() { - $restricted = 'restricted_html'; - $original_markup = '

Small headers

small headers are allowed in restricted html by default'; - - // Check that the filter cache is empty for the test markup. - $cid = $this->computeFilterCacheId($original_markup, $restricted, '', TRUE); - $this->assertFalse(\Drupal::cache('filter')->get($cid)); - - // Check that the filter cache gets populated when check_markup is called. - $actual_markup = check_markup($original_markup, $restricted, '', TRUE); - $this->assertTrue(\Drupal::cache('filter')->get($cid)); - $this->assertIdentical(strpos($actual_markup, '

'), 0, 'The h4 tag is present in the resulting markup'); - - // Edit the restricted filter format. - $edit = array(); - $edit['filters[filter_html][settings][allowed_html]'] = ' '; - $this->drupalPostForm('admin/config/content/formats/manage/' . $restricted, $edit, t('Save configuration')); - $this->assertUrl('admin/config/content/formats'); - $this->drupalGet('admin/config/content/formats/manage/' . $restricted); - $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], 'Allowed HTML tag added.'); - - // Check that the filter cache is empty after the format was changed. - $this->assertFalse(\Drupal::cache('filter')->get($cid)); - - // Check that after changind the filter, the changes are reflected in the - // filtered markup. - $actual_markup = check_markup($original_markup, $restricted, '', TRUE); - $this->assertIdentical(strpos($actual_markup, '

'), FALSE, 'The h4 tag is not present in the resulting markup'); - } - - - /** - * Computes the cache-key for the given text just like check_markup(). - * - * Note that this is copied over from check_markup(). - * - * @return string|NULL - * The cache-key used to store the text in the filter cache. - */ - protected function computeFilterCacheId($text, $format_id = NULL, $langcode = '', $cache = FALSE, $filter_types_to_skip = array()) { - if (!isset($format_id)) { - $format_id = filter_fallback_format(); - } - // If the requested text format does not exist, the text cannot be filtered. - if (!$format = entity_load('filter_format', $format_id)) { - return; - } - - // Prevent FilterInterface::TYPE_HTML_RESTRICTOR from being skipped. - if (in_array(FilterInterface::TYPE_HTML_RESTRICTOR, $filter_types_to_skip)) { - $filter_types_to_skip = array_diff($filter_types_to_skip, array(FilterInterface::TYPE_HTML_RESTRICTOR)); - } - - // When certain filters should be skipped, don't perform caching. - if ($filter_types_to_skip) { - $cache = FALSE; - } - - // Compute the cache key if the text is cacheable. - $cache = $cache && !empty($format->cache); - $cache_id = ''; - if ($cache) { - return $format->format . ':' . $langcode . ':' . hash('sha256', $text); - } - } - } diff --git a/core/modules/filter/src/Tests/FilterCrudTest.php b/core/modules/filter/src/Tests/FilterCrudTest.php index 041f309..b592c5f 100644 --- a/core/modules/filter/src/Tests/FilterCrudTest.php +++ b/core/modules/filter/src/Tests/FilterCrudTest.php @@ -65,8 +65,8 @@ function testTextFormatCrud() { $format->save(); $this->verifyTextFormat($format); - // Add a uncacheable filter and save again. - $format->setFilterConfig('filter_test_uncacheable', array( + // Add a filter_test_replace filter and save again. + $format->setFilterConfig('filter_test_replace', array( 'status' => 1, )); $format->save(); @@ -90,22 +90,9 @@ function verifyTextFormat($format) { $filter_format = entity_load('filter_format', $format->format); $this->assertEqual($filter_format->format, $format->format, format_string('filter_format_load: Proper format id for text format %format.', $t_args)); $this->assertEqual($filter_format->name, $format->name, format_string('filter_format_load: Proper title for text format %format.', $t_args)); - $this->assertEqual($filter_format->cache, $format->cache, format_string('filter_format_load: Proper cache indicator for text format %format.', $t_args)); $this->assertEqual($filter_format->weight, $format->weight, format_string('filter_format_load: Proper weight for text format %format.', $t_args)); // Check that the filter was created in site default language. $this->assertEqual($format->langcode, $default_langcode, format_string('filter_format_load: Proper language code for text format %format.', $t_args)); - - // Verify the 'cache' text format property according to enabled filters. - $cacheable = TRUE; - foreach ($format->filters() as $name => $filter) { - // If this filter is not cacheable, update $cacheable accordingly, so we - // can verify $format->cache after iterating over all filters. - if ($filter->status && !$filter->cache) { - $cacheable = FALSE; - break; - } - } - $this->assertEqual($filter_format->cache, $cacheable, 'Text format contains proper cache property.'); } } diff --git a/core/modules/filter/src/Tests/FilterDefaultConfigTest.php b/core/modules/filter/src/Tests/FilterDefaultConfigTest.php index 9d8f3ca..b166e41 100644 --- a/core/modules/filter/src/Tests/FilterDefaultConfigTest.php +++ b/core/modules/filter/src/Tests/FilterDefaultConfigTest.php @@ -49,7 +49,6 @@ function testInstallation() { // Verify that format default property values have been added/injected. $this->assertTrue($format->uuid()); - $this->assertEqual($format->get('cache'), 1); // Verify that the loaded format does not contain any roles. $this->assertEqual($format->get('roles'), NULL); diff --git a/core/modules/filter/src/Tests/FilterSecurityTest.php b/core/modules/filter/src/Tests/FilterSecurityTest.php index 6321c1b..6aea5de 100644 --- a/core/modules/filter/src/Tests/FilterSecurityTest.php +++ b/core/modules/filter/src/Tests/FilterSecurityTest.php @@ -103,7 +103,7 @@ function testDisableFilterModule() { function testSkipSecurityFilters() { $text = "Text with some disallowed tags: " />'; $expected = '
alert(\'Loquacious llama!\')
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); // Only data-align attribute: all 3 allowed values. $input = ''; $expected = ''; - $this->assertIdentical($expected, $test($input)); + $this->assertIdentical($expected, $test($input)->getProcessedText()); $input = ''; $expected = ''; - $this->assertIdentical($expected, $test($input)); + $this->assertIdentical($expected, $test($input)->getProcessedText()); $input = ''; $expected = ''; - $this->assertIdentical($expected, $test($input)); + $this->assertIdentical($expected, $test($input)->getProcessedText()); // Only data-align attribute: a disallowed value. $input = ''; $expected = ''; - $this->assertIdentical($expected, $test($input)); + $this->assertIdentical($expected, $test($input)->getProcessedText()); // Empty data-align attribute. $input = ''; $expected = ''; - $this->assertIdentical($expected, $test($input)); + $this->assertIdentical($expected, $test($input)->getProcessedText()); // Both data-caption and data-align attributes: all 3 allowed values for the // data-align attribute. $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); // Both data-caption and data-align attributes, but a disallowed data-align // attribute value. $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); // Ensure the filter also works with uncommon yet valid attribute quoting. $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); // Security test: attempt to inject an additional class. $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); // Security test: attempt an XSS. $input = ''; $expected = '
Loquacious llama!
'; - $this->assertIdentical($expected, $test($input)); + $output = $test($input); + $this->assertIdentical($expected, $output->getProcessedText()); + $this->assertIdentical($attached_library, $output->getAssets()); // Finally, ensure that this also works on any other tag. $input = '