diff --git a/address.module b/address.module index 239590a..89b4b29 100644 --- a/address.module +++ b/address.module @@ -67,3 +67,14 @@ function _address_update_entity($entity, $field_name) { $address->dependent_locality = $update_helper::updateSubdivision($address->dependent_locality); } } + +/** + * Implements hook_module_implements_alter() for hook_tokens. + */ +function address_module_implements_alter(&$implementations, $hook) { + if ($hook == 'tokens') { + $group = $implementations['address']; + unset($implementations['address']); + $implementations['address'] = $group; + } +} diff --git a/address.tokens.inc b/address.tokens.inc new file mode 100644 index 0000000..b49e1d5 --- /dev/null +++ b/address.tokens.inc @@ -0,0 +1,100 @@ +getDefinitions() as $entity_type_id => $entity_type) { + if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { + continue; + } + + // Make sure a token type exists for this entity. + $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id); + if (empty($token_type)) { + continue; + } + + // Build country name tokens for all address fields. + $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id); + foreach ($fields as $field_name => $field) { + if ($field->getType() != 'address') { + continue; + } + + $types[$token_type . '-' . $field_name . '-country_name'] = [ + 'name' => 'Country name', + 'description' => NULL, + 'needs-data' => $token_type . '-' . $field_name . '-country_name', + ]; + $tokens[$token_type . '-' . $field_name]['country_name'] = [ + 'name' => t('The country name'), + 'description' => NULL, + 'module' => 'address', + 'type' => $token_type . '-' . $field_name . '-country_name', + ]; + } + } + + return [ + 'types' => $types, + 'tokens' => $tokens, + ]; +} + +/** + * Implements hook_tokens(). + */ +function address_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { + $replacements = []; + + if (!empty($data['field_property'])) { + foreach ($tokens as $token => $original) { + $delta = 0; + $parts = explode(':', $token); + if (is_numeric($parts[0])) { + if (count($parts) > 1) { + $delta = $parts[0]; + $property_name = $parts[1]; + } + else { + continue; + } + } + else { + $property_name = $parts[0]; + } + if ($property_name != 'country_name') { + continue; + } + if (!isset($data[$data['field_name']][$delta])) { + continue; + } + + $field_item = $data[$data['field_name']][$delta]; + + $country_name = ''; + if ($country_code = $field_item->country_code) { + $country_repository = \Drupal::service('address.country_repository'); + $locale = isset($options['langcode']) ? $options['langcode'] : NULL; + if ($country = $country_repository->get($country_code, $locale)) { + $country_name = $country->getName(); + } + } + $replacements[$original] = $country_name; + } + } + + return $replacements; +} diff --git a/tests/src/Kernel/CountryNameTokenTest.php b/tests/src/Kernel/CountryNameTokenTest.php new file mode 100644 index 0000000..257c545 --- /dev/null +++ b/tests/src/Kernel/CountryNameTokenTest.php @@ -0,0 +1,306 @@ +installEntitySchema('address'); + $this->installEntitySchema('node'); + $this->installEntitySchema('taxonomy_term'); + $this->installEntitySchema('user'); + $this->enableModules(['token']); + + // Create the article content type with an address field. + $node_type = NodeType::create([ + 'type' => 'article', + ]); + $node_type->save(); + + $field_storage = FieldStorageConfig::create([ + 'field_name' => 'test_address', + 'entity_type' => 'node', + 'type' => 'address', + ]); + $field_storage->save(); + + $field = FieldConfig::create([ + 'field_name' => 'test_address', + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => 'Test address field', + ]); + $field->save(); + + // Create a reference field with the same name on user. + $field_storage = FieldStorageConfig::create([ + 'field_name' => 'test_address', + 'entity_type' => 'user', + 'type' => 'entity_reference', + ]); + $field_storage->save(); + + $field = FieldConfig::create([ + 'field_name' => 'test_address', + 'entity_type' => 'user', + 'bundle' => 'user', + 'label' => 'Test address field', + ]); + $field->save(); + + // Create a multi-value address field. + $field_storage = FieldStorageConfig::create([ + 'field_name' => 'multi_address_test', + 'entity_type' => 'node', + 'type' => 'address', + 'cardinality' => 2, + ]); + $field_storage->save(); + + $this->field = FieldConfig::create([ + 'field_name' => 'multi_address_test', + 'entity_type' => 'node', + 'bundle' => 'article', + ])->save(); + + // Add an untranslatable node reference field. + FieldStorageConfig::create([ + 'field_name' => 'test_reference', + 'type' => 'entity_reference', + 'entity_type' => 'node', + 'settings' => [ + 'target_type' => 'node', + ], + 'translatable' => FALSE, + ])->save(); + FieldConfig::create([ + 'field_name' => 'test_reference', + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => 'Test reference', + ])->save(); + + // Add an untranslatable taxonomy term reference field. + $this->vocabulary = $this->createVocabulary(); + + FieldStorageConfig::create([ + 'field_name' => 'test_term_reference', + 'type' => 'entity_reference', + 'entity_type' => 'node', + 'settings' => [ + 'target_type' => 'taxonomy_term', + ], + 'translatable' => FALSE, + ])->save(); + FieldConfig::create([ + 'field_name' => 'test_term_reference', + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => 'Test term reference', + 'settings' => [ + 'handler' => 'default:taxonomy_term', + 'handler_settings' => [ + 'target_bundles' => [ + $this->vocabulary->id() => $this->vocabulary->id(), + ], + ], + ], + ])->save(); + + // Add an address field to terms of the created vocabulary. + $storage = FieldStorageConfig::create([ + 'field_name' => 'term_address_field', + 'entity_type' => 'taxonomy_term', + 'type' => 'address', + ]); + $storage->save(); + $field = FieldConfig::create([ + 'field_name' => 'term_address_field', + 'entity_type' => 'taxonomy_term', + 'bundle' => $this->vocabulary->id(), + ]); + $field->save(); + } + + /** + * Tests [entity:country_name] tokens. + */ + public function testEntityCountryNameTokens() { + // Create a node with a value in its fields and test its country_name tokens. + $entity = Node::create([ + 'title' => 'Test node title', + 'type' => 'article', + 'test_address' => [ + 'country_code' => 'AD', + 'locality' => 'Canillo', + 'postal_code' => 'AD500', + 'address_line1' => 'C. Prat de la Creu, 62-64', + ], + 'multi_address_test' => [ + [ + 'country_code' => 'SV', + 'administrative_area' => 'Ahuachapán', + 'locality' => 'Ahuachapán', + 'address_line1' => 'Some Street 12', + ], + [ + 'country_code' => 'US', + 'administrative_area' => 'CA', + 'address_line1' => '1098 Alta Ave', + 'postal_code' => '94043', + ], + ], + ]); + $entity->save(); + $this->assertTokens('node', ['node' => $entity], [ + 'test_address:country_name' => 'Andorra', + 'multi_address_test:0:country_name' => 'El Salvador', + 'multi_address_test:1:country_name' => 'United States', + ]); + } + + /** + * Test tokens for multilingual fields and entities. + */ + public function testMultilingualFields() { + // Create an english term and add a german translation for it. + $term = $this->createTerm($this->vocabulary, [ + 'name' => 'english-test-term', + 'langcode' => 'en', + 'term_address_field' => [ + 'country_code' => 'US', + 'administrative_area' => 'CA', + 'address_line1' => '1098 Alta Ave', + 'postal_code' => '94043', + ], + ]); + $term->addTranslation('de', [ + 'name' => 'german-test-term', + 'term_address_field' => [ + 'country_code' => 'US', + 'administrative_area' => 'CA', + 'address_line1' => '1098 Alta Ave', + 'postal_code' => '94043', + ], + ])->save(); + $german_term = $term->getTranslation('de'); + + // Create an english node, add a german translation for it and add the + // english term to the english node's entity reference field and the + // german term to the german's entity reference field. + $node = Node::create([ + 'title' => 'english-node-title', + 'type' => 'article', + 'test_term_reference' => [ + 'target_id' => $term->id(), + ], + 'test_address' => [ + 'country_code' => 'FR', + 'locality' => 'Paris', + 'postal_code' => '75014', + 'address_line1' => '218 rue de la Tombe-Issoire', + ], + ]); + $node->addTranslation('de', [ + 'title' => 'german-node-title', + 'test_term_reference' => [ + 'target_id' => $german_term->id(), + ], + 'test_address' => [ + 'country_code' => 'FR', + 'locality' => 'Paris', + 'postal_code' => '75014', + 'address_line1' => '218 rue de la Tombe-Issoire', + ], + ])->save(); + + // Verify the :country_name token of the english term the english node + // refers to. Also verify the value of the term's country_name token. + $this->assertTokens('node', ['node' => $node], [ + 'test_term_reference:entity:term_address_field:country_name' => 'United States', + 'test_address:country_name' => 'France', + ]); + + // Same test for the german node and its german term. + $german_node = $node->getTranslation('de'); + $this->assertTokens('node', ['node' => $german_node], [ + 'test_term_reference:entity:term_address_field:country_name' => 'Vereinigte Staaten', + 'test_address:country_name' => 'Frankreich', + ]); + + // If the langcode is specified, it should have priority over the node's + // active language. + $tokens = [ + 'test_term_reference:entity:term_adddress_field:country_name' => 'Vereinigte Staaten', + 'test_address:country_name' => 'Frankreich', + ]; + $this->assertTokens('node', ['node' => $node], $tokens, ['langcode' => 'de']); + } + + /** + * Helper function. + * + * @see Drupal\token\Tests\TokenTestTrait. + */ + function assertTokens($type, array $data, array $tokens, array $options = []) { + // Map token names. + $input = []; + foreach (array_keys($tokens) as $token) { + $input[$token] = "[$type:$token]"; + } + $bubbleable_metadata = new BubbleableMetadata(); + $replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata); + foreach ($tokens as $name => $expected) { + $token = $input[$name]; + if (!isset($expected)) { + $this->assertTrue(!isset($replacements[$token]), t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token])); + } + elseif (!isset($replacements[$token])) { + $this->fail(t("Token value for @token was not generated.", ['@type' => $type, '@token' => $token])); + } + elseif (!empty($options['regex'])) { + $this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), t("Token value for @token was '@actual', matching regular expression pattern '@expected'.", ['@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected])); + } + else { + $this->assertEqual($replacements[$token], $expected, t("Token value for @token was '@actual', expected value '@expected'.", ['@type' => $type, '@token' => $token, '@actual' => $replacements[$token], '@expected' => $expected])); + } + } + + return $replacements; + } + +}