Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
158 / 158 |
|
100.00% |
12 / 12 |
CRAP | |
100.00% |
1 / 1 |
| NameWidget | |
100.00% |
158 / 158 |
|
100.00% |
12 / 12 |
36 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| create | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
| formElement | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| buildFormElementDefaults | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 | |||
| applyFieldTitleDisplay | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| applyComponentDefinitions | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
| resolveSettings | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
| buildComponentProperties | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
8 | |||
| massageFormValues | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
| defaultSettings | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| settingsForm | |
100.00% |
43 / 43 |
|
100.00% |
1 / 1 |
1 | |||
| settingsSummary | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\name\Plugin\Field\FieldWidget; |
| 6 | |
| 7 | use Drupal\Component\Utility\Html; |
| 8 | use Drupal\Core\Field\Attribute\FieldWidget; |
| 9 | use Drupal\Core\Field\FieldDefinitionInterface; |
| 10 | use Drupal\Core\Field\FieldItemListInterface; |
| 11 | use Drupal\Core\Field\WidgetBase; |
| 12 | use Drupal\Core\Form\FormStateInterface; |
| 13 | use Drupal\Core\Plugin\ContainerFactoryPluginInterface; |
| 14 | use Drupal\Core\Security\TrustedCallbackInterface; |
| 15 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 16 | use Drupal\name\Service\NameComponentMetadataInterface; |
| 17 | use Drupal\name\Service\NameOptionInterface; |
| 18 | use Drupal\name\Traits\NameFormDisplaySettingsTrait; |
| 19 | use Drupal\name\Traits\NameFormSettingsHelperTrait; |
| 20 | use Symfony\Component\DependencyInjection\ContainerInterface; |
| 21 | |
| 22 | /** |
| 23 | * Plugin implementation of the 'name' widget. |
| 24 | */ |
| 25 | #[FieldWidget( |
| 26 | id: "name_default", |
| 27 | label: new TranslatableMarkup("Name components"), |
| 28 | field_types: ["name"] |
| 29 | )] |
| 30 | class NameWidget extends WidgetBase implements ContainerFactoryPluginInterface, TrustedCallbackInterface { |
| 31 | |
| 32 | use NameFormDisplaySettingsTrait; |
| 33 | use NameFormSettingsHelperTrait; |
| 34 | |
| 35 | /** |
| 36 | * Name options provider service. |
| 37 | * |
| 38 | * @var \Drupal\name\Service\NameOptionInterface |
| 39 | */ |
| 40 | protected $optionsProvider; |
| 41 | |
| 42 | /** |
| 43 | * Translated component labels and related metadata. |
| 44 | * |
| 45 | * @var \Drupal\name\Service\NameComponentMetadataInterface |
| 46 | */ |
| 47 | protected NameComponentMetadataInterface $componentMetadata; |
| 48 | |
| 49 | /** |
| 50 | * Constructs a NameWidget object. |
| 51 | * |
| 52 | * @param string $plugin_id |
| 53 | * The plugin_id for the widget. |
| 54 | * @param mixed $plugin_definition |
| 55 | * The plugin implementation definition. |
| 56 | * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition |
| 57 | * The definition of the field to which the widget is associated. |
| 58 | * @param array $settings |
| 59 | * The widget settings. |
| 60 | * @param array $third_party_settings |
| 61 | * Any third party settings. |
| 62 | * @param \Drupal\name\Service\NameOptionInterface|null $options_provider |
| 63 | * Name options provider service. |
| 64 | * @param \Drupal\name\Service\NameComponentMetadataInterface|null $component_metadata |
| 65 | * Component label metadata. |
| 66 | */ |
| 67 | public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ?NameOptionInterface $options_provider, ?NameComponentMetadataInterface $component_metadata) { |
| 68 | parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); |
| 69 | // @phpstan-ignore-next-line |
| 70 | $this->optionsProvider = $options_provider ?? \Drupal::service('name.options_provider'); |
| 71 | // @phpstan-ignore-next-line |
| 72 | $this->componentMetadata = $component_metadata ?? \Drupal::service('name.component_metadata'); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * {@inheritdoc} |
| 77 | */ |
| 78 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { |
| 79 | return new static( |
| 80 | $plugin_id, |
| 81 | $plugin_definition, |
| 82 | $configuration['field_definition'], |
| 83 | $configuration['settings'], |
| 84 | $configuration['third_party_settings'], |
| 85 | $container->get('name.options_provider', ContainerInterface::NULL_ON_INVALID_REFERENCE), |
| 86 | $container->get('name.component_metadata', ContainerInterface::NULL_ON_INVALID_REFERENCE) |
| 87 | ); |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * {@inheritdoc} |
| 92 | */ |
| 93 | public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { |
| 94 | $settings = $this->resolveSettings($form_state); |
| 95 | $widget_settings = $this->getSettings(); |
| 96 | |
| 97 | $element += $this->buildFormElementDefaults($items, $delta, $settings, $widget_settings); |
| 98 | $element = $this->applyFieldTitleDisplay($element, $settings); |
| 99 | $element['#components'] = $this->applyComponentDefinitions($settings); |
| 100 | |
| 101 | return $element; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Builds default render array properties for the name form element. |
| 106 | * |
| 107 | * @param \Drupal\Core\Field\FieldItemListInterface $items |
| 108 | * The field items. |
| 109 | * @param int $delta |
| 110 | * The item delta. |
| 111 | * @param array $settings |
| 112 | * Active widget settings. |
| 113 | * @param array $widget_settings |
| 114 | * Widget plugin settings. |
| 115 | * |
| 116 | * @return array |
| 117 | * Default element properties. |
| 118 | */ |
| 119 | private function buildFormElementDefaults( |
| 120 | FieldItemListInterface $items, |
| 121 | int $delta, |
| 122 | array $settings, |
| 123 | array $widget_settings, |
| 124 | ): array { |
| 125 | $element = [ |
| 126 | '#type' => 'name', |
| 127 | '#title' => $this->fieldDefinition->getLabel(), |
| 128 | '#minimum_components' => array_filter($settings['minimum_components']), |
| 129 | '#allow_family_or_given' => !empty($settings['allow_family_or_given']), |
| 130 | '#default_value' => isset($items[$delta]) ? $items[$delta]->getValue() : NULL, |
| 131 | '#field' => $this, |
| 132 | '#credentials_inline' => empty($settings['credentials_inline']) ? 0 : 1, |
| 133 | '#widget_layout' => empty($settings['widget_layout']) ? 'stacked' : $settings['widget_layout'], |
| 134 | '#wrapper_type' => empty($widget_settings['wrapper_type']) ? 'fieldset' : $widget_settings['wrapper_type'], |
| 135 | '#component_layout' => empty($settings['component_layout']) ? 'default' : $settings['component_layout'], |
| 136 | '#show_component_required_marker' => !empty($settings['show_component_required_marker']), |
| 137 | '#flag_required_input' => !empty($settings['flag_required_input']), |
| 138 | ]; |
| 139 | |
| 140 | return $element; |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Applies field-level title display when the widget defaults to "before". |
| 145 | * |
| 146 | * @param array $element |
| 147 | * The form element render array. |
| 148 | * @param array $settings |
| 149 | * Active widget settings. |
| 150 | * |
| 151 | * @return array |
| 152 | * The element with title display updated when applicable. |
| 153 | */ |
| 154 | private function applyFieldTitleDisplay(array $element, array $settings): array { |
| 155 | $may_override_title = ( |
| 156 | !empty($settings['field_title_display']) |
| 157 | && ($element['#title_display'] ?? NULL) === 'before' |
| 158 | ); |
| 159 | if ($may_override_title) { |
| 160 | $element['#title_display'] = $settings['field_title_display']; |
| 161 | } |
| 162 | |
| 163 | return $element; |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Builds per-component definitions for the name form element. |
| 168 | * |
| 169 | * @param array $settings |
| 170 | * Active widget settings. |
| 171 | * |
| 172 | * @return array |
| 173 | * Component render definitions keyed by component machine name. |
| 174 | */ |
| 175 | private function applyComponentDefinitions(array $settings): array { |
| 176 | $components = []; |
| 177 | $active = array_filter($settings['components']); |
| 178 | $translation_keys = array_keys($this->componentMetadata->getTranslations()); |
| 179 | foreach ($translation_keys as $key) { |
| 180 | if (!isset($active[$key])) { |
| 181 | $components[$key]['exclude'] = TRUE; |
| 182 | continue; |
| 183 | } |
| 184 | $components[$key] = $this->buildComponentProperties($key, $settings); |
| 185 | } |
| 186 | |
| 187 | return $components; |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Resolves the active field widget settings. |
| 192 | * |
| 193 | * @param \Drupal\Core\Form\FormStateInterface $form_state |
| 194 | * The current form state. |
| 195 | * |
| 196 | * @return array |
| 197 | * The active widget settings. |
| 198 | */ |
| 199 | protected function resolveSettings(FormStateInterface $form_state): array { |
| 200 | $widget_settings = $this->getSettings(); |
| 201 | $field_settings = $this->getFieldSettings(); |
| 202 | $should_use_widget_settings = ( |
| 203 | !empty($widget_settings['override_field_settings']) |
| 204 | && !$this->isDefaultValueWidget($form_state) |
| 205 | ); |
| 206 | if ($should_use_widget_settings) { |
| 207 | return $widget_settings + $field_settings; |
| 208 | } |
| 209 | |
| 210 | return $field_settings; |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Builds the render properties for a single name component. |
| 215 | * |
| 216 | * @param string $key |
| 217 | * The component key. |
| 218 | * @param array $settings |
| 219 | * The active widget settings. |
| 220 | * |
| 221 | * @return array |
| 222 | * The component render properties. |
| 223 | */ |
| 224 | private function buildComponentProperties(string $key, array $settings): array { |
| 225 | $component = [ |
| 226 | 'type' => 'textfield', |
| 227 | 'title' => Html::escape((string) $settings['labels'][$key]), |
| 228 | 'title_display' => $settings['title_display'][$key] ?? 'description', |
| 229 | 'size' => !empty($settings['size'][$key]) ? $settings['size'][$key] : 60, |
| 230 | 'maxlength' => !empty($settings['max_length'][$key]) ? $settings['max_length'][$key] : 255, |
| 231 | ]; |
| 232 | |
| 233 | // Provides backwards compatibility with Drupal 6 modules. |
| 234 | $field_type = ($key === 'title' || $key === 'generational') ? 'select' : 'text'; |
| 235 | $field_type = $settings['field_type'][$key] ?? ($settings[$key . '_field'] ?? $field_type); |
| 236 | |
| 237 | if ($field_type === 'select') { |
| 238 | $component['type'] = 'select'; |
| 239 | $component['size'] = 1; |
| 240 | $component['options'] = $this->optionsProvider->getOptions($this->fieldDefinition, $key); |
| 241 | } |
| 242 | elseif ($field_type === 'autocomplete') { |
| 243 | $sources = array_filter($settings['autocomplete_source'][$key] ?? []); |
| 244 | if (!empty($sources)) { |
| 245 | $component['autocomplete'] = [ |
| 246 | '#autocomplete_route_name' => 'name.autocomplete', |
| 247 | '#autocomplete_route_parameters' => [ |
| 248 | 'field_name' => $this->fieldDefinition->getName(), |
| 249 | 'entity_type' => $this->fieldDefinition->getTargetEntityTypeId(), |
| 250 | 'bundle' => $this->fieldDefinition->getTargetBundle(), |
| 251 | 'component' => $key, |
| 252 | ], |
| 253 | ]; |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | return $component; |
| 258 | } |
| 259 | |
| 260 | /** |
| 261 | * {@inheritdoc} |
| 262 | */ |
| 263 | public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { |
| 264 | $values = parent::massageFormValues($values, $form, $form_state); |
| 265 | |
| 266 | // Get fields that use selection. |
| 267 | $selection_fields = array_keys($this->fieldDefinition->getSettings()['field_type'], 'select', TRUE); |
| 268 | |
| 269 | $new_values = []; |
| 270 | foreach ($values as $item) { |
| 271 | // For all selection fields, replace '_none' with an empty string. |
| 272 | foreach ($selection_fields as $field_name) { |
| 273 | $is_none_selection = (isset($item[$field_name]) && $item[$field_name] == '_none'); |
| 274 | if ($is_none_selection) { |
| 275 | $item[$field_name] = ''; |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | $value = implode('', array_intersect_key($item, $this->componentMetadata->getTranslations())); |
| 280 | if (strlen($value)) { |
| 281 | $new_values[] = $item; |
| 282 | } |
| 283 | } |
| 284 | return $new_values; |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * {@inheritdoc} |
| 289 | */ |
| 290 | public static function defaultSettings() { |
| 291 | $settings = self::getDefaultNameFormDisplaySettings(); |
| 292 | $settings['override_field_settings'] = FALSE; |
| 293 | $settings['wrapper_type'] = 'fieldset'; |
| 294 | return $settings + parent::defaultSettings(); |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * {@inheritdoc} |
| 299 | */ |
| 300 | public function settingsForm(array $form, FormStateInterface $form_state) { |
| 301 | $element = parent::settingsForm($form, $form_state); |
| 302 | $settings = $this->getSettings(); |
| 303 | |
| 304 | $element['override_field_settings'] = [ |
| 305 | '#type' => 'checkbox', |
| 306 | '#title' => $this->t('Override shared field settings'), |
| 307 | '#default_value' => $this->getSetting('override_field_settings'), |
| 308 | '#table_group' => 'above', |
| 309 | '#weight' => -100, |
| 310 | ]; |
| 311 | $element['wrapper_type'] = [ |
| 312 | '#type' => 'radios', |
| 313 | '#title' => $this->t('Wrapper type'), |
| 314 | '#default_value' => $settings['wrapper_type'] ?? 'fieldset', |
| 315 | '#options' => [ |
| 316 | 'container' => $this->t('Container (invisible)'), |
| 317 | 'details' => $this->t('Details (collapsible)'), |
| 318 | 'fieldset' => $this->t('Fieldset (non-collapsible)'), |
| 319 | ], |
| 320 | '#table_group' => 'above', |
| 321 | '#weight' => -99, |
| 322 | ]; |
| 323 | |
| 324 | $element += $this->getDefaultNameFormDisplaySettingsForm($settings); |
| 325 | |
| 326 | // Remove inaccessible name components as defined in the field settings. |
| 327 | $field_settings = $this->getFieldSettings(); |
| 328 | $components = array_keys(array_filter($field_settings['components'])); |
| 329 | $components = array_combine($components, $components); |
| 330 | $element['#excluded_components'] = array_diff_key($this->componentMetadata->getTranslations(), $components); |
| 331 | $element['#pre_render'][] = [$this, 'fieldSettingsFormPreRender']; |
| 332 | $element['widget_layout']['#states'] = [ |
| 333 | 'visible' => [ |
| 334 | ':input[name$="[override_field_settings]"]' => [ |
| 335 | 'checked' => TRUE, |
| 336 | ], |
| 337 | ], |
| 338 | ]; |
| 339 | $element['field_title_display']['#states'] = [ |
| 340 | 'visible' => [ |
| 341 | ':input[name$="[override_field_settings]"]' => [ |
| 342 | 'checked' => TRUE, |
| 343 | ], |
| 344 | ], |
| 345 | ]; |
| 346 | $element['name_settings']['#states'] = $element['widget_layout']['#states']; |
| 347 | |
| 348 | return $element; |
| 349 | } |
| 350 | |
| 351 | /** |
| 352 | * {@inheritdoc} |
| 353 | */ |
| 354 | public function settingsSummary() { |
| 355 | $summary = parent::settingsSummary(); |
| 356 | $widget_settings = $this->getSettings(); |
| 357 | $wrapper_options = [ |
| 358 | 'container' => $this->t('Container (invisible)'), |
| 359 | 'details' => $this->t('Details (collapsible)'), |
| 360 | 'fieldset' => $this->t('Fieldset (non-collapsible)'), |
| 361 | ]; |
| 362 | $summary_label = empty($widget_settings['override_field_settings']) |
| 363 | ? $this->t('Using shared settings') |
| 364 | : $this->t('Overridden settings'); |
| 365 | array_unshift($summary, $summary_label); |
| 366 | $wrapper_type = $widget_settings['wrapper_type'] ?? 'fieldset'; |
| 367 | $wrapper_label = $wrapper_options[$wrapper_type] ?? $wrapper_options['fieldset']; |
| 368 | $summary[] = $this->t('Wrapper type: @wrapper_type', ['@wrapper_type' => $wrapper_label]); |
| 369 | |
| 370 | return $summary; |
| 371 | } |
| 372 | |
| 373 | } |