diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php index 1e55f29..12a6bbf 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php @@ -54,8 +54,8 @@ function testNodeTokenReplacement() { $tests['[node:type]'] = 'article'; $tests['[node:type-name]'] = 'Article'; $tests['[node:title]'] = check_plain($node->getTitle()); - $tests['[node:body]'] = text_sanitize($instance['settings']['text_processing'], $node->language()->id, $node->body[0]->getValue(), 'value'); - $tests['[node:summary]'] = text_sanitize($instance['settings']['text_processing'], $node->language()->id, $node->body[0]->getValue(), 'summary'); + $tests['[node:body]'] = $node->body->processed; + $tests['[node:summary]'] = $node->body->summary_processed; $tests['[node:langcode]'] = check_plain($node->language()->id); $tests['[node:url]'] = url('node/' . $node->id(), $url_options); $tests['[node:edit-url]'] = url('node/' . $node->id() . '/edit', $url_options); @@ -97,7 +97,7 @@ function testNodeTokenReplacement() { // Generate and test sanitized token - use full body as expected value. $tests = array(); - $tests['[node:summary]'] = text_sanitize($instance['settings']['text_processing'], $node->language()->id, $node->body[0]->getValue(), 'value'); + $tests['[node:summary]'] = $node->body->processed; // Test to make sure that we generated something for each token. $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated for node without a summary.'); diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc index 9240497..397211f 100644 --- a/core/modules/node/node.tokens.inc +++ b/core/modules/node/node.tokens.inc @@ -144,11 +144,11 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr // If the summary was requested and is not empty, use it. if ($name == 'summary' && !empty($item->summary)) { - $output = $sanitize ? text_sanitize($instance['settings']['text_processing'], $field_langcode, $item->getValue(), 'summary') : $item->summary; + $output = $sanitize ? $item->summary_processed : $item->summary; } // Attempt to provide a suitable version of the 'body' field. else { - $output = $sanitize ? text_sanitize($instance['settings']['text_processing'], $field_langcode, $item->getValue(), 'value') : $item->value; + $output = $sanitize ? $item->processed : $item->value; // A summary was requested. if ($name == 'summary') { // Generate an optionally trimmed summary of the body field. diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/field_type/TextItemBase.php b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/TextItemBase.php index 417d354..54630fd 100644 --- a/core/modules/text/lib/Drupal/text/Plugin/field/field_type/TextItemBase.php +++ b/core/modules/text/lib/Drupal/text/Plugin/field/field_type/TextItemBase.php @@ -71,22 +71,34 @@ public function isEmpty() { * {@inheritdoc} */ public function prepareCache() { - // Where possible, generate the sanitized version of each textual property - // (e.g., 'value', 'summary') within this field item early so that it is - // cached in the field cache. This avoids the need to look up the sanitized - // value in the filter cache separately. + // Where possible, generate the processed (sanitized) version of each + // textual property (e.g., 'value', 'summary') within this field item early + // so that it is cached in the field cache. This avoids the need to look up + // the sanitized value in the filter cache separately. $text_processing = $this->getFieldSetting('text_processing'); if (!$text_processing || filter_format_allowcache($this->get('format')->getValue())) { - $itemBC = $this->getValue(); - $langcode = $this->getParent()->getParent()->language()->id; - // The properties that need sanitizing are the ones that are the 'text - // source' of a TextProcessed computed property. - // @todo Clean up this mess by making the TextProcessed property type - // support its own cache integration: https://drupal.org/node/2026339. - foreach ($this->getPropertyDefinitions() as $definition) { + foreach ($this->getPropertyDefinitions() as $property => $definition) { if (isset($definition['class']) && ($definition['class'] == '\Drupal\text\TextProcessed')) { - $source_property = $definition['settings']['text source']; - $this->set('safe_' . $source_property, text_sanitize($text_processing, $langcode, $itemBC, $source_property)); + $this->get($property)->getValue(); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function onChange($property_name) { + // Notify the parent of changes. + if (isset($this->parent)) { + $this->parent->onChange($this->name); + } + + // Unset processed properties that are affected by the change. + foreach ($this->getPropertyDefinitions() as $property => $definition) { + if (isset($definition['class']) && ($definition['class'] == '\Drupal\text\TextProcessed')) { + if ($property_name == 'format' || (isset($definition['settings']['text source']) && $definition['settings']['text source'] == $property_name)) { + $this->set($property, NULL, FALSE); } } } diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php b/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php index dd0ee71..a34da43 100644 --- a/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php +++ b/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php @@ -38,11 +38,7 @@ public function viewElements(EntityInterface $entity, $langcode, FieldInterface $elements = array(); foreach ($items as $delta => $item) { - // @todo Convert text_sanitize() to work on an NG $item. See - // https://drupal.org/node/2026339. - $itemBC = $item->getValue(TRUE); - $output = text_sanitize($this->getFieldSetting('text_processing'), $langcode, $itemBC, 'value'); - $elements[$delta] = array('#markup' => $output); + $elements[$delta] = array('#markup' => $item->processed); } return $elements; diff --git a/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php b/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php index f2ca9e4..b59c73b 100644 --- a/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php +++ b/core/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php @@ -71,10 +71,10 @@ public function viewElements(EntityInterface $entity, $langcode, FieldInterface $text_processing = $this->getFieldSetting('text_processing'); foreach ($items as $delta => $item) { if ($this->getPluginId() == 'text_summary_or_trimmed' && !empty($item->summary)) { - $output = text_sanitize($text_processing, $langcode, $item->getValue(TRUE), 'summary'); + $output = $item->summary_processed; } else { - $output = text_sanitize($text_processing, $langcode, $item->getValue(TRUE), 'value'); + $output = $item->processed; $output = text_summary($output, $text_processing ? $item->format : NULL, $this->getSetting('trim_length')); } $elements[$delta] = array('#markup' => $output); diff --git a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php new file mode 100644 index 0000000..4cc2249 --- /dev/null +++ b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php @@ -0,0 +1,177 @@ + 'Text summary field item', + 'description' => 'Tests using entity fields of the text summary field type.', + 'group' => 'Field types', + ); + } + + public function setUp() { + parent::setUp(); + + $this->installSchema('entity_test', array('entity_test_rev', 'entity_test_rev_revision')); + + // Create the necessary formats. + $this->installConfig(array('filter')); + entity_create('filter_format', array( + 'format' => 'no_filters', + 'filters' => array(), + ))->save(); + } + + /** + * Tests processed properties. + */ + public function testCrudAndUpdate() { + $entity_type = 'entity_test'; + $this->createField($entity_type); + + // Create an entity with a summary and no text format. + $entity = entity_create($entity_type, array()); + $entity->summary_field->value = $value = $this->randomName(); + $entity->summary_field->summary = $summary = $this->randomName(); + $entity->summary_field->format = NULL; + $entity->name->value = $this->randomName(); + $entity->save(); + + $entity = entity_load($entity_type, $entity->id()); + $this->assertTrue($entity->summary_field instanceof FieldInterface, 'Field implements interface.'); + $this->assertTrue($entity->summary_field[0] instanceof FieldItemInterface, 'Field item implements interface.'); + $this->assertEqual($entity->summary_field->value, $value); + $this->assertEqual($entity->summary_field->processed, $value); + $this->assertEqual($entity->summary_field->summary, $summary); + $this->assertEqual($entity->summary_field->summary_processed, $summary); + $this->assertNull($entity->summary_field->format); + + // Enable text processing. + $this->instance->settings['text_processing'] = 1; + $this->instance->save(); + + // Re-load the entity. + $entity = entity_load($entity_type, $entity->id(), TRUE); + + // Even if no format is given, if text processing is enabled, the default + // format is used. + $this->assertEqual($entity->summary_field->processed, "

$value

\n"); + $this->assertEqual($entity->summary_field->summary_processed, "

$summary

\n"); + + // Change the format, this should update the processed properties. + $entity->summary_field->format = 'no_filters'; + $this->assertEqual($entity->summary_field->processed, $value); + $this->assertEqual($entity->summary_field->summary_processed, $summary); + } + + /** + * Tests that the processed values are cached. + */ + function testProcessedCache() { + // Use an entity type that has caching enabled. + $entity_type = 'entity_test_rev'; + + $this->createField($entity_type); + + // Create an entity with a summary and a text format. + $entity = entity_create($entity_type, array()); + $entity->summary_field->value = $value = $this->randomName(); + $entity->summary_field->summary = $summary = $this->randomName(); + $entity->summary_field->format = 'plain_text'; + $entity->name->value = $this->randomName(); + $entity->save(); + + // Inject values into the cache to make sure that these are used as-is and + // not re-calculated. + $data = array( + 'summary_field' => array( + Language::LANGCODE_DEFAULT => array( + 0 => array( + 'value' => $value, + 'summary' => $summary, + 'format' => 'plain_text', + 'processed' => 'Cached processed value', + 'summary_processed' => 'Cached summary processed value', + ), + ), + ), + ); + cache('field')->set("field:$entity_type:" . $entity->id(), $data); + + $entity = entity_load($entity_type, $entity->id()); + $this->assertEqual($entity->summary_field->processed, 'Cached processed value'); + $this->assertEqual($entity->summary_field->summary_processed, 'Cached summary processed value'); + + // Change the format, this should update the processed properties. + $entity->summary_field->format = 'no_filters'; + $this->assertEqual($entity->summary_field->processed, $value); + $this->assertEqual($entity->summary_field->summary_processed, $summary); + } + + /** + * Creates a text_with_summary field and field instance. + * + * @param string $entity_type + * Entity type for which the field should be created. + */ + protected function createField($entity_type) { + // Create a field . + $this->field = entity_create('field_entity', array( + 'name' => 'summary_field', + 'entity_type' => $entity_type, + 'type' => 'text_with_summary', + 'settings' => array( + 'max_length' => 10, + ) + )); + $this->field->save(); + $this->instance = entity_create('field_instance', array( + 'field_name' => $this->field->name, + 'entity_type' => $entity_type, + 'bundle' => $entity_type, + 'settings' => array( + 'text_processing' => 0, + ) + )); + $this->instance->save(); + } + +} diff --git a/core/modules/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index 352420d..69c9f91 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -21,18 +21,11 @@ class TextProcessed extends TypedData { /** - * The text property. + * Cached processed text. * - * @var \Drupal\Core\TypedData\TypedDataInterface + * @var string|null */ - protected $text; - - /** - * The text format property. - * - * @var \Drupal\Core\TypedData\TypedDataInterface - */ - protected $format; + protected $processed = NULL; /** * Overrides TypedData::__construct(). @@ -46,46 +39,37 @@ public function __construct(array $definition, $name = NULL, TypedDataInterface } /** - * Overrides TypedData::setContext(). - */ - public function setContext($name = NULL, TypedDataInterface $parent = NULL) { - parent::setContext($name, $parent); - if (isset($parent)) { - $this->text = $parent->get($this->definition['settings']['text source']); - $this->format = $parent->get('format'); - } - } - - /** * Implements \Drupal\Core\TypedData\TypedDataInterface::getValue(). */ public function getValue($langcode = NULL) { - - if (!isset($this->text)) { - throw new InvalidArgumentException('Computed properties require context for computation.'); + if ($this->processed !== NULL) { + return $this->processed; } - $field = $this->parent->getParent(); - $entity = $field->getParent(); - $instance = field_info_instance($entity->entityType(), $field->getName(), $entity->bundle()); - - if (!empty($instance['settings']['text_processing']) && $this->format->getValue()) { - return check_markup($this->text->getValue(), $this->format->getValue(), $entity->language()->id); + $item = $this->getParent(); + $text = $item->{($this->definition['settings']['text source'])}; + if ($item->getFieldDefinition()->getFieldSetting('text_processing')) { + // @todo: The entity language might not be the correct language to use, + // fix in https://drupal.org/node/2061331. + $entity = $item->getParent()->getParent(); + $this->processed = check_markup($text, $item->format, $entity->language()->id); } else { - // If no format is available, still make sure to sanitize the text. - return check_plain($this->text->getValue()); + // Escape all HTML and retain newlines. + // @see \Drupal\text\Plugin\field\formatter\TextPlainFormatter + $this->processed = nl2br(check_plain($text)); } + return $this->processed; } /** * Implements \Drupal\Core\TypedData\TypedDataInterface::setValue(). */ public function setValue($value, $notify = TRUE) { - if (isset($value)) { - // @todo This is triggered from DatabaseStorageController::invokeFieldMethod() - // in the case of case of non-NG entity types. - // throw new ReadOnlyException('Unable to set a computed property.'); + $this->processed = $value; + // Notify the parent of any changes. + if ($notify && isset($this->parent)) { + $this->parent->onChange($this->name); } } diff --git a/core/modules/text/text.module b/core/modules/text/text.module index 9014113..ecc0767 100644 --- a/core/modules/text/text.module +++ b/core/modules/text/text.module @@ -41,42 +41,6 @@ function text_help($path, $arg) { } /** - * Sanitizes the 'value' or 'summary' data of a text value. - * - * Depending on whether the field instance uses text processing, data is run - * through check_plain() or check_markup(). - * - * @param bool $text_processing - * Whether to process the text via check_markup(). - * @param string $langcode - * The language associated with $item. - * @param array $item - * The field value to sanitize. - * @param string $column - * The column to sanitize (either 'value' or 'summary'). - * - * @return string - * The sanitized string. - */ -function text_sanitize($text_processing, $langcode, $item, $column) { - if (isset($item["safe_$column"])) { - return $item["safe_$column"]; - } - - // Optimize by opting out for the trivial 'empty string' case. - if ($item[$column] == '') { - return ''; - } - - if ($text_processing) { - return check_markup($item[$column], $item['format'], $langcode); - } - // Escape all HTML and retain newlines. - // @see \Drupal\text\Plugin\field\formatter\TextPlainFormatter - return nl2br(check_plain($item[$column])); -} - -/** * Generates a trimmed, formatted version of a text field value. * * If the end of the summary is not indicated using the delimiter