Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
NameComponents
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
6 / 6
27
100.00% covered (success)
100.00%
1 / 1
 coreKeys
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 applyLayout
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 sanitizeValue
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
8
 isEmptyForValidation
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 isIgnorableComponentKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 hasMeaningfulRawValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Utility;
6
7use Drupal\Component\Utility\Html;
8
9/**
10 * Stateless helpers for name field components (keys, layout, sanitization).
11 *
12 * @internal
13 */
14final class NameComponents {
15
16  /**
17   * Core name component columns (machine name => machine name).
18   *
19   * @return array<string, string>
20   *   Keyed list of component machine names.
21   */
22  public static function coreKeys(): array {
23    return [
24      'title' => 'title',
25      'given' => 'given',
26      'middle' => 'middle',
27      'family' => 'family',
28      'credentials' => 'credentials',
29      'generational' => 'generational',
30    ];
31  }
32
33  /**
34   * Applies component order and visibility for a cultural layout.
35   *
36   * @param array $element
37   *   A name sub-element tree (e.g. $element['_name']).
38   * @param string $layout
39   *   Layout machine name: default, asian, eastern, or german.
40   */
41  public static function applyLayout(array &$element, string $layout = 'default'): void {
42    $weights = [
43      'asian' => [
44        'family' => 1,
45        'middle' => 2,
46        'given' => 3,
47        'title' => 4,
48        // The 'generational' value is removed from the display.
49        'generational' => 5,
50        'credentials' => 6,
51      ],
52      'eastern' => [
53        'title' => 1,
54        'family' => 2,
55        'given' => 3,
56        'middle' => 4,
57        'generational' => 5,
58        'credentials' => 6,
59      ],
60      'german' => [
61        'title' => 1,
62        'credentials' => 2,
63        'given' => 3,
64        'middle' => 4,
65        'family' => 5,
66        // The 'generational' value is removed from the display.
67        'generational' => 7,
68      ],
69    ];
70    if (isset($weights[$layout])) {
71      foreach ($weights[$layout] as $component => $weight) {
72        if (isset($element[$component])) {
73          $element[$component]['#weight'] = $weight;
74        }
75      }
76    }
77
78    $is_asian_or_german = ($layout === 'asian' || $layout === 'german');
79    if ($is_asian_or_german) {
80      if (isset($element['generational'])) {
81        $element['generational']['#default_value'] = '';
82        $element['generational']['#access'] = FALSE;
83      }
84    }
85  }
86
87  /**
88   * Sanitizes a name component value or full string.
89   *
90   * @param mixed $item
91   *   A string or a component value array.
92   * @param string|null $column
93   *   When $item is an array, the component key to read.
94   * @param string $type
95   *   Default (escaped), plain (strip_tags), or raw (unchanged).
96   *
97   * @return string
98   *   Sanitized or raw string.
99   */
100  public static function sanitizeValue(mixed $item, ?string $column = NULL, string $type = 'default'): string {
101    $safe_key = 'safe' . ($type === 'default' ? '' : '_' . $type);
102    $has_safe_value = (is_array($item) && isset($item[$safe_key]));
103    if ($has_safe_value) {
104      return $item[$safe_key][$column];
105    }
106
107    $value = is_array($item) ? (string) $item[$column] : $item;
108    return match ($type) {
109      'plain' => strip_tags((string) $value),
110      'raw' => (string) $value,
111      default => Html::escape((string) $value),
112    };
113  }
114
115  /**
116   * Whether a name value array is empty for validation purposes.
117   *
118   * Title and generational components are ignored for this check so validation
119   * follows the same minimum-input semantics as the element validator.
120   */
121  public static function isEmptyForValidation(array $item): bool {
122    foreach (self::coreKeys() as $key) {
123      if (self::isIgnorableComponentKey($key)) {
124        continue;
125      }
126      if (!empty($item[$key])) {
127        return FALSE;
128      }
129    }
130
131    return TRUE;
132  }
133
134  /**
135   * Whether a component key is ignored when deciding if a name item is empty.
136   *
137   * Title and generational have no meaning by themselves; unknown keys are
138   * ignored as well.
139   */
140  public static function isIgnorableComponentKey(string $key): bool {
141    $is_ignorable_key = ($key === 'title' || $key === 'generational');
142    if ($is_ignorable_key) {
143      return TRUE;
144    }
145
146    return !isset(self::coreKeys()[$key]);
147  }
148
149  /**
150   * Whether a raw stored value counts as meaningful content.
151   *
152   * @param string $key
153   *   Component machine name.
154   * @param mixed $value
155   *   Raw value from field item storage.
156   */
157  public static function hasMeaningfulRawValue(string $key, mixed $value): bool {
158    if (self::isIgnorableComponentKey($key)) {
159      return FALSE;
160    }
161
162    return isset($value) && is_string($value) && strlen($value) > 0;
163  }
164
165}