Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
NameOptionValidator
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
4 / 4
20
100.00% covered (success)
100.00%
1 / 1
 validate
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
7
 applyAggregateValidation
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 classifyLine
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 validateVocabularyLine
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Utility;
6
7use Drupal\Core\Form\FormStateInterface;
8use Drupal\name\Service\NameOptionInterface;
9
10/**
11 * Validates name field option textarea values.
12 *
13 * @internal
14 */
15final class NameOptionValidator {
16
17  /**
18   * Validates parsed option lines and updates the form element value.
19   *
20   * @param array $element
21   *   Element being validated.
22   * @param \Drupal\Core\Form\FormStateInterface $form_state
23   *   The form state.
24   * @param array $values
25   *   Values to check.
26   * @param int $max_length
27   *   The max length.
28   */
29  public static function validate(array $element, FormStateInterface $form_state, array $values, int $max_length): void {
30    $label = $element['#title'];
31
32    $long_options = [];
33    $valid_options = [];
34    $default_options = [];
35    foreach ($values as $value) {
36      $value = trim((string) $value);
37      $classification = self::classifyLine($value, $max_length);
38      if ($classification === 'default') {
39        $default_options[] = $value;
40        continue;
41      }
42      if ($classification === 'vocabulary') {
43        if (self::validateVocabularyLine($element, $form_state, $value, (string) $label)) {
44          $valid_options[] = $value;
45        }
46        continue;
47      }
48      if ($classification === 'long') {
49        $long_options[] = $value;
50        continue;
51      }
52      if ($classification === 'valid') {
53        $valid_options[] = $value;
54      }
55    }
56
57    self::applyAggregateValidation(
58      $element,
59      $form_state,
60      (string) $label,
61      $long_options,
62      $valid_options,
63      $default_options,
64    );
65  }
66
67  /**
68   * Applies validation errors after all lines are classified.
69   *
70   * @param array $element
71   *   Element being validated.
72   * @param \Drupal\Core\Form\FormStateInterface $form_state
73   *   The form state.
74   * @param string $label
75   *   The element label.
76   * @param array $long_options
77   *   Options that exceed max length.
78   * @param array $valid_options
79   *   Valid non-default options.
80   * @param array $default_options
81   *   Blank default options.
82   */
83  private static function applyAggregateValidation(
84    array $element,
85    FormStateInterface $form_state,
86    string $label,
87    array $long_options,
88    array $valid_options,
89    array $default_options,
90  ): void {
91    if (count($long_options)) {
92      $form_state->setError($element, t('The following options exceed the maximum allowed %label length: %options', [
93        '%options' => implode(', ', $long_options),
94        '%label' => $label,
95      ]));
96      return;
97    }
98    if (empty($valid_options)) {
99      $form_state->setError($element, t('%label are required.', [
100        '%label' => $label,
101      ]));
102      return;
103    }
104    if (count($default_options) > 1) {
105      $form_state->setError($element, t('%label can only have one blank value assigned to it.', [
106        '%label' => $label,
107      ]));
108      return;
109    }
110
111    $form_state->setValueForElement($element, array_merge($default_options, $valid_options));
112  }
113
114  /**
115   * Classifies a trimmed option line.
116   *
117   * @param string $value
118   *   The trimmed option line.
119   * @param int $max_length
120   *   The maximum allowed length for plain values.
121   *
122   * @return string
123   *   One of default, vocabulary, long, valid, or empty.
124   */
125  private static function classifyLine(string $value, int $max_length): string {
126    if (str_starts_with($value, '--')) {
127      return 'default';
128    }
129    if (preg_match(NameOptionInterface::VOCABULARY_REGEX, $value)) {
130      return 'vocabulary';
131    }
132    if (mb_strlen($value) > $max_length) {
133      return 'long';
134    }
135    if ($value !== '') {
136      return 'valid';
137    }
138    return 'empty';
139  }
140
141  /**
142   * Validates a vocabulary import tag line.
143   *
144   * @param array $element
145   *   Element being validated.
146   * @param \Drupal\Core\Form\FormStateInterface $form_state
147   *   The form state.
148   * @param string $value
149   *   The trimmed option line.
150   * @param string $label
151   *   The element label.
152   *
153   * @return bool
154   *   TRUE when the vocabulary tag is valid.
155   */
156  private static function validateVocabularyLine(array $element, FormStateInterface $form_state, string $value, string $label): bool {
157    preg_match(NameOptionInterface::VOCABULARY_REGEX, $value, $matches);
158    if (!\Drupal::moduleHandler()->moduleExists('taxonomy')) {
159      $form_state->setError($element, t("The taxonomy module must be enabled before using the '%tag' tag in %label.", [
160        '%tag' => $matches[0],
161        '%label' => $label,
162      ]));
163      return FALSE;
164    }
165    if ($value !== $matches[0]) {
166      $form_state->setError($element, t("The '%tag' tag in %label should be on a line by itself.", [
167        '%tag' => $matches[0],
168        '%label' => $label,
169      ]));
170      return FALSE;
171    }
172    $vocabulary = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load($matches[1]);
173    if (!$vocabulary) {
174      $form_state->setError($element, t("The vocabulary '%tag' in %label could not be found.", [
175        '%tag' => $matches[1],
176        '%label' => $label,
177      ]));
178      return FALSE;
179    }
180    return TRUE;
181  }
182
183}