diff --git a/core/includes/common.inc b/core/includes/common.inc index 654d849..4e51fc0 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -5469,7 +5469,7 @@ function drupal_render_cache_set(&$markup, $elements) { } $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache'; $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CacheBackendInterface::CACHE_PERMANENT; - $tags = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array(); + $tags = drupal_render_collect_cache_tags($elements, TRUE); cache($bin)->set($cid, $data, $expire, $tags); } @@ -5517,6 +5517,57 @@ function drupal_render_collect_attached($elements, $return = FALSE) { } /** + * Collects cache tags for an element and its children into a single array. + * + * When caching elements, it is necessary to collect all cache tags into a + * single array, from both the element itself and all child elements. This + * allows items to be invalidated based on all tags attached to the content + * they're constituted from. + * + * @param array $elements + * The element to collect cache tags from. + * @param bool $return + * Whether to return the attached elements and reset the internal static. + * + * @return array + * The cache tags array for this element and its descendants. + */ +function drupal_render_collect_cache_tags($elements, $return = FALSE) { + $tags = &drupal_static(__FUNCTION__, array()); + + // Collect all cache tags for this element. + if (isset($elements['#cache']['tags'])) { + foreach ($elements['#cache']['tags'] as $namespace => $values) { + if (is_array($values)) { + foreach ($values as $value) { + if (!isset($tags[$namespace][$value])) { + $tags[$namespace][$value] = $value; + } + } + } + else { + if (!isset($tags[$namespace])) { + $tags[$namespace] = $values; + } + } + } + } + if ($children = element_children($elements)) { + foreach ($children as $child) { + drupal_render_collect_cache_tags($elements[$child]); + } + } + + // If this was the first call to the function, return all attached elements + // and reset the static cache. + if ($return) { + $return = $tags; + $tags = array(); + return $return; + } +} + +/** * Prepares an element for caching based on a query. * * This smart caching strategy saves Drupal from querying and rendering to HTML diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 3f2133b..7d0fe20 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -46,6 +46,18 @@ function entity_info_cache_clear() { } /** + * Clears the entity render cache for all entity types. + */ +function entity_render_cache_clear() { + $entity_manager = drupal_container()->get('plugin.manager.entity'); + foreach ($entity_manager->getDefinitions() as $entity_type => $info) { + if (isset($info['render_controller_class'])) { + $entity_manager->getRenderController($entity_type)->resetCache(); + } + } +} + +/** * Returns the entity bundle info. * * @param string|null $entity_type @@ -126,6 +138,7 @@ function entity_get_view_modes($entity_type = NULL) { foreach ($entity_info as $view_mode => $view_mode_info) { $view_modes[$type][$view_mode] += array( 'custom_settings' => FALSE, + 'cache' => TRUE, ); } } @@ -582,14 +595,19 @@ function entity_render_controller($entity_type) { * @param string $langcode * (optional) For which language the entity should be rendered, defaults to * the current content language. + * @param bool $reset + * (optional) Whether to reset the render cache for the requested entity. + * Defaults to FALSE. * * @return array * A render array for the entity. */ -function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL) { - return drupal_container()->get('plugin.manager.entity') - ->getRenderController($entity->entityType()) - ->view($entity, $view_mode, $langcode); +function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL, $reset = FALSE) { + $render_controller = drupal_container()->get('plugin.manager.entity')->getRenderController($entity->entityType()); + if ($reset) { + $render_controller->resetCache(array($entity)); + } + return $render_controller->view($entity, $view_mode, $langcode); } /** @@ -602,15 +620,20 @@ function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL) { * @param string $langcode * (optional) For which language the entity should be rendered, defaults to * the current content language. + * @param bool $reset + * (optional) Whether to reset the render cache for the requested entities. + * Defaults to FALSE. * * @return array * A render array for the entities, indexed by the same keys as the * entities array passed in $entities. */ -function entity_view_multiple(array $entities, $view_mode, $langcode = NULL) { - return drupal_container()->get('plugin.manager.entity') - ->getRenderController(reset($entities)->entityType()) - ->viewMultiple($entities, $view_mode, $langcode); +function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $reset = FALSE) { + $render_controller = drupal_container()->get('plugin.manager.entity')->getRenderController(reset($entities)->entityType()); + if ($reset) { + $render_controller->resetCache($entities); + } + return $render_controller->viewMultiple($entities, $view_mode, $langcode); } /** diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php index 50b604c..a188e6d 100644 --- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php +++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php @@ -134,6 +134,14 @@ class EntityType extends Plugin { public $static_cache = TRUE; /** + * Boolean indicating whether the rendered output of entities should be + * cached. + * + * @var bool (optional) + */ + public $render_cache = TRUE; + + /** * Boolean indicating whether entities of this type have mutlilingual support. * * @var bool (optional) diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index b1d8ec3..c423a51 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -537,6 +537,7 @@ public function save(EntityInterface $entity) { $this->saveRevision($entity); } $this->resetCache(array($entity->id())); + $this->postSave($entity, TRUE); $this->invokeHook('update', $entity); } diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 1771f50..303c0a0 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -312,6 +312,7 @@ public function save(EntityInterface $entity) { $this->savePropertyData($entity); } $this->resetCache(array($entity->id())); + $this->postSave($entity, TRUE); $this->invokeHook('update', $entity); } diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 98d4a3d..7317ed8 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -263,7 +263,13 @@ public function submit(array $form, array &$form_state) { * A reference to a keyed array containing the current state of the form. */ public function save(array $form, array &$form_state) { - // @todo Perform common save operations. + // Clear the render cache. + try { + \Drupal::service('plugin.manager.entity')->getRenderController($this->entity->entityType())->resetCache(array($this->entity)); + } + catch (\Exception $e) { + // Nothing to do if the entity type doesn't have a render controller. + } } /** diff --git a/core/lib/Drupal/Core/Entity/EntityRenderController.php b/core/lib/Drupal/Core/Entity/EntityRenderController.php index 53457aa..ee5aa65 100644 --- a/core/lib/Drupal/Core/Entity/EntityRenderController.php +++ b/core/lib/Drupal/Core/Entity/EntityRenderController.php @@ -20,8 +20,37 @@ class EntityRenderController implements EntityRenderControllerInterface { */ protected $entityType; + /** + * The entity info array. + * + * @var array + * + * @see entity_get_info() + */ + protected $entityInfo; + + /** + * An array of view mode info for the type of entities for which this + * controller is instantiated. + * + * @var array + */ + protected $viewModesInfo; + + /** + * The cache bin used to store the render cache. + * + * @todo Defaults to 'cache' for now, until http://drupal.org/node/1194136 is + * fixed. + * + * @var string + */ + protected $cacheBin = 'cache'; + public function __construct($entity_type) { $this->entityType = $entity_type; + $this->entityInfo = entity_get_info($entity_type); + $this->viewModesInfo = entity_get_view_modes($entity_type); } /** @@ -60,6 +89,21 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco '#view_mode' => $view_mode, '#langcode' => $langcode, ); + + // Cache the rendered output if permitted by the view mode settings. + $view_mode_is_cacheable = !isset($this->viewModesInfo[$view_mode]) || (isset($this->viewModesInfo[$view_mode]) && $this->viewModesInfo[$view_mode]['cache']); + if (!isset($entity->in_preview) && $this->entityInfo['render_cache'] && $view_mode_is_cacheable) { + $return['#cache'] = array( + 'keys' => array('entity_view', $this->entityType ,$entity->id(), $view_mode), + 'granularity' => DRUPAL_CACHE_PER_ROLE, + 'bin' => $this->cacheBin, + 'tags' => array( + $this->entityType . '_view' => TRUE, + $this->entityType => array($entity->id()), + ), + ); + } + return $return; } @@ -162,4 +206,35 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la return $build; } + + /** + * Implements \Drupal\Core\Entity\EntityRenderControllerInterface::resetCache(). + */ + public function resetCache(array $entities = NULL) { + if (isset($entities)) { + $tags = array(); + foreach ($entities as $entity) { + $tags[$this->entityType][$entity->id()] = $entity->id(); + + // @todo Remove when all entities are converted to EntityNG. + if ($entity->getPropertyDefinitions() === NULL) { + continue; + } + + // Add all the referenced entity types and IDs to the tags that will be + // cleared. + foreach ($entity->getPropertyDefinitions() as $name => $definition) { + if ($definition['type'] == 'entity_reference_field' && $field_values = $entity->get($name)->getValue()) { + foreach ($field_values as $value) { + $tags[$definition['settings']['target_type']][$value['target_id']] = $value['target_id']; + } + } + } + } + \Drupal::cache($this->cacheBin)->deleteTags($tags); + } + else { + \Drupal::cache($this->cacheBin)->deleteTags(array($this->entityType . '_view' => TRUE)); + } + } } diff --git a/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php index 03ba014..88e1461 100644 --- a/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php @@ -75,4 +75,12 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N * be available for loading. */ public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL); + + /** + * Resets the entity render cache. + * + * @param array|null $entities + * (optional) If specified, the cache is reset for the given entities only. + */ + public function resetCache(array $entities = NULL); } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php index 459a99b..50adbb9 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php @@ -111,6 +111,8 @@ public function validate(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $feed = $this->entity; $insert = (bool) $feed->id(); if (!empty($form_state['values']['category'])) { diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php index 1a09dbf..b548634 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php @@ -159,6 +159,8 @@ public function submit(array $form, array &$form_state) { * Overrides \Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $block = $this->entity; $insert = empty($block->id->value); $block->save(); diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php index 6e5de33..bb9159a 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php @@ -87,6 +87,8 @@ public function form(array $form, array &$form_state) { * Overrides \Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $block_type = $this->entity; $status = $block_type->save(); diff --git a/core/modules/block/lib/Drupal/block/BlockRenderController.php b/core/modules/block/lib/Drupal/block/BlockRenderController.php index e1c560e..4715f66 100644 --- a/core/modules/block/lib/Drupal/block/BlockRenderController.php +++ b/core/modules/block/lib/Drupal/block/BlockRenderController.php @@ -87,4 +87,10 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la return $build; } + /** + * Implements \Drupal\Core\Entity\EntityRenderControllerInterface::resetCache(). + */ + public function resetCache(array $entities = NULL) { + // @todo Move block render caching logic to this controller? + } } diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc index 488c2f9..0025e8e 100644 --- a/core/modules/comment/comment.admin.inc +++ b/core/modules/comment/comment.admin.inc @@ -203,6 +203,7 @@ function comment_admin_overview_submit($form, &$form_state) { } drupal_set_message(t('The update has been performed.')); $form_state['redirect'] = 'admin/content/comment'; + Drupal::service('plugin.manager.entity')->getRenderController('comment')->resetCache(array($comment)); cache_invalidate_tags(array('content' => TRUE)); } @@ -250,8 +251,15 @@ function comment_multiple_delete_confirm($form, &$form_state) { */ function comment_multiple_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { - comment_delete_multiple(array_keys($form_state['values']['comments'])); + $entity_manager = \Drupal::service('plugin.manager.entity'); + + $controller = $entity_manager->getStorageController('comment'); + $entities = $controller->load(array_keys($form_state['values']['comments'])); + $controller->delete($entities); + + $entity_manager->getRenderController('comment')->resetCache($entities); cache_invalidate_tags(array('content' => TRUE)); + $count = count($form_state['values']['comments']); watchdog('content', 'Deleted @count comments.', array('@count' => $count)); drupal_set_message(format_plural($count, 'Deleted 1 comment.', 'Deleted @count comments.')); @@ -307,6 +315,7 @@ function comment_confirm_delete_submit($form, &$form_state) { drupal_set_message(t('The comment and all its replies have been deleted.')); watchdog('content', 'Deleted comment @cid and its replies.', array('@cid' => $comment->id())); // Clear the cache so an anonymous user sees that his comment was deleted. + Drupal::service('plugin.manager.entity')->getRenderController('comment')->resetCache(array($comment)); cache_invalidate_tags(array('content' => TRUE)); $form_state['redirect'] = "node/{$comment->nid->target_id}"; diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index c39fcff..b26161c 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -1947,6 +1947,7 @@ function comment_unpublish_by_keyword_action_submit($form, $form_state) { */ function comment_save_action(Comment $comment) { comment_save($comment); + Drupal::service('plugin.manager.entity')->getRenderController('comment')->resetCache(array($comment)); cache_invalidate_tags(array('content' => TRUE)); watchdog('action', 'Saved comment %title', array('%title' => $comment->subject->value)); } diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc index 5e1b190..3e84048 100644 --- a/core/modules/comment/comment.pages.inc +++ b/core/modules/comment/comment.pages.inc @@ -80,6 +80,7 @@ function comment_reply(EntityInterface $node, $pid = NULL) { // This is the case where the comment is in response to a node. Display the node. elseif (user_access('access content')) { $build['comment_node'] = node_view($node); + unset($build['comment_node']['#cache']); } // Should we show the reply box? @@ -118,6 +119,7 @@ function comment_approve(Comment $comment) { $comment->status->value = COMMENT_PUBLISHED; $comment->save(); + Drupal::service('plugin.manager.entity')->getRenderController('comment')->resetCache(array($comment)); drupal_set_message(t('Comment approved.')); drupal_goto('node/' . $comment->nid->target_id); diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php index 179410c..a2fecc6 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php @@ -309,6 +309,8 @@ public function preview(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $node = node_load($form_state['values']['nid']); $comment = $this->entity; diff --git a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php index dab8dc4..0e1ce5e 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php @@ -79,9 +79,10 @@ protected function alterBuild(array &$build, EntityInterface $comment, EntityDis $is_threaded = isset($comment->divs) && variable_get('comment_default_mode_' . $comment->bundle(), COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED; - // Add 'new' anchor if needed. + // Add 'new' anchor and disable render cache if needed. if (!empty($comment->first_new)) { $prefix .= "\n"; + unset($build['#cache']); } // Add indentation div or close open divs as needed. diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php index c023c56..369a09e 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php @@ -267,6 +267,7 @@ function setCommentsPerPage($number) { */ function setCommentSettings($name, $value, $message) { variable_set($name . '_article', $value); + \Drupal::service('plugin.manager.entity')->getRenderController('comment')->resetCache(); // Display status message. $this->pass($message); } diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php index efba401..1c3894f 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php @@ -64,6 +64,8 @@ public function form(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $entity = $this->entity; $status = $entity->save(); diff --git a/core/modules/contact/lib/Drupal/contact/CategoryFormController.php b/core/modules/contact/lib/Drupal/contact/CategoryFormController.php index 8febed8..ee95b57 100644 --- a/core/modules/contact/lib/Drupal/contact/CategoryFormController.php +++ b/core/modules/contact/lib/Drupal/contact/CategoryFormController.php @@ -94,6 +94,8 @@ public function validate(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $category = $this->entity; $status = $category->save(); diff --git a/core/modules/contact/lib/Drupal/contact/MessageFormController.php b/core/modules/contact/lib/Drupal/contact/MessageFormController.php index 6f73d96..a754711 100644 --- a/core/modules/contact/lib/Drupal/contact/MessageFormController.php +++ b/core/modules/contact/lib/Drupal/contact/MessageFormController.php @@ -137,6 +137,8 @@ public function preview(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + global $user; $language_interface = language(LANGUAGE_TYPE_INTERFACE); diff --git a/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php b/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php index 461a240..8033e09 100644 --- a/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php +++ b/core/modules/entity/lib/Drupal/entity/Plugin/Core/Entity/EntityDisplay.php @@ -120,7 +120,17 @@ public function save() { if (empty($this->id)) { $this->id = $this->id(); } - return parent::save(); + $return = parent::save(); + + // Reset the render cache for the target entity type. + try { + \Drupal::service('plugin.manager.entity')->getRenderController($this->targetEntityType)->resetCache(); + } + catch (\Exception $e) { + // Nothing to do if the entity type doesn't have a render controller. + } + + return $return; } /** diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php index a46b465..6646714 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php @@ -106,6 +106,14 @@ public function view(EntityInterface $entity, $langcode, array $items) { '#formatter' => $this->getPluginId(), ); + // Gather cache tags from reference fields. + foreach ($items as $item) { + if (isset($item['entity'])) { + $info['#cache']['tags'][$item['entity']->entityType()][] = $item['entity']->id(); + $info['#cache']['tags'][$item['entity']->entityType() . '_view'] = TRUE; + } + } + $addition[$field['field_name']] = array_merge($info, $elements); } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityFormController.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityFormController.php index d5a4508..7d48c0d 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityFormController.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityFormController.php @@ -37,6 +37,8 @@ public function form(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $entity = $this->entity; $is_new = $entity->isNew(); $entity->save(); diff --git a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php index 836cc88..9a354ad 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php @@ -496,6 +496,9 @@ public function submitForm(array &$form, array &$form_state) { field_bundle_settings($this->entity_type, $this->bundle, $bundle_settings); } + // Clear the render cache for this entity type. + \Drupal::service('plugin.manager.entity')->getRenderController($this->entity_type)->resetCache(); + drupal_set_message(t('Your settings have been saved.')); } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php index 5342547..db1dce9 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php @@ -229,6 +229,7 @@ function testImageFieldDefaultImage() { $this->drupalPost("admin/structure/types/manage/article/fields/node.article.$field_name/field-settings", $edit, t('Save field settings')); // Clear field info cache so the new default image is detected. field_info_cache_clear(); + \Drupal::service('plugin.manager.entity')->getRenderController('node')->resetCache(array($node)); $field = field_info_field($field_name); $image = file_load($field['settings']['default_image'][0]); $this->assertTrue($image->status == FILE_STATUS_PERMANENT, 'The default image status is permanent.'); diff --git a/core/modules/menu/lib/Drupal/menu/MenuFormController.php b/core/modules/menu/lib/Drupal/menu/MenuFormController.php index 4eeab27..7aeac04 100644 --- a/core/modules/menu/lib/Drupal/menu/MenuFormController.php +++ b/core/modules/menu/lib/Drupal/menu/MenuFormController.php @@ -83,6 +83,8 @@ protected function actions(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $menu = $this->entity; $system_menus = menu_list_system_menus(); diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php index 68b0bcb..3ca3eec 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php @@ -202,8 +202,9 @@ public function submit(array $form, array &$form_state) { * Overrides EntityFormController::save(). */ public function save(array $form, array &$form_state) { - $menu_link = $this->entity; + parent::save($form, $form_state); + $menu_link = $this->entity; $saved = $menu_link->save(); if ($saved) { diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index 5145a57..a6d6042 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -427,6 +427,8 @@ public function unpublish(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $node = $this->entity; $insert = empty($node->nid); $node->save(); diff --git a/core/modules/node/lib/Drupal/node/NodeRenderController.php b/core/modules/node/lib/Drupal/node/NodeRenderController.php index 6a6608a..5973fbd 100644 --- a/core/modules/node/lib/Drupal/node/NodeRenderController.php +++ b/core/modules/node/lib/Drupal/node/NodeRenderController.php @@ -91,6 +91,10 @@ protected function alterBuild(array &$build, EntityInterface $entity, EntityDisp if (!empty($entity->nid)) { $build['#contextual_links']['node'] = array('node', array($entity->nid)); } + + // The node 'submitted' info is not rendered in a standard way (renderable + // array) so we have to add a cache tag manually. + $build['#cache']['tags']['user'][] = $entity->uid; } } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php index 46244b8..8209071 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php @@ -139,6 +139,7 @@ function testRevisions() { $new_node_revision->setNewRevision(); $new_node_revision->isDefaultRevision = FALSE; node_save($new_node_revision); + \Drupal::service('plugin.manager.entity')->getRenderController('node')->resetCache(array($node)); $this->drupalGet("node/$node->nid"); $this->assertNoText($new_body, 'Revision body text is not present on default version of node.'); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index de5b8d5..a83bab9 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1071,16 +1071,18 @@ function node_revision_delete($revision_id) { * @see node_menu() */ function node_show(EntityInterface $node, $message = FALSE) { + // For markup consistency with other pages, use node_view_multiple() rather than node_view(). + $nodes = array('nodes' => node_view_multiple(array($node->id() => $node), 'full')); + if ($message) { drupal_set_title(t('Revision of %title from %date', array('%title' => $node->label(), '%date' => format_date($node->revision_timestamp))), PASS_THROUGH); + // Don't use the render cache when a revision is displayed. + unset($nodes['nodes'][$node->id()]['#cache']); } - // For markup consistency with other pages, use node_view_multiple() rather than node_view(). - $nodes = array('nodes' => node_view_multiple(array($node->nid => $node), 'full')); - // Update the history table, stating that this user viewed this node. if (module_exists('history')) { - history_write($node->nid); + history_write($node->id()); } return $nodes; @@ -2236,7 +2238,7 @@ function _node_index_node(EntityInterface $node) { foreach ($languages as $language) { // Render the node. - $build = node_view($node, 'search_index', $language->langcode); + $build = entity_view($node, 'search_index', $language->langcode, TRUE); unset($build['#theme']); $node->rendered = drupal_render($build); diff --git a/core/modules/picture/lib/Drupal/picture/PictureMappingFormController.php b/core/modules/picture/lib/Drupal/picture/PictureMappingFormController.php index f100d3a..8b04cbf 100644 --- a/core/modules/picture/lib/Drupal/picture/PictureMappingFormController.php +++ b/core/modules/picture/lib/Drupal/picture/PictureMappingFormController.php @@ -126,6 +126,8 @@ public function validate(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $picture_mapping = $this->entity; $picture_mapping->save(); diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php index dbdba31..23d930f 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/CommentAttributesTest.php @@ -285,6 +285,7 @@ function saveComment($nid, $uid, $contact = NULL, $pid = 0) { $comment = entity_create('comment', $values); $comment->save(); + \Drupal::service('plugin.manager.entity')->getRenderController('comment')->resetCache(array($comment)); return $comment; } } diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php index 1e25679..0eed29e 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutFormController.php @@ -73,6 +73,8 @@ public function validate(array $form, array &$form_state) { * Overrides \Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $entity = $this->entity; $is_new = !$entity->getOriginalID(); $entity->save(); diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 66842a5..4609517 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -73,6 +73,17 @@ function statistics_node_view(EntityInterface $node, EntityDisplay $display, $vi } /** + * Implements hook_node_view_alter(). + */ +function statistics_node_view_alter(&$build, EntityInterface $node, EntityDisplay $display) { + // If statistics were added to the node render array, we can't use the render + // cache. + if (isset($build['links']['statistics'])) { + unset($build['#cache']); + } +} + +/** * Implements hook_menu(). */ function statistics_menu() { diff --git a/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php index c867581..a469c75 100644 --- a/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/System/DateTimeTest.php @@ -65,6 +65,7 @@ function testTimeZoneHandling() { // Set time zone to Los Angeles time. $config->set('default', 'America/Los_Angeles')->save(); + \Drupal::service('plugin.manager.entity')->getRenderController('node')->resetCache(array($node1, $node2)); // Confirm date format and time zone. $this->drupalGet("node/$node1->nid"); diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 9d6d4b6..746ba2c 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -2995,6 +2995,7 @@ function system_date_format_localize_reset_form($form, &$form_state, $langcode) function system_date_format_localize_reset_form_submit($form, &$form_state) { config('locale.config.' . $form['langcode']['#value'] . '.system.date') ->delete(); + entity_render_cache_clear(); $form_state['redirect'] = 'admin/config/regional/date-time/locale'; } diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php index acea3a1..f1431df 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php @@ -57,6 +57,8 @@ public function form(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $entity = $this->entity; $is_new = $entity->isNew(); $entity->save(); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php index 2196d39..a072015 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php @@ -153,6 +153,8 @@ public function buildEntity(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $term = $this->entity; $status = taxonomy_term_save($term); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php index 705ff60..59b5412 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php @@ -333,6 +333,7 @@ function testTermInterface() { // Check that it does NOT show a description when description is blank. $term->description = ''; taxonomy_term_save($term); + \Drupal::service('plugin.manager.entity')->getRenderController('taxonomy_term')->resetCache(array($term)); $this->drupalGet('taxonomy/term/' . $term->id()); $this->assertNoPattern('|class="taxonomy-term-description"|', 'Term page did not display the term description when description was blank.'); diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php index a498aa7..9040bf9 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php @@ -164,6 +164,8 @@ public function submit(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $vocabulary = $this->entity; // Prevent leading and trailing spaces in vocabulary names. diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc index 929543d..3494b0b 100644 --- a/core/modules/taxonomy/taxonomy.admin.inc +++ b/core/modules/taxonomy/taxonomy.admin.inc @@ -513,13 +513,15 @@ function taxonomy_term_confirm_delete($form, &$form_state, Term $term) { * @see taxonomy_term_confirm_delete() */ function taxonomy_term_confirm_delete_submit($form, &$form_state) { - taxonomy_term_delete($form_state['values']['tid']); + $entity = taxonomy_term_load($form_state['values']['tid']); + $entity->delete(); taxonomy_check_vocabulary_hierarchy($form_state['taxonomy']['vocabulary'], $form_state['values']); drupal_set_message(t('Deleted term %name.', array('%name' => $form_state['values']['name']))); watchdog('taxonomy', 'Deleted term %name.', array('%name' => $form_state['values']['name']), WATCHDOG_NOTICE); if (!isset($_GET['destination'])) { $form_state['redirect'] = 'admin/structure/taxonomy'; } + Drupal::service('plugin.manager.entity')->getRenderController('taxonomy_term')->resetCache(array($entity)); cache_invalidate_tags(array('content' => TRUE)); return; } diff --git a/core/modules/user/lib/Drupal/user/ProfileFormController.php b/core/modules/user/lib/Drupal/user/ProfileFormController.php index 484a4af..8e72e76 100644 --- a/core/modules/user/lib/Drupal/user/ProfileFormController.php +++ b/core/modules/user/lib/Drupal/user/ProfileFormController.php @@ -41,6 +41,8 @@ public function submit(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::save(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $account = $this->entity; $account->save(); $form_state['values']['uid'] = $account->id(); diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php index ceecd65..3c0bdbd 100644 --- a/core/modules/user/lib/Drupal/user/RegisterFormController.php +++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php @@ -93,6 +93,8 @@ public function submit(array $form, array &$form_state) { * Overrides Drupal\Core\Entity\EntityFormController::submit(). */ public function save(array $form, array &$form_state) { + parent::save($form, $form_state); + $account = $this->entity; $pass = $account->pass; $admin = $form_state['values']['administer_users']; diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 9a1b32c..c2b0baa 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -1981,6 +1981,8 @@ function user_role_grant_permissions($rid, array $permissions = array()) { // Clear the user access cache. drupal_static_reset('user_access'); drupal_static_reset('user_role_permissions'); + // Entity render cache is based on user roles, so we need to clear it as well. + entity_render_cache_clear(); } /** @@ -2004,6 +2006,8 @@ function user_role_revoke_permissions($rid, array $permissions = array()) { // Clear the user access cache. drupal_static_reset('user_access'); drupal_static_reset('user_role_permissions'); + // Entity render cache is based on user roles, so we need to clear it as well. + entity_render_cache_clear(); } /**