Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
92 / 92
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
HelpHooks
100.00% covered (success)
100.00%
92 / 92
100.00% covered (success)
100.00%
10 / 10
20
100.00% covered (success)
100.00%
1 / 1
 help
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 routeHelp
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 moduleHelpPageHelp
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 settingsHelp
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 nameFormatAdminHelp
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 nameListFormatAdminHelp
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 isNameFieldConfigRoute
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 fieldConfigHelp
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
2
 relatedHelpTopics
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 helpTopicLink
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\name\Hook;
6
7use Drupal\Core\Hook\Attribute\Hook;
8use Drupal\Core\Link;
9use Drupal\Core\Routing\RouteMatchInterface;
10use Drupal\Core\StringTranslation\StringTranslationTrait;
11use Drupal\Core\StringTranslation\TranslatableMarkup;
12use Drupal\Core\Url;
13use Drupal\field\FieldConfigInterface;
14
15/**
16 * Hook implementations for help pages.
17 *
18 * @internal
19 */
20final class HelpHooks {
21
22  use StringTranslationTrait;
23
24  /**
25   * Implements hook_help().
26   */
27  // phpcs:ignore Drupal.Commenting.PostStatementComment.Found -- #[Hook] is a PHP attribute, not a trailing comment.
28  #[Hook('help')]   // @phpstan-ignore attribute.notFound
29  public function help(string $route_name, RouteMatchInterface $route_match): string {
30    if ($this->isNameFieldConfigRoute($route_name, $route_match)) {
31      return $this->fieldConfigHelp($route_match);
32    }
33
34    return $this->routeHelp($route_name);
35  }
36
37  /**
38   * Builds route-specific help output.
39   *
40   * @param string $route_name
41   *   The route name.
42   *
43   * @return string
44   *   The rendered help text.
45   */
46  private function routeHelp(string $route_name): string {
47    return match ($route_name) {
48      'help.page.name' => $this->moduleHelpPageHelp(),
49      'name.settings' => $this->settingsHelp(),
50      'name.name_format_list',
51      'name.name_format_add',
52      'entity.name_format.edit_form' => $this->nameFormatAdminHelp(),
53      'name.name_list_format_list',
54      'name.name_list_format_add',
55      'entity.name_list_format.edit_form' => $this->nameListFormatAdminHelp(),
56      default => '',
57    };
58  }
59
60  /**
61   * Builds help for the module help page.
62   *
63   * @return string
64   *   The rendered help text.
65   */
66  private function moduleHelpPageHelp(): string {
67    $output = '<p><a href="https://git.drupalcode.org/project/name/-/commits/8.x-1.x"><img alt="coverage report" src="https://git.drupalcode.org/project/name/badges/8.x-1.x/coverage.svg" /></a> &nbsp;';
68    $output .= '<a href="https://git.drupalcode.org/project/name/-/commits/8.x-1.x"><img alt="pipeline status" src="https://git.drupalcode.org/project/name/badges/8.x-1.x/pipeline.svg" /></a> </p>';
69    $output .= '<p> <a href="https://www.drupal.org/project/name">Homepage</a> </p>';
70    $output .= '<p> <a href="https://www.drupal.org/node/3559658">Documentation</a> </p>';
71    $output .= '<p> <a href="https://project.pages.drupalcode.org/name/">Developer Documentation</a> </p>';
72    $output .= '<p> <a href="https://www.drupal.org/project/issues/name?version=any_8.x-">Issues</a> </p>';
73    $output .= '<p>' . $this->t("The Name field stores a person's name in parts. You can store title, given name, middle name, family name, generational suffix, and credentials.") . '</p>';
74    $output .= '<p>' . $this->t('Use this field when you need better control than one plain text box. You can choose which parts are shown, required, and used for display.') . '</p>';
75    $output .= '<h2>' . $this->t('Common tasks') . '</h2>';
76    $output .= '<ul>';
77    $output .= '<li>' . $this->t('Choose which name parts are enabled in field settings.') . '</li>';
78    $output .= '<li>' . $this->t('Pick a format for how names are shown on pages.') . '</li>';
79    $output .= '<li>' . $this->t('Adjust widget labels and layout for editors.') . '</li>';
80    $output .= '<li>' . $this->t('Set list format rules for fields with many names.') . '</li>';
81    $output .= '</ul>';
82    $output .= '<h2>' . $this->t('Related help') . '</h2>';
83    $output .= '<ul>';
84    foreach ($this->relatedHelpTopics() as $topic => $label) {
85      $output .= '<li>' . $this->helpTopicLink($topic, $label) . '</li>';
86    }
87    $output .= '</ul>';
88
89    return $output;
90  }
91
92  /**
93   * Builds help for Name settings.
94   *
95   * @return string
96   *   The rendered help text.
97   */
98  private function settingsHelp(): string {
99    $output = '<p>' . $this->t('Configure the separator replacement tokens used by Name formats.') . '</p>';
100    $output .= '<p>' . $this->t('For guidance on separators and token usage, see') . ' ';
101    $output .= $this->helpTopicLink('name.formats', $this->t('Name formats'));
102    $output .= '.</p>';
103
104    return $output;
105  }
106
107  /**
108   * Builds help for Name format admin routes.
109   *
110   * @return string
111   *   The rendered help text.
112   */
113  private function nameFormatAdminHelp(): string {
114    $output = '<p>' . $this->t('Name formats control how a single name is shown.') . '</p>';
115    $output .= '<p>' . $this->t('For pattern examples, see') . ' ';
116    $output .= $this->helpTopicLink('name.formats', $this->t('Name formats'));
117    $output .= '.</p>';
118
119    return $output;
120  }
121
122  /**
123   * Builds help for Name list format admin routes.
124   *
125   * @return string
126   *   The rendered help text.
127   */
128  private function nameListFormatAdminHelp(): string {
129    $output = '<p>' . $this->t('Name list formats control how multiple names are joined together.') . '</p>';
130    $output .= '<p>' . $this->t('For list behavior and examples, see') . ' ';
131    $output .= $this->helpTopicLink(
132      'name.list_formats',
133      $this->t('Name list formats'),
134    );
135    $output .= '.</p>';
136
137    return $output;
138  }
139
140  /**
141   * Determines whether this is a Name field configuration route.
142   *
143   * @param string $route_name
144   *   The route name.
145   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
146   *   The route match.
147   *
148   * @return bool
149   *   TRUE when the route configures a Name field.
150   */
151  private function isNameFieldConfigRoute(
152    string $route_name,
153    RouteMatchInterface $route_match,
154  ): bool {
155    if (!str_starts_with($route_name, 'entity.field_config.')) {
156      return FALSE;
157    }
158
159    $field_config = $route_match->getParameter('field_config');
160    return $field_config instanceof FieldConfigInterface
161      && $field_config->getType() === 'name';
162  }
163
164  /**
165   * Builds help for Name field configuration pages.
166   *
167   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
168   *   The route match.
169   *
170   * @return string
171   *   The rendered help text.
172   */
173  private function fieldConfigHelp(RouteMatchInterface $route_match): string {
174    $field_config = $route_match->getParameter('field_config');
175    assert($field_config instanceof FieldConfigInterface);
176
177    $output = '<p>' . $this->t('Name field settings control which name parts are active and how editors enter data.') . '</p>';
178    $output .= '<p>' . $this->t('For field options, see') . ' ';
179    $output .= $this->helpTopicLink(
180      'name.field_settings',
181      $this->t('Field settings'),
182    );
183    $output .= '. ' . $this->t('For widget labels and layout, see') . ' ';
184    $output .= $this->helpTopicLink(
185      'name.widget_options',
186      $this->t('Widget options'),
187    );
188    $output .= '.</p>';
189    $output .= '<p>' . $this->t('For autocomplete sources and title values, see') . ' ';
190    $output .= $this->helpTopicLink('name.autocomplete', $this->t('Autocomplete'));
191    $output .= ' ' . $this->t('and') . ' ';
192    $output .= $this->helpTopicLink('name.titles', $this->t('Titles'));
193    $output .= '.</p>';
194
195    if ($field_config->getTargetEntityTypeId() === 'user') {
196      $output .= '<p>' . $this->t('For using a Name field as the shown user name, see') . ' ';
197      $output .= $this->helpTopicLink(
198        'name.user_display_name',
199        $this->t('User display name'),
200      );
201      $output .= '.</p>';
202    }
203
204    return $output;
205  }
206
207  /**
208   * Returns related help topics for the Name help page.
209   *
210   * @return array<string, \Drupal\Core\StringTranslation\TranslatableMarkup>
211   *   Help topic labels keyed by help topic ID.
212   */
213  private function relatedHelpTopics(): array {
214    return [
215      'name.components'        => $this->t('Name components'),
216      'name.field_settings'    => $this->t('Field settings'),
217      'name.formats'           => $this->t('Name formats'),
218      'name.widget_options'    => $this->t('Widget options'),
219      'name.formatter_options' => $this->t('Formatter options'),
220      'name.list_formats'      => $this->t('Name list formats'),
221    ];
222  }
223
224  /**
225   * Builds a link to a Name help topic.
226   *
227   * @param string $topic
228   *   The help topic ID.
229   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $label
230   *   The link label.
231   *
232   * @return string
233   *   The rendered help topic link.
234   */
235  private function helpTopicLink(string $topic, TranslatableMarkup $label): string {
236    return (string) Link::fromTextAndUrl(
237      $label,
238      Url::fromUri('internal:/admin/help/topic/' . $topic),
239    )->toString();
240  }
241
242}