From d15f246bd9e3ca2b901af14bcd3f88fc30cd7baa Mon Sep 17 00:00:00 2001 From: Chi Date: Wed, 12 Feb 2020 11:33:23 +0000 Subject: [PATCH] Buble access result cache metadata --- src/TwigExtension.php | 201 +++--- tests/src/Kernel/AccessTest.php | 598 ++++++++++++++++++ .../src/Plugin/Block/FooBlock.php | 44 ++ tests/twig_tweak_test/twig_tweak_test.module | 18 + 4 files changed, 790 insertions(+), 71 deletions(-) create mode 100644 tests/src/Kernel/AccessTest.php create mode 100644 tests/twig_tweak_test/src/Plugin/Block/FooBlock.php diff --git a/src/TwigExtension.php b/src/TwigExtension.php index fd3c25c..5fedc92 100644 --- a/src/TwigExtension.php +++ b/src/TwigExtension.php @@ -5,8 +5,10 @@ namespace Drupal\twig_tweak; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\Unicode; use Drupal\Component\Uuid\Uuid; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Block\TitleBlockPluginInterface; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\FieldItemInterface; @@ -140,42 +142,39 @@ class TwigExtension extends \Twig_Extension { \Drupal::service('context.handler')->applyContextMapping($block_plugin, $contexts); } - if (!$block_plugin->access(\Drupal::currentUser())) { - return; - } + $build = []; + $access = $block_plugin->access(\Drupal::currentUser(), TRUE); + if ($access->isAllowed()) { + // Title block needs special treatment. + if ($block_plugin instanceof TitleBlockPluginInterface) { + $request = \Drupal::request(); + $route_match = \Drupal::routeMatch(); + $title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject()); + $block_plugin->setTitle($title); + } - // Title block needs special treatment. - if ($block_plugin instanceof TitleBlockPluginInterface) { - $request = \Drupal::request(); - $route_match = \Drupal::routeMatch(); - $title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject()); - $block_plugin->setTitle($title); - } + $build['content'] = $block_plugin->build(); - $build = [ - 'content' => $block_plugin->build(), - '#cache' => [ - 'contexts' => $block_plugin->getCacheContexts(), - 'tags' => $block_plugin->getCacheTags(), - 'max-age' => $block_plugin->getCacheMaxAge(), - ], - ]; + if ($block_plugin instanceof TitleBlockPluginInterface) { + $build['content']['#cache']['contexts'][] = 'url'; + } - if ($block_plugin instanceof TitleBlockPluginInterface) { - $build['#cache']['contexts'][] = 'url'; + if ($wrapper && !Element::isEmpty($build['content'])) { + $build += [ + '#theme' => 'block', + '#attributes' => [], + '#contextual_links' => [], + '#configuration' => $block_plugin->getConfiguration(), + '#plugin_id' => $block_plugin->getPluginId(), + '#base_plugin_id' => $block_plugin->getBaseId(), + '#derivative_plugin_id' => $block_plugin->getDerivativeId(), + ]; + } } - if ($wrapper && !Element::isEmpty($build['content'])) { - $build += [ - '#theme' => 'block', - '#attributes' => [], - '#contextual_links' => [], - '#configuration' => $block_plugin->getConfiguration(), - '#plugin_id' => $block_plugin->getPluginId(), - '#base_plugin_id' => $block_plugin->getBaseId(), - '#derivative_plugin_id' => $block_plugin->getDerivativeId(), - ]; - } + CacheableMetadata::createFromRenderArray($build) + ->merge(CacheableMetadata::createFromObject($access)) + ->applyTo($build); return $build; } @@ -202,6 +201,7 @@ class TwigExtension extends \Twig_Extension { * A render array to display the region content. */ public function drupalRegion($region, $theme = NULL) { + $entity_type_manager = \Drupal::entityTypeManager(); $blocks = $entity_type_manager->getStorage('block')->loadByProperties([ 'region' => $region, @@ -212,9 +212,13 @@ class TwigExtension extends \Twig_Extension { $build = []; + $cache_metadata = new CacheableMetadata(); + /* @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $id => $block) { - if ($block->access('view')) { + $access = $block->access('view', NULL, TRUE); + $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($access)); + if ($access->isAllowed()) { $block_plugin = $block->getPlugin(); if ($block_plugin instanceof TitleBlockPluginInterface) { $request = \Drupal::request(); @@ -229,6 +233,7 @@ class TwigExtension extends \Twig_Extension { if ($build) { $build['#region'] = $region; $build['#theme_wrappers'] = ['region']; + $cache_metadata->applyTo($build); } return $build; @@ -274,10 +279,23 @@ class TwigExtension extends \Twig_Extension { @trigger_error('Loading entities from route is deprecated in Twig Tweak 2.4 and will not be supported in Twig Tweak 3.0', E_USER_DEPRECATED); $entity = \Drupal::routeMatch()->getParameter($entity_type); } - if ($entity && (!$check_access || $entity->access('view'))) { - $render_controller = $entity_type_manager->getViewBuilder($entity_type); - return $render_controller->view($entity, $view_mode, $langcode); + + $build = []; + + if ($entity) { + $access = $check_access ? $entity->access('view', NULL, TRUE) : AccessResult::allowed(); + if ($access->isAllowed()) { + $build = $entity_type_manager + ->getViewBuilder($entity_type) + ->view($entity, $view_mode, $langcode); + } + CacheableMetadata::createFromRenderArray($build) + ->merge(CacheableMetadata::createFromObject($entity)) + ->merge(CacheableMetadata::createFromObject($access)) + ->applyTo($build); } + + return $build; } /** @@ -320,9 +338,21 @@ class TwigExtension extends \Twig_Extension { $entity = $entity_storage->create($values); $operation = 'create'; } - if ($entity && (!$check_access || $entity->access($operation))) { - return \Drupal::service('entity.form_builder')->getForm($entity, $form_mode); + + $build = []; + + if ($entity) { + $access = $check_access ? $entity->access($operation, NULL, TRUE) : AccessResult::allowed(); + if ($access->isAllowed()) { + $build = \Drupal::service('entity.form_builder')->getForm($entity, $form_mode); + } + CacheableMetadata::createFromRenderArray($build) + ->merge(CacheableMetadata::createFromObject($entity)) + ->merge(CacheableMetadata::createFromObject($access)) + ->applyTo($build); } + + return $build; } /** @@ -352,20 +382,36 @@ class TwigExtension extends \Twig_Extension { * A render array for the field or NULL if the value does not exist. */ public function drupalField($field_name, $entity_type, $id = NULL, $view_mode = 'default', $langcode = NULL, $check_access = TRUE) { + $entity_type_manager = \Drupal::entityTypeManager(); + if ($id) { - $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($id); + $entity = $entity_type_manager->getStorage($entity_type)->load($id); } else { @trigger_error('Loading entities from route is deprecated in Twig Tweak 2.4 and will not be supported in Twig Tweak 3.0', E_USER_DEPRECATED); $entity = \Drupal::routeMatch()->getParameter($entity_type); } - if ($entity && (!$check_access || $entity->access('view'))) { - $entity = \Drupal::service('entity.repository') - ->getTranslationFromContext($entity, $langcode); - if (isset($entity->{$field_name})) { - return $entity->{$field_name}->view($view_mode); + + $build = []; + + if ($entity) { + $access = $check_access ? $entity->access('view', NULL, TRUE) : AccessResult::allowed(); + if ($access->isAllowed()) { + $entity = \Drupal::service('entity.repository') + ->getTranslationFromContext($entity, $langcode); + if (!isset($entity->{$field_name})) { + // @todo Trigger error here. + return; + } + $build = $entity->{$field_name}->view($view_mode); } + CacheableMetadata::createFromRenderArray($build) + ->merge(CacheableMetadata::createFromObject($access)) + ->merge(CacheableMetadata::createFromObject($entity)) + ->applyTo($build); } + + return $build; } /** @@ -490,35 +536,38 @@ class TwigExtension extends \Twig_Extension { ->getStorage('file') ->loadByProperties([$property_type => $property]); - // To avoid ambiguity render nothing unless exact one image was found. + $build = []; + + // To avoid ambiguity render nothing unless exact one image has been found. if (count($files) != 1) { - return; + return $build; } $file = reset($files); - if ($check_access && !$file->access('view')) { - return; - } - - $build = [ - '#uri' => $file->getFileUri(), - '#attributes' => $attributes, - ]; + $access = $check_access ? $file->access('view', NULL, TRUE) : AccessResult::allowed(); - if ($style) { - if ($responsive) { - $build['#type'] = 'responsive_image'; - $build['#responsive_image_style_id'] = $style; + if ($access->isAllowed()) { + $build['#uri'] = $file->getFileUri(); + $build['#attributes'] = $attributes; + if ($style) { + if ($responsive) { + $build['#type'] = 'responsive_image'; + $build['#responsive_image_style_id'] = $style; + } + else { + $build['#theme'] = 'image_style'; + $build['#style_name'] = $style; + } } else { - $build['#theme'] = 'image_style'; - $build['#style_name'] = $style; + $build['#theme'] = 'image'; } } - else { - $build['#theme'] = 'image'; - } + + CacheableMetadata::createFromRenderArray($build) + ->merge(CacheableMetadata::createFromObject($access)) + ->applyTo($build); return $build; } @@ -637,8 +686,8 @@ class TwigExtension extends \Twig_Extension { * @param bool $check_access * (optional) Indicates that access check is required. * - * @return \Drupal\Core\Url - * A new Url object based on user input. + * @return \Drupal\Core\Url|null + * A new Url object or null if the URL is not accessible. * * @see \Drupal\Core\Url::fromUserInput() */ @@ -679,8 +728,8 @@ class TwigExtension extends \Twig_Extension { * @param bool $check_access * (optional) Indicates that access check is required. * - * @return \Drupal\Core\Link - * A new Link object. + * @return \Drupal\Core\Link|null + * A new Link object or null of the URL is not accessible. * * @see \Drupal\Core\Link::fromTextAndUrl() */ @@ -991,20 +1040,30 @@ class TwigExtension extends \Twig_Extension { * (optional) For which language the entity should be rendered, defaults to * the current content language. * @param bool $check_access - * (optional) Indicates that access check is required. + * (optional) Indicates that access check for an entity is required. * * @return array * A render array to represent the object. */ public function view($object, $display_options = 'default', $langcode = NULL, $check_access = TRUE) { + $build = []; if ($object instanceof FieldItemListInterface || $object instanceof FieldItemInterface) { return $object->view($display_options); } - elseif ($object instanceof EntityInterface && (!$check_access || $object->access('view'))) { - return \Drupal::entityTypeManager() - ->getViewBuilder($object->getEntityTypeId()) - ->view($object, $display_options, $langcode); + elseif ($object instanceof EntityInterface) { + $build = []; + $access = $check_access ? $object->access('view', NULL, TRUE) : AccessResult::allowed(); + if ($access->isAllowed()) { + $build = \Drupal::entityTypeManager() + ->getViewBuilder($object->getEntityTypeId()) + ->view($object, $display_options, $langcode); + } + CacheableMetadata::createFromRenderArray($build) + ->merge(CacheableMetadata::createFromObject($object)) + ->merge(CacheableMetadata::createFromObject($access)) + ->applyTo($build); } + return $build; } /** diff --git a/tests/src/Kernel/AccessTest.php b/tests/src/Kernel/AccessTest.php new file mode 100644 index 0000000..ee8d547 --- /dev/null +++ b/tests/src/Kernel/AccessTest.php @@ -0,0 +1,598 @@ +installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installConfig(['system']); + + $node_type = NodeType::create([ + 'type' => 'article', + 'name' => 'Article', + ]); + $node_type->save(); + + $values = [ + 'type' => 'article', + 'status' => NodeInterface::PUBLISHED, + // @see twig_tweak_test_node_access() + 'title' => 'Entity access test', + ]; + $this->node = Node::create($values); + $this->node->save(); + + $this->twigExtension = $this->container->get('twig_tweak.twig_extension'); + } + + /** + * Test callback. + */ + public function testDrupalEntity() { + + // -- Unprivileged user with access check. + $this->setUpCurrentUser(['name' => 'User 1']); + + $build = $this->twigExtension->drupalEntity('node', $this->node->id()); + self::assertArrayNotHasKey('#node', $build); + $expected_cache = [ + 'contexts' => ['user.permissions'], + 'tags' => ['node:1'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Unprivileged user without access check. + $build = $this->twigExtension->drupalEntity('node', $this->node->id(), NULL, NULL, FALSE); + self::assertArrayHasKey('#node', $build); + $expected_cache = [ + 'tags' => [ + 'node:1', + 'node_view', + ], + 'contexts' => [], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user with access check. + $this->setUpCurrentUser(['name' => 'User 2'], ['access content']); + + $build = $this->twigExtension->drupalEntity('node', $this->node->id()); + self::assertArrayHasKey('#node', $build); + $expected_cache = [ + 'tags' => [ + 'node:1', + 'node_view', + 'tag_from_twig_tweak_test_node_access', + ], + 'contexts' => [ + 'user', + 'user.permissions', + ], + 'max-age' => 50, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user without access check. + $build = $this->twigExtension->drupalEntity('node', $this->node->id(), NULL, NULL, FALSE); + self::assertArrayHasKey('#node', $build); + $expected_cache = [ + 'tags' => [ + 'node:1', + 'node_view', + ], + 'contexts' => [], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + } + + /** + * Test callback. + */ + public function testDrupalField() { + + // -- Unprivileged user with access check. + $this->setUpCurrentUser(['name' => 'User 1']); + + $build = $this->twigExtension->drupalField('title', 'node', $this->node->id()); + self::assertArrayNotHasKey('#items', $build); + $expected_cache = [ + 'contexts' => ['user.permissions'], + 'tags' => ['node:1'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Unprivileged user without access check. + $build = $this->twigExtension->drupalField('title', 'node', $this->node->id(), 'default', NULL, FALSE); + self::assertArrayHasKey('#items', $build); + $expected_cache = [ + 'contexts' => [], + 'tags' => ['node:1'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user with access check. + $this->setUpCurrentUser(['name' => 'User 2'], ['access content']); + + $build = $this->twigExtension->drupalField('title', 'node', $this->node->id()); + self::assertArrayHasKey('#items', $build); + $expected_cache = [ + 'contexts' => [ + 'user', + 'user.permissions', + ], + 'tags' => [ + 'node:1', + 'tag_from_twig_tweak_test_node_access', + ], + 'max-age' => 50, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user without access check. + $build = $this->twigExtension->drupalField('title', 'node', $this->node->id(), 'default', NULL, FALSE); + self::assertArrayHasKey('#items', $build); + $expected_cache = [ + 'contexts' => [], + 'tags' => ['node:1'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + } + + /** + * Test callback. + */ + public function testDrupalEntityEditForm() { + + // -- Unprivileged user with access check. + $this->setUpCurrentUser(['name' => 'User 1']); + + $build = $this->twigExtension->drupalEntityForm('node', $this->node->id()); + self::assertArrayNotHasKey('form_id', $build); + $expected_cache = [ + 'contexts' => ['user.permissions'], + 'tags' => ['node:1'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Unprivileged user without access check. + $build = $this->twigExtension->drupalEntityForm('node', $this->node->id(), 'default', [], FALSE); + self::assertArrayHasKey('form_id', $build); + $expected_cache = [ + 'contexts' => ['user.roles:authenticated'], + 'tags' => [ + 'config:core.entity_form_display.node.article.default', + 'node:1', + ], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user with access check. + $this->setUpCurrentUser(['name' => 'User 2'], ['access content']); + + $build = $this->twigExtension->drupalEntityForm('node', $this->node->id()); + self::assertArrayHasKey('#form_id', $build); + $expected_cache = [ + 'contexts' => [ + 'user', + 'user.permissions', + 'user.roles:authenticated', + ], + 'tags' => [ + 'config:core.entity_form_display.node.article.default', + 'node:1', + 'tag_from_twig_tweak_test_node_access', + ], + 'max-age' => 50, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user without access check. + $build = $this->twigExtension->drupalEntityForm('node', $this->node->id(), 'default', [], FALSE); + self::assertArrayHasKey('#form_id', $build); + $expected_cache = [ + 'contexts' => ['user.roles:authenticated'], + 'tags' => [ + 'config:core.entity_form_display.node.article.default', + 'node:1', + ], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + } + + /** + * Test callback. + */ + public function testDrupalEntityAddForm() { + + $node_values = ['type' => 'article']; + + // -- Unprivileged user with access check. + $this->setUpCurrentUser(['name' => 'User 1']); + + $build = $this->twigExtension->drupalEntityForm('node', NULL, 'default', $node_values); + self::assertArrayNotHasKey('form_id', $build); + $expected_cache = [ + 'contexts' => ['user.permissions'], + 'tags' => [], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Unprivileged user without access check. + $build = $this->twigExtension->drupalEntityForm('node', NULL, 'default', $node_values, FALSE); + self::assertArrayHasKey('form_id', $build); + $expected_cache = [ + 'contexts' => ['user.roles:authenticated'], + 'tags' => ['config:core.entity_form_display.node.article.default'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user with access check. + $this->setUpCurrentUser(['name' => 'User 2'], ['access content', 'create article content']); + + $build = $this->twigExtension->drupalEntityForm('node', NULL, 'default', $node_values); + self::assertArrayHasKey('form_id', $build); + $expected_cache = [ + 'contexts' => [ + 'user.permissions', + 'user.roles:authenticated', + ], + 'tags' => ['config:core.entity_form_display.node.article.default'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user without access check. + $build = $this->twigExtension->drupalEntityForm('node', NULL, 'default', $node_values); + self::assertArrayHasKey('form_id', $build); + $expected_cache = [ + 'contexts' => [ + 'user.permissions', + 'user.roles:authenticated', + ], + 'tags' => ['config:core.entity_form_display.node.article.default'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + } + + /** + * Test callback. + * + * @see \Drupal\twig_tweak_test\Plugin\Block\FooBlock + */ + public function testDrupalBlock() { + + // -- Privileged user. + $this->setUpCurrentUser(['name' => 'User 1']); + + $build = $this->twigExtension->drupalBlock('twig_tweak_test_foo'); + $expected_content = [ + '#markup' => 'Foo', + '#cache' => [ + 'contexts' => ['url'], + 'tags' => ['tag_from_build'], + ], + ]; + self::assertSame($expected_content, $build['content']); + $expected_cache = [ + 'contexts' => ['user'], + 'tags' => ['tag_from_blockAccess'], + 'max-age' => 35, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Unprivileged user. + $this->setUpCurrentUser(['name' => 'User 2']); + + $build = $this->twigExtension->drupalBlock('twig_tweak_test_foo'); + self::assertArrayNotHasKey('content', $build); + $expected_cache = [ + 'contexts' => ['user'], + 'tags' => ['tag_from_blockAccess'], + 'max-age' => 35, + ]; + self::assertSame($expected_cache, $build['#cache']); + } + + /** + * Test callback. + */ + public function testDrupalRegion() { + + // @codingStandardsIgnoreStart + $create_block = function ($id) { + return new class(['id' => $id], 'block') extends Block { + public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { + $result = AccessResult::allowedIf($this->id == 'block_1'); + $result->cachePerUser(); + $result->addCacheTags(['tag_for_' . $this->id]); + $result->setCacheMaxAge(123); + return $return_as_object ? $result : $result->isAllowed(); + } + public function getPlugin() { + return NULL; + } + }; + }; + // @codingStandardsIgnoreEnd + + $storage = $this->createMock(EntityStorageInterface::class); + $blocks = [ + 'block_1' => $create_block('block_1'), + 'block_2' => $create_block('block_2'), + ]; + $storage->expects($this->any()) + ->method('loadByProperties') + ->willReturn($blocks); + + $view_builder = $this->createMock(BlockViewBuilder::class); + $content = [ + '#markup' => 'foo', + '#cache' => [ + 'tags' => ['tag_from_view'], + ], + ]; + $view_builder->expects($this->any()) + ->method('view') + ->willReturn($content); + + $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class); + $entity_type_manager->expects($this->any()) + ->method('getStorage') + ->willReturn($storage); + $entity_type_manager->expects($this->any()) + ->method('getViewBuilder') + ->willReturn($view_builder); + + $this->container->set('entity_type.manager', $entity_type_manager); + + $build = $this->twigExtension->drupalRegion('bar'); + $expected_build = [ + 'block_1' => [ + '#markup' => 'foo', + '#cache' => [ + 'tags' => ['tag_from_view'], + ], + ], + '#region' => 'bar', + '#theme_wrappers' => ['region'], + '#cache' => [ + 'contexts' => ['user'], + 'tags' => [ + 'tag_for_block_1', + 'tag_for_block_2', + ], + 'max-age' => 123, + ], + ]; + self::assertSame($expected_build, $build); + } + + /** + * Test callback. + */ + public function testDrupalImage() { + + // @codingStandardsIgnoreStart + $create_image = function ($uri) { + $file = new class(['uri' => $uri], 'file') extends File { + public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { + $is_public = parse_url($this->getFileUri(), PHP_URL_SCHEME) == 'public'; + $result = AccessResult::allowedIf($is_public); + $result->cachePerUser(); + $result->addCacheTags(['tag_for_' . $this->getFileUri()]); + $result->setCacheMaxAge(123); + return $return_as_object ? $result : $result->isAllowed(); + } + public function getPlugin() { + return NULL; + } + }; + $file->setFileUri($uri); + return $file; + }; + // @codingStandardsIgnoreEnd + + $storage = $this->createMock(EntityStorageInterface::class); + $map = [ + [ + ['uri' => 'public://ocean.jpg'], + [$create_image('public://ocean.jpg')], + ], + [ + ['uri' => 'private://sea.jpg'], + [$create_image('private://sea.jpg')], + ], + ]; + $storage->method('loadByProperties') + ->will($this->returnValueMap($map)); + + $entity_type_manager = $this->createMock(EntityTypeManagerInterface::class); + $entity_type_manager->method('getStorage')->willReturn($storage); + + $this->container->set('entity_type.manager', $entity_type_manager); + + // -- Public image with access check. + $build = $this->twigExtension->drupalImage('public://ocean.jpg'); + $expected_build = [ + '#uri' => 'public://ocean.jpg', + '#attributes' => [], + '#theme' => 'image', + '#cache' => [ + 'contexts' => ['user'], + 'tags' => ['tag_for_public://ocean.jpg'], + 'max-age' => 123, + ], + ]; + self::assertSame($expected_build, $build); + + // -- Public image without access check. + $build = $this->twigExtension->drupalImage('public://ocean.jpg', NULL, [], NULL, FALSE); + $expected_build = [ + '#uri' => 'public://ocean.jpg', + '#attributes' => [], + '#theme' => 'image', + '#cache' => [ + 'contexts' => [], + 'tags' => [], + 'max-age' => Cache::PERMANENT, + ], + ]; + self::assertSame($expected_build, $build); + + // -- Private image with access check. + $build = $this->twigExtension->drupalImage('private://sea.jpg'); + $expected_build = [ + '#cache' => [ + 'contexts' => ['user'], + 'tags' => ['tag_for_private://sea.jpg'], + 'max-age' => 123, + ], + ]; + self::assertSame($expected_build, $build); + + // -- Private image without access check. + $build = $this->twigExtension->drupalImage('private://sea.jpg', NULL, [], NULL, FALSE); + $expected_build = [ + '#uri' => 'private://sea.jpg', + '#attributes' => [], + '#theme' => 'image', + '#cache' => [ + 'contexts' => [], + 'tags' => [], + 'max-age' => Cache::PERMANENT, + ], + ]; + self::assertSame($expected_build, $build); + } + + /** + * Test callback. + */ + public function testView() { + + // -- Unprivileged user with access check. + $this->setUpCurrentUser(['name' => 'User 1']); + + $build = $this->twigExtension->view($this->node); + self::assertArrayNotHasKey('#node', $build); + $expected_cache = [ + 'contexts' => ['user.permissions'], + 'tags' => ['node:1'], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Unprivileged user without access check. + $build = $this->twigExtension->view($this->node, NULL, NULL, FALSE); + self::assertArrayHasKey('#node', $build); + $expected_cache = [ + 'tags' => [ + 'node:1', + 'node_view', + ], + 'contexts' => [], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user with access check. + $this->setUpCurrentUser(['name' => 'User 2'], ['access content']); + + $build = $this->twigExtension->view($this->node, NULL); + self::assertArrayHasKey('#node', $build); + $expected_cache = [ + 'tags' => [ + 'node:1', + 'node_view', + 'tag_from_twig_tweak_test_node_access', + ], + 'contexts' => [ + 'user', + 'user.permissions', + ], + 'max-age' => 50, + ]; + self::assertSame($expected_cache, $build['#cache']); + + // -- Privileged user without access check. + $build = $this->twigExtension->view($this->node, NULL, NULL, FALSE); + self::assertArrayHasKey('#node', $build); + $expected_cache = [ + 'tags' => [ + 'node:1', + 'node_view', + ], + 'contexts' => [], + 'max-age' => Cache::PERMANENT, + ]; + self::assertSame($expected_cache, $build['#cache']); + } + +} diff --git a/tests/twig_tweak_test/src/Plugin/Block/FooBlock.php b/tests/twig_tweak_test/src/Plugin/Block/FooBlock.php new file mode 100644 index 0000000..88028c4 --- /dev/null +++ b/tests/twig_tweak_test/src/Plugin/Block/FooBlock.php @@ -0,0 +1,44 @@ +getAccountName() == 'User 1'); + $result->addCacheTags(['tag_from_' . __FUNCTION__]); + $result->setCacheMaxAge(35); + $result->cachePerUser(); + return $result; + } + + /** + * {@inheritdoc} + */ + public function build() { + return [ + '#markup' => 'Foo', + '#cache' => [ + 'contexts' => ['url'], + 'tags' => ['tag_from_' . __FUNCTION__], + ], + ]; + } + +} diff --git a/tests/twig_tweak_test/twig_tweak_test.module b/tests/twig_tweak_test/twig_tweak_test.module index aef0598..744ff08 100644 --- a/tests/twig_tweak_test/twig_tweak_test.module +++ b/tests/twig_tweak_test/twig_tweak_test.module @@ -5,6 +5,9 @@ * Primary module hooks for Twig Tweak test module. */ +use Drupal\Core\Access\AccessResult; +use Drupal\node\NodeInterface; + /** * Implements hook_page_bottom(). */ @@ -26,3 +29,18 @@ function twig_tweak_test_theme() { function template_preprocess_twig_tweak_test(&$vars) { $vars['node'] = Drupal::routeMatch()->getParameter('node'); } + +/** + * Implements hook_node_access(). + * + * @see \Drupal\Tests\twig_tweak\Kernel\AccessTest + */ +function twig_tweak_test_node_access(NodeInterface $node) { + if ($node->getTitle() == 'Entity access test') { + $result = AccessResult::allowed(); + $result->addCacheTags(['tag_from_' . __FUNCTION__]); + $result->cachePerUser(); + $result->setCacheMaxAge(50); + return $result; + } +}