Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
85 / 85
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
TokenHooks
100.00% covered (success)
100.00%
85 / 85
100.00% covered (success)
100.00%
4 / 4
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 alterTokenInfo
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
6
 getChainReplacements
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
6
 alterReplacements
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Hook;
6
7use Drupal\Core\Entity\ContentEntityInterface;
8use Drupal\Core\Entity\EntityFieldManagerInterface;
9use Drupal\Core\Entity\EntityTypeManagerInterface;
10use Drupal\Core\Hook\Attribute\Hook;
11use Drupal\Core\Render\BubbleableMetadata;
12use Drupal\field\Entity\FieldConfig;
13use Drupal\name\Service\NameFormatterInterface;
14use Drupal\name\Utility\FormattedNameTokenInfoRegistrar;
15use Drupal\name\Utility\FormattedNameTokenParser;
16use Drupal\name\Utility\FormattedNameTokenReplacement;
17use Drupal\name\Utility\TokenFieldSubTypeResolver;
18
19/**
20 * Formatted name field token metadata and replacements for core Token hooks.
21 *
22 * @internal
23 */
24class TokenHooks {
25
26  public function __construct(
27    private readonly EntityFieldManagerInterface $entityFieldManager,
28    private readonly EntityTypeManagerInterface $entityTypeManager,
29    private readonly NameFormatterInterface $nameFormatter,
30  ) {}
31
32  /**
33   * Implements hook_token_info_alter().
34   *
35   * Alters token info to register formatted name chains and browser entries.
36   */
37  // phpcs:ignore Drupal.Commenting.PostStatementComment.Found -- #[Hook] is a PHP attribute, not a trailing comment.
38  #[Hook('token_info_alter')] // @phpstan-ignore attribute.notFound
39  public function alterTokenInfo(array &$info): void {
40    $field_map = $this->entityFieldManager->getFieldMapByFieldType('name');
41    $formats   = $this->entityTypeManager
42      ->getStorage('name_format')
43      ->loadMultiple();
44    foreach ($field_map as $entity_type_id => $fields) {
45      if (!isset($info['tokens'][$entity_type_id])) {
46        continue;
47      }
48      foreach ($fields as $field_name => $field_data) {
49        $label = $field_name;
50        $bundle_keys = array_keys($field_data['bundles']);
51        if ($bundle_keys) {
52          $field_config = FieldConfig::loadByName(
53            $entity_type_id,
54            reset($bundle_keys),
55            $field_name,
56          );
57          if ($field_config) {
58            $label = $field_config->getLabel();
59          }
60        }
61        $field_sub_type = TokenFieldSubTypeResolver::resolve(
62          $info,
63          $entity_type_id,
64          $field_name,
65        );
66        FormattedNameTokenInfoRegistrar::register(
67          $info,
68          $entity_type_id,
69          $field_name,
70          $label,
71          $formats,
72          $this->nameFormatter,
73          $field_sub_type,
74        );
75      }
76    }
77  }
78
79  /**
80   * Implements hook_tokens().
81   */
82  // phpcs:ignore Drupal.Commenting.PostStatementComment.Found -- #[Hook] is a PHP attribute, not a trailing comment.
83  #[Hook('tokens')] // @phpstan-ignore attribute.notFound
84  public function getChainReplacements(
85    string $type,
86    array $tokens,
87    array $data,
88    array $options,
89    BubbleableMetadata $bubbleable_metadata,
90  ): array {
91    $replacements = [];
92    if (!str_starts_with($type, 'name_formatted|')) {
93      return $replacements;
94    }
95    $pieces = explode('|', $type, 3);
96    if (count($pieces) !== 3) {
97      return $replacements;
98    }
99    [, $entity_type_id, $field_name] = $pieces;
100    $entity_is_missing = (
101      !isset($data[$entity_type_id])
102      || !$data[$entity_type_id] instanceof ContentEntityInterface
103    );
104    if ($entity_is_missing) {
105      return $replacements;
106    }
107    $entity = $data[$entity_type_id];
108    foreach ($tokens as $format_id => $original) {
109      $replacements[$original] = FormattedNameTokenReplacement::resolve(
110        $entity,
111        $field_name,
112        0,
113        $format_id,
114        $options,
115        $bubbleable_metadata,
116        $this->entityTypeManager,
117        $this->nameFormatter,
118      );
119    }
120    return $replacements;
121  }
122
123  /**
124   * Implements hook_tokens_alter().
125   */
126  // phpcs:ignore Drupal.Commenting.PostStatementComment.Found -- #[Hook] is a PHP attribute, not a trailing comment.
127  #[Hook('tokens_alter')] // @phpstan-ignore attribute.notFound
128  public function alterReplacements(
129    array &$replacements,
130    array $context,
131    BubbleableMetadata $bubbleable_metadata,
132  ): void {
133    $type    = $context['type'];
134    $tokens  = $context['tokens'];
135    $data    = $context['data'];
136    $options = $context['options'];
137    if (!$tokens) {
138      return;
139    }
140    $entity_is_missing = (
141      !isset($data[$type])
142      || !$data[$type] instanceof ContentEntityInterface
143    );
144    if ($entity_is_missing) {
145      return;
146    }
147    $entity = $data[$type];
148
149    foreach ($tokens as $name => $original) {
150      $parsed = FormattedNameTokenParser::parse($name);
151      if ($parsed === NULL) {
152        continue;
153      }
154      [$field_name, $delta, $format_id] = $parsed;
155      $replacements[$original] = FormattedNameTokenReplacement::resolve(
156        $entity,
157        $field_name,
158        $delta,
159        $format_id,
160        $options,
161        $bubbleable_metadata,
162        $this->entityTypeManager,
163        $this->nameFormatter,
164      );
165    }
166  }
167
168}