Compare commits

...

2 Commits
3.x ... 8.x-1.x

  1. 29
      composer.json
  2. 68
      src/TwigExtension.php
  3. 6
      tests/src/Functional/TwigTweakTest.php
  4. 212
      tests/src/Kernel/AccessTest.php
  5. 18
      tests/twig_tweak_test/twig_tweak_test.module

29
composer.json

@ -1,15 +1,18 @@
{
"name": "drupal/twig_tweak",
"type": "drupal-module",
"description": "A Twig extension with some useful functions and filters for Drupal development.",
"keywords": ["Drupal", "Twig"],
"license": "GPL-2.0+",
"homepage": "https://www.drupal.org/project/twig_tweak",
"suggest": {
"symfony/var-dumper": "better dump() function for debugging Twig variables"
},
"support": {
"issues": "https://www.drupal.org/project/issues/twig_tweak",
"source": "http://cgit.drupalcode.org/twig_tweak"
}
"name": "drupal/twig_tweak",
"type": "drupal-module",
"description": "A Twig extension with some useful functions and filters for Drupal development.",
"keywords": ["Drupal", "Twig"],
"license": "GPL-2.0+",
"homepage": "https://www.drupal.org/project/twig_tweak",
"suggest": {
"symfony/var-dumper": "better dump() function for debugging Twig variables"
},
"support": {
"issues": "https://www.drupal.org/project/issues/twig_tweak",
"source": "http://cgit.drupalcode.org/twig_tweak"
},
"require": {
"drupal/core": "^8.5"
}
}

68
src/TwigExtension.php

@ -2,7 +2,9 @@
namespace Drupal\twig_tweak;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
@ -80,8 +82,16 @@ class TwigExtension extends \Twig_Extension {
public function drupalBlock($id, $check_access = TRUE) {
$entity_type_manager = \Drupal::entityTypeManager();
$block = $entity_type_manager->getStorage('block')->load($id);
if ($block && (!$check_access || $this->entityAccess($block))) {
return $entity_type_manager->getViewBuilder('block')->view($block);
if ($block) {
$access = $check_access ? $this->entityAccess($block) : AccessResult::allowed();
if ($access->isAllowed()) {
$build = $entity_type_manager->getViewBuilder('block')->view($block);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($block))
->merge(CacheableMetadata::createFromObject($access))
->applyTo($build);
return $build;
}
}
}
@ -108,9 +118,13 @@ class TwigExtension extends \Twig_Extension {
$build = [];
$cache_metadata = new CacheableMetadata();
/* @var $blocks \Drupal\block\BlockInterface[] */
foreach ($blocks as $id => $block) {
if ($this->entityAccess($block)) {
$access = $this->entityAccess($block);
$cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($access));
if ($access->isAllowed()) {
$block_plugin = $block->getPlugin();
if ($block_plugin instanceof TitleBlockPluginInterface) {
$request = \Drupal::request();
@ -122,6 +136,10 @@ class TwigExtension extends \Twig_Extension {
}
}
if ($build) {
$cache_metadata->applyTo($build);
}
return $build;
}
@ -146,9 +164,18 @@ class TwigExtension extends \Twig_Extension {
$entity = $id
? $entity_type_manager->getStorage($entity_type)->load($id)
: \Drupal::routeMatch()->getParameter($entity_type);
if ($entity && $this->entityAccess($entity)) {
$render_controller = $entity_type_manager->getViewBuilder($entity_type);
return $render_controller->view($entity, $view_mode, $langcode);
if ($entity) {
$access = $this->entityAccess($entity);
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;
}
}
}
@ -174,12 +201,20 @@ class TwigExtension extends \Twig_Extension {
$entity = $id
? \Drupal::entityTypeManager()->getStorage($entity_type)->load($id)
: \Drupal::routeMatch()->getParameter($entity_type);
if ($entity && $this->entityAccess($entity)) {
if ($langcode && $entity->hasTranslation($langcode)) {
$entity = $entity->getTranslation($langcode);
}
if (isset($entity->{$field_name})) {
return $entity->{$field_name}->view($view_mode);
if ($entity) {
$access = $this->entityAccess($entity);
if ($access->isAllowed()) {
if ($langcode && $entity->hasTranslation($langcode)) {
$entity = $entity->getTranslation($langcode);
}
if (isset($entity->{$field_name})) {
$build = $entity->{$field_name}->view($view_mode);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($access))
->merge(CacheableMetadata::createFromObject($entity))
->applyTo($build);
return $build;
}
}
}
}
@ -312,11 +347,9 @@ class TwigExtension extends \Twig_Extension {
*
* @return array
* A render array to disable caching.
*
* @see drupal_set_message()
*/
public function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
drupal_set_message($message, $type, $repeat);
\Drupal::messenger()->addMessage($message, $type, $repeat);
$build['#cache']['max-age'] = 0;
return $build;
}
@ -483,7 +516,7 @@ class TwigExtension extends \Twig_Extension {
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity to check access.
*
* @return bool
* @return \Drupal\Core\Access\AccessResultInterface
* The access check result.
*
* @TODO Remove "check_access" option in 9.x.
@ -491,7 +524,8 @@ class TwigExtension extends \Twig_Extension {
protected function entityAccess(EntityInterface $entity) {
// Prior version 8.x-1.7 entity access was not checked. The "check_access"
// option provides a workaround for possible BC issues.
return !Settings::get('twig_tweak_check_access', TRUE) || $entity->access('view');
return Settings::get('twig_tweak_check_access', TRUE) ?
$entity->access('view', NULL, TRUE) : AccessResult::allowed();
}
}

6
tests/src/Functional/TwigTweakTest.php

@ -12,6 +12,11 @@ use Drupal\Tests\BrowserTestBase;
*/
class TwigTweakTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'classy';
/**
* {@inheritdoc}
*/
@ -73,7 +78,6 @@ class TwigTweakTest extends BrowserTestBase {
// Test region.
$xpath = '//div[@class = "tt-region"]';
$xpath .= '/div[contains(@class, "block-page-title-block") and h1[@class="page-title" and text() = "Log in"]]';
$xpath .= '/following-sibling::div[@class="messages messages--warning" and contains(., "Hi!")]';
$xpath .= '/following-sibling::div[contains(@class, "block-system-powered-by-block")]/span[. = "Powered by Drupal"]';
$this->assertByXpath($xpath);

212
tests/src/Kernel/AccessTest.php

@ -0,0 +1,212 @@
<?php
namespace Drupal\Tests\twig_tweak\Kernel;
use Drupal\block\BlockViewBuilder;
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
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',
'user',
'system',
];
/**
* {@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.
$this->setUpCurrentUser(['name' => 'User 1']);
$build = $this->twigExtension->drupalEntity('node', $this->node->id());
self::assertNull($build);
// -- Privileged user.
$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']);
}
/**
* Test callback.
*/
public function testDrupalField() {
// -- Unprivileged user.
$this->setUpCurrentUser(['name' => 'User 1']);
$build = $this->twigExtension->drupalField('title', 'node', $this->node->id());
self::assertNull($build);
// -- Privileged user.
$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']);
}
/**
* 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'],
],
],
'#cache' => [
'contexts' => ['user'],
'tags' => [
'tag_for_block_1',
'tag_for_block_2',
],
'max-age' => 123,
],
];
self::assertSame($expected_build, $build);
}
}

18
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().
*/
@ -25,3 +28,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;
}
}

Loading…
Cancel
Save