diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php index 43ff5f1..767d3a2 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerBase.php @@ -173,67 +173,6 @@ public function invokeFieldMethod($method, EntityInterface $entity) { } } - /** - * {@inheritdoc} - */ - public function invokeFieldItemPrepareCache(EntityInterface $entity) { - foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { - // @todo getTranslation() only works on NG entities. Remove the condition - // and the second code branch when all core entity types are converted. - if ($translation = $entity->getTranslation($langcode)) { - foreach ($translation->getPropertyDefinitions() as $property => $definition) { - $type_definition = \Drupal::typedData()->getDefinition($definition['type']); - // Only create the item objects if needed. - if (is_subclass_of($type_definition['class'], '\Drupal\Core\Entity\Field\PrepareCacheInterface') - // Prevent legacy field types from skewing performance too much by - // checking the existence of the legacy function directly, instead - // of making LegacyConfigFieldItem implement PrepareCacheInterface. - // @todo Remove once all core field types have been converted (see - // http://drupal.org/node/2014671). - || (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem') - && isset($type_definition['provider']) && function_exists($type_definition['provider'] . '_field_load'))) { - - // Call the prepareCache() method directly on each item - // individually. - foreach ($translation->get($property) as $item) { - $item->prepareCache(); - } - } - } - } - else { - // For BC entities, iterate through the fields and instantiate NG items - // objects manually. - $definitions = \Drupal::entityManager()->getFieldDefinitions($entity->entityType(), $entity->bundle()); - foreach ($definitions as $field_name => $definition) { - if (!empty($definition['configurable'])) { - $type_definition = \Drupal::typedData()->getDefinition($definition['type']); - // Only create the item objects if needed. - if (is_subclass_of($type_definition['class'], '\Drupal\Core\Entity\Field\PrepareCacheInterface') - // @todo Remove once all core field types have been converted - // (see http://drupal.org/node/2014671). - || (is_subclass_of($type_definition['class'], '\Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem') && function_exists($type_definition['provider'] . '_field_load'))) { - - // Create the items object. - $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); - $itemsNG = \Drupal::typedData()->create($definition, $items, $field_name, $entity); - - foreach ($itemsNG as $item) { - $item->prepareCache(); - } - - // Put back the items values in the entity. - $items = $itemsNG->getValue(TRUE); - if ($items !== array() || isset($entity->{$field_name}[$langcode])) { - $entity->{$field_name}[$langcode] = $items; - } - } - } - } - } - } - } - /** * Invokes a hook on behalf of the entity. * diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php index c7f72f7..63a38bf 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php @@ -163,12 +163,4 @@ public function getQueryServicename(); */ public function invokeFieldMethod($method, EntityInterface $entity); - /** - * Invokes the prepareCache() method on all the relevant FieldItem objects. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity object. - */ - public function invokeFieldItemPrepareCache(EntityInterface $entity); - } diff --git a/core/lib/Drupal/Core/Entity/Field/PrepareCacheInterface.php b/core/lib/Drupal/Core/Entity/Field/PrepareCacheInterface.php deleted file mode 100644 index 5701523..0000000 --- a/core/lib/Drupal/Core/Entity/Field/PrepareCacheInterface.php +++ /dev/null @@ -1,31 +0,0 @@ -doLoadFieldItems($queried_entities, $age); - // Invoke the field type's prepareCache() method. - foreach ($queried_entities as $entity) { - $this->invokeFieldItemPrepareCache($entity); - } - // Build cache data. if ($use_cache) { foreach ($queried_entities as $id => $entity) { $data = array(); - $instances = field_info_instances($this->entityType, $entity->bundle()); foreach ($entity->getTranslationLanguages() as $langcode => $language) { $translation = $entity->getTranslation($langcode); - foreach ($instances as $instance) { - $data[$langcode][$instance['field_name']] = $translation->{$instance['field_name']}->getValue(); + foreach ($translation as $field_name => $items) { + if (!$items->isEmpty() && $items instanceof ConfigFieldInterface) { + foreach ($items as $delta => $item) { + if ($item instanceof ConfigFieldItemInterface) { + $data[$langcode][$field_name][$delta] = $item->prepareCache(); + } + } + } } } $cid = "field:{$this->entityType}:$id"; diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/field_type/DateTimeItem.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/field_type/DateTimeItem.php index 558ca47..17a9cf1 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/field_type/DateTimeItem.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/field_type/DateTimeItem.php @@ -31,7 +31,7 @@ * default_formatter = "datetime_default" * ) */ -class DateTimeItem extends ConfigFieldItemBase implements PrepareCacheInterface { +class DateTimeItem extends ConfigFieldItemBase { /** * Field definitions of the contained properties. @@ -115,23 +115,24 @@ public function instanceSettingsForm(array $form, array &$form_state) { * {@inheritdoc} */ public function prepareCache() { + $values = parent::prepareCache(); // The function generates a Date object for each field early so that it is // cached in the field cache. This avoids the need to generate the object // later. The date will be retrieved in UTC, the local timezone adjustment // must be made in real time, based on the preferences of the site and user. - $value = $this->get('value')->getValue(); - if (!empty($value)) { + if (!empty($values['value'])) { $storage_format = $this->getFieldSetting('datetime_type') == 'date' ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT; try { - $date = DrupalDateTime::createFromFormat($storage_format, $value, DATETIME_STORAGE_TIMEZONE); + $date = DrupalDateTime::createFromFormat($storage_format, $values['value'], DATETIME_STORAGE_TIMEZONE); if ($date instanceOf DrupalDateTime && !$date->hasErrors()) { - $this->set('date', $date); + $values['date'] = $date; } } catch (\Exception $e) { // @todo Handle this. } } + return $values; } /** diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimeDefaultFormatter.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimeDefaultFormatter.php index 03f172f..3ea6333 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimeDefaultFormatter.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimeDefaultFormatter.php @@ -103,11 +103,14 @@ public function viewElements(FieldInterface $items) { $formatted_date = ''; $iso_date = ''; + $date = NULL; if (!empty($item->date)) { - // The date was created and verified during field_load(), so it is safe - // to use without further inspection. $date = $item->date; - + } + elseif ($item->value) { + $date = $item->get('value')->getDateTime(); + } + if ($date) { // Create the ISO date in Universal Time. $iso_date = $date->format("Y-m-d\TH:i:s") . 'Z'; diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimePlainFormatter.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimePlainFormatter.php index 600092f..f9ad329 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimePlainFormatter.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/formatter/DatetimePlainFormatter.php @@ -36,10 +36,16 @@ public function viewElements(FieldInterface $items) { foreach ($items as $delta => $item) { $output = ''; + $date = NULL; if (!empty($item->date)) { + $date = $item->date; + } + elseif ($item->value) { + $date = $item->get('value')->getDateTime(); + } + if ($date) { // The date was created and verified during field_load(), so it is safe // to use without further inspection. - $date = $item->date; $date->setTimeZone(timezone_open(drupal_get_user_timezone())); $format = DATETIME_DATETIME_STORAGE_FORMAT; if ($this->getFieldSetting('datetime_type') == 'date') { diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php index 37d2c3b..ad9fa2b 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDatelistWidget.php @@ -126,8 +126,15 @@ public function formElement(FieldInterface $items, $delta, array $element, array // validator will not have access to the field definition. $element['value']['#date_storage_format'] = $storage_format; + $date = NULL; if (!empty($items[$delta]->date)) { $date = $items[$delta]->date; + } + elseif ($items[$delta]->value) { + $date = $items[$delta]->get('value')->getDateTime(); + } + if ($date) { + $date = $items[$delta]->date; // The date was created and verified during field_load(), so it is safe to // use without further inspection. $date->setTimezone( new \DateTimeZone($element['value']['#date_timezone'])); diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php index 6628dbb..058310a 100644 --- a/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php +++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/field/widget/DatetimeDefaultWidget.php @@ -115,8 +115,14 @@ public function formElement(FieldInterface $items, $delta, array $element, array $element['value']['#date_element_format'] = $element_format; $element['value']['#date_storage_format'] = $storage_format; + $date = NULL; if (!empty($items[$delta]->date)) { $date = $items[$delta]->date; + } + elseif ($items[$delta]->value) { + $date = $items[$delta]->get('value')->getDateTime(); + } + if ($date) { // The date was created and verified during field_load(), so it is safe to // use without further inspection. $date->setTimezone(new \DateTimeZone($element['value']['#date_timezone'])); diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigEntityReferenceItemBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigEntityReferenceItemBase.php index 6b3c137..7e5399f 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigEntityReferenceItemBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigEntityReferenceItemBase.php @@ -172,4 +172,11 @@ protected function getLegacyCallback($hook) { } } + /** + * {@inheritdoc} + */ + public function prepareCache() { + return $this->getValue(); + } + } diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemBase.php index eb16d87..456d42d 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemBase.php @@ -28,4 +28,12 @@ public function instanceSettingsForm(array $form, array &$form_state) { return array(); } + /** + * {@inheritdoc} + */ + public function prepareCache() { + return $this->getValue(); + } + + } diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemInterface.php index 88de2ac..161deeb 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemInterface.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemInterface.php @@ -89,4 +89,20 @@ public function settingsForm(array $form, array &$form_state, $has_data); */ public function instanceSettingsForm(array $form, array &$form_state); + /** + * Massages loaded field values before they enter the field cache. + * + * You should never load fieldable entities within this method, since this is + * likely to cause infinite recursions. Use the prepareView() method instead. + * + * Also note that the method is not called on field values displayed during + * entity preview. If the method adds elements that might be needed during + * display, you might want to also use prepareView() to add those elements in + * case they are not present. + * + * @return array + * Array of field item values that should be cached. + */ + public function prepareCache(); + } diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyConfigFieldItem.php b/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyConfigFieldItem.php index e46622f..39070f9 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyConfigFieldItem.php +++ b/core/modules/field/lib/Drupal/field/Plugin/field/field_type/LegacyConfigFieldItem.php @@ -26,7 +26,7 @@ * @todo Remove once all core field types have been converted (see * http://drupal.org/node/2014671). */ -abstract class LegacyConfigFieldItem extends ConfigFieldItemBase implements PrepareCacheInterface { +abstract class LegacyConfigFieldItem extends ConfigFieldItemBase { /** * {@inheritdoc} @@ -105,7 +105,9 @@ public function prepareCache() { ); call_user_func_array($callback, $args); $this->setValue($items[$entity_id][0]); + return $items[$entity_id][0]; } + return $this->getValue(); } /** diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php index 2de2456..7bccfd8 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php @@ -90,8 +90,6 @@ function testFieldAttachSaveLoad() { for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { // The field value loaded matches the one inserted or updated. $this->assertEqual($entity->{$this->field_name}[$delta]->value, $values[$revision_id][$delta]['value'], format_string('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$delta]->additional_key, 'additional_value', format_string('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); } } } @@ -100,7 +98,7 @@ function testFieldAttachSaveLoad() { * Test the 'multiple' load feature. */ function testFieldAttachLoadMultiple() { - $entity_type = 'entity_test'; + $entity_type = 'entity_test_rev'; // Define 2 bundles. $bundles = array( diff --git a/core/modules/file/lib/Drupal/file/Plugin/field/field_type/FileItem.php b/core/modules/file/lib/Drupal/file/Plugin/field/field_type/FileItem.php index 2ee23f4..02893e0 100644 --- a/core/modules/file/lib/Drupal/file/Plugin/field/field_type/FileItem.php +++ b/core/modules/file/lib/Drupal/file/Plugin/field/field_type/FileItem.php @@ -344,4 +344,11 @@ public function getUploadValidators() { return $validators; } + /** + * {@inheritdoc} + */ + public function prepareCache() { + return $this->getValue(); + } + } 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 54630fd..625d4e8 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 @@ -13,7 +13,7 @@ /** * Base class for 'text' configurable field types. */ -abstract class TextItemBase extends ConfigFieldItemBase implements PrepareCacheInterface { +abstract class TextItemBase extends ConfigFieldItemBase { /** * Definitions of the contained properties. @@ -71,6 +71,7 @@ public function isEmpty() { * {@inheritdoc} */ public function prepareCache() { + $values = parent::prepareCache(); // 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 @@ -79,10 +80,11 @@ public function prepareCache() { if (!$text_processing || filter_format_allowcache($this->get('format')->getValue())) { foreach ($this->getPropertyDefinitions() as $property => $definition) { if (isset($definition['class']) && ($definition['class'] == '\Drupal\text\TextProcessed')) { - $this->get($property)->getValue(); + $values[$property] = $this->get($property)->getValue(); } } } + return $values; } /** diff --git a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php index ef9a7df..f3808c9 100644 --- a/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php +++ b/core/modules/text/lib/Drupal/text/Tests/TextWithSummaryItemTest.php @@ -119,8 +119,30 @@ function testProcessedCache() { $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. + // Check that the processed values are correctly computed. + $this->assertEqual($entity->summary_field->processed, $value); + $this->assertEqual($entity->summary_field->summary_processed, $summary); + + // Load the entity and check that the field cache contains the expected + // data. + $entity = entity_load($entity_type, $entity->id()); + $cache = cache('field')->get("field:$entity_type:" . $entity->id()); + $this->assertEqual($cache->data, array( + Language::LANGCODE_DEFAULT => array( + 'summary_field' => array( + 0 => array( + 'value' => $value, + 'summary' => $summary, + 'format' => 'plain_text', + 'processed' => $value, + 'summary_processed' => $summary, + ), + ), + ), + )); + + // Inject fake processed values into the cache to make sure that these are + // used as-is and not re-calculated when the entity is loaded. $data = array( Language::LANGCODE_DEFAULT => array( 'summary_field' => array( @@ -135,8 +157,7 @@ function testProcessedCache() { ), ); cache('field')->set("field:$entity_type:" . $entity->id(), $data); - - $entity = entity_load($entity_type, $entity->id()); + $entity = entity_load($entity_type, $entity->id(), TRUE); $this->assertEqual($entity->summary_field->processed, 'Cached processed value'); $this->assertEqual($entity->summary_field->summary_processed, 'Cached summary processed value'); diff --git a/core/modules/text/lib/Drupal/text/TextProcessed.php b/core/modules/text/lib/Drupal/text/TextProcessed.php index f3c65e2..bbf0fd0 100644 --- a/core/modules/text/lib/Drupal/text/TextProcessed.php +++ b/core/modules/text/lib/Drupal/text/TextProcessed.php @@ -47,7 +47,12 @@ public function getValue($langcode = NULL) { $item = $this->getParent(); $text = $item->{($this->definition['settings']['text source'])}; - if ($item->getFieldDefinition()->getFieldSetting('text_processing')) { + + // Avoid running check_markup() or check_plain() on empty strings. + if (!isset($text) || $text === '') { + $this->processed = ''; + } + elseif ($item->getFieldDefinition()->getFieldSetting('text_processing')) { $this->processed = check_markup($text, $item->format, $item->getLangcode()); } else {