summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2014-06-08 21:25:15 (GMT)
committerAlex Pott2014-06-08 21:25:15 (GMT)
commitd5be5c04543803aeffc2809d31c615760975d6c7 (patch)
treebd795839b560c427942862fe5a6a23cc4a9ed2ff
parente05d622e0192044b993f48a39d257a815785c505 (diff)
Issue #2217877 by Wim Leers, Dave Reid, damiankloip, xjm, chx | catch: Fixed Text filters should be able to add #attached, #post_render_cache, and cache tags.
-rw-r--r--core/includes/common.inc5
-rw-r--r--core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php98
-rw-r--r--core/modules/comment/comment.module5
-rw-r--r--core/modules/comment/src/CommentViewBuilder.php15
-rw-r--r--core/modules/editor/src/EditorController.php2
-rw-r--r--core/modules/editor/src/Plugin/Filter/EditorFileReference.php94
-rw-r--r--core/modules/editor/src/Tests/EditorFileReferenceFilterTest.php121
-rw-r--r--core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php1
-rw-r--r--core/modules/entity_reference/src/Tests/EntityReferenceFormatterTest.php9
-rw-r--r--core/modules/filter/config/install/filter.format.plain_text.yml1
-rw-r--r--core/modules/filter/config/schema/filter.schema.yml9
-rw-r--r--core/modules/filter/filter.module248
-rw-r--r--core/modules/filter/filter.services.yml7
-rw-r--r--core/modules/filter/src/Annotation/Filter.php10
-rw-r--r--core/modules/filter/src/Entity/FilterFormat.php16
-rw-r--r--core/modules/filter/src/FilterProcessResult.php250
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterAutoP.php5
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterCaption.php20
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterHtml.php5
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterHtmlCorrector.php5
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterHtmlEscape.php5
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterHtmlImageSecure.php5
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterNull.php5
-rw-r--r--core/modules/filter/src/Plugin/Filter/FilterUrl.php5
-rw-r--r--core/modules/filter/src/Plugin/FilterBase.php10
-rw-r--r--core/modules/filter/src/Plugin/FilterInterface.php45
-rw-r--r--core/modules/filter/src/Tests/FilterAPITest.php73
-rw-r--r--core/modules/filter/src/Tests/FilterAdminTest.php70
-rw-r--r--core/modules/filter/src/Tests/FilterCrudTest.php17
-rw-r--r--core/modules/filter/src/Tests/FilterDefaultConfigTest.php1
-rw-r--r--core/modules/filter/src/Tests/FilterSecurityTest.php4
-rw-r--r--core/modules/filter/src/Tests/FilterUnitTest.php80
-rw-r--r--core/modules/filter/tests/filter_test/config/install/filter.format.filter_test.yml1
-rw-r--r--core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestAssets.php38
-rw-r--r--core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestCacheTags.php35
-rw-r--r--core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestPostRenderCache.php59
-rw-r--r--core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestReplace.php9
-rw-r--r--core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php5
-rw-r--r--core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestUncacheable.php32
-rw-r--r--core/modules/system/src/Tests/Common/RenderTest.php10
-rw-r--r--core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php65
-rw-r--r--core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php78
-rw-r--r--core/modules/text/src/Tests/Formatter/TextFormatterTest.php137
-rw-r--r--core/modules/text/src/TextProcessed.php2
-rw-r--r--core/modules/user/src/Tests/UserSignatureTest.php18
-rw-r--r--core/modules/views/src/Plugin/views/area/Text.php13
-rw-r--r--core/modules/views/src/Plugin/views/field/Markup.php2
-rw-r--r--core/modules/views/src/Tests/Handler/AreaTextTest.php9
-rw-r--r--core/profiles/standard/config/install/filter.format.basic_html.yml10
-rw-r--r--core/profiles/standard/config/install/filter.format.full_html.yml10
-rw-r--r--core/profiles/standard/config/install/filter.format.restricted_html.yml1
51 files changed, 1372 insertions, 408 deletions
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 5572e54..188268c 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) {
@@ -3662,7 +3661,7 @@ function drupal_render_cache_set(&$markup, array $elements) {
* placeholders, but should also be called by #post_render_cache callbacks that
* want to replace the placeholder with the final markup.
*
- * @param callable $callback
+ * @param string $callback
* The #post_render_cache callback that will replace the placeholder with its
* eventual markup.
* @param array $context
@@ -3681,7 +3680,7 @@ function drupal_render_cache_generate_placeholder($callback, array &$context) {
'token' => \Drupal\Component\Utility\Crypt::randomBytesBase64(55),
);
- return '<drupal:render-cache-placeholder callback="' . $callback . '" token="' . $context['token'] . '" />';
+ return '<drupal-render-cache-placeholder callback="' . $callback . '" token="' . $context['token'] . '"></drupal-render-cache-placeholder>';
}
/**
diff --git a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
index 7272357..02049b6 100644
--- a/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
+++ b/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
@@ -9,8 +9,12 @@ namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
use Drupal\ckeditor\CKEditorPluginBase;
use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Plugin\FilterInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the "internal" plugin (i.e. core plugins part of our CKEditor build).
@@ -20,7 +24,55 @@ use Drupal\filter\Plugin\FilterInterface;
* label = @Translation("CKEditor core")
* )
*/
-class Internal extends CKEditorPluginBase {
+class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInterface {
+
+ /**
+ * The cache backend.
+ *
+ * @var \Drupal\Core\Cache\CacheBackendInterface
+ */
+ protected $cache;
+
+ /**
+ * Constructs a \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+ * The cache backend.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, CacheBackendInterface $cache_backend) {
+ $this->cache = $cache_backend;
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ }
+
+ /**
+ * Creates an instance of the plugin.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+ * The container to pull out services used in the plugin.
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ *
+ * @return static
+ * Returns an instance of this plugin.
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('cache.default')
+ );
+ }
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
@@ -251,22 +303,42 @@ class Internal extends CKEditorPluginBase {
* An array containing the "format_tags" configuration.
*/
protected function generateFormatTagsSetting(Editor $editor) {
- // The <p> tag is always allowed — HTML without <p> 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();
+ }
+
+ $format = $editor->getFilterFormat();
+ $cid = 'ckeditor_internal_format_tags:' . $format->id();
+
+ if ($cached = $this->cache->get($cid)) {
+ $format_tags = $cached->data;
+ }
+ else {
+ // The <p> tag is always allowed — HTML without <p> 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</' . $tag . '>';
- $output = trim(check_markup($input, $editor->id(), '', TRUE));
- if ($input == $output) {
- $format_tags[] = $tag;
+ // 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</' . $tag . '>';
+ $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.
+ $this->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 dc59c3c..fce19d9 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -1399,8 +1399,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 @@ class CommentViewBuilder extends EntityViewBuilder {
'#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 @@ class EditorController extends ControllerBase {
// 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..b22bda6
--- /dev/null
+++ b/core/modules/editor/src/Plugin/Filter/EditorFileReference.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\Plugin\Filter\EditorFileReference.
+ */
+
+namespace Drupal\editor\Plugin\Filter;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a filter to track images uploaded via a Text Editor.
+ *
+ * Passes the text unchanged, but associates the cache tags of referenced files.
+ *
+ * @Filter(
+ * id = "editor_file_reference",
+ * title = @Translation("Track images uploaded via a Text Editor"),
+ * description = @Translation("Ensures that the latest versions of images uploaded via a Text Editor are displayed."),
+ * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
+ * )
+ */
+class EditorFileReference extends FilterBase implements ContainerFactoryPluginInterface {
+
+ /**
+ * An entity manager object.
+ *
+ * @var \Drupal\Core\Entity\EntityManagerInterface
+ */
+ protected $entityManager;
+
+ /**
+ * Constructs a \Drupal\editor\Plugin\Filter\EditorFileReference object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin_id for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+ * An entity manager object.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
+ $this->entityManager = $entity_manager;
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ static public function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process($text, $langcode) {
+ $result = new FilterProcessResult($text);
+
+ if (stristr($text, 'data-editor-file-uuid') !== FALSE) {
+ $dom = Html::load($text);
+ $xpath = new \DOMXPath($dom);
+ $processed_uuids = array();
+ foreach ($xpath->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 = $this->entityManager->loadEntityByUuid('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..3474613
--- /dev/null
+++ b/core/modules/editor/src/Tests/EditorFileReferenceFilterTest.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\Tests\EditorFileReferenceFilterTest.
+ */
+
+namespace Drupal\editor\Tests;
+
+use Drupal\simpletest\KernelTestBase;
+use Drupal\filter\FilterBag;
+
+/**
+ * Tests for the EditorFileReference filter.
+ */
+class EditorFileReferenceFilterTest extends KernelTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('system', 'filter', 'editor', 'field', 'file', 'user');
+
+ /**
+ * @var \Drupal\filter\Plugin\FilterInterface[]
+ */
+ protected $filters;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => '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->installEntitySchema('file');
+ $this->installSchema('file', array('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 = '<img src="llama.jpg" />';
+ $output = $test($input);
+ $this->assertIdentical($input, $output->getProcessedText());
+
+ $this->pass('One data-editor-file-uuid attribute.');
+ $input = '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
+ $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 = '<img src="llama.jpg" DATA-editor-file-UUID = "' . $uuid . '" />';
+ $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 = '<video src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
+ $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 an invalid value.');
+ $input = '<img src="llama.jpg" data-editor-file-uuid="invalid-' . $uuid . '" />';
+ $output = $test($input);
+ $this->assertIdentical($input, $output->getProcessedText());
+ $this->assertEqual(array(), $output->getCacheTags());
+
+ $this->pass('Two different data-editor-file-uuid attributes.');
+ $input = '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
+ $input .= '<img src="alpaca.jpg" data-editor-file-uuid="' . $uuid_2 . '" />';
+ $output = $test($input);
+ $this->assertIdentical($input, $output->getProcessedText());
+ $this->assertEqual(array('file' => array($id, $id_2)), $output->getCacheTags());
+
+ $this->pass('Two identical data-editor-file-uuid attributes.');
+ $input = '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
+ $input .= '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
+ $output = $test($input);
+ $this->assertIdentical($input, $output->getProcessedText());
+ $this->assertEqual(array('file' => array($id)), $output->getCacheTags());
+ }
+
+}
diff --git a/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
index 410a256..4065ea2 100644
--- a/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
+++ b/core/modules/entity_reference/src/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
@@ -106,6 +106,7 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase {
// its cache tags to be bubbled up and included with those of the
// main entity when cache tags are collected for a renderable array
// in drupal_render().
+ // @todo remove this work-around, see https://drupal.org/node/2273277
$referenced_entity_build = entity_view($item->entity, $view_mode, $item->getLangcode());
drupal_render($referenced_entity_build, TRUE);
$elements[$delta] = $referenced_entity_build;
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceFormatterTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceFormatterTest.php
index 37fd4b1..61d0ee5 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceFormatterTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceFormatterTest.php
@@ -60,8 +60,6 @@ class EntityReferenceFormatterTest extends EntityUnitTestBase {
public function setUp() {
parent::setUp();
- $this->installConfig(array('filter'));
-
entity_reference_create_instance($this->entityType, $this->bundle, $this->fieldName, 'Field test', $this->entityType);
// Set up a field, so that the entity that'll be referenced bubbles up a
@@ -88,6 +86,11 @@ class EntityReferenceFormatterTest extends EntityUnitTestBase {
))
->save();
+ entity_create('filter_format', array(
+ 'format' => 'full_html',
+ 'name' => 'Full HTML',
+ ))->save();
+
// Create the entity to be referenced.
$this->referencedEntity = entity_create($this->entityType, array('name' => $this->randomName()));
$this->referencedEntity->body = array(
@@ -180,7 +183,7 @@ class EntityReferenceFormatterTest extends EntityUnitTestBase {
$expected_rendered_body_field = '<div class="field field-entity-test--body field-name-body field-type-text field-label-above">
<div class="field-label">Body:&nbsp;</div>
<div class="field-items">
- <div class="field-item"></div>
+ <div class="field-item"><p>Hello, world!</p></div>
</div>
</div>
';
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 25c3541..6a28b4e 100644
--- a/core/modules/filter/config/schema/filter.schema.yml
+++ b/core/modules/filter/config/schema/filter.schema.yml
@@ -30,14 +30,17 @@ filter.format.*:
sequence:
- type: string
label: 'Role'
- cache:
- type: boolean
- label: 'Cache'
filters:
type: sequence
label: 'Enabled filters'
sequence:
- type: filter
+ 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 f25fe79..9c140a5 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,132 @@ 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' => '',
+ '#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 an empty string.
+ * - #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;
+ }
+
+ $filter_must_be_applied = function($filter) use ($filter_types_to_skip) {
+ $enabled = $filter->status === TRUE;
+ $type = $filter->getType();
+ // Prevent FilterInterface::TYPE_HTML_RESTRICTOR from being skipped.
+ $filter_type_must_be_applied = $type == FilterInterface::TYPE_HTML_RESTRICTOR || !in_array($type, $filter_types_to_skip);
+ return $enabled && $filter_type_must_be_applied;
+ };
+
+ // 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 ($filter_must_be_applied($filter)) {
+ $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 ($filter_must_be_applied($filter)) {
+ $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 +424,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,94 +432,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 '';
- }
-
- // 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();
-
- $filter_must_be_applied = function($filter) use ($filter_types_to_skip) {
- $enabled = $filter->status === TRUE;
- $type = $filter->getType();
- // Prevent FilterInterface::TYPE_HTML_RESTRICTOR from being skipped.
- $filter_type_must_be_applied = $type == FilterInterface::TYPE_HTML_RESTRICTOR || !in_array($type, $filter_types_to_skip);
- return $enabled && $filter_type_must_be_applied;
- };
-
- // Give filters the chance to escape HTML-like data such as code or formulas.
- foreach ($filters as $filter) {
- if ($filter_must_be_applied($filter)) {
- $text = $filter->prepare($text, $langcode, $cache, $cache_id);
- }
- }
-
- // Perform filtering.
- foreach ($filters as $filter) {
- if ($filter_must_be_applied($filter)) {
- $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 = '', $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);
}
/**
@@ -1139,10 +1196,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 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface, En
// @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..71edc45
--- /dev/null
+++ b/core/modules/filter/src/FilterProcessResult.php
@@ -0,0 +1,250 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filter\FilterProcessResult.
+ */
+
+namespace Drupal\filter;
+
+use Drupal\Component\Utility\NestedArray;
+
+/**
+ * Used to return values from a text filter plugin's processing method.
+ *
+ * The typical use case for a text filter plugin's processing method is to just
+ * apply some filtering to the given text, but for more advanced use cases,
+ * it may be necessary to also:
+ * 1. declare asset libraries to be loaded;
+ * 2. declare cache tags that the filtered text depends upon, so when either of
+ * those cache tags is invalidated, the filtered text should also be
+ * invalidated;
+ * 3. apply uncacheable filtering, for example because it differs per user.
+ *
+ * In case a filter needs one or more of these advanced use cases, it can use
+ * the additional methods available.
+ *
+ * The typical use case:
+ * @code
+ * public function process($text, $langcode) {
+ * // Modify $text.
+ *
+ * return new FilterProcess($text);
+ * }
+ * @endcode
+ *
+ * The advanced use cases:
+ * @code
+ * public function process($text, $langcode) {
+ * // Modify $text.
+ *
+ * $result = new FilterProcess($text);
+ *
+ * // Associate assets to be attached.
+ * $result->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 = drupal_merge_attached($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 = NestedArray::mergeDeep($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 string $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\Html;
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 @@ class FilterCaption extends FilterBase {
}
continue;
}
+ else {
+ $caption_found = TRUE;
+ }
// Given the updated node, caption and alignment: re-render it with a
// caption.
@@ -96,10 +102,18 @@ class FilterCaption extends FilterBase {
$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 @@ class FilterHtml extends FilterBase {
/**
* {@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 @@ class FilterNull extends FilterBase {
/**
* {@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 @@ class FilterUrl extends FilterBase {
/**
* {@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 @@ abstract class FilterBase extends PluginBase implements FilterInterface {
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 @@ abstract class FilterBase extends PluginBase implements FilterInterface {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->provider = $this->pluginDefinition['provider'];
- $this->cache = $this->pluginDefinition['cache'];
$this->setConfiguration($configuration);
}
@@ -154,7 +146,7 @@ abstract class FilterBase extends PluginBase implements FilterInterface {
/**
* {@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,18 +36,19 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
* 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.
+ * @see filter_process_text()
+ * @see check_markup()
*
- * 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.
+ * 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 check_markup()
+ * @see \Drupal\filter\Plugin\FilterInterface::process()
*
* Filters are discovered through annotations, which may contain the following
* definition properties:
@@ -67,9 +68,6 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface;
* - 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 @@ interface FilterInterface extends ConfigurablePluginInterface, PluginInspectionI
* 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 @@ interface FilterInterface extends ConfigurablePluginInterface, PluginInspectionI
* 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 @@ class FilterAPITest extends EntityUnitTestBase {
$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 @@ class FilterAPITest extends EntityUnitTestBase {
// 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 @@ class FilterAPITest extends EntityUnitTestBase {
}
/**
+ * 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' => '<p>Hello, world!</p>',
+ '#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 = '<p>Hello, world!</p><p>This is a dynamic llama.</p>';
+ $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 @@ class FilterAdminTest extends WebTestBase {
$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 = '<h4>Small headers</h4> small headers are <em>allowed</em> 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, '<h4>'), 0, 'The h4 tag is present in the resulting markup');
-
- // Edit the restricted filter format.
- $edit = array();
- $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code>';
- $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, '<h4>'), 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 @@ class FilterCrudTest extends DrupalUnitTestBase {
$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 @@ class FilterCrudTest extends DrupalUnitTestBase {
$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 7423b70..1ff9773 100644
--- a/core/modules/filter/src/Tests/FilterDefaultConfigTest.php
+++ b/core/modules/filter/src/Tests/FilterDefaultConfigTest.php
@@ -49,7 +49,6 @@ class FilterDefaultConfigTest extends DrupalUnitTestBase {
// 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 @@ class FilterSecurityTest extends WebTestBase {
function testSkipSecurityFilters() {
$text = "Text with some disallowed tags: <script />, <em><object>unicorn</object></em>, <i><table></i>.";
$expected_filtered_text = "Text with some disallowed tags: , <em>unicorn</em>, .";
- $this->assertEqual(check_markup($text, 'filtered_html', '', FALSE, array()), $expected_filtered_text, 'Expected filter result.');
- $this->assertEqual(check_markup($text, 'filtered_html', '', FALSE, array(FilterInterface::TYPE_HTML_RESTRICTOR)), $expected_filtered_text, 'Expected filter result, even when trying to disable filters of the FilterInterface::TYPE_HTML_RESTRICTOR type.');
+ $this->assertEqual(check_markup($text, 'filtered_html', '', array()), $expected_filtered_text, 'Expected filter result.');
+ $this->assertEqual(check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_HTML_RESTRICTOR)), $expected_filtered_text, 'Expected filter result, even when trying to disable filters of the FilterInterface::TYPE_HTML_RESTRICTOR type.');
}
}
diff --git a/core/modules/filter/src/Tests/FilterUnitTest.php b/core/modules/filter/src/Tests/FilterUnitTest.php
index a51d6b9..6426bf6 100644
--- a/core/modules/filter/src/Tests/FilterUnitTest.php
+++ b/core/modules/filter/src/Tests/FilterUnitTest.php
@@ -52,107 +52,141 @@ class FilterUnitTest extends DrupalUnitTestBase {
$filter = $this->filters['filter_caption'];
$test = function($input) use ($filter) {
- return $filter->process($input, 'und', FALSE, '');
+ return $filter->process($input, 'und');
};
+ $attached_library = array(
+ 'library' => array(
+ 'filter/caption',
+ ),
+ );
+
// No data-caption nor data-align attributes.
$input = '<img src="llama.jpg" />';
$expected = $input;
- $this->assertIdentical($expected, $test($input));
+ $this->assertIdentical($expected, $test($input)->getProcessedText());
// Only data-caption attribute.
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
// Empty data-caption attribute.
$input = '<img src="llama.jpg" data-caption="" />';
$expected = '<img src="llama.jpg" />';
- $this->assertIdentical($expected, $test($input));
+ $this->assertIdentical($expected, $test($input)->getProcessedText());
// HTML entities in the caption.
$input = '<img src="llama.jpg" data-caption="&ldquo;Loquacious llama!&rdquo;" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>“Loquacious llama!”</figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
// HTML encoded as HTML entities in data-caption attribute.
$input = '<img src="llama.jpg" data-caption="&lt;em&gt;Loquacious llama!&lt;/em&gt;" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption><em>Loquacious llama!</em></figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
// HTML (not encoded as HTML entities) in data-caption attribute, which is
// not allowed by the HTML spec, but may happen when people manually write
// HTML, so we explicitly support it.
$input = '<img src="llama.jpg" data-caption="<em>Loquacious llama!</em>" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption><em>Loquacious llama!</em></figcaption></figure>';
- $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 = '<img src="llama.jpg" data-caption="<script>alert(\'Loquacious llama!\')</script>" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>alert(\'Loquacious llama!\')</figcaption></figure>';
- $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 = '<img src="llama.jpg" data-align="left" />';
$expected = '<img src="llama.jpg" class="align-left" />';
- $this->assertIdentical($expected, $test($input));
+ $this->assertIdentical($expected, $test($input)->getProcessedText());
$input = '<img src="llama.jpg" data-align="center" />';
$expected = '<img src="llama.jpg" class="align-center" />';
- $this->assertIdentical($expected, $test($input));
+ $this->assertIdentical($expected, $test($input)->getProcessedText());
$input = '<img src="llama.jpg" data-align="right" />';
$expected = '<img src="llama.jpg" class="align-right" />';
- $this->assertIdentical($expected, $test($input));
+ $this->assertIdentical($expected, $test($input)->getProcessedText());
// Only data-align attribute: a disallowed value.
$input = '<img src="llama.jpg" data-align="left foobar" />';
$expected = '<img src="llama.jpg" />';
- $this->assertIdentical($expected, $test($input));
+ $this->assertIdentical($expected, $test($input)->getProcessedText());
// Empty data-align attribute.
$input = '<img src="llama.jpg" data-align="" />';
$expected = '<img src="llama.jpg" />';
- $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 = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="left" />';
$expected = '<figure class="caption caption-img align-left"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center" />';
$expected = '<figure class="caption caption-img align-center"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="right" />';
$expected = '<figure class="caption caption-img align-right"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $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 = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="left foobar" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $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 = '<img src=llama.jpg data-caption=\'Loquacious llama!\' data-align=right />';
$expected = '<figure class="caption caption-img align-right"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $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 = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center another-class-here" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $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 = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center \'onclick=\'alert(foo);" />';
$expected = '<figure class="caption caption-img"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
- $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 = '<video src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<figure class="caption caption-video"><video src="llama.jpg"></video><figcaption>Loquacious llama!</figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
$input = '<foobar data-caption="Loquacious llama!">baz</foobar>';
$expected = '<figure class="caption caption-foobar"><foobar>baz</foobar><figcaption>Loquacious llama!</figcaption></figure>';
- $this->assertIdentical($expected, $test($input));
+ $output = $test($input);
+ $this->assertIdentical($expected, $output->getProcessedText());
+ $this->assertIdentical($attached_library, $output->getAssets());
}
/**
@@ -676,7 +710,7 @@ www.example.com with a newline in comments -->
*/
function assertFilteredString($filter, $tests) {
foreach ($tests as $source => $tasks) {
- $result = $filter->process($source, $filter, FALSE, '');
+ $result = $filter->process($source, $filter)->getProcessedText();
foreach ($tasks as $value => $is_expected) {
// Not using assertIdentical, since combination with strpos() is hard to grok.
if ($is_expected) {
diff --git a/core/modules/filter/tests/filter_test/config/install/filter.format.filter_test.yml b/core/modules/filter/tests/filter_test/config/install/filter.format.filter_test.yml
index 9b48b54..d3d77fc 100644
--- a/core/modules/filter/tests/filter_test/config/install/filter.format.filter_test.yml
+++ b/core/modules/filter/tests/filter_test/config/install/filter.format.filter_test.yml
@@ -4,7 +4,6 @@ weight: 2
roles:
- anonymous
- authenticated
-cache: true
status: true
langcode: en
filters:
diff --git a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestAssets.php b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestAssets.php
new file mode 100644
index 0000000..a7de6d9
--- /dev/null
+++ b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestAssets.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filter_test\Plugin\Filter\FilterTestAssets.
+ */
+
+namespace Drupal\filter_test\Plugin\Filter;
+
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+
+/**
+ * Provides a test filter to attach assets
+ *
+ * @Filter(
+ * id = "filter_test_assets",
+ * title = @Translation("Testing filter"),
+ * description = @Translation("Does not change content; attaches assets."),
+ * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
+ * )
+ */
+class FilterTestAssets extends FilterBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process($text, $langcode) {
+ $result = new FilterProcessResult($text);
+ $result->addAssets(array(
+ 'library' => array(
+ 'filter/caption',
+ ),
+ ));
+ return $result;
+ }
+
+}
diff --git a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestCacheTags.php b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestCacheTags.php
new file mode 100644
index 0000000..3c03c3f
--- /dev/null
+++ b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestCacheTags.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filter_test\Plugin\Filter\FilterTestCacheTags.
+ */
+
+namespace Drupal\filter_test\Plugin\Filter;
+
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+
+/**
+ * Provides a test filter to associate cache tags
+ *
+ * @Filter(
+ * id = "filter_test_cache_tags",
+ * title = @Translation("Testing filter"),
+ * description = @Translation("Does not change content; associates cache tags."),
+ * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
+ * )
+ */
+class FilterTestCacheTags extends FilterBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process($text, $langcode) {
+ $result = new FilterProcessResult($text);
+ $result->addCacheTags(array('foo' => array('bar')));
+ $result->addCacheTags(array('foo' => array('baz')));
+ return $result;
+ }
+
+}
diff --git a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestPostRenderCache.php b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestPostRenderCache.php
new file mode 100644
index 0000000..aaeb400
--- /dev/null
+++ b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestPostRenderCache.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\filter_test\Plugin\Filter\FilterTestPostRenderCache.
+ */
+
+namespace Drupal\filter_test\Plugin\Filter;
+
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+
+/**
+ * Provides a test filter to associate #post_render_cache callbacks.
+ *
+ * @Filter(
+ * id = "filter_test_post_render_cache",
+ * title = @Translation("Testing filter"),
+ * description = @Translation("Appends a placeholder to the content; associates #post_render_cache callbacks."),
+ * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
+ * )
+ */
+class FilterTestPostRenderCache extends FilterBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process($text, $langcode) {
+ $callback = '\Drupal\filter_test\Plugin\Filter\FilterTestPostRenderCache::renderDynamicThing';
+ $context = array(
+ 'thing' => 'llama',
+ );
+ $placeholder = drupal_render_cache_generate_placeholder($callback, $context);
+ $result = new FilterProcessResult($text . '<p>' . $placeholder . '</p>');
+ $result->addPostRenderCacheCallback($callback, $context);
+ return $result;
+ }
+
+ /**
+ * #post_render_cache callback; replaces placeholder with a dynamic thing.
+ *
+ * @param array $element
+ * The renderable array that contains the to be replaced placeholder.
+ * @param array $context
+ * An array with the following keys:
+ * - thing: a "thing" string
+ *
+ * @return array
+ * A renderable array containing the comment form.
+ */
+ public static function renderDynamicThing(array $element, array $context) {
+ $callback = '\Drupal\filter_test\Plugin\Filter\FilterTestPostRenderCache::renderDynamicThing';
+ $placeholder = drupal_render_cache_generate_placeholder($callback, $context);
+ $markup = format_string('This is a dynamic @thing.', array('@thing' => $context['thing']));
+ $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
+ return $element;
+ }
+
+}
diff --git a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestReplace.php b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestReplace.php
index f48c5b1..735c02d 100644
--- a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestReplace.php
+++ b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestReplace.php
@@ -7,6 +7,7 @@
namespace Drupal\filter_test\Plugin\Filter;
+use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
/**
@@ -24,15 +25,11 @@ class FilterTestReplace extends FilterBase {
/**
* {@inheritdoc}
*/
- public function process($text, $langcode, $cache, $cache_id) {
+ public function process($text, $langcode) {
$text = array();
$text[] = 'Filter: ' . $this->getLabel() . ' (' . $this->getPluginId() . ')';
$text[] = 'Language: ' . $langcode;
- $text[] = 'Cache: ' . ($cache ? 'Enabled' : 'Disabled');
- if ($cache_id) {
- $text[] = 'Cache ID: ' . $cache_id;
- }
- return implode("<br />\n", $text);
+ return new FilterProcessResult(implode("<br />\n", $text));
}
}
diff --git a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php
index 6e95d2e..c11160d 100644
--- a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php
+++ b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestRestrictTagsAndAttributes.php
@@ -7,6 +7,7 @@
namespace Drupal\filter_test\Plugin\Filter;
+use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
use Drupal\Component\Utility\Xss;
@@ -25,11 +26,11 @@ class FilterTestRestrictTagsAndAttributes extends FilterBase {
/**
* {@inheritdoc}
*/
- public function process($text, $langcode, $cache, $cache_id) {
+ public function process($text, $langcode) {
$allowed_tags = array_filter($this->settings['restrictions']['allowed'], function($value) {
return is_array($value) || (bool) $value !== FALSE;
});
- return Xss::filter($text, array_keys($allowed_tags));
+ return new FilterProcessResult(Xss::filter($text, array_keys($allowed_tags)));
}
/**
diff --git a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestUncacheable.php b/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestUncacheable.php
deleted file mode 100644
index fd8318f..0000000
--- a/core/modules/filter/tests/filter_test/src/Plugin/Filter/FilterTestUncacheable.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\filter_test\Plugin\Filter\FilterTestUncacheable.
- */
-
-namespace Drupal\filter_test\Plugin\Filter;
-
-use Drupal\filter\Plugin\FilterBase;
-
-/**
- * Provides a test filter that is uncacheable.
- *
- * @Filter(
- * id = "filter_test_uncacheable",
- * title = @Translation("Uncacheable filter"),
- * description = @Translation("Does nothing, but makes a text format uncacheable"),
- * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
- * cache = false
- * )
- */
-class FilterTestUncacheable extends FilterBase {
-
- /**
- * {@inheritdoc}
- */
- public function process($text, $langcode, $cache, $cache_id) {
- return $text;
- }
-
-}
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index 8edbb07..7587af6 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -774,6 +774,8 @@ class RenderTest extends DrupalUnitTestBase {
);
$callback = 'common_test_post_render_cache_placeholder';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
+ $this->assertIdentical($placeholder, Html::normalize($placeholder), 'Placeholder unaltered by Html::normalize() which is used by FilterHtmlCorrector.');
+
$test_element = array(
'#post_render_cache' => array(
$callback => array(
@@ -825,7 +827,7 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($token, $expected_token, 'The tokens are identical');
// Verify the token is in the cached element.
$expected_element = array(
- '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '" /></foo>',
+ '#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>',
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context
@@ -923,7 +925,7 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($token, $expected_token, 'The tokens are identical for the child element');
// Verify the token is in the cached element.
$expected_element = array(
- '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '" /></foo>',
+ '#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>',
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context,
@@ -948,7 +950,7 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($token, $expected_token, 'The tokens are identical for the parent element');
// Verify the token is in the cached element.
$expected_element = array(
- '#markup' => '<div><foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '" /></foo></div>' . "\n",
+ '#markup' => '<div><foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo></div>' . "\n",
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context,
@@ -977,7 +979,7 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($token, $expected_token, 'The tokens are identical for the child element');
// Verify the token is in the cached element.
$expected_element = array(
- '#markup' => '<foo><drupal:render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '" /></foo>',
+ '#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>',
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context,
diff --git a/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php b/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
index 5e0f80b..cfd83dc 100644
--- a/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
+++ b/core/modules/text/src/Plugin/Field/FieldFormatter/TextDefaultFormatter.php
@@ -32,18 +32,67 @@ class TextDefaultFormatter extends FormatterBase {
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
+ if ($this->getFieldSetting('text_processing')) {
+ return $this->viewElementsWithTextProcessing($items);
+ }
+ else {
+ return $this->viewElementsWithoutTextProcessing($items);
+ }
+ }
+
+ /**
+ * Builds a renderable array when text processing is enabled.
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $items
+ * The text field values to be rendered.
+ *
+ * @return array
+ * A renderable array for $items, as an array of child elements keyed by
+ * consecutive numeric indexes starting from 0.
+ */
+ protected function viewElementsWithTextProcessing(FieldItemListInterface $items) {
+ $elements = array();
+
+ foreach ($items as $delta => $item) {
+ $elements[$delta] = array(
+ '#type' => 'processed_text',
+ '#text' => $item->value,
+ '#format' => $item->format,
+ '#langcode' => $item->getLangcode(),
+ );
+ // The viewElements() method of entity field formatters is run
+ // during the #pre_render phase of rendering an entity. A formatter
+ // builds the content of the field in preparation for theming.
+ // All cache tags must be available after the #pre_render phase. In order
+ // to collect the cache tags associated with the processed text, it must
+ // be passed to drupal_render() so that its #pre_render callback is
+ // invoked and its full build array is assembled. Rendering the processed
+ // text in place here will allow its cache tags to be bubbled up and
+ // included with those of the main entity when cache tags are collected
+ // for a renderable array in drupal_render().
+ // @todo remove this work-around, see https://drupal.org/node/2273277
+ drupal_render($elements[$delta], TRUE);
+ }
+
+ return $elements;
+ }
+
+ /**
+ * Builds a renderable array when text processing is disabled.
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $items
+ * The text field values to be rendered.
+ *
+ * @return array
+ * A renderable array for $items, as an array of child elements keyed by
+ * consecutive numeric indexes starting from 0.
+ */
+ protected function viewElementsWithoutTextProcessing(FieldItemListInterface $items) {
$elements = array();
foreach ($items as $delta => $item) {
$elements[$delta] = array(
- '#markup' => $item->processed,
- '#cache' => array(
- 'tags' => array(
- 'filter_format' => array(
- $item->format,
- ),
- ),
- ),
+ '#markup' => $item->processed,
);
}
diff --git a/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php b/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
index d4b3c34..f2bbefc 100644
--- a/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
+++ b/core/modules/text/src/Plugin/Field/FieldFormatter/TextTrimmedFormatter.php
@@ -68,26 +68,84 @@ class TextTrimmedFormatter extends FormatterBase {
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
+ if ($this->getFieldSetting('text_processing')) {
+ return $this->viewElementsWithTextProcessing($items);
+ }
+ else {
+ return $this->viewElementsWithoutTextProcessing($items);
+ }
+ }
+
+ /**
+ * Builds a renderable array when text processing is enabled.
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $items
+ * The text field values to be rendered.
+ *
+ * @return array
+ * A renderable array for $items, as an array of child elements keyed by
+ * consecutive numeric indexes starting from 0.
+ */
+ protected function viewElementsWithTextProcessing(FieldItemListInterface $items) {
+ $elements = array();
+
+ foreach ($items as $delta => $item) {
+ $elements[$delta] = array(
+ '#type' => 'processed_text',
+ '#text' => NULL,
+ '#format' => $item->format,
+ '#langcode' => $item->getLangcode(),
+ );
+
+ // The viewElements() method of entity field formatters is run
+ // during the #pre_render phase of rendering an entity. A formatter
+ // builds the content of the field in preparation for theming.
+ // All cache tags must be available after the #pre_render phase. In order
+ // to collect the cache tags associated with the processed text, it must
+ // be passed to drupal_render() so that its #pre_render callback is
+ // invoked and its full build array is assembled. Rendering the processed
+ // text in place here will allow its cache tags to be bubbled up and
+ // included with those of the main entity when cache tags are collected
+ // for a renderable array in drupal_render().
+ if ($this->getPluginId() == 'text_summary_or_trimmed' && !empty($item->summary)) {
+ $elements[$delta]['#text'] = $item->summary;
+ // @todo remove this work-around, see https://drupal.org/node/2273277
+ drupal_render($elements[$delta], TRUE);
+ }
+ else {
+ $elements[$delta]['#text'] = $item->value;
+ // @todo remove this work-around, see https://drupal.org/node/2273277
+ drupal_render($elements[$delta], TRUE);
+ $elements[$delta]['#markup'] = text_summary($elements[$delta]['#markup'], $item->format, $this->getSetting('trim_length'));
+ }
+ }
+
+ return $elements;
+ }
+
+ /**
+ * Builds a renderable array when text processing is disabled.
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $items
+ * The text field values to be rendered.
+ *
+ * @return array
+ * A renderable array for $items, as an array of child elements keyed by
+ * consecutive numeric indexes starting from 0.
+ */
+ protected function viewElementsWithoutTextProcessing(FieldItemListInterface $items) {
$elements = array();
- $text_processing = $this->getFieldSetting('text_processing');
foreach ($items as $delta => $item) {
if ($this->getPluginId() == 'text_summary_or_trimmed' && !empty($item->summary)) {
$output = $item->summary_processed;
}
else {
- $output = $item->processed;
- $output = text_summary($output, $text_processing ? $item->format : NULL, $this->getSetting('trim_length'));
+ $output = text_summary($item->processed, NULL, $this->getSetting('trim_length'));
}
+
$elements[$delta] = array(
'#markup' => $output,
- '#cache' => array(
- 'tags' => array(
- 'filter_format' => array(
- $item->format,
- ),
- ),
- ),
);
}
diff --git a/core/modules/text/src/Tests/Formatter/TextFormatterTest.php b/core/modules/text/src/Tests/Formatter/TextFormatterTest.php
new file mode 100644
index 0000000..3745a5a
--- /dev/null
+++ b/core/modules/text/src/Tests/Formatter/TextFormatterTest.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\text\Tests\Formatter\TextFormatterTest.
+ */
+
+namespace Drupal\text\Tests\Formatter;
+
+use Drupal\system\Tests\Entity\EntityUnitTestBase;
+
+/**
+ * Tests Text formatters.
+ */
+class TextFormatterTest extends EntityUnitTestBase {
+
+ /**
+ * The entity type used in this test.
+ *
+ * @var string
+ */
+ protected $entityType = 'entity_test';
+
+ /**
+ * The bundle used in this test.
+ *
+ * @var string
+ */
+ protected $bundle = 'entity_test';
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('text');
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getInfo() {
+ return array(
+ 'name' => 'Text formatters',
+ 'description' => 'Tests the text formatters functionality.',
+ 'group' => 'Text ',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setUp() {
+ parent::setUp();
+
+ entity_create('filter_format', array(
+ 'format' => 'my_text_format',
+ 'name' => 'My text format',
+ 'filters' => array(
+ 'filter_autop' => array(
+ 'module' => 'filter',
+ 'status' => TRUE,
+ ),
+ ),
+ ))->save();
+
+ // Set up two fields: one with text processing enabled, the other disabled.
+ entity_create('field_config', array(
+ 'name' => 'processed_text',
+ 'entity_type' => $this->entityType,
+ 'type' => 'text',
+ 'settings' => array(),
+ ))->save();
+ entity_create('field_instance_config', array(
+ 'entity_type' => $this->entityType,
+ 'bundle' => $this->bundle,
+ 'field_name' => 'processed_text',
+ 'label' => 'Processed text',
+ 'settings' => array(
+ 'text_processing' => TRUE,
+ ),
+ ))->save();
+ entity_create('field_config', array(
+ 'name' => 'unprocessed_text',
+ 'entity_type' => $this->entityType,
+ 'type' => 'text',
+ 'settings' => array(),
+ ))->save();
+ entity_create('field_instance_config', array(
+ 'entity_type' => $this->entityType,
+ 'bundle' => $this->bundle,
+ 'field_name' => 'unprocessed_text',
+ 'label' => 'Unprocessed text',
+ 'settings' => array(
+ 'text_processing' => FALSE,
+ ),
+ ))->save();
+ }
+
+ /**
+ * Tests all text field formatters.
+ */
+ public function testFormatters() {
+ $formatters = array(
+ 'text_default',
+ 'text_trimmed',
+ 'text_summary_or_trimmed',
+ );
+
+ // Create the entity to be referenced.
+ $entity = entity_create($this->entityType, array('name' => $this->randomName()));
+ $entity->processed_text = array(
+ 'value' => 'Hello, world!',
+ 'format' => 'my_text_format',
+ );
+ $entity->unprocessed_text = array(
+ 'value' => 'Hello, world!',
+ );
+ $entity->save();
+
+ foreach ($formatters as $formatter) {
+ // Verify the processed text field formatter's render array.
+ $build = $entity->get('processed_text')->view(array('type' => $formatter));
+ $this->assertEqual($build[0]['#markup'], "<p>Hello, world!</p>\n");
+ $expected_cache_tags = array(
+ 'filter_format' => array('my_text_format' => 'my_text_format'),
+ );
+ $this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags when formatting a processed text field.', array('@formatter' => $formatter)));
+
+ // Verify the unprocessed text field formatter's render array.
+ $build = $entity->get('unprocessed_text')->view(array('type' => $formatter));
+ debug($build[0]);
+ $this->assertEqual($build[0]['#markup'], 'Hello, world!');
+ $this->assertTrue(!isset($build[0]['#cache']), format_string('The @formatter formatter has the expected cache tags when formatting an unprocessed text field.', array('@formatter' => $formatter)));
+ }
+ }
+
+}
diff --git a/core/modules/text/src/TextProcessed.php b/core/modules/text/src/TextProcessed.php
index 5d99de3..3666d29 100644
--- a/core/modules/text/src/TextProcessed.php
+++ b/core/modules/text/src/TextProcessed.php
@@ -34,7 +34,7 @@ class TextProcessed extends TypedData {
parent::__construct($definition, $name, $parent);
if ($definition->getSetting('text source') === NULL) {
- throw new \InvalidArgumentException("The definition's 'source' key has to specify the name of the text property to be processed.");
+ throw new \InvalidArgumentException("The definition's 'text source' key has to specify the name of the text property to be processed.");
}
}
diff --git a/core/modules/user/src/Tests/UserSignatureTest.php b/core/modules/user/src/Tests/UserSignatureTest.php
index 83f5973..ce6c5ff 100644
--- a/core/modules/user/src/Tests/UserSignatureTest.php
+++ b/core/modules/user/src/Tests/UserSignatureTest.php
@@ -68,7 +68,7 @@ class UserSignatureTest extends WebTestBase {
// Create regular and administrative users.
$this->web_user = $this->drupalCreateUser(array('post comments'));
- $admin_permissions = array('administer comments');
+ $admin_permissions = array('post comments', 'administer comments');
foreach (filter_formats() as $format) {
if ($permission = $format->getPermissionName()) {
$admin_permissions[] = $permission;
@@ -82,7 +82,14 @@ class UserSignatureTest extends WebTestBase {
* upon display.
*/
function testUserSignature() {
- $node = $this->drupalCreateNode();
+ $node = $this->drupalCreateNode(array(
+ 'body' => array(
+ 0 => array(
+ 'value' => $this->randomName(32),
+ 'format' => 'full_html',
+ ),
+ ),
+ ));
// Verify that user signature field is not displayed on registration form.
$this->drupalGet('user/register');
@@ -99,6 +106,10 @@ class UserSignatureTest extends WebTestBase {
// Verify that values were stored.
$this->assertFieldByName('signature[value]', $edit['signature[value]'], 'Submitted signature text found.');
+ // Verify that the user signature's text format's cache tag is absent.
+ $this->drupalGet('node/' . $node->id());
+ $this->assertTrue(!in_array('filter_format:filtered_html_format', explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'))));
+
// Create a comment.
$edit = array();
$edit['subject'] = $this->randomName(8);
@@ -121,5 +132,8 @@ class UserSignatureTest extends WebTestBase {
$this->drupalGet('node/' . $node->id());
$this->assertNoRaw($signature_text, 'Unfiltered signature text not found.');
$this->assertRaw(check_markup($signature_text, $this->filtered_html_format->format), 'Filtered signature text found.');
+ // Verify that the user signature's text format's cache tag is present.
+ $this->drupalGet('node/' . $node->id());
+ $this->assertTrue(in_array('filter_format:filtered_html_format', explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'))));
}
}
diff --git a/core/modules/views/src/Plugin/views/area/Text.php b/core/modules/views/src/Plugin/views/area/Text.php
index 86288b0..de89210 100644
--- a/core/modules/views/src/Plugin/views/area/Text.php
+++ b/core/modules/views/src/Plugin/views/area/Text.php
@@ -58,20 +58,13 @@ class Text extends TokenizeAreaPluginBase {
$format = isset($this->options['format']) ? $this->options['format'] : filter_default_format();
if (!$empty || !empty($this->options['empty'])) {
return array(
- '#markup' => $this->renderTextarea($this->options['content'], $format),
+ '#type' => 'processed_text',
+ '#text' => $this->tokenizeValue($this->options['content']),
+ '#format' => $format,
);
}
return array();
}
- /**
- * Render a text area, using the proper format.
- */
- public function renderTextarea($value, $format) {
- if ($value) {
- return check_markup($this->tokenizeValue($value), $format, '', FALSE);
- }
- }
-
}
diff --git a/core/modules/views/src/Plugin/views/field/Markup.php b/core/modules/views/src/Plugin/views/field/Markup.php
index ff43934..465f655 100644
--- a/core/modules/views/src/Plugin/views/field/Markup.php
+++ b/core/modules/views/src/Plugin/views/field/Markup.php
@@ -53,7 +53,7 @@ class Markup extends FieldPluginBase {
}
if ($value) {
$value = str_replace('<!--break-->', '', $value);
- return check_markup($value, $format, '');
+ return check_markup($value, $format);
}
}
diff --git a/core/modules/views/src/Tests/Handler/AreaTextTest.php b/core/modules/views/src/Tests/Handler/AreaTextTest.php
index 3ab29d7..7bc2bc2 100644
--- a/core/modules/views/src/Tests/Handler/AreaTextTest.php
+++ b/core/modules/views/src/Tests/Handler/AreaTextTest.php
@@ -60,16 +60,19 @@ class AreaTextTest extends ViewUnitTestBase {
$this->executeView($view);
$view->display_handler->handlers['header']['area']->options['format'] = $this->randomString();
- $this->assertEqual(array('#markup' => ''), $view->display_handler->handlers['header']['area']->render(), 'Nonexistent format should return empty markup.');
+ $build = $view->display_handler->handlers['header']['area']->render();
+ $this->assertEqual('', drupal_render($build), 'Nonexistent format should return empty markup.');
$view->display_handler->handlers['header']['area']->options['format'] = filter_default_format();
- $this->assertEqual(array('#markup' => check_markup($string)), $view->display_handler->handlers['header']['area']->render(), 'Existant format should return something');
+ $build = $view->display_handler->handlers['header']['area']->render();
+ $this->assertEqual(check_markup($string), drupal_render($build), 'Existent format should return something');
// Empty results, and it shouldn't be displayed .
$this->assertEqual(array(), $view->display_handler->handlers['header']['area']->render(TRUE), 'No result should lead to no header');
// Empty results, and it should be displayed.
$view->display_handler->handlers['header']['area']->options['empty'] = TRUE;
- $this->assertEqual(array('#markup' => check_markup($string)), $view->display_handler->handlers['header']['area']->render(TRUE), 'No result, but empty enabled lead to a full header');
+ $build = $view->display_handler->handlers['header']['area']->render(TRUE);
+ $this->assertEqual(check_markup($string), drupal_render($build), 'No result, but empty enabled lead to a full header');
}
}
diff --git a/core/profiles/standard/config/install/filter.format.basic_html.yml b/core/profiles/standard/config/install/filter.format.basic_html.yml
index 0a9ad13..810a484 100644
--- a/core/profiles/standard/config/install/filter.format.basic_html.yml
+++ b/core/profiles/standard/config/install/filter.format.basic_html.yml
@@ -4,7 +4,6 @@ status: true
weight: 0
roles:
- authenticated
-cache: true
filters:
filter_html:
id: filter_html
@@ -33,4 +32,13 @@ filters:
status: true
weight: 10
settings: { }
+ editor_file_reference:
+ id: editor_file_reference
+ provider: editor
+ status: true
+ weight: 11
+ settings: { }
langcode: en
+dependencies:
+ module:
+ - editor
diff --git a/core/profiles/standard/config/install/filter.format.full_html.yml b/core/profiles/standard/config/install/filter.format.full_html.yml
index 31b6692..9f5ea79 100644
--- a/core/profiles/standard/config/install/filter.format.full_html.yml
+++ b/core/profiles/standard/config/install/filter.format.full_html.yml
@@ -4,7 +4,6 @@ status: true
weight: 1
roles:
- administrator
-cache: true
filters:
filter_caption:
id: filter_caption
@@ -18,4 +17,13 @@ filters:
status: true
weight: 10
settings: { }
+ editor_file_reference:
+ id: editor_file_reference
+ provider: editor
+ status: true
+ weight: 11
+ settings: { }
langcode: en
+dependencies:
+ module:
+ - editor
diff --git a/core/profiles/standard/config/install/filter.format.restricted_html.yml b/core/profiles/standard/config/install/filter.format.restricted_html.yml
index 12a94aa..95a20e5 100644
--- a/core/profiles/standard/config/install/filter.format.restricted_html.yml
+++ b/core/profiles/standard/config/install/filter.format.restricted_html.yml
@@ -4,7 +4,6 @@ status: true
weight: 0
roles:
- anonymous
-cache: true
filters:
filter_html:
id: filter_html