Commit b353cd51 authored by catch's avatar catch

Issue #2511516 by lauriii, Xano, Wim Leers, dawehner: Make local tasks and...

Issue #2511516 by lauriii, Xano, Wim Leers, dawehner: Make local tasks and actions hooks provide cacheability metadata, to make the blocks showing them cacheable
parent efeb5971
......@@ -181,6 +181,7 @@ public function __construct(array $values, $entity_type) {
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
// Reset the render cache for the target entity type.
parent::postSave($storage, $update);
if (\Drupal::entityManager()->hasHandler($this->targetEntityType, 'view_builder')) {
\Drupal::entityManager()->getViewBuilder($this->targetEntityType)->resetCache();
}
......
......@@ -8,6 +8,8 @@
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\PluginBase;
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;
......@@ -15,7 +17,7 @@
/**
* Default object used for LocalTaskPlugins.
*/
class LocalTaskDefault extends PluginBase implements LocalTaskInterface {
class LocalTaskDefault extends PluginBase implements LocalTaskInterface, CacheableDependencyInterface {
use DependencySerializationTrait;
......@@ -143,4 +145,34 @@ protected function routeProvider() {
return $this->routeProvider;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
if (!isset($this->pluginDefinition['cache_tags'])) {
return [];
}
return $this->pluginDefinition['cache_tags'];
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
if (!isset($this->pluginDefinition['cache_contexts'])) {
return [];
}
return $this->pluginDefinition['cache_contexts'];
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
if (!isset($this->pluginDefinition['cache_max_age'])) {
return Cache::PERMANENT;
}
return $this->pluginDefinition['cache_max_age'];
}
}
......@@ -10,7 +10,9 @@
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Access\AccessManagerInterface;
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;
......@@ -288,7 +290,7 @@ public function getLocalTasksForRoute($route_name) {
/**
* {@inheritdoc}
*/
public function getTasksBuild($current_route_name) {
public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability) {
$tree = $this->getLocalTasksForRoute($current_route_name);
$build = array();
......@@ -303,14 +305,15 @@ public function getTasksBuild($current_route_name) {
// of SQL queries that would otherwise be triggered by the access manager.
$routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array();
// @todo add cacheability data in https://www.drupal.org/node/2511516 so
// that we are not re-building inaccessible links on every page request.
foreach ($tree as $level => $instances) {
/** @var $instances \Drupal\Core\Menu\LocalTaskInterface[] */
foreach ($instances as $plugin_id => $child) {
$route_name = $child->getRouteName();
$route_parameters = $child->getRouteParameters($this->routeMatch);
// Given that the active flag depends on the route we have to add the
// route cache context.
$cacheability->addCacheContexts(['route']);
$active = $this->isRouteActive($current_route_name, $route_name, $route_parameters);
// The plugin may have been set active in getLocalTasksForRoute() if
......@@ -323,13 +326,15 @@ public function getTasksBuild($current_route_name) {
'url' => Url::fromRoute($route_name, $route_parameters),
'localized_options' => $child->getOptions($this->routeMatch),
];
$access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE);
$build[$level][$plugin_id] = [
'#theme' => 'menu_local_task',
'#link' => $link,
'#active' => $active,
'#weight' => $child->getWeight(),
'#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE),
'#access' => $access,
];
$cacheability->addCacheableDependency($access)->addCacheableDependency($child);
}
}
......@@ -341,21 +346,25 @@ public function getTasksBuild($current_route_name) {
*/
public function getLocalTasks($route_name, $level = 0) {
if (!isset($this->taskData[$route_name])) {
$cacheability = new CacheableMetadata();
$cacheability->addCacheContexts(['route']);
// Look for route-based tabs.
$this->taskData[$route_name] = [
'tabs' => [],
'cacheability' => $cacheability,
];
if (!$this->requestStack->getCurrentRequest()->attributes->has('exception')) {
// Safe to build tasks only when no exceptions raised.
$data = [];
$local_tasks = $this->getTasksBuild($route_name);
$local_tasks = $this->getTasksBuild($route_name, $cacheability);
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);
$this->moduleHandler->alter('menu_local_tasks', $this->taskData[$route_name], $route_name, $cacheability);
$this->taskData[$route_name]['cacheability'] = $cacheability;
}
}
......@@ -363,12 +372,14 @@ public function getLocalTasks($route_name, $level = 0) {
return [
'tabs' => $this->taskData[$route_name]['tabs'][$level],
'route_name' => $route_name,
'cacheability' => $this->taskData[$route_name]['cacheability'],
];
}
return [
'tabs' => [],
'route_name' => $route_name,
'cacheability' => $this->taskData[$route_name]['cacheability'],
];
}
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\Menu;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
/**
* Manages discovery and instantiation of menu local task plugins.
......@@ -47,11 +48,13 @@ public function getLocalTasksForRoute($route_name);
*
* @param string $current_route_name
* The route for which to make renderable local tasks.
* @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
* The cacheability metadata for the local tasks.
*
* @return array
* A render array as expected by menu-local-tasks.html.twig.
*/
public function getTasksBuild($current_route_name);
public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability);
/**
* Collects the local tasks (tabs) for the current route.
......
......@@ -91,35 +91,11 @@ public function build() {
return $local_actions;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
// The "Primary admin actions" block is never cacheable because hooks creating local
// actions don't provide cacheability metadata.
// @todo Remove after https://www.drupal.org/node/2511516 has landed.
$form['cache']['#disabled'] = TRUE;
$form['cache']['#description'] = $this->t('This block is never cacheable.');
$form['cache']['max_age']['#value'] = 0;
return $form;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// @todo Remove after https://www.drupal.org/node/2511516 has landed.
return 0;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['route.name'];
return ['route'];
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Menu\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\LocalTaskManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
......@@ -88,7 +89,7 @@ public function defaultConfiguration() {
*/
public function build() {
$config = $this->configuration;
$cacheability = new CacheableMetadata();
$tabs = [
'#theme' => 'menu_local_tasks',
];
......@@ -96,6 +97,7 @@ public function build() {
// Add only selected levels for the printed output.
if ($config['primary']) {
$links = $this->localTaskManager->getLocalTasks($this->routeMatch->getRouteName(), 0);
$cacheability = $cacheability->merge($links['cacheability']);
// Do not display single tabs.
$tabs += [
'#primary' => count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [],
......@@ -103,48 +105,20 @@ public function build() {
}
if ($config['secondary']) {
$links = $this->localTaskManager->getLocalTasks($this->routeMatch->getRouteName(), 1);
$cacheability = $cacheability->merge($links['cacheability']);
// Do not display single tabs.
$tabs += [
'#secondary' => count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : [],
];
}
$build = [];
$cacheability->applyTo($build);
if (empty($tabs['#primary']) && empty($tabs['#secondary'])) {
return [];
return $build;
}
return $tabs;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
// The "Page actions" block is never cacheable because of hooks creating
// local tasks doesn't provide cacheability metadata.
// @todo Remove after https://www.drupal.org/node/2511516 has landed.
$form['cache']['#disabled'] = TRUE;
$form['cache']['#description'] = $this->t('This block is never cacheable.');
$form['cache']['max_age']['#value'] = 0;
return $form;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// @todo Remove after https://www.drupal.org/node/2511516 has landed.
return 0;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['route.name'];
return $build + $tabs;
}
/**
......
......@@ -29,6 +29,8 @@ comment.admin_approval:
class: Drupal\comment\Plugin\Menu\LocalTask\UnapprovedComments
parent_id: comment.admin
weight: 1
cache_tags:
- comment_list
# Default tab for comment type editing.
entity.comment_type.edit_form:
......
......@@ -7,6 +7,7 @@
namespace Drupal\field_ui\Plugin\Derivative;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
......@@ -138,6 +139,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
),
'parent_id' => "field_ui.fields:form_display_overview_$entity_type_id",
'weight' => $weight++,
'cache_tags' => $this->entityManager->getDefinition('entity_form_display')->getListCacheTags(),
);
}
......@@ -152,6 +154,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
),
'parent_id' => "field_ui.fields:display_overview_$entity_type_id",
'weight' => $weight++,
'cache_tags' => $this->entityManager->getDefinition('entity_view_display')->getListCacheTags(),
);
}
}
......@@ -189,5 +192,4 @@ public function alterLocalTasks(&$local_tasks) {
}
}
}
}
......@@ -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));
}
/**
* Ensure that entity view display changes invalidates cache tags.
*/
public function testEntityDisplayInvalidateCacheTags() {
$cache = \Drupal::cache();
$cache->set('cid', 'kittens', Cache::PERMANENT, ['config:entity_view_display_list']);
$display = EntityViewDisplay::create([
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
]);
$display->setComponent('kitten');
$display->save();
$this->assertFalse($cache->get('cid'));
}
}
......@@ -27,11 +27,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
protected $defaultCacheContexts = [
'languages:language_interface',
'theme',
'route.name',
'route.menu_active_trails:account',
'route.menu_active_trails:footer',
'route.menu_active_trails:main',
'route.menu_active_trails:tools',
'route',
'timezone',
'url',
'user'
......
......@@ -74,13 +74,7 @@ function testPageCacheTags() {
'route',
'theme',
'timezone',
'user.permissions',
// The user login block access depends on whether the current user is
// logged in or not.
'user.roles:anonymous',
// The cache contexts associated with the (in)accessible menu links are
// bubbled.
'user.roles:authenticated',
'user',
// The placed block is only visible on certain URLs through a visibility
// condition.
'url',
......
......@@ -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.
......
......@@ -159,6 +159,9 @@ public function testPluginLocalTask() {
$this->assertEqual('Settings', (string) $result[0], 'The settings tab is active.');
$this->assertEqual('Dynamic title for TestTasksSettingsSub1', (string) $result[1], 'The sub1 tab is active.');
$this->assertCacheTag('kittens:ragdoll');
$this->assertCacheTag('kittens:dwarf-cat');
$this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']));
$this->assertLocalTasks($sub_tasks, 1);
......
......@@ -5,6 +5,7 @@
* Module that implements various hooks for menu tests.
*/
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Url;
/**
......@@ -29,7 +30,7 @@ function menu_test_menu_links_discovered_alter(&$links) {
/**
* Implements hook_menu_local_tasks_alter().
*/
function menu_test_menu_local_tasks_alter(&$data, $route_name) {
function menu_test_menu_local_tasks_alter(&$data, $route_name, RefinableCacheableDependencyInterface &$cacheability) {
if (in_array($route_name, array('menu_test.tasks_default'))) {
$data['tabs'][0]['foo'] = array(
'#theme' => 'menu_local_task',
......@@ -48,6 +49,7 @@ function menu_test_menu_local_tasks_alter(&$data, $route_name) {
'#weight' => 20,
);
}
$cacheability->addCacheTags(['kittens:dwarf-cat']);
}
/**
......
......@@ -21,4 +21,11 @@ function getTitle() {
return $this->t('Dynamic title for @class', array('@class' => 'TestTasksSettingsSub1'));
}
/**
* {@inheritdoc}
*/
function getCacheTags() {
return ['kittens:ragdoll'];
}
}
......@@ -86,7 +86,7 @@ function testTrackerAll() {
$this->assertLink(t('My recent content'), 0, 'User tab shows up on the global tracker page.');
// Assert cache contexts, specifically the pager and node access contexts.
$this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user.node_grants:view', 'user']);
$this->assertCacheContexts(['languages:language_interface', 'route', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user.node_grants:view', 'user']);
// Assert cache tags for the action/tabs blocks, visible node, and node list
// cache tag.
$expected_tags = Cache::mergeTags($published->getCacheTags(), $published->getOwner()->getCacheTags());
......@@ -164,7 +164,7 @@ function testTrackerUser() {
$this->assertText($other_published_my_comment->label(), "Nodes that the user has commented on appear in the user's tracker listing.");
// Assert cache contexts.
$this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
$this->assertCacheContexts(['languages:language_interface', 'route', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
// Assert cache tags for the visible nodes (including owners) and node list
// cache tag.
$expected_tags = Cache::mergeTags($my_published->getCacheTags(), $my_published->getOwner()->getCacheTags());
......@@ -184,7 +184,7 @@ function testTrackerUser() {
$expected_tags = Cache::mergeTags($expected_tags, $additional_tags);
$this->assertCacheTags($expected_tags);
$this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
$this->assertCacheContexts(['languages:language_interface', 'route', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
$this->assertLink($my_published->label());
$this->assertNoLink($unpublished->label());
......
......@@ -57,7 +57,7 @@ public function testArguments() {
$this->drupalGet('test_route_with_argument/1');
$this->assertResponse(200);
$this->assertCacheContexts(['languages:language_interface', 'route.name', 'theme', 'url']);
$this->assertCacheContexts(['languages:language_interface', 'route', 'theme', 'url']);
$result = $this->xpath('//span[@class="field-content"]');
$this->assertEqual(count($result), 1, 'Ensure that just the filtered entry was returned.');
$this->assertEqual((string) $result[0], 1, 'The passed ID was returned.');
......
......@@ -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);
......
......@@ -301,6 +301,23 @@ public function testGetOptions() {
), $this->localTaskBase->getOptions($route_match));
}
/**
* @covers ::getCacheContexts
* @covers ::getCacheTags
* @covers ::getCacheMaxAge
*/
public function testCacheabilityMetadata() {
$this->pluginDefinition['cache_contexts'] = ['route'];
$this->pluginDefinition['cache_tags'] = ['kitten'];
$this->pluginDefinition['cache_max_age'] = 3600;
$this->setupLocalTaskDefault();
$this->assertEquals(['route'], $this->localTaskBase->getCacheContexts());
$this->assertEquals(['kitten'], $this->localTaskBase->getCacheTags());
$this->assertEquals(3600, $this->localTaskBase->getCacheMaxAge());
}
}
class TestLocalTaskDefault extends LocalTaskDefault {
......
......@@ -7,10 +7,18 @@
namespace Drupal\Tests\Core\Menu;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Language\Language;
use Drupal\Core\Menu\LocalTaskInterface;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Zend\Stdlib\ArrayObject;
......@@ -84,6 +92,13 @@ class LocalTaskManagerTest extends UnitTestCase {
*/
protected $routeMatch;
/**
* The mocked account.
*
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $account;
/**
* {@inheritdoc}
*/
......@@ -98,8 +113,10 @@ protected function setUp() {
$this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->accessManager = $this->getMock('Drupal\Core\Access\AccessManagerInterface');
$this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->setupLocalTaskManager();
$this->setupNullCacheabilityMetadataValidation();
}
/**
......@@ -249,8 +266,7 @@ protected function setupLocalTaskManager() {
->method('getCurrentLanguage')
->will($this->returnValue(new Language(array('id' => 'en'))));
$account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $account);
$this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $this->account);
$property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery');
$property->setAccessible(TRUE);
......@@ -393,5 +409,88 @@ protected function getLocalTasksCache() {
return $local_tasks;
}
/**
* @covers ::getTasksBuild
*/
public function testGetTasksBuildWithCacheabilityMetadata() {
$definitions = $this->getLocalTaskFixtures();
$this->pluginDiscovery->expects($this->once())
->method('getDefinitions')
->will($this->returnValue($definitions));
// Set up some cacheablity metadata and ensure its merged together.
$definitions['menu_local_task_test_tasks_settings']['cache_tags'] = ['tag.example1'];
$definitions['menu_local_task_test_tasks_settings']['cache_contexts'] = ['context.example1'];
$definitions['menu_local_task_test_tasks_edit']['cache_tags'] = ['tag.example2'];
$definitions['menu_local_task_test_tasks_edit']['cache_contexts'] = ['context.example2'];
// Test the cacheability metadata of access checking.
$definitions['menu_local_task_test_tasks_view_child1']['access'] = AccessResult::allowed()->addCacheContexts(['user.permissions']);
$this->setupFactoryAndLocalTaskPlugins($definitions, 'menu_local_task_test_tasks_view');
$this->setupLocalTaskManager();
$this->controllerResolver->expects($this->any())
->method('getArguments')
->willReturn([]);
$this->routeMatch->expects($this->any())
->method('getRouteName')
->willReturn('menu_local_task_test_tasks_view');
$this->routeMatch->expects($this->any())
->method('getRawParameters')
->willReturn(new ParameterBag());
$cacheability = new CacheableMetadata();
$local_tasks = $this->manager->getTasksBuild('menu_local_task_test_tasks_view', $cacheability);
// Ensure that all cacheability metadata is merged together.
$this->assertEquals(['tag.example1', 'tag.example2'], $cacheability->getCacheTags());
$this->assertEquals(['context.example1', 'context.example2', 'route', 'user.permissions'], $cacheability->getCacheContexts());
}
protected function setupFactoryAndLocalTaskPlugins(array $definitions, $active_plugin_id) {
$map = [];
$access_manager_map = [];
foreach ($definitions as $plugin_id => $info) {
$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->getCacheContexts()->willReturn(isset($info['cache_contexts']) ? $info['cache_contexts'] : []);
$mock->getCacheTags()->willReturn(isset($info['cache_tags']) ? $info['cache_tags'] : []);
$mock->getCacheMaxAge()->willReturn(isset($info['cache_max_age']) ? $info['cache_max_age'] : Cache::PERMANENT);
$access_manager_map[] = [$info['route_name'], [], $this->account, TRUE, $info['access']];
$map[] = [$info['id'], [], $mock->reveal()];
}
$this->accessManager->expects($this->any())
->method('checkNamedRoute')
->willReturnMap($access_manager_map);
$this->factory->expects($this->any())
->method('createInstance')
->will($this->returnValueMap($map));
}
protected function setupNullCacheabilityMetadataValidation() {
$container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
$cache_context_manager = $this->prophesize(CacheContextsManager::class);
$container->set('cache_contexts_manager', $cache_context_manager->reveal());
\Drupal::setContainer($container);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment