Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
FormattedNameTokenParser
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
5 / 5
14
100.00% covered (success)
100.00%
1 / 1
 formattedPointer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formattedChainType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parse
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseLegacyColon
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 parsePointer
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Utility;
6
7/**
8 * Parses formatted name field token names and builds chain identifiers.
9 *
10 * @internal
11 */
12final class FormattedNameTokenParser {
13
14  /**
15   * Machine token pointer for chained "formatted" subtree.
16   */
17  public static function formattedPointer(string $field_name): string {
18    return 'formatted_' . $field_name;
19  }
20
21  /**
22   * Token type id for chained formatted replacements (hook_tokens).
23   */
24  public static function formattedChainType(string $entity_type_id, string $field_name): string {
25    return 'name_formatted|' . $entity_type_id . '|' . $field_name;
26  }
27
28  /**
29   * Parses a field token name for explicit name format output.
30   *
31   * @param string $name
32   *   Token name without the entity type prefix, e.g.
33   *   field_realname:formatted:full, formatted_field_realname:full,
34   *   or field_realname:1:formatted:full.
35   *
36   * @return array{0: string, 1: int, 2: string}|null
37   *   Field machine name, delta, format id; NULL if not a formatted token.
38   */
39  public static function parse(string $name): ?array {
40    $legacy = self::parseLegacyColon($name);
41    if ($legacy !== NULL) {
42      return $legacy;
43    }
44    return self::parsePointer($name);
45  }
46
47  /**
48   * Parses legacy colon syntax for formatted name field tokens.
49   *
50   * Matches {field}:formatted:{id} and {field}:{delta}:formatted:{id}.
51   *
52   * @return array{0: string, 1: int, 2: string}|null
53   *   Field machine name, delta, format id; NULL if the name does not match.
54   */
55  public static function parseLegacyColon(string $name): ?array {
56    $parts = explode(':', $name);
57    $formatted_index = array_search('formatted', $parts, TRUE);
58    $format_token_is_invalid = (
59      $formatted_index === FALSE || !isset($parts[$formatted_index + 1])
60    );
61    if ($format_token_is_invalid) {
62      return NULL;
63    }
64    $format_id = $parts[$formatted_index + 1];
65    if ($formatted_index === 1) {
66      return [$parts[0], 0, $format_id];
67    }
68    $has_delta = ($formatted_index === 2 && is_numeric($parts[1]));
69    if ($has_delta) {
70      return [$parts[0], (int) $parts[1], $format_id];
71    }
72    return NULL;
73  }
74
75  /**
76   * Parses chained formatted tokens: formatted_{field}:{format_id}.
77   *
78   * Used for token-browser grouping
79   * (e.g. [node:formatted_field_realname:given]).
80   *
81   * @return array{0: string, 1: int, 2: string}|null
82   *   Field machine name, delta, format id; NULL if the name does not match.
83   */
84  public static function parsePointer(string $name): ?array {
85    $parts = explode(':', $name, 2);
86    $pointer_is_invalid = (
87      count($parts) !== 2 || !str_starts_with($parts[0], 'formatted_')
88    );
89    if ($pointer_is_invalid) {
90      return NULL;
91    }
92    $field_name = substr($parts[0], strlen('formatted_'));
93    if ($field_name === '') {
94      return NULL;
95    }
96    return [$field_name, 0, $parts[1]];
97  }
98
99}