3 namespace Drupal\Tests\token\Kernel;
5 use Drupal\Component\Utility\Unicode;
6 use Drupal\contact\Entity\ContactForm;
7 use Drupal\Core\Entity\Entity\EntityViewMode;
8 use Drupal\Core\Render\Markup;
9 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
10 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
11 use Drupal\field\Entity\FieldConfig;
12 use Drupal\field\Entity\FieldStorageConfig;
13 use Drupal\filter\Entity\FilterFormat;
14 use Drupal\node\Entity\Node;
15 use Drupal\node\Entity\NodeType;
16 use Drupal\contact\Entity\Message;
17 use Drupal\Component\Utility\Html;
18 use Drupal\Core\Field\FieldStorageDefinitionInterface;
19 use Drupal\taxonomy\Tests\TaxonomyTestTrait;
20 use Drupal\language\Entity\ConfigurableLanguage;
27 class FieldTest extends KernelTestBase {
29 use TaxonomyTestTrait;
32 * @var \Drupal\filter\FilterFormatInterface
34 protected $testFormat;
38 * Vocabulary for testing chained token support.
40 * @var \Drupal\taxonomy\VocabularyInterface
42 protected $vocabulary;
49 public static $modules = ['node', 'text', 'field', 'filter', 'contact', 'options', 'taxonomy', 'language', 'datetime', 'datetime_range'];
54 public function setUp() {
57 $this->installEntitySchema('user');
58 $this->installEntitySchema('node');
59 $this->installEntitySchema('taxonomy_term');
61 // Create the article content type with a text field.
62 $node_type = NodeType::create([
67 $field_storage = FieldStorageConfig::create([
68 'field_name' => 'test_field',
69 'entity_type' => 'node',
72 $field_storage->save();
74 $field = FieldConfig::create([
75 'field_name' => 'test_field',
76 'entity_type' => 'node',
77 'bundle' => 'article',
78 'label' => 'Test field',
82 // Create a reference field with the same name on user.
83 $field_storage = FieldStorageConfig::create([
84 'field_name' => 'test_field',
85 'entity_type' => 'user',
86 'type' => 'entity_reference',
88 $field_storage->save();
90 $field = FieldConfig::create([
91 'field_name' => 'test_field',
92 'entity_type' => 'user',
94 'label' => 'Test field',
98 $this->testFormat = FilterFormat::create([
102 'filter_html_escape' => ['status' => TRUE],
105 $this->testFormat->save();
107 // Create a multi-value list_string field.
108 $field_storage = FieldStorageConfig::create([
109 'field_name' => 'test_list',
110 'entity_type' => 'node',
111 'type' => 'list_string',
114 'allowed_values' => [
120 $field_storage->save();
122 $this->field = FieldConfig::create([
123 'field_name' => 'test_list',
124 'entity_type' => 'node',
125 'bundle' => 'article',
128 // Add an untranslatable node reference field.
129 FieldStorageConfig::create([
130 'field_name' => 'test_reference',
131 'type' => 'entity_reference',
132 'entity_type' => 'node',
134 'target_type' => 'node',
136 'translatable' => FALSE,
138 FieldConfig::create([
139 'field_name' => 'test_reference',
140 'entity_type' => 'node',
141 'bundle' => 'article',
142 'label' => 'Test reference',
145 // Add an untranslatable taxonomy term reference field.
146 $this->vocabulary = $this->createVocabulary();
148 FieldStorageConfig::create([
149 'field_name' => 'test_term_reference',
150 'type' => 'entity_reference',
151 'entity_type' => 'node',
153 'target_type' => 'taxonomy_term',
155 'translatable' => FALSE,
157 FieldConfig::create([
158 'field_name' => 'test_term_reference',
159 'entity_type' => 'node',
160 'bundle' => 'article',
161 'label' => 'Test term reference',
163 'handler' => 'default:taxonomy_term',
164 'handler_settings' => [
165 'target_bundles' => [
166 $this->vocabulary->id() => $this->vocabulary->id(),
172 // Add a field to terms of the created vocabulary.
173 $storage = FieldStorageConfig::create([
174 'field_name' => 'term_field',
175 'entity_type' => 'taxonomy_term',
179 $field = FieldConfig::create([
180 'field_name' => 'term_field',
181 'entity_type' => 'taxonomy_term',
182 'bundle' => $this->vocabulary->id(),
186 // Add a second language.
187 $language = ConfigurableLanguage::create([
193 // Add a datetime field.
194 $field_datetime_storage = FieldStorageConfig::create(array(
195 'field_name' => 'field_datetime',
196 'type' => 'datetime',
197 'entity_type' => 'node',
198 'settings' => array('datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME),
200 $field_datetime_storage->save();
201 $field_datetime = FieldConfig::create([
202 'field_storage' => $field_datetime_storage,
203 'bundle' => 'article',
205 $field_datetime->save();
207 // Add a daterange field.
208 $field_daterange_storage = FieldStorageConfig::create(array(
209 'field_name' => 'field_daterange',
210 'type' => 'daterange',
211 'entity_type' => 'node',
212 'settings' => array('datetime_type' => DateRangeItem::DATETIME_TYPE_DATETIME),
214 $field_daterange_storage->save();
215 $field_daterange = FieldConfig::create([
216 'field_storage' => $field_daterange_storage,
217 'bundle' => 'article',
219 $field_daterange->save();
223 * Tests [entity:field_name] tokens.
225 public function testEntityFieldTokens() {
226 // Create a node with a value in its fields and test its tokens.
227 $entity = Node::create([
228 'title' => 'Test node title',
232 'format' => $this->testFormat->id(),
240 $this->assertTokens('node', ['node' => $entity], [
241 'test_field' => Markup::create('foo'),
242 'test_field:0' => Markup::create('foo'),
243 'test_field:0:value' => 'foo',
244 'test_field:value' => 'foo',
245 'test_field:0:format' => $this->testFormat->id(),
246 'test_field:format' => $this->testFormat->id(),
247 'test_list:0' => Markup::create('value1'),
248 'test_list:1' => Markup::create('value2'),
249 'test_list:0:value' => Markup::create('value1'),
250 'test_list:value' => Markup::create('value1'),
251 'test_list:1:value' => Markup::create('value2'),
254 // Verify that no third token was generated for the list_string field.
255 $this->assertNoTokens('node', ['node' => $entity], [
260 // Test the test_list token metadata.
261 $tokenService = \Drupal::service('token');
262 $token_info = $tokenService->getTokenInfo('node', 'test_list');
263 $this->assertEqual($token_info['name'], 'test_list');
264 $this->assertEqual($token_info['module'], 'token');
265 $this->assertEqual($token_info['type'], 'list<node-test_list>');
266 $typeInfo = $tokenService->getTypeInfo('list<node-test_list>');
267 $this->assertEqual($typeInfo['name'], 'List of test_list values');
268 $this->assertEqual($typeInfo['type'], 'list<node-test_list>');
270 // Create a node type that does not have test_field field.
271 $node_type = NodeType::create([
276 $node_without_test_field = Node::create([
277 'title' => 'Node without test_field',
280 $node_without_test_field->save();
282 // Ensure that trying to generate tokens for a non-existing field does not
283 // throw an exception.
284 $this->assertNoTokens('node', ['node' => $node_without_test_field], ['test_field']);
286 // Create a node without a value in the text field and test its token.
287 $entity = Node::create([
288 'title' => 'Test node title',
293 $this->assertNoTokens('node', ['node' => $entity], [
299 * Tests the token metadata for a field token.
301 public function testFieldTokenInfo() {
302 /** @var \Drupal\token\Token $tokenService */
303 $tokenService = \Drupal::service('token');
305 // Test the token info of the text field of the artcle content type.
306 $token_info = $tokenService->getTokenInfo('node', 'test_field');
307 $this->assertEqual($token_info['name'], 'Test field', 'The token info name is correct.');
308 $this->assertEqual($token_info['description'], 'Text (formatted) field.', 'The token info description is correct.');
309 $this->assertEqual($token_info['module'], 'token', 'The token info module is correct.');
311 // Now create two more content types that share the field but the last
312 // of them sets a different label. This should show an alternative label
313 // at the token info.
314 $node_type = NodeType::create([
315 'type' => 'article2',
318 $field = FieldConfig::create([
319 'field_name' => 'test_field',
320 'entity_type' => 'node',
321 'bundle' => 'article2',
322 'label' => 'Test field',
326 $node_type = NodeType::create([
327 'type' => 'article3',
330 $field = FieldConfig::create([
331 'field_name' => 'test_field',
332 'entity_type' => 'node',
333 'bundle' => 'article3',
334 'label' => 'Different test field',
338 $token_info = $tokenService->getTokenInfo('node', 'test_field');
339 $this->assertEqual($token_info['name'], 'Test field', 'The token info name is correct.');
340 $this->assertEqual((string) $token_info['description'], 'Text (formatted) field. Also known as <em class="placeholder">Different test field</em>.', 'When a field is used in several bundles with different labels, this is noted at the token info description.');
341 $this->assertEqual($token_info['module'], 'token', 'The token info module is correct.');
342 $this->assertEqual($token_info['type'], 'node-test_field', 'The field property token info type is correct.');
344 // Test field property token info.
345 $token_info = $tokenService->getTokenInfo('node-test_field', 'value');
346 $this->assertEqual($token_info['name'], 'Text', 'The field property token info name is correct.');
347 // This particular field property description happens to be empty.
348 $this->assertEqual((string) $token_info['description'], '', 'The field property token info description is correct.');
349 $this->assertEqual($token_info['module'], 'token', 'The field property token info module is correct.');
353 * Test tokens on node with the token view mode overriding default formatters.
355 public function testTokenViewMode() {
356 $value = 'A really long string that should be trimmed by the special formatter on token view we are going to have.';
358 // The formatter we are going to use will eventually call Unicode::strlen.
359 // This expects that the Unicode has already been explicitly checked, which
360 // happens in DrupalKernel. But since that doesn't run in kernel tests, we
361 // explicitly call this here.
364 // Create a node with a value in the text field and test its token.
365 $entity = Node::create([
366 'title' => 'Test node title',
370 'format' => $this->testFormat->id(),
375 $this->assertTokens('node', ['node' => $entity], [
376 'test_field' => Markup::create($value),
379 // Now, create a token view mode which sets a different format for
380 // test_field. When replacing tokens, this formatter should be picked over
381 // the default formatter for the field type.
382 // @see field_tokens().
383 $view_mode = EntityViewMode::create([
384 'id' => 'node.token',
385 'targetEntityType' => 'node',
388 $entity_display = entity_get_display('node', 'article', 'token');
389 $entity_display->setComponent('test_field', [
390 'type' => 'text_trimmed',
395 $entity_display->save();
397 $this->assertTokens('node', ['node' => $entity], [
398 'test_field' => Markup::create(substr($value, 0, 50)),
403 * Test that tokens are properly created for an entity's base fields.
405 public function testBaseFieldTokens() {
406 // Create a new contact_message entity and verify that tokens are generated
407 // for its base fields. The contact_message entity type is used because it
408 // provides no tokens by default.
409 $contact_form = ContactForm::create([
412 $contact_form->save();
414 $entity = Message::create([
415 'contact_form' => 'form_id',
418 'name' => 'Test name',
419 'mail' => 'Test mail',
420 'subject' => 'Test subject',
421 'message' => 'Test message',
425 $this->assertTokens('contact_message', ['contact_message' => $entity], [
426 'uuid' => Markup::create('123'),
427 'langcode' => Markup::create('English'),
428 'name' => Markup::create('Test name'),
429 'mail' => Markup::create('Test mail'),
430 'subject' => Markup::create('Test subject'),
431 'message' => Markup::create('Test message'),
435 // Test the metadata of one of the tokens.
436 $tokenService = \Drupal::service('token');
437 $token_info = $tokenService->getTokenInfo('contact_message', 'subject');
438 $this->assertEquals($token_info['name'], 'Subject');
439 $this->assertEquals($token_info['description'], 'Text (plain) field.');
440 $this->assertEquals($token_info['module'], 'token');
442 // Verify that node entity type doesn't have a uid token.
443 $this->assertNull($tokenService->getTokenInfo('node', 'uid'));
447 * Tests chaining entity reference tokens.
449 public function testEntityReferenceTokens() {
450 $reference = Node::create([
451 'title' => 'Test node to reference',
455 'format' => $this->testFormat->id(),
459 $term_reference_field_value = $this->randomString();
460 $term_reference = $this->createTerm($this->vocabulary, [
461 'name' => 'Term to reference',
463 'value' => $term_reference_field_value,
464 'format' => $this->testFormat->id(),
467 $entity = Node::create([
468 'title' => 'Test entity reference',
470 'test_reference' => ['target_id' => $reference->id()],
471 'test_term_reference' => ['target_id' => $term_reference->id()],
475 $this->assertTokens('node', ['node' => $entity], [
476 'test_reference:entity:title' => Markup::create('Test node to reference'),
477 'test_reference:entity:test_field' => Markup::create('foo'),
478 'test_term_reference:entity:term_field' => Html::escape($term_reference_field_value),
479 'test_reference:target_id' => $reference->id(),
480 'test_term_reference:target_id' => $term_reference->id(),
481 'test_term_reference:entity:url:path' => '/' . $term_reference->toUrl('canonical')->getInternalPath(),
482 // Expects the entity's label to be returned for :entity tokens.
483 'test_reference:entity' => $reference->label(),
484 'test_term_reference:entity' => $term_reference->label(),
487 // Test some non existent tokens.
488 $this->assertNoTokens('node', ['node' => $entity], [
489 'test_reference:1:title',
490 'test_reference:entity:does_not_exist',
491 'test_reference:does_not:exist',
492 'test_term_reference:does_not_exist',
493 'test_term_reference:does:not:exist',
494 'test_term_reference:does_not_exist:0',
495 'non_existing_field:entity:title',
498 /** @var \Drupal\token\Token $token_service */
499 $token_service = \Drupal::service('token');
501 $token_info = $token_service->getTokenInfo('node', 'test_reference');
502 $this->assertEquals('Test reference', $token_info['name']);
503 $this->assertEquals('Entity reference field.', (string) $token_info['description']);
504 $this->assertEquals('token', $token_info['module']);
505 $this->assertEquals('node-test_reference', $token_info['type']);
507 // Test target_id field property token info.
508 $token_info = $token_service->getTokenInfo('node-test_reference', 'target_id');
509 $this->assertEquals('Content ID', $token_info['name']);
510 $this->assertEquals('token', $token_info['module']);
511 $this->assertEquals('token', $token_info['module']);
513 // Test entity field property token info.
514 $token_info = $token_service->getTokenInfo('node-test_reference', 'entity');
515 $this->assertEquals('Content', $token_info['name']);
516 $this->assertEquals('The referenced entity', $token_info['description']);
517 $this->assertEquals('token', $token_info['module']);
518 $this->assertEquals('node', $token_info['type']);
520 // Test entity field property token info of the term reference.
521 $token_info = $token_service->getTokenInfo('node-test_term_reference', 'entity');
522 $this->assertEquals('Taxonomy term', $token_info['name']);
523 $this->assertEquals('The referenced entity', $token_info['description']);
524 $this->assertEquals('token', $token_info['module']);
525 $this->assertEquals('term', $token_info['type']);
530 * Tests support for cardinality > 1 for entity reference tokens.
532 public function testEntityReferenceTokensCardinality() {
533 /** @var \Drupal\field\FieldStorageConfigInterface $storage */
534 $storage = FieldStorageConfig::load('node.test_term_reference');
535 $storage->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
541 foreach (range(1, 3) as $i) {
542 $terms_value[$i] = $this->randomString();
543 $terms[$i] = $this->createTerm($this->vocabulary, [
544 'name' => $this->randomString(),
546 'value' => $terms_value[$i],
547 'format' => $this->testFormat->id(),
552 $entity = Node::create([
553 'title' => 'Test multivalue chained tokens',
555 'test_term_reference' => [
556 ['target_id' => $terms[1]->id()],
557 ['target_id' => $terms[2]->id()],
558 ['target_id' => $terms[3]->id()],
563 $this->assertTokens('node', ['node' => $entity], [
564 'test_term_reference:0:entity:term_field' => Html::escape($terms[1]->term_field->value),
565 'test_term_reference:1:entity:term_field' => Html::escape($terms[2]->term_field->value),
566 'test_term_reference:2:entity:term_field' => Html::escape($terms[3]->term_field->value),
567 'test_term_reference:0:target_id' => $terms[1]->id(),
568 'test_term_reference:1:target_id' => $terms[2]->id(),
569 'test_term_reference:2:target_id' => $terms[3]->id(),
570 // Expects the entity's label to be returned for :entity tokens.
571 'test_term_reference:0:entity' => $terms[1]->label(),
572 'test_term_reference:1:entity' => $terms[2]->label(),
573 'test_term_reference:2:entity' => $terms[3]->label(),
574 // To make sure tokens without an explicit delta can also be replaced in
575 // the same token replacement call.
576 'test_term_reference:entity:term_field' => Html::escape($terms[1]->term_field->value),
577 'test_term_reference:target_id' => $terms[1]->id(),
580 // Test some non existent tokens.
581 $this->assertNoTokens('node', ['node' => $entity], [
582 'test_term_reference:3:term_field',
583 'test_term_reference:0:does_not_exist',
584 'test_term_reference:1:does:not:exist',
585 'test_term_reference:1:2:does_not_exist',
590 * Test tokens for multilingual fields and entities.
592 public function testMultilingualFields() {
593 // Create an english term and add a german translation for it.
594 $term = $this->createTerm($this->vocabulary, [
595 'name' => 'english-test-term',
598 'value' => 'english-term-field-value',
599 'format' => $this->testFormat->id(),
602 $term->addTranslation('de', [
603 'name' => 'german-test-term',
605 'value' => 'german-term-field-value',
606 'format' => $this->testFormat->id(),
609 $german_term = $term->getTranslation('de');
611 // Create an english node, add a german translation for it and add the
612 // english term to the english node's entity reference field and the
613 // german term to the german's entity reference field.
614 $node = Node::create([
615 'title' => 'english-node-title',
617 'test_term_reference' => [
618 'target_id' => $term->id(),
621 'value' => 'test-english-field',
622 'format' => $this->testFormat->id(),
625 $node->addTranslation('de', [
626 'title' => 'german-node-title',
627 'test_term_reference' => [
628 'target_id' => $german_term->id(),
631 'value' => 'test-german-field',
632 'format' => $this->testFormat->id(),
636 // Verify the :title token of the english node and the :name token of the
637 // english term it refers to. Also verify the value of the term's field.
638 $this->assertTokens('node', ['node' => $node], [
639 'title' => 'english-node-title',
640 'test_term_reference:entity:name' => 'english-test-term',
641 'test_term_reference:entity:term_field:value' => 'english-term-field-value',
642 'test_term_reference:entity:term_field' => 'english-term-field-value',
643 'test_field' => 'test-english-field',
644 'test_field:value' => 'test-english-field',
647 // Same test for the german node and its german term.
648 $german_node = $node->getTranslation('de');
649 $this->assertTokens('node', ['node' => $german_node], [
650 'title' => 'german-node-title',
651 'test_term_reference:entity:name' => 'german-test-term',
652 'test_term_reference:entity:term_field:value' => 'german-term-field-value',
653 'test_term_reference:entity:term_field' => 'german-term-field-value',
654 'test_field' => 'test-german-field',
655 'test_field:value' => 'test-german-field',
658 // If the langcode is specified, it should have priority over the node's
661 'test_field' => 'test-german-field',
662 'test_field:value' => 'test-german-field',
663 'test_term_reference:entity:term_field' => 'german-term-field-value',
664 'test_term_reference:entity:term_field:value' => 'german-term-field-value',
666 $this->assertTokens('node', ['node' => $node], $tokens, ['langcode' => 'de']);
670 * Tests support for a datetime fields.
672 public function testDatetimeFieldTokens() {
674 $node = Node::create([
675 'title' => 'Node for datetime field',
679 $node->set('field_datetime', '1925-09-28T00:00:00')->save();
680 $this->assertTokens('node', ['node' => $node], [
681 'field_datetime:date:custom:Y' => '1925',
682 'field_datetime:date:html_month' => '1925-09',
683 'field_datetime:date' => $node->field_datetime->date->getTimestamp(),
688 * Tests support for a daterange fields.
690 public function testDatetimeRangeFieldTokens() {
692 $node = Node::create([
693 'title' => 'Node for daterange field',
697 $node->field_daterange->value = '2013-12-22T00:00:00';
698 $node->field_daterange->end_value = '2016-08-26T00:00:00';
700 $this->assertTokens('node', ['node' => $node], [
701 'field_daterange:start_date:html_month' => '2013-12',
702 'field_daterange:start_date:custom:Y' => '2013',
703 'field_daterange:end_date:custom:Y' => '2016',
704 'field_daterange:start_date' => $node->field_daterange->start_date->getTimestamp(),