View source
<?php
namespace Drupal\Tests\typed_data\Kernel;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
class PlaceholderResolverTest extends KernelTestBase {
protected $typedDataManager;
protected $node;
protected $entityTypeManager;
protected $placeholderResolver;
protected $simpleTestContext;
protected static $modules = [
'typed_data',
'system',
'node',
'field',
'text',
'user',
'typed_data_global_context_test',
];
protected function setUp() : void {
parent::setUp();
$this
->installEntitySchema('user');
$this
->installEntitySchema('node');
$this
->installSchema('system', [
'sequences',
]);
$this
->installConfig([
'system',
]);
$this->typedDataManager = $this->container
->get('typed_data_manager');
$this->entityTypeManager = $this->container
->get('entity_type.manager');
$this->placeholderResolver = $this->container
->get('typed_data.placeholder_resolver');
$this->simpleTestContext = $this->container
->get('typed_data_global_context_test.simple_test_context');
$this->entityTypeManager
->getStorage('node_type')
->create([
'type' => 'page',
])
->save();
FieldStorageConfig::create([
'field_name' => 'field_integer',
'type' => 'integer',
'entity_type' => 'node',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
])
->save();
FieldConfig::create([
'field_name' => 'field_integer',
'entity_type' => 'node',
'bundle' => 'page',
])
->save();
$this->node = $this->entityTypeManager
->getStorage('node')
->create([
'title' => 'test',
'type' => 'page',
]);
}
public function testScanningForPlaceholders() {
$text = 'token {{example.foo}} and {{example.foo.bar}} just as {{example.foo|default(bar)}} and {{ example.whitespace }}';
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'example' => [
'foo' => '{{example.foo}}',
'foo.bar' => '{{example.foo.bar}}',
'foo|default(bar)' => '{{example.foo|default(bar)}}',
'whitespace' => '{{ example.whitespace }}',
],
], $placeholders);
$text = "text {{ date | filter }} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'date' => [
'| filter' => '{{ date | filter }}',
],
], $placeholders);
$text = "text {{ date | filter }} text {{ date }}";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'date' => [
'| filter' => '{{ date | filter }}',
'' => '{{ date }}',
],
], $placeholders);
$text = "text {{ node.title.value | lower }} text {{ node.title.value }}";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'node' => [
'title.value | lower' => '{{ node.title.value | lower }}',
'title.value' => '{{ node.title.value }}',
],
], $placeholders);
$text = "global context variable token {{ @service_id:context.property }}";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'@service_id:context' => [
'property' => '{{ @service_id:context.property }}',
],
], $placeholders);
$text = "global context variable token {{ @service.id:context.property }}";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'@service.id:context' => [
'property' => '{{ @service.id:context.property }}',
],
], $placeholders);
}
public function testEmptyPlaceholders() {
$text = 'text {{ }} text';
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'' => [
'' => '{{ }}',
],
], $placeholders);
}
public function testNoPlaceholders() {
$text = 'test text does not have any placeholders';
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([], $placeholders);
}
public function testMalformedPlaceholders() {
$text = "text {{ node. title }} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([], $placeholders);
$text = "text {{ node .title }} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([], $placeholders);
$text = "text {{node.}} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([], $placeholders);
$text = "text {{ node| }} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([], $placeholders);
$text = "text {{ no de }} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([], $placeholders);
}
public function testFilterOnly() {
$text = "text {{ |filter }} text";
$placeholders = $this->placeholderResolver
->scan($text);
$this
->assertEquals([
'' => [
'|filter' => '{{ |filter }}',
],
], $placeholders);
}
public function testResolvingPlaceholders() {
$text = 'test {{node.title}} and {{node.title.value}}';
$result = $this->placeholderResolver
->resolvePlaceholders($text, [
'node' => $this->node
->getTypedData(),
]);
$expected = [
'{{node.title}}' => 'test',
'{{node.title.value}}' => 'test',
];
$this
->assertEquals($expected, $result);
$this->node->title->value = 'tEsT';
$result = $this->placeholderResolver
->resolvePlaceHolders("test {{ node.title.value | lower }} and {{ node.title.value }}", [
'node' => $this->node
->getTypedData(),
]);
$expected = [
'{{ node.title.value | lower }}' => 'test',
'{{ node.title.value }}' => 'tEsT',
];
$this
->assertEquals($expected, $result);
$text = 'test {{string}}';
$result = $this->placeholderResolver
->resolvePlaceholders($text, [
'string' => $this->typedDataManager
->create(DataDefinition::create('string'), 'replacement'),
]);
$expected = [
'{{string}}' => 'replacement',
];
$this
->assertEquals($expected, $result);
$text = 'test {{ @typed_data_global_context_test.simple_test_context:dragons }}';
$context = $this->simpleTestContext
->getRuntimeContexts([
'dragons',
]);
$result = $this->placeholderResolver
->resolvePlaceholders($text, [
'@typed_data_global_context_test.simple_test_context:dragons' => $context['dragons']
->getContextData(),
]);
$expected = [
'{{ @typed_data_global_context_test.simple_test_context:dragons }}' => 'Dragons are better than unicorns!',
];
$this
->assertEquals($expected, $result);
}
public function testReplacePlaceholders() {
$text = 'test {{node.title}} and {{node.title.value}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test test and test', $result);
}
public function testPlaceholdersAcrossReferences() {
$user = $this->entityTypeManager
->getStorage('user')
->create([
'name' => 'test',
'type' => 'user',
]);
$this->node->uid->entity = $user;
$text = 'test {{node.title}} and {{node.uid.entity.name}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test test and test', $result);
}
public function testPlaceholdersWithMissingData() {
$text = 'test {{node.title.1.value}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
], NULL, []);
$this
->assertEquals('test {{node.title.1.value}}', $result);
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
], NULL, [
'clear' => FALSE,
]);
$this
->assertEquals('test {{node.title.1.value}}', $result);
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
], NULL, [
'clear' => TRUE,
]);
$this
->assertEquals('test ', $result);
}
public function testStringEncoding() {
$this->node->title->value = '<b>XSS</b>';
$text = 'test {{node.title}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test ' . new HtmlEscapedText('<b>XSS</b>'), $result);
}
public function testIntegerPlaceholder() {
$this->node->field_integer->value = 3;
$text = 'test {{node.field_integer.0.value}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test 3', $result);
}
public function testListPlaceholder() {
$this->node->field_integer = [
1,
2,
];
$text = 'test {{node.field_integer}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test 1, 2', $result);
}
public function testApplyingFilters() {
$this->node->field_integer = [
1,
2,
NULL,
];
$this->node->title->value = NULL;
$result = $this->placeholderResolver
->replacePlaceHolders("test {{node.field_integer.2.value|default('0')}}", [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test 0', $result);
$result = $this->placeholderResolver
->replacePlaceHolders("test {{node.title.value|default('tEsT')|lower}}", [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test test', $result);
$result = $this->placeholderResolver
->replacePlaceHolders("test {{ node.title.value | default('tEsT') | lower }}", [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test test', $result);
$this->node->title->value = 'tEsT';
$result = $this->placeholderResolver
->replacePlaceHolders("test {{ node.title.value | lower }} and {{ node.title.value }}", [
'node' => $this->node
->getTypedData(),
]);
$this
->assertEquals('test test and tEsT', $result);
$text = 'test {{string | lower}}';
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'string' => $this->typedDataManager
->create(DataDefinition::create('string'), 'Replacement'),
]);
$this
->assertEquals('test replacement', $result);
$text = "The year is {{ date | format_date('custom', 'Y') }}.";
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'date' => $this->typedDataManager
->create(DataDefinition::create('timestamp'), '3600'),
]);
$this
->assertEquals('The year is 1970.', $result);
}
public function testBubbleableMetadata() {
$bubbleable_metadata = new BubbleableMetadata();
$this->node
->save();
$this->placeholderResolver
->replacePlaceHolders('test {{node.field_integer}}', [
'node' => $this->node
->getTypedData(),
], $bubbleable_metadata);
$expected = [
'node:' . $this->node
->id(),
];
$this
->assertEquals($expected, $bubbleable_metadata
->getCacheTags());
$bubbleable_metadata = new BubbleableMetadata();
$this->placeholderResolver
->replacePlaceHolders("test {{ node.created.value | format_date('medium') }}", [
'node' => $this->node
->getTypedData(),
], $bubbleable_metadata);
$expected = Cache::mergeTags([
'node:' . $this->node
->id(),
], DateFormat::load('medium')
->getCacheTags());
$this
->assertEquals($expected, $bubbleable_metadata
->getCacheTags());
}
public function testGlobalContextVariable() {
$text = 'test {{ @typed_data_global_context_test.simple_test_context:dragons }}';
$context = $this->simpleTestContext
->getRuntimeContexts([
'dragons',
]);
$result = $this->placeholderResolver
->replacePlaceHolders($text, [
'@typed_data_global_context_test.simple_test_context:dragons' => $context['dragons']
->getContextData(),
]);
$this
->assertEquals('test Dragons are better than unicorns!', $result);
}
}