Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
NameFormatParserService
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
12 / 12
20
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
 parse
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
7
 format
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addComponent
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 applyModifiers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 closingBracketPosition
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 generateTokens
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 renderFirstComponent
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 renderComponent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMarkupOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tokenHelp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 renderableTokenHelp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Service;
6
7use Drupal\Core\Config\ConfigFactoryInterface;
8use Drupal\Core\StringTranslation\StringTranslationTrait;
9use Drupal\name\Utility\NameFormatHelp;
10use Drupal\name\Utility\NameFormatOutput;
11use Drupal\name\Utility\NameFormatParser;
12use Drupal\name\Utility\NameFormatTokens;
13
14/**
15 * Converts a name from an array of components into a defined format.
16 *
17 * The parsing and rendering mechanics have been extracted into stateless
18 * utility classes under Drupal\name\Utility. This class retains its full
19 * public and protected API for backwards compatibility with subclasses
20 * and existing service consumers.
21 *
22 * @see \Drupal\name\Utility\NameFormatParser
23 * @see \Drupal\name\Utility\NameFormatTokens
24 * @see \Drupal\name\Utility\NameFormatOutput
25 * @see \Drupal\name\Utility\NameFormatHelp
26 */
27class NameFormatParserService implements NameFormatParserInterface {
28
29  // StringTranslationTrait is retained for backwards compatibility; subclasses
30  // may call $this->t(). Internal strings now use TranslatableMarkup directly
31  // via NameFormatHelp.
32  use StringTranslationTrait;
33
34  /**
35   * The config factory.
36   *
37   * @var \Drupal\Core\Config\ConfigFactoryInterface
38   */
39  protected $configFactory;
40
41  /**
42   * Markup style for decorating name components.
43   *
44   * @var string
45   */
46  protected $markup = 'none';
47
48  /**
49   * First separator.
50   *
51   * @var string
52   */
53  protected $sep1 = ' ';
54
55  /**
56   * Second separator.
57   *
58   * @var string
59   */
60  protected $sep2 = ', ';
61
62  /**
63   * Third separator.
64   *
65   * @var string
66   */
67  protected $sep3 = '';
68
69  /**
70   * Used to separate words using the "b" and "B" modifiers.
71   *
72   * @var string
73   */
74  protected $boundaryRegExp = '/[\b,\s]/';
75
76  /**
77   * Constructs a NameFormatParserService object.
78   *
79   * @param \Drupal\Core\Config\ConfigFactoryInterface|null $config_factory
80   *   The config factory, or NULL to use the global container (backward
81   *   compatibility for legacy service definitions with no arguments or for
82   *   manual construction). Prefer passing this explicitly in new code.
83   */
84  public function __construct(?ConfigFactoryInterface $config_factory = NULL) {
85    // @phpstan-ignore-next-line
86    $this->configFactory = $config_factory ?? \Drupal::configFactory();
87  }
88
89  /**
90   * Parses a name component array into the given format.
91   *
92   * @param array $name_components
93   *   Keyed array of name components.
94   * @param string $format
95   *   The name format pattern to generate the name.
96   * @param array $settings
97   *   Additional settings to control the parser.
98   *   - sep1 (string): first separator.
99   *   - sep2 (string): second separator.
100   *   - sep3 (string): third separator.
101   *   - markup (string): key of the markup type.
102   *
103   * @return string
104   *   A renderable object representing the name.
105   */
106  public function parse(array $name_components, string $format = '', array $settings = []): mixed {
107    if ($settings !== []) {
108      foreach (['sep1', 'sep2', 'sep3'] as $sep_key) {
109        if (isset($settings[$sep_key])) {
110          $this->{$sep_key} = (string) $settings[$sep_key];
111        }
112      }
113      $this->markup = !empty($settings['markup']) ? $settings['markup'] : 'none';
114    }
115    if ($settings === []) {
116      $config       = $this->configFactory->get('name.settings');
117      $this->sep1   = (string) $config->get('sep1');
118      $this->sep2   = (string) $config->get('sep2');
119      $this->sep3   = (string) $config->get('sep3');
120      $this->markup = 'none';
121    }
122
123    if ($format === '') {
124      return NameFormatOutput::wrap('', $this->markup);
125    }
126
127    $tokens = NameFormatTokens::build(
128      $name_components,
129      $this->sep1,
130      $this->sep2,
131      $this->sep3,
132      $this->markup,
133    );
134    $name_string = NameFormatParser::format($format, $tokens);
135
136    return NameFormatOutput::wrap($name_string, $this->markup);
137  }
138
139  /**
140   * Formats an array of name components into the supplied format.
141   *
142   * Retained as a protected method for backwards compatibility with
143   * subclasses. Delegates to NameFormatParser::format() after generating
144   * tokens via generateTokens().
145   *
146   * @param array $name_components
147   *   A keyed array of the components.
148   * @param string $format
149   *   The name format string or segment to parse.
150   * @param array $tokens
151   *   The generated tokens.
152   *
153   * @return string
154   *   The formatted string.
155   */
156  protected function format(array $name_components, $format = '', ?array $tokens = NULL) {
157    if (empty($format)) {
158      return '';
159    }
160
161    $tokens ??= $this->generateTokens($name_components);
162    return NameFormatParser::format((string) $format, $tokens);
163  }
164
165  /**
166   * Adds a component.
167   *
168   * Retained as a protected method for backwards compatibility with
169   * subclasses. The by-reference parameters are preserved so existing
170   * override signatures remain valid.
171   *
172   * @param string $string
173   *   The token string to process.
174   * @param string $modifiers
175   *   The modifiers to apply.
176   * @param string $conditions
177   *   The conditional flags.
178   *
179   * @return array
180   *   The processed piece.
181   */
182  protected function addComponent($string, &$modifiers = '', &$conditions = '') {
183    $value      = NameFormatParser::applyModifiers($string, $modifiers, $this->boundaryRegExp);
184    $piece      = ['value' => $value, 'conditions' => $conditions];
185    $conditions = '';
186    $modifiers  = '';
187    return $piece;
188  }
189
190  /**
191   * Applies the specified modifiers to the string.
192   *
193   * Retained as a protected method for backwards compatibility with
194   * subclasses. Delegates to NameFormatParser::applyModifiers() using the
195   * instance boundary regexp.
196   *
197   * @param string $string
198   *   The token string to process.
199   * @param string $modifiers
200   *   The modifiers to apply.
201   *
202   * @return string
203   *   The processed string.
204   */
205  protected function applyModifiers($string, $modifiers) {
206    return NameFormatParser::applyModifiers($string, $modifiers, $this->boundaryRegExp);
207  }
208
209  /**
210   * Helper function to put out the first matched bracket position.
211   *
212   * Retained as a protected method for backwards compatibility with
213   * subclasses. Delegates to NameFormatParser::closingBracketPosition().
214   *
215   * @param string $string
216   *   Accepts strings in the format, ^ marks the matched bracket.
217   *
218   *   i.e. '(xxx^)xxx(xxxx)xxxx' or '(xxx(xxx(xxxx))xxx^)'.
219   *
220   * @return mixed
221   *   The closing bracket position or FALSE if not found.
222   */
223  protected function closingBracketPosition($string) {
224    return NameFormatParser::closingBracketPosition($string);
225  }
226
227  /**
228   * Generates the tokens from the name item.
229   *
230   * Retained as a protected method for backwards compatibility with
231   * subclasses. Delegates to NameFormatTokens::build() using the current
232   * instance separators and markup mode.
233   *
234   * @param array $name_components
235   *   The array of name components.
236   *
237   * @return array
238   *   The keyed tokens generated for the given name.
239   */
240  protected function generateTokens(array $name_components) {
241    return NameFormatTokens::build(
242      $name_components,
243      $this->sep1,
244      $this->sep2,
245      $this->sep3,
246      $this->markup,
247    );
248  }
249
250  /**
251   * Finds and renders the first renderable name component value.
252   *
253   * Retained as a protected method for backwards compatibility with
254   * subclasses. Delegates to NameFormatTokens::renderFirstComponent().
255   *
256   * @param array $values
257   *   An array of values to find the first to render.
258   * @param string $component_key
259   *   The component context.
260   * @param string $modifier
261   *   Internal flag for processing.
262   *
263   * @return string|null
264   *   The rendered component.
265   */
266  protected function renderFirstComponent(array $values, $component_key, $modifier = NULL) {
267    return NameFormatTokens::renderFirstComponent(
268      $values,
269      $component_key,
270      $this->markup,
271      $modifier,
272    );
273  }
274
275  /**
276   * Renders a name component value.
277   *
278   * Retained as a protected method for backwards compatibility with
279   * subclasses. Delegates to NameFormatTokens::renderComponent().
280   *
281   * @param string $value
282   *   A value to render.
283   * @param string $component_key
284   *   The component context.
285   * @param string $modifier
286   *   Internal flag for processing.
287   *
288   * @return string|null
289   *   The rendered component.
290   */
291  protected function renderComponent($value, $component_key, $modifier = NULL) {
292    return NameFormatTokens::renderComponent($value, $component_key, $this->markup, $modifier);
293  }
294
295  /**
296   * Supported markup options.
297   *
298   * @return array
299   *   A keyed array of markup options.
300   */
301  public function getMarkupOptions(): array {
302    return NameFormatHelp::markupOptions();
303  }
304
305  /**
306   * Supported tokens.
307   *
308   * @param bool $describe
309   *   Appends the description of the letter to the description.
310   *
311   * @return string[]
312   *   An array of strings keyed by the token.
313   */
314  public function tokenHelp(bool $describe = TRUE): array {
315    return $describe ? NameFormatHelp::tokenHelp() : NameFormatHelp::tokenHelpPlain();
316  }
317
318  /**
319   * Helper function to provide name format token help.
320   *
321   * @return array
322   *   A renderable array of tokens in a details element.
323   */
324  public function renderableTokenHelp(): array {
325    return NameFormatHelp::renderableTokenHelp();
326  }
327
328}