Browse Source

Buble access result cache metadata

merge-requests/2/head
Chi 5 years ago
parent
commit
d15f246bd9
  1. 201
      src/TwigExtension.php
  2. 598
      tests/src/Kernel/AccessTest.php
  3. 44
      tests/twig_tweak_test/src/Plugin/Block/FooBlock.php
  4. 18
      tests/twig_tweak_test/twig_tweak_test.module

201
src/TwigExtension.php

@ -5,8 +5,10 @@ namespace Drupal\twig_tweak;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Unicode;
use Drupal\Component\Uuid\Uuid; use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Block\TitleBlockPluginInterface; use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemInterface;
@ -140,42 +142,39 @@ class TwigExtension extends \Twig_Extension {
\Drupal::service('context.handler')->applyContextMapping($block_plugin, $contexts); \Drupal::service('context.handler')->applyContextMapping($block_plugin, $contexts);
} }
if (!$block_plugin->access(\Drupal::currentUser())) { $build = [];
return; $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. $build['content'] = $block_plugin->build();
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 = [ if ($block_plugin instanceof TitleBlockPluginInterface) {
'content' => $block_plugin->build(), $build['content']['#cache']['contexts'][] = 'url';
'#cache' => [ }
'contexts' => $block_plugin->getCacheContexts(),
'tags' => $block_plugin->getCacheTags(),
'max-age' => $block_plugin->getCacheMaxAge(),
],
];
if ($block_plugin instanceof TitleBlockPluginInterface) { if ($wrapper && !Element::isEmpty($build['content'])) {
$build['#cache']['contexts'][] = 'url'; $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'])) { CacheableMetadata::createFromRenderArray($build)
$build += [ ->merge(CacheableMetadata::createFromObject($access))
'#theme' => 'block', ->applyTo($build);
'#attributes' => [],
'#contextual_links' => [],
'#configuration' => $block_plugin->getConfiguration(),
'#plugin_id' => $block_plugin->getPluginId(),
'#base_plugin_id' => $block_plugin->getBaseId(),
'#derivative_plugin_id' => $block_plugin->getDerivativeId(),
];
}
return $build; return $build;
} }
@ -202,6 +201,7 @@ class TwigExtension extends \Twig_Extension {
* A render array to display the region content. * A render array to display the region content.
*/ */
public function drupalRegion($region, $theme = NULL) { public function drupalRegion($region, $theme = NULL) {
$entity_type_manager = \Drupal::entityTypeManager(); $entity_type_manager = \Drupal::entityTypeManager();
$blocks = $entity_type_manager->getStorage('block')->loadByProperties([ $blocks = $entity_type_manager->getStorage('block')->loadByProperties([
'region' => $region, 'region' => $region,
@ -212,9 +212,13 @@ class TwigExtension extends \Twig_Extension {
$build = []; $build = [];
$cache_metadata = new CacheableMetadata();
/* @var $blocks \Drupal\block\BlockInterface[] */ /* @var $blocks \Drupal\block\BlockInterface[] */
foreach ($blocks as $id => $block) { 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(); $block_plugin = $block->getPlugin();
if ($block_plugin instanceof TitleBlockPluginInterface) { if ($block_plugin instanceof TitleBlockPluginInterface) {
$request = \Drupal::request(); $request = \Drupal::request();
@ -229,6 +233,7 @@ class TwigExtension extends \Twig_Extension {
if ($build) { if ($build) {
$build['#region'] = $region; $build['#region'] = $region;
$build['#theme_wrappers'] = ['region']; $build['#theme_wrappers'] = ['region'];
$cache_metadata->applyTo($build);
} }
return $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); @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); $entity = \Drupal::routeMatch()->getParameter($entity_type);
} }
if ($entity && (!$check_access || $entity->access('view'))) {
$render_controller = $entity_type_manager->getViewBuilder($entity_type); $build = [];
return $render_controller->view($entity, $view_mode, $langcode);
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); $entity = $entity_storage->create($values);
$operation = 'create'; $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. * 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) { public function drupalField($field_name, $entity_type, $id = NULL, $view_mode = 'default', $langcode = NULL, $check_access = TRUE) {
$entity_type_manager = \Drupal::entityTypeManager();
if ($id) { if ($id) {
$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($id); $entity = $entity_type_manager->getStorage($entity_type)->load($id);
} }
else { 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); @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); $entity = \Drupal::routeMatch()->getParameter($entity_type);
} }
if ($entity && (!$check_access || $entity->access('view'))) {
$entity = \Drupal::service('entity.repository') $build = [];
->getTranslationFromContext($entity, $langcode);
if (isset($entity->{$field_name})) { if ($entity) {
return $entity->{$field_name}->view($view_mode); $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') ->getStorage('file')
->loadByProperties([$property_type => $property]); ->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) { if (count($files) != 1) {
return; return $build;
} }
$file = reset($files); $file = reset($files);
if ($check_access && !$file->access('view')) { $access = $check_access ? $file->access('view', NULL, TRUE) : AccessResult::allowed();
return;
}
$build = [
'#uri' => $file->getFileUri(),
'#attributes' => $attributes,
];
if ($style) { if ($access->isAllowed()) {
if ($responsive) { $build['#uri'] = $file->getFileUri();
$build['#type'] = 'responsive_image'; $build['#attributes'] = $attributes;
$build['#responsive_image_style_id'] = $style; if ($style) {
if ($responsive) {
$build['#type'] = 'responsive_image';
$build['#responsive_image_style_id'] = $style;
}
else {
$build['#theme'] = 'image_style';
$build['#style_name'] = $style;
}
} }
else { else {
$build['#theme'] = 'image_style'; $build['#theme'] = 'image';
$build['#style_name'] = $style;
} }
} }
else {
$build['#theme'] = 'image'; CacheableMetadata::createFromRenderArray($build)
} ->merge(CacheableMetadata::createFromObject($access))
->applyTo($build);
return $build; return $build;
} }
@ -637,8 +686,8 @@ class TwigExtension extends \Twig_Extension {
* @param bool $check_access * @param bool $check_access
* (optional) Indicates that access check is required. * (optional) Indicates that access check is required.
* *
* @return \Drupal\Core\Url * @return \Drupal\Core\Url|null
* A new Url object based on user input. * A new Url object or null if the URL is not accessible.
* *
* @see \Drupal\Core\Url::fromUserInput() * @see \Drupal\Core\Url::fromUserInput()
*/ */
@ -679,8 +728,8 @@ class TwigExtension extends \Twig_Extension {
* @param bool $check_access * @param bool $check_access
* (optional) Indicates that access check is required. * (optional) Indicates that access check is required.
* *
* @return \Drupal\Core\Link * @return \Drupal\Core\Link|null
* A new Link object. * A new Link object or null of the URL is not accessible.
* *
* @see \Drupal\Core\Link::fromTextAndUrl() * @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 * (optional) For which language the entity should be rendered, defaults to
* the current content language. * the current content language.
* @param bool $check_access * @param bool $check_access
* (optional) Indicates that access check is required. * (optional) Indicates that access check for an entity is required.
* *
* @return array * @return array
* A render array to represent the object. * A render array to represent the object.
*/ */
public function view($object, $display_options = 'default', $langcode = NULL, $check_access = TRUE) { public function view($object, $display_options = 'default', $langcode = NULL, $check_access = TRUE) {
$build = [];
if ($object instanceof FieldItemListInterface || $object instanceof FieldItemInterface) { if ($object instanceof FieldItemListInterface || $object instanceof FieldItemInterface) {
return $object->view($display_options); return $object->view($display_options);
} }
elseif ($object instanceof EntityInterface && (!$check_access || $object->access('view'))) { elseif ($object instanceof EntityInterface) {
return \Drupal::entityTypeManager() $build = [];
->getViewBuilder($object->getEntityTypeId()) $access = $check_access ? $object->access('view', NULL, TRUE) : AccessResult::allowed();
->view($object, $display_options, $langcode); 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;
} }
/** /**

598
tests/src/Kernel/AccessTest.php

@ -0,0 +1,598 @@
<?php
namespace Drupal\Tests\twig_tweak\Kernel;
use Drupal\block\BlockViewBuilder;
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests for the Twig Tweak access control.
*
* @group twig_tweak
*/
class AccessTest extends KernelTestBase {
use UserCreationTrait;
/**
* A node for testing.
*
* @var \Drupal\node\NodeInterface
*/
private $node;
/**
* The Twig extension.
*
* @var \Drupal\twig_tweak\TwigExtension
*/
private $twigExtension;
/**
* {@inheritdoc}
*/
public static $modules = [
'twig_tweak',
'twig_tweak_test',
'node',
'file',
'user',
'system',
'block',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->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']);
}
}

44
tests/twig_tweak_test/src/Plugin/Block/FooBlock.php

@ -0,0 +1,44 @@
<?php
namespace Drupal\twig_tweak_test\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;
/**
* Provides a foo block.
*
* @Block(
* id = "twig_tweak_test_foo",
* admin_label = @Translation("Foo"),
* category = @Translation("Twig Tweak")
* )
*/
class FooBlock extends BlockBase {
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
$result = AccessResult::allowedIf($account->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__],
],
];
}
}

18
tests/twig_tweak_test/twig_tweak_test.module

@ -5,6 +5,9 @@
* Primary module hooks for Twig Tweak test module. * Primary module hooks for Twig Tweak test module.
*/ */
use Drupal\Core\Access\AccessResult;
use Drupal\node\NodeInterface;
/** /**
* Implements hook_page_bottom(). * Implements hook_page_bottom().
*/ */
@ -26,3 +29,18 @@ function twig_tweak_test_theme() {
function template_preprocess_twig_tweak_test(&$vars) { function template_preprocess_twig_tweak_test(&$vars) {
$vars['node'] = Drupal::routeMatch()->getParameter('node'); $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;
}
}

Loading…
Cancel
Save