diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php index b1579f2445..b90d9b941c 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -240,9 +240,21 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f 'effect' => 'fade', ], ]; + + // Allow modules to alter the field multiple widget form element. + $context = [ + 'form' => $form, + 'widget' => $this, + 'items' => $items, + 'delta' => $delta, + 'default' => $this->isDefaultValueWidget($form_state), + ]; + \Drupal::moduleHandler()->alter([ + 'field_widget_multiple_form', + 'field_widget_multiple_' . $this->getPluginId() . '_form', + ], $elements, $form_state, $context); } } - return $elements; } diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index 130f923180..aed3dde820 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -183,6 +183,7 @@ function hook_field_widget_info_alter(array &$info) { * @see \Drupal\Core\Field\WidgetBaseInterface::form() * @see \Drupal\Core\Field\WidgetBase::formSingleElement() * @see hook_field_widget_WIDGET_TYPE_form_alter() + * @see hook_field_widget_multiple_form_alter() */ function hook_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) { // Add a css class to widget form elements for all fields of type mytype. @@ -212,6 +213,7 @@ function hook_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInte * @see \Drupal\Core\Field\WidgetBaseInterface::form() * @see \Drupal\Core\Field\WidgetBase::formSingleElement() * @see hook_field_widget_form_alter() + * @see hook_field_widget_multiple_WIDGET_TYPE_form_alter() */ function hook_field_widget_WIDGET_TYPE_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) { // Code here will only act on widgets of type WIDGET_TYPE. For example, @@ -220,6 +222,78 @@ function hook_field_widget_WIDGET_TYPE_form_alter(&$element, \Drupal\Core\Form\F $element['#autocomplete_route_name'] = 'mymodule.autocomplete_route'; } +/** + * Alter forms for multivalue field widgets provided by other modules. + * + * To adjust the individual widgets for the values themselves, use + * hook_field_widget_form_alter() instead. + * + * @param $element + * The field widget form element as constructed by + * \Drupal\Core\Field\WidgetBaseInterface::form(). + * @param $form_state + * The current state of the form. + * @param $context + * An associative array containing the following key-value pairs: + * - form: The form structure to which widgets are being attached. This may be + * a full form structure, or a sub-element of a larger form. + * - widget: The widget plugin instance. + * - items: The field values, as a + * \Drupal\Core\Field\FieldItemListInterface object. + * - delta: The order of this item in the array of subelements (0, 1, 2, etc). + * - default: A boolean indicating whether the form is being shown as a dummy + * form to set default values. + * + * @see \Drupal\Core\Field\WidgetBaseInterface::form() + * @see \Drupal\Core\Field\WidgetBase::formSingleElement() + * @see hook_field_widget_form_alter() + * @see hook_field_widget_multiple_WIDGET_TYPE_form_alter() + */ +function hook_field_widget_multiple_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) { + // Add a css class to the overall element of any widget. + $element['#attributes']['class'][] = 'myclass'; +} + +/** + * Alter multi-value widget forms for a widget provided by another module. + * + * Modules can implement hook_field_widget_multiple_WIDGET_TYPE_form_alter() to + * modify a specific widget form, rather than using + * hook_field_widget_form_alter() and checking the widget type. + * + * To modify the widget for individual itesm, use + * hook_field_widget_WIDGET_TYPE_form_alter() instead. + * + * @param $element + * The field widget form element as constructed by + * \Drupal\Core\Field\WidgetBaseInterface::form(). + * @param $form_state + * The current state of the form. + * @param $context + * An associative array. See hook_field_widget_form_alter() for the structure + * and content of the array. + * + * @see \Drupal\Core\Field\WidgetBaseInterface::form() + * @see \Drupal\Core\Field\WidgetBase::formSingleElement() + * @see hook_field_widget_multiple_form_alter() + */ +function hook_field_widget_multiple_WIDGET_TYPE_form_alter(&$elements, \Drupal\Core\Form\FormStateInterface $form_state, $context) { + // Alter the overall title for the multivalue widget (the table header, + // above the rows). + $elements['#title'] = \Drupal::t('Review your %title entries', ['%title' => $elements['#title']]); + + if (!empty($elements['#description'])) { + // Add a paragraph to the description, above all the rows. + $elements['#description'] = [ + '#markup' => \Drupal::t('These items might be out of date.'), + '#markup' => $elements['#description'], + ]; + } + else { + $elements['#description'] = \Drupal::t('These items might be out of date.'); + } +} + /** * @} End of "defgroup field_widget". */ diff --git a/core/modules/media/media.module b/core/modules/media/media.module index 296945973a..fa76bb406a 100644 --- a/core/modules/media/media.module +++ b/core/modules/media/media.module @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Render\Markup; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; @@ -172,3 +173,99 @@ function media_form_field_ui_field_storage_add_form_alter(&$form, FormStateInter $form['add']['new_storage_type']['#weight'] = 0; $form['add']['description_wrapper']['#weight'] = 1; } + +/** + * Implements hook_field_widget_WIDGET_TYPE_form_alter(). + */ +function media_field_widget_entity_reference_autocomplete_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) { + // Only act on Media entity reference autocomplete fields. + if (!empty($element['target_id']['#type']) && ($element['target_id']['#type'] === 'entity_autocomplete') && ($element['target_id']['#target_type'] === 'media')) { + + $label = $element['target_id']['#title']; + $element['target_id']['#title'] = t('

Use existing %title media

', ['%title' => $label]); + + $reuse_message = t('Type part of the media name. See the media list (opens a new window) to help locate media.', [':list' => Url::fromRoute('entity.media.collection')->toString()]); + + if (empty($element['target_id']['#description'])) { + $element['target_id']['#description'] = $reuse_message; + } + else { + $element['target_id']['#description'] = ['#markup' => $element['target_id']['#description'], '#markup' => $reuse_message]; + } + // @todo There must be an easier way to get this. + $cardinality = $context['items']->getFieldDefinition()->getFieldStorageDefinition()->getCardinality(); + + // Only add the suffix if the user also has permission to create + // media. Some sites might allow content authors to pick approved media + // but not to submit their own. + // Don't add a suffix if the field is multi-value, as that would result in + // repetitive, spammy headers on every item. + if ($cardinality === 1) { + $access = FALSE; + $settings = $context['items']->getSettings(); + if (!empty($settings['handler_settings']['target_bundles'])) { + foreach ($settings['handler_settings']['target_bundles'] as $bundle) { + if (\Drupal::entityTypeManager()->getAccessControlHandler('media')->createAccess($bundle, \Drupal::currentUser())) { + $access = TRUE; + break; + } + } + } + else { + $access = \Drupal::entityTypeManager()->getAccessControlHandler('media')->createAccess(NULL, \Drupal::currentUser()); + } + if ($access) { + $new_message = t('

Uploading new %title media

Upload your media on the media upload form (opens a new window), then add it by name to the field below.', ['%title' => $label, ':add' => Url::fromRoute('entity.media.add_page')->toString()]); + if (empty($element['#prefix'])) { + $element['#prefix'] = $new_message; + } else { + $element['#prefix'] = Markup::create($element['#prefix'] . $new_message); + } + } + } + } +} + + +/** + * Implements hook_field_widget_multiple_WIDGET_TYPE_form_alter(). + */ +function media_field_widget_multiple_entity_reference_autocomplete_form_alter(&$elements, \Drupal\Core\Form\FormStateInterface $form_state, $context) { + // Only act on Media entity reference autocomplete fields. + if (!empty($elements[0]['target_id']['#type']) && ($elements[0]['target_id']['#type'] === 'entity_autocomplete') && ($elements[0]['target_id']['#target_type'] === 'media')) { + + // Use the user-defined label for the field to indicate that the form + // allows reusing existing media. + $label = $elements['#title']; + $elements['#title'] = t('

Use existing %title media

', ['%title' => $label]); + + // Only add the suffix if the user also has permission to create + // media. Some sites might allow content authors to pick approved media + // but not to submit their own. + $access = FALSE; + $settings = $context['items']->getSettings(); + if (!empty($settings['handler_settings']['target_bundles'])) { + foreach ($settings['handler_settings']['target_bundles'] as $bundle) { + if (\Drupal::entityTypeManager()->getAccessControlHandler('media')->createAccess($bundle, \Drupal::currentUser())) { + $access = TRUE; + break; + } + } + } + else { + $access = \Drupal::entityTypeManager()->getAccessControlHandler('media')->createAccess(NULL, \Drupal::currentUser()); + } + if ($access) { + $new_message = t('

Uploading new %title media

Upload your media on the media upload form (opens a new window), then add it by name to the field below.', ['%title' => $label, ':add' => Url::fromRoute('entity.media.add_page')->toString()]); + if (empty($elements['#prefix'])) { + $elements['#prefix'] = $new_message; + } + else { + // @todo #prefix doesn't seem to accept a render array. We need to + // append to the beginning of the current suffix, which is used to + // clearfix floating things. + $elements['#prefix'] = Markup::create($elements['#prefix'] . $new_message); + } + } + } +}