diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index b23cd9d..727b155 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -203,11 +203,18 @@ class EntityType implements EntityTypeInterface { protected $field_ui_base_route; /** + * The list cache contexts for this entity type. + * + * @var string[] + */ + protected $list_cache_contexts = []; + + /** * The list cache tags for this entity type. * - * @var array + * @var string[] */ - protected $list_cache_tags = array(); + protected $list_cache_tags = []; /** * Constructs a new EntityType. @@ -695,6 +702,13 @@ public function getGroupLabel() { /** * {@inheritdoc} */ + public function getListCacheContexts() { + return $this->list_cache_contexts; + } + + /** + * {@inheritdoc} + */ public function getListCacheTags() { return $this->list_cache_tags; } diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index 49302d3..c33c6ec 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -656,6 +656,17 @@ public function getUriCallback(); public function setUriCallback($callback); /** + * The list cache contexts associated with this entity type. + * + * Enables code listing entities of this type to ensure that rendered listings + * are varied as necessary, typically to ensure users of role A see other + * entities listed as users of role B. + * + * @return string[] + */ + public function getListCacheContexts(); + + /** * The list cache tags associated with this entity type. * * Enables code listing entities of this type to ensure that newly created diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index 13ad744..85e1bb3 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -45,6 +45,7 @@ * revision_table = "node_revision", * revision_data_table = "node_field_revision", * translatable = TRUE, + * list_cache_contexts = { "node_view_grants" }, * entity_keys = { * "id" = "nid", * "revision" = "vid", diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php index a27b39c..aa4faff 100644 --- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php @@ -378,6 +378,8 @@ public function testReferencedEntity() { $this->pass("Test empty listing.", 'Debug'); + // @todo verify ::getListCacheTags() are present, blocked on + // https://www.drupal.org/node/2445761 // Prime the page cache for the empty listing. $this->verifyPageCache($empty_entity_listing_url, 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. @@ -385,6 +387,8 @@ public function testReferencedEntity() { $this->pass("Test listing containing referenced entity.", 'Debug'); + // @todo verify ::getListCacheTags() are present, blocked on + // https://www.drupal.org/node/2445761 // Prime the page cache for the listing containing the referenced entity. $this->verifyPageCache($nonempty_entity_listing_url, 'MISS'); // Verify a cache hit, but also the presence of the correct cache tags. diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php index bf5fa29..1d6e4e8 100644 --- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php +++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php @@ -162,6 +162,7 @@ public function listEntitiesAlphabetically($entity_type_id) { '#items' => $labels, '#title' => $entity_type_id . ' entities', '#cache' => [ + 'contexts' => $entity_type_definition->getListCacheContexts(), 'tags' => $cache_tags, ], ]; @@ -182,11 +183,13 @@ public function listEntitiesAlphabetically($entity_type_id) { * A renderable array. */ public function listEntitiesEmpty($entity_type_id) { + $entity_type_definition = $this->entityManager()->getDefinition($entity_type_id); return [ '#theme' => 'item_list', '#items' => [], '#cache' => [ - 'tags' => $this->entityManager()->getDefinition($entity_type_id)->getListCacheTags(), + 'contexts' => $entity_type_definition->getListCacheContexts(), + 'tags' => $entity_type_definition->getListCacheTags(), ], ]; } diff --git a/core/modules/views/config/schema/views.data_types.schema.yml b/core/modules/views/config/schema/views.data_types.schema.yml index 50a075e..58fc688 100644 --- a/core/modules/views/config/schema/views.data_types.schema.yml +++ b/core/modules/views/config/schema/views.data_types.schema.yml @@ -250,16 +250,6 @@ views_display: rendering_language: type: string label: 'Entity language' - cache_metadata: - type: mapping - label: 'Cache metadata' - mapping: - cacheable: - type: boolean - label: 'Cacheable' - contexts: - type: sequence - label: 'Cache contexts' exposed_block: type: boolean label: 'Put the exposed form in a block' diff --git a/core/modules/views/config/schema/views.schema.yml b/core/modules/views/config/schema/views.schema.yml index 6243b46..939db66 100644 --- a/core/modules/views/config/schema/views.schema.yml +++ b/core/modules/views/config/schema/views.schema.yml @@ -114,6 +114,18 @@ views.view.*: label: 'Position' display_options: type: views.display.[%parent.display_plugin] + cache_metadata: + type: mapping + label: 'Cache metadata' + mapping: + cacheable: + type: boolean + label: 'Cacheable' + contexts: + type: sequence + label: 'Cache contexts' + sequence: + - type: string views_block: type: block_settings diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php index ecd541e..2a3b7a5 100644 --- a/core/modules/views/src/Entity/View.php +++ b/core/modules/views/src/Entity/View.php @@ -310,7 +310,8 @@ protected function addCacheMetadata() { $current_display = $executable->current_display; $displays = $this->get('display'); - foreach ($displays as $display_id => $display) { + foreach (array_keys($displays) as $display_id) { + $display =& $this->getDisplay($display_id); $executable->setDisplay($display_id); list($display['cache_metadata']['cacheable'], $display['cache_metadata']['contexts']) = $executable->getDisplay()->calculateCacheMetadata(); diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php index 0efd164..c36d5ef 100644 --- a/core/modules/views/src/EntityViewsData.php +++ b/core/modules/views/src/EntityViewsData.php @@ -132,6 +132,7 @@ public function getViewsData() { $data[$base_table]['table']['base'] = [ 'field' => $base_field, 'title' => $this->entityType->getLabel(), + 'cache_contexts' => $this->entityType->getListCacheContexts(), ]; if ($label_key = $this->entityType->getKey('label')) { diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 835c106..194c817 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -117,6 +117,17 @@ protected static $unpackOptions = array(); /** + * The display information coming directly from the view entity. + * + * @see \Drupal\views\Entity\View::getDisplay() + * + * @todo \Drupal\views\Entity\View::duplicateDisplayAsType directly access it. + * + * @var array + */ + public $display; + + /** * Constructs a new DisplayPluginBase object. * * Because DisplayPluginBase::initDisplay() takes the display configuration by @@ -2281,6 +2292,9 @@ public function buildRenderable(array $args = []) { '#arguments' => $args, '#embed' => FALSE, '#view' => $this->view, + '#cache' => [ + 'contexts' => isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : [], + ], ]; } diff --git a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php index 1c254dc..14a1703 100644 --- a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php +++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php @@ -8,6 +8,7 @@ namespace Drupal\views\Plugin\views\query; use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Plugin\CacheablePluginInterface; use Drupal\views\Plugin\views\PluginBase; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\ViewExecutable; @@ -35,7 +36,7 @@ /** * Base plugin class for Views queries. */ -abstract class QueryPluginBase extends PluginBase { +abstract class QueryPluginBase extends PluginBase implements CacheablePluginInterface { /** * A pager plugin that should be provided by the display. @@ -312,6 +313,30 @@ public function getEntityTableInfo() { return $entity_tables; } + /** + * {@inheritdoc} + */ + public function isCacheable() { + // This plugin can't really determine that. + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $contexts = []; + if (($views_data = Views::viewsData()->get($this->view->storage->get('base_table'))) && !empty($views_data['table']['entity type'])) { + $entity_type_id = $views_data['table']['entity type']; + $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id); + $contexts = $entity_type->getListCacheContexts(); + foreach ($contexts as $key => $context) { + $contexts[$key] = 'cache.context.' . $context; + } + } + return $contexts; + } + } /** diff --git a/core/modules/views/src/Tests/RenderCacheIntegrationTest.php b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php new file mode 100644 index 0000000..e9e17bd --- /dev/null +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php @@ -0,0 +1,54 @@ +getDisplay('default'); + $display['cache_metadata']['contexts'] = ['beatles_tag']; + $view->save(); + + + $view = Views::getView('test_view'); + $build = $view->buildRenderable(); + $this->assertEqual(['beatles_tag'], $build['#cache']['contexts']); + } + + /** + * Ensures that saving a view calculates the cache contexts. + */ + public function testViewAddCacheMetadata() { + $view = View::load('test_display'); + $view->save(); + + $this->assertEqual(['cache.context.node_view_grants', 'cache.context.language'], $view->getDisplay('default')['cache_metadata']['contexts']); + } + +} diff --git a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php index c1aac0f..21e5971 100644 --- a/core/modules/views/tests/src/Unit/EntityViewsDataTest.php +++ b/core/modules/views/tests/src/Unit/EntityViewsDataTest.php @@ -89,6 +89,7 @@ protected function setUp() { 'label' => 'Entity test', 'entity_keys' => ['id' => 'id', 'langcode' => 'langcode'], 'provider' => 'entity_test', + 'list_cache_contexts' => ['entity_test_list_cache_context'], ]); $this->translationManager = $this->getStringTranslationStub(); @@ -164,6 +165,7 @@ public function testBaseTables() { $this->assertEquals('entity_test', $data['entity_test']['table']['provider']); $this->assertEquals('id', $data['entity_test']['table']['base']['field']); + $this->assertEquals(['entity_test_list_cache_context'], $data['entity_test']['table']['base']['cache_contexts']); $this->assertEquals('Entity test', $data['entity_test']['table']['base']['title']); $this->assertFalse(isset($data['entity_test']['table']['defaults'])); @@ -173,7 +175,6 @@ public function testBaseTables() { $this->assertFalse(isset($data['revision_data_table'])); } - /** * Tests data_table support. */