diff -u b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php --- b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskDefault.php @@ -8,7 +8,8 @@ namespace Drupal\Core\Menu; use Drupal\Component\Plugin\PluginBase; -use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\HttpFoundation\Request; @@ -16,7 +17,7 @@ /** * Default object used for LocalTaskPlugins. */ -class LocalTaskDefault extends PluginBase implements LocalTaskInterface { +class LocalTaskDefault extends PluginBase implements LocalTaskInterface, CacheableDependencyInterface { use DependencySerializationTrait; @@ -147,12 +148,31 @@ /** * {@inheritdoc} */ - public function getCacheableMetadata() { - $cacheability = new CacheableMetadata(); - if (isset($this->pluginDefinition['cache_tags'])) { - $cacheability->addCacheTags($this->pluginDefinition['cache_tags']); + public function getCacheTags() { + if (!isset($this->pluginDefinition['cacheability_metadata'])) { + return []; } - return $cacheability; + return $this->pluginDefinition['cacheability_metadata']->getCacheTags(); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + if (!isset($this->pluginDefinition['cacheability_metadata'])) { + return []; + } + return $this->pluginDefinition['cacheability_metadata']->getCacheContexts(); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + if (!isset($this->pluginDefinition['cacheability_metadata'])) { + return Cache::PERMANENT; + } + return $this->pluginDefinition['cacheability_metadata']->getCacheMaxAge(); } } reverted: --- b/core/lib/Drupal/Core/Menu/LocalTaskInterface.php +++ a/core/lib/Drupal/Core/Menu/LocalTaskInterface.php @@ -90,11 +90,4 @@ */ public function getActive(); - /** - * Get cacheable metadata. - * - * @return \Drupal\Core\Cache\CacheableMetadata - */ - public function getCacheableMetadata(); - } diff -u b/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php --- b/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -12,6 +12,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Controller\ControllerResolverInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -178,22 +179,6 @@ return call_user_func_array($controller, $arguments); } - - /** - * Gets the cacheable metadata. - * - * @param \Drupal\Core\Menu\LocalTaskInterface $local_task - * A local task plugin instance to get the cacheable metadata for. - * - * @return \Drupal\Core\Cache\CacheableMetadata - */ - protected function getCacheableMetadata(LocalTaskInterface $local_task) { - $controller = array($local_task, 'getCacheableMetadata'); - $request = $this->requestStack->getCurrentRequest(); - $arguments = $this->controllerResolver->getArguments($request, $controller); - return call_user_func_array($controller, $arguments); - } - /** * {@inheritdoc} */ @@ -305,7 +290,7 @@ /** * {@inheritdoc} */ - public function getTasksBuild($current_route_name, CacheableMetadata &$cacheable_metadata) { + public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheable_metadata) { $tree = $this->getLocalTasksForRoute($current_route_name); $build = array(); @@ -350,7 +335,7 @@ '#access' => $access, ]; $cacheable_metadata->addCacheableDependency($access); - $cacheable_metadata = $cacheable_metadata->merge($this->getCacheableMetadata($child)); + $cacheable_metadata->addCacheableDependency($child); } } @@ -362,25 +347,25 @@ */ public function getLocalTasks($route_name, $level = 0) { if (!isset($this->taskData[$route_name])) { - $cacheable_metadata = new CacheableMetadata(); - $cacheable_metadata->addCacheContexts(['route']); + $cacheability_metadata = new CacheableMetadata(); + $cacheability_metadata->addCacheContexts(['route']); // Look for route-based tabs. $this->taskData[$route_name] = [ 'tabs' => [], - 'cacheable_metadata' => $cacheable_metadata, + 'cacheable_metadata' => $cacheability_metadata, ]; if (!$this->requestStack->getCurrentRequest()->attributes->has('exception')) { // Safe to build tasks only when no exceptions raised. $data = []; - $local_tasks = $this->getTasksBuild($route_name, $cacheable_metadata); + $local_tasks = $this->getTasksBuild($route_name, $cacheability_metadata); foreach ($local_tasks as $tab_level => $items) { $data[$tab_level] = empty($data[$tab_level]) ? $items : array_merge($data[$tab_level], $items); } $this->taskData[$route_name]['tabs'] = $data; // Allow modules to alter local tasks. - $this->moduleHandler->alter('menu_local_tasks', $this->taskData[$route_name], $route_name, $cacheable_metadata); - $this->taskData[$route_name]['cacheable_metadata'] = $cacheable_metadata; + $this->moduleHandler->alter('menu_local_tasks', $this->taskData[$route_name], $route_name, $cacheability_metadata); + $this->taskData[$route_name]['cacheable_metadata'] = $cacheability_metadata; } } diff -u b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php --- b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManagerInterface.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Menu; use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; /** * Manages discovery and instantiation of menu local task plugins. @@ -52,7 +52,7 @@ * @return array * A render array as expected by menu-local-tasks.html.twig. */ - public function getTasksBuild($current_route_name, CacheableMetadata &$cacheable_metadata); + public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheable_metadata); /** * Collects the local tasks (tabs) for the current route. diff -u b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php --- b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php +++ b/core/lib/Drupal/Core/Menu/Plugin/Block/LocalActionsBlock.php @@ -91,7 +91,7 @@ return $local_actions; } - /**2g + /** * {@inheritdoc} */ public function getCacheContexts() { diff -u b/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php b/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php --- b/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php +++ b/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php @@ -8,7 +8,8 @@ namespace Drupal\comment\Plugin\Menu\LocalTask; use Drupal\comment\CommentStorageInterface; -use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Menu\LocalTaskDefault; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -28,6 +29,11 @@ protected $commentStorage; /** + * The entity manager. + */ + protected $entityManager; + + /** * Construct the UnapprovedComments object. * * @param array $configuration @@ -38,10 +44,13 @@ * The plugin implementation definition. * @param \Drupal\comment\CommentStorageInterface $comment_storage * The comment storage service. + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, CommentStorageInterface $comment_storage) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, CommentStorageInterface $comment_storage, EntityManagerInterface $entity_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->commentStorage = $comment_storage; + $this->entityManager = $entity_manager; } /** @@ -52,7 +61,8 @@ $configuration, $plugin_id, $plugin_definition, - $container->get('entity.manager')->getStorage('comment') + $container->get('entity.manager')->getStorage('comment'), + $container->get('entity.manager') ); } @@ -66,9 +76,8 @@ /** * {@inheritdoc} */ - public function getCacheableMetadata() { - $cacheable_metadata = new CacheableMetadata(); - return $cacheable_metadata->setCacheTags(['comment_list']); + public function getCacheTags() { + return Cache::mergeTags(parent::getCacheTags(), $this->entityManager->getDefinition('comment')->getListCacheTags()); } } diff -u b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php --- b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php +++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php @@ -139,7 +139,7 @@ ), 'parent_id' => "field_ui.fields:form_display_overview_$entity_type_id", 'weight' => $weight++, - 'cache_tags' => $this->entityManager->getDefinition('entity_form_display')->getListCacheTags(), + 'cacheability_metadata' => (new CacheableMetadata())->setCacheTags($this->entityManager->getDefinition('entity_form_display')->getListCacheTags()), ); } @@ -154,7 +154,7 @@ ), 'parent_id' => "field_ui.fields:display_overview_$entity_type_id", 'weight' => $weight++, - 'cache_tags' => $this->entityManager->getDefinition('entity_view_display')->getListCacheTags(), + 'cacheability_metadata' => (new CacheableMetadata())->setCacheTags($this->entityManager->getDefinition('entity_view_display')->getListCacheTags()), ); } } reverted: --- b/core/modules/node/node.module +++ a/core/modules/node/node.module @@ -912,7 +912,7 @@ return AccessResult::allowed()->cachePerPermissions(); } else { + return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node); - return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cacheUntilEntityChanges($node); } case 'delete': @@ -920,7 +920,7 @@ return AccessResult::allowed()->cachePerPermissions(); } else { + return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node); - return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cacheUntilEntityChanges($node); } default: diff -u b/core/modules/system/tests/modules/menu_test/src/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php b/core/modules/system/tests/modules/menu_test/src/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php --- b/core/modules/system/tests/modules/menu_test/src/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php +++ b/core/modules/system/tests/modules/menu_test/src/Plugin/Menu/LocalTask/TestTasksSettingsSub1.php @@ -25,9 +25,8 @@ /** * {@inheritdoc} */ - function getCacheableMetadata() { - $cacheable_metadata = new CacheableMetadata(); - return $cacheable_metadata->addCacheTags(['kittens:ragdoll']); + function getCacheContexts() { + return ['kittens:ragdoll']; } } diff -u b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php --- b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php @@ -302,22 +302,20 @@ } /** - * @covers ::getCacheableMetadata + * @covers ::getCacheContexts + * @covers ::getCacheTags + * @covers ::getCacheMaxAge */ - public function testGetCacheableMetadataWithoutCacheTags() { - $this->setupLocalTaskDefault(); - - $this->assertEquals([], $this->localTaskBase->getCacheableMetadata()->getCacheTags()); - } + public function testCacheabilityMetadata() { + $this->pluginDefinition['cache_contexts'] = ['route']; + $this->pluginDefinition['cache_tags'] = ['kitten']; + $this->pluginDefinition['cache_max_age'] = 3600; - /** - * @covers ::getCacheableMetadata - */ - public function testGetCacheableMetadataWithCacheTags() { - $this->pluginDefinition['cache_tags'] = ['example']; $this->setupLocalTaskDefault(); - $this->assertEquals(['example'], $this->localTaskBase->getCacheableMetadata()->getCacheTags()); + $this->assertEquals(['route'], $this->localTaskBase->getCacheContexts()); + $this->assertEquals(['kitten'], $this->localTaskBase->getCacheTags()); + $this->assertEquals(3600, $this->localTaskBase->getCacheMaxAge()); } } diff -u b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php --- b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Language\Language; use Drupal\Core\Menu\LocalTaskInterface; @@ -417,8 +418,8 @@ ->will($this->returnValue($definitions)); // Setup some cacheablity metadata and ensure its merged together. - $definitions['menu_local_task_test_tasks_settings']['cacheable_metadata'] = (new CacheableMetadata())->setCacheTags(['tag.example1'])->setCacheContexts(['context.example1']); - $definitions['menu_local_task_test_tasks_edit']['cacheable_metadata'] = (new CacheableMetadata())->setCacheTags(['tag.example2'])->setCacheContexts(['context.example2']); + $definitions['menu_local_task_test_tasks_settings']['cacheability_metadata'] = (new CacheableMetadata())->setCacheTags(['tag.example1'])->setCacheContexts(['context.example1']); + $definitions['menu_local_task_test_tasks_edit']['cacheability_metadata'] = (new CacheableMetadata())->setCacheTags(['tag.example2'])->setCacheContexts(['context.example2']); // Test the cacheable metadata of access checking. $definitions['menu_local_task_test_tasks_view_child1']['access'] = AccessResult::allowed()->addCacheContexts(['user.permissions']); @@ -452,13 +453,17 @@ $info += ['access' => AccessResult::allowed()]; $mock = $this->prophesize(LocalTaskInterface::class); + $mock->willImplement(CacheableDependencyInterface::class); $mock->getRouteName()->willReturn($info['route_name']); $mock->getTitle()->willReturn($info['title']); $mock->getRouteParameters(Argument::cetera())->willReturn([]); $mock->getOptions(Argument::cetera())->willReturn([]); $mock->getActive()->willReturn($plugin_id === $active_plugin_id); $mock->getWeight()->willReturn(isset($info['weight']) ? $info['weight'] : 0); - $mock->getCacheableMetadata()->willReturn(isset($info['cacheable_metadata']) ? $info['cacheable_metadata'] : new CacheableMetadata()); + $mock->getCacheContexts()->willReturn(isset($info['cacheability_metadata']) ? $info['cacheability_metadata']->getCacheContexts() : []); + $mock->getCacheTags()->willReturn(isset($info['cacheability_metadata']) ? $info['cacheability_metadata']->getCacheTags() : []); + $mock->getCacheMaxAge()->willReturn(isset($info['cacheability_metadata']) ? $info['cacheability_metadata']->getCacheMaxAge() : Cache::PERMANENT); + $access_manager_map[] = [$info['route_name'], [], $this->account, TRUE, $info['access']]; only in patch2: unchanged: --- a/core/modules/field_ui/src/Tests/EntityDisplayTest.php +++ b/core/modules/field_ui/src/Tests/EntityDisplayTest.php @@ -7,12 +7,14 @@ namespace Drupal\field_ui\Tests; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Entity\Entity\EntityViewMode; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\node\Entity\NodeType; use Drupal\simpletest\KernelTestBase; +use Drupal\Tests\Core\Entity\EntityManagerTest; /** * Tests the entity display configuration entities. @@ -435,4 +437,21 @@ public function testOnDependencyRemoval() { $display = entity_get_display('entity_test', 'entity_test', 'default'); $this->assertFalse($display->getComponent($field_name)); } + + /** + * Ensures that entity view mode changes invalidates cache tags. + */ + public function testEntityDisplayInvalidateCacheTags() { + $cache = \Drupal::cache(); + $cache->set('cid', 'kittens', Cache::PERMANENT, ['entity_test_list']); + $display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + ]); + $display->setComponent('kitten'); + $display->save(); + $this->assertFalse($cache->get('cid')); + } + } only in patch2: unchanged: --- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php +++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php @@ -49,6 +49,7 @@ public function testEntityViewBuilderCache() { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); $cache_contexts_manager = \Drupal::service("cache_contexts_manager"); + $cache = \Drupal::cache(); // Force a request via GET so we can get drupal_render() cache working. $request = \Drupal::request(); @@ -78,8 +79,10 @@ public function testEntityViewBuilderCache() { $this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.'); // Re-save the entity and check that the cache entry has been deleted. + $cache->set('kittens', 'Kitten data', Cache::PERMANENT, $build['#cache']['tags']); $entity_test->save(); $this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was saved.'); + $this->assertFalse($cache->get('kittens'), 'The entity saving has invalidated cache tags.'); // Rebuild the render array (creating a new cache entry in the process) and // delete the entity to check the cache entry is deleted. only in patch2: unchanged: --- a/core/profiles/standard/src/Tests/StandardTest.php +++ b/core/profiles/standard/src/Tests/StandardTest.php @@ -193,10 +193,11 @@ function testStandard() { $this->drupalGet($url); $this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Frontpage is cached by Dynamic Page Cache.'); - $url = Url::fromRoute('entity.node.canonical', ['node' => 1]); - $this->drupalGet($url); - $this->drupalGet($url); - $this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Full node page is cached by Dynamic Page Cache.'); + // @todo uncomment after https://www.drupal.org/node/2543334 has landed. + //url = Url::fromRoute('entity.node.canonical', ['node' => 1]); + //$this->drupalGet($url); + //$this->drupalGet($url); + //$this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Full node page is cached by Dynamic Page Cache.'); $url = Url::fromRoute('entity.user.canonical', ['user' => 1]); $this->drupalGet($url);