class AddFormBase

Same name in other branches
  1. 9 core/modules/media_library/src/Form/AddFormBase.php \Drupal\media_library\Form\AddFormBase
  2. 8.9.x core/modules/media_library/src/Form/AddFormBase.php \Drupal\media_library\Form\AddFormBase
  3. 11.x core/modules/media_library/src/Form/AddFormBase.php \Drupal\media_library\Form\AddFormBase

Provides a base class for creating media items from within the media library.

Hierarchy

Expanded class hierarchy of AddFormBase

1 file declares its use of AddFormBase
TestAddForm.php in core/modules/media_library/tests/modules/media_library_form_overwrite_test/src/Form/TestAddForm.php

File

core/modules/media_library/src/Form/AddFormBase.php, line 30

Namespace

Drupal\media_library\Form
View source
abstract class AddFormBase extends FormBase implements BaseFormIdInterface, TrustedCallbackInterface {
    
    /**
     * The entity type manager.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     */
    protected $entityTypeManager;
    
    /**
     * The media library UI builder.
     *
     * @var \Drupal\media_library\MediaLibraryUiBuilder
     */
    protected $libraryUiBuilder;
    
    /**
     * The type of media items being created by this form.
     *
     * @var \Drupal\media\MediaTypeInterface
     */
    protected $mediaType;
    
    /**
     * The media view builder.
     *
     * @var \Drupal\Core\Entity\EntityViewBuilderInterface
     */
    protected $viewBuilder;
    
    /**
     * The opener resolver.
     *
     * @var \Drupal\media_library\OpenerResolverInterface
     */
    protected $openerResolver;
    
    /**
     * Constructs an AddFormBase object.
     *
     * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
     *   The entity type manager.
     * @param \Drupal\media_library\MediaLibraryUiBuilder $library_ui_builder
     *   The media library UI builder.
     * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
     *   The opener resolver.
     */
    public function __construct(EntityTypeManagerInterface $entity_type_manager, MediaLibraryUiBuilder $library_ui_builder, OpenerResolverInterface $opener_resolver) {
        $this->entityTypeManager = $entity_type_manager;
        $this->libraryUiBuilder = $library_ui_builder;
        $this->viewBuilder = $this->entityTypeManager
            ->getViewBuilder('media');
        $this->openerResolver = $opener_resolver;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container) {
        return new static($container->get('entity_type.manager'), $container->get('media_library.ui_builder'), $container->get('media_library.opener_resolver'));
    }
    
    /**
     * {@inheritdoc}
     */
    public function getBaseFormId() {
        return 'media_library_add_form';
    }
    
    /**
     * Get the media type from the form state.
     *
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return \Drupal\media\MediaTypeInterface
     *   The media type.
     *
     * @throws \InvalidArgumentException
     *   If the selected media type does not exist.
     */
    protected function getMediaType(FormStateInterface $form_state) {
        if ($this->mediaType) {
            return $this->mediaType;
        }
        $state = $this->getMediaLibraryState($form_state);
        $selected_type_id = $state->getSelectedTypeId();
        $this->mediaType = $this->entityTypeManager
            ->getStorage('media_type')
            ->load($selected_type_id);
        if (!$this->mediaType) {
            throw new \InvalidArgumentException("The '{$selected_type_id}' media type does not exist.");
        }
        return $this->mediaType;
    }
    
    /**
     * {@inheritdoc}
     */
    public function buildForm(array $form, FormStateInterface $form_state) {
        // @todo Remove the ID when we can use selectors to replace content via
        //   AJAX in https://www.drupal.org/project/drupal/issues/2821793.
        $form['#prefix'] = '<div id="media-library-add-form-wrapper">';
        $form['#suffix'] = '</div>';
        // The media library is loaded via AJAX, which means that the form action
        // URL defaults to the current URL. However, to add media, we always need to
        // submit the form to the media library URL, not whatever the current URL
        // may be.
        $form['#action'] = Url::fromRoute('media_library.ui', [], [
            'query' => $this->getMediaLibraryState($form_state)
                ->all(),
        ])
            ->toString();
        // The form is posted via AJAX. When there are messages set during the
        // validation or submission of the form, the messages need to be shown to
        // the user.
        $form['status_messages'] = [
            '#type' => 'status_messages',
        ];
        $form['#attributes']['class'] = [
            'js-media-library-add-form',
        ];
        $added_media = $this->getAddedMediaItems($form_state);
        if (empty($added_media)) {
            $form = $this->buildInputElement($form, $form_state);
        }
        else {
            $form['#attributes']['data-input'] = 'true';
            // This deserves to be themeable, but it doesn't need to be its own "real"
            // template.
            $form['description'] = [
                '#type' => 'inline_template',
                '#template' => '<p>{{ text }}</p>',
                '#context' => [
                    'text' => $this->formatPlural(count($added_media), 'The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.', 'The media items have been created but have not yet been saved. Fill in any required fields and save to add them to the media library.'),
                ],
            ];
            $form['media'] = [
                '#pre_render' => [
                    [
                        $this,
                        'preRenderAddedMedia',
                    ],
                ],
                '#attributes' => [
                    'class' => [
                        // This needs to be focus-able by an AJAX response.
                        // @see ::updateFormCallback()
'js-media-library-add-form-added-media',
                    ],
                    'aria-label' => $this->t('Added media items'),
                    // Add the tabindex '-1' to allow the focus to be shifted to the added
                    // media wrapper when items are added. We set focus to the container
                    // because a media item does not necessarily have required fields and
                    // we do not want to set focus to the remove button automatically.
                    // @see ::updateFormCallback()
'tabindex' => '-1',
                ],
            ];
            foreach ($added_media as $delta => $media) {
                $form['media'][$delta] = $this->buildEntityFormElement($media, $form, $form_state, $delta);
            }
            $form['selection'] = $this->buildCurrentSelectionArea($form, $form_state);
            $form['actions'] = $this->buildActions($form, $form_state);
        }
        // Allow the current selection to be set in a hidden field so the selection
        // can be passed between different states of the form. This field is filled
        // via JavaScript so the default value should be empty.
        // @see Drupal.behaviors.MediaLibraryItemSelection
        $form['current_selection'] = [
            '#type' => 'hidden',
            '#default_value' => '',
            '#attributes' => [
                'class' => [
                    'js-media-library-add-form-current-selection',
                ],
            ],
        ];
        return $form;
    }
    
    /**
     * Builds the element for submitting source field value(s).
     *
     * The input element needs to have a submit handler to create media items from
     * the user input and store them in the form state using
     * ::processInputValues().
     *
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array
     *   The complete form, with the element added.
     *
     * @see ::processInputValues()
     */
    protected abstract function buildInputElement(array $form, FormStateInterface $form_state);
    
    /**
     * Builds the sub-form for setting required fields on a new media item.
     *
     * @param \Drupal\media\MediaInterface $media
     *   A new, unsaved media item.
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     * @param int $delta
     *   The delta of the media item.
     *
     * @return array
     *   The element containing the required fields sub-form.
     */
    protected function buildEntityFormElement(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
        // We need to make sure each button has a unique name attribute. The default
        // name for button elements is 'op'. If the name is not unique, the
        // triggering element is not set correctly and the wrong media item is
        // removed.
        // @see ::removeButtonSubmit()
        $parents = $form['#parents'] ?? [];
        $id_suffix = $parents ? '-' . implode('-', $parents) : '';
        $element = [
            '#wrapper_attributes' => [
                'aria-label' => $media->getName(),
                // Add the tabindex '-1' to allow the focus to be shifted to the next
                // media item when an item is removed. We set focus to the container
                // because a media item does not necessarily have required fields and we
                // do not want to set focus to the remove button automatically.
                // @see ::updateFormCallback()
'tabindex' => '-1',
                // Add a data attribute containing the delta to allow us to easily shift
                // the focus to a specific media item.
                // @see ::updateFormCallback()
'data-media-library-added-delta' => $delta,
            ],
            'preview' => [
                '#type' => 'container',
                '#weight' => 10,
            ],
            'fields' => [
                '#type' => 'container',
                '#weight' => 20,
                // The '#parents' are set here because the entity form display needs it
                // to build the entity form fields.
'#parents' => [
                    'media',
                    $delta,
                    'fields',
                ],
            ],
            'remove_button' => [
                '#type' => 'submit',
                '#value' => $this->t('Remove'),
                '#name' => 'media-' . $delta . '-remove-button' . $id_suffix,
                '#weight' => 30,
                '#attributes' => [
                    'aria-label' => $this->t('Remove @label', [
                        '@label' => $media->getName(),
                    ]),
                ],
                '#ajax' => [
                    'callback' => '::updateFormCallback',
                    'wrapper' => 'media-library-add-form-wrapper',
                    'message' => $this->t('Removing @label.', [
                        '@label' => $media->getName(),
                    ]),
                ],
                '#submit' => [
                    '::removeButtonSubmit',
                ],
                // Ensure errors in other media items do not prevent removal.
'#limit_validation_errors' => [],
            ],
        ];
        // @todo Make the image style configurable in
        //   https://www.drupal.org/node/2988223
        $source = $media->getSource();
        $plugin_definition = $source->getPluginDefinition();
        if ($thumbnail_uri = $source->getMetadata($media, $plugin_definition['thumbnail_uri_metadata_attribute'])) {
            $element['preview']['thumbnail'] = [
                '#theme' => 'image_style',
                '#style_name' => 'media_library',
                '#uri' => $thumbnail_uri,
            ];
        }
        $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
        // When the name is not added to the form as an editable field, output
        // the name as a fixed element to confirm the right file was uploaded.
        if (!$form_display->getComponent('name')) {
            $element['fields']['name'] = [
                '#type' => 'item',
                '#title' => $this->t('Name'),
                '#markup' => $media->getName(),
            ];
        }
        $form_display->buildForm($media, $element['fields'], $form_state);
        // Add source field name so that it can be identified in form alter and
        // widget alter hooks.
        $element['fields']['#source_field_name'] = $this->getSourceFieldName($media->bundle->entity);
        // The revision log field is currently not configurable from the form
        // display, so hide it by changing the access.
        // @todo Make the revision_log_message field configurable in
        //   https://www.drupal.org/project/drupal/issues/2696555
        if (isset($element['fields']['revision_log_message'])) {
            $element['fields']['revision_log_message']['#access'] = FALSE;
        }
        return $element;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function trustedCallbacks() {
        return [
            'preRenderAddedMedia',
        ];
    }
    
    /**
     * Converts the set of newly added media into an item list for rendering.
     *
     * @param array $element
     *   The render element to transform.
     *
     * @return array
     *   The transformed render element.
     */
    public function preRenderAddedMedia(array $element) {
        // Transform the element into an item list for rendering.
        $element['#theme'] = 'item_list__media_library_add_form_media_list';
        $element['#list_type'] = 'ul';
        foreach (Element::children($element) as $delta) {
            $element['#items'][$delta] = $element[$delta];
            unset($element[$delta]);
        }
        return $element;
    }
    
    /**
     * Returns a render array containing the current selection.
     *
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array
     *   A render array containing the current selection.
     */
    protected function buildCurrentSelectionArea(array $form, FormStateInterface $form_state) {
        $pre_selected_items = $this->getPreSelectedMediaItems($form_state);
        if (!$pre_selected_items || !$this->isAdvancedUi()) {
            return [];
        }
        $selection = [
            '#type' => 'details',
            '#theme_wrappers' => [
                'details__media_library_add_form_selected_media',
            ],
            '#open' => FALSE,
            '#title' => $this->t('Additional selected media'),
        ];
        foreach ($pre_selected_items as $media_id => $media) {
            $selection[$media_id] = $this->buildSelectedItemElement($media, $form, $form_state);
        }
        return $selection;
    }
    
    /**
     * Returns a render array for a single pre-selected media item.
     *
     * @param \Drupal\media\MediaInterface $media
     *   The media item.
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array
     *   A render array of a pre-selected media item.
     */
    protected function buildSelectedItemElement(MediaInterface $media, array $form, FormStateInterface $form_state) {
        return [
            '#theme' => 'media_library_item__small',
            '#attributes' => [
                'class' => [
                    'js-media-library-item',
                    'js-click-to-select',
                ],
            ],
            'select' => [
                '#type' => 'container',
                '#attributes' => [
                    'class' => [
                        'js-click-to-select-checkbox',
                    ],
                ],
                'select_checkbox' => [
                    '#type' => 'checkbox',
                    '#title' => $this->t('Select @name', [
                        '@name' => $media->label(),
                    ]),
                    '#title_display' => 'invisible',
                    '#return_value' => $media->id(),
                    // The checkbox's value is never processed by this form. It is present
                    // for usability and accessibility reasons, and only used by
                    // JavaScript to track whether or not this media item is selected. The
                    // hidden 'current_selection' field is used to store the actual IDs of
                    // selected media items.
'#value' => FALSE,
                ],
            ],
            'rendered_entity' => $this->viewBuilder
                ->view($media, 'media_library'),
        ];
    }
    
    /**
     * Returns an array of supported actions for the form.
     *
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array
     *   An actions element containing the actions of the form.
     */
    protected function buildActions(array $form, FormStateInterface $form_state) {
        $actions = [
            '#type' => 'actions',
            'save_select' => [
                '#type' => 'submit',
                '#button_type' => 'primary',
                '#value' => $this->t('Save'),
                '#ajax' => [
                    'callback' => '::updateLibrary',
                    'wrapper' => 'media-library-add-form-wrapper',
                ],
            ],
        ];
        if ($this->isAdvancedUi()) {
            $actions['save_select']['#value'] = $this->t('Save and select');
            $actions['save_insert'] = [
                '#type' => 'submit',
                '#value' => $this->t('Save and insert'),
                '#ajax' => [
                    'callback' => '::updateWidget',
                    'wrapper' => 'media-library-add-form-wrapper',
                ],
            ];
        }
        return $actions;
    }
    
    /**
     * Creates media items from source field input values.
     *
     * @param mixed[] $source_field_values
     *   The values for source fields of the media items.
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     */
    protected function processInputValues(array $source_field_values, array $form, FormStateInterface $form_state) {
        $media_type = $this->getMediaType($form_state);
        $media_storage = $this->entityTypeManager
            ->getStorage('media');
        $source_field_name = $this->getSourceFieldName($media_type);
        $media = array_map(function ($source_field_value) use ($media_type, $media_storage, $source_field_name) {
            return $this->createMediaFromValue($media_type, $media_storage, $source_field_name, $source_field_value);
        }, $source_field_values);
        // Re-key the media items before setting them in the form state.
        $form_state->set('media', array_values($media));
        // Save the selected items in the form state so they are remembered when an
        // item is removed.
        $media = $this->entityTypeManager
            ->getStorage('media')
            ->loadMultiple(explode(',', $form_state->getValue('current_selection')));
        // Any ID can be passed to the form, so we have to check access.
        $form_state->set('current_selection', array_filter($media, function ($media_item) {
            return $media_item->access('view');
        }));
        $form_state->setRebuild();
    }
    
    /**
     * Creates a new, unsaved media item from a source field value.
     *
     * @param \Drupal\media\MediaTypeInterface $media_type
     *   The media type of the media item.
     * @param \Drupal\Core\Entity\EntityStorageInterface $media_storage
     *   The media storage.
     * @param string $source_field_name
     *   The name of the media type's source field.
     * @param mixed $source_field_value
     *   The value for the source field of the media item.
     *
     * @return \Drupal\media\MediaInterface
     *   An unsaved media entity.
     */
    protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $source_field_value) {
        $media = $media_storage->create([
            'bundle' => $media_type->id(),
            $source_field_name => $source_field_value,
        ]);
        $media->setName($media->getName());
        return $media;
    }
    
    /**
     * Prepares a created media item to be permanently saved.
     *
     * @param \Drupal\media\MediaInterface $media
     *   The unsaved media item.
     */
    protected function prepareMediaEntityForSave(MediaInterface $media) {
        // Intentionally empty by default.
    }
    
    /**
     * Submit handler for the remove button.
     *
     * @param array $form
     *   The form render array.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The form state.
     */
    public function removeButtonSubmit(array $form, FormStateInterface $form_state) {
        // Retrieve the delta of the media item from the parents of the remove
        // button.
        $triggering_element = $form_state->getTriggeringElement();
        $delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
        $added_media = $form_state->get('media');
        $removed_media = $added_media[$delta];
        // Update the list of added media items in the form state.
        unset($added_media[$delta]);
        // Update the media items in the form state.
        $form_state->set('media', $added_media)
            ->setRebuild();
        // Show a message to the user to confirm the media is removed.
        $this->messenger()
            ->addStatus($this->t('The media item %label has been removed.', [
            '%label' => $removed_media->label(),
        ]));
    }
    
    /**
     * AJAX callback to update the entire form based on source field input.
     *
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return \Drupal\Core\Ajax\AjaxResponse|array
     *   The form render array or an AJAX response object.
     */
    public function updateFormCallback(array &$form, FormStateInterface $form_state) {
        $triggering_element = $form_state->getTriggeringElement();
        $wrapper_id = $triggering_element['#ajax']['wrapper'];
        $added_media = $form_state->get('media');
        $response = new AjaxResponse();
        // When the source field input contains errors, replace the existing form to
        // let the user change the source field input. If the user input is valid,
        // the entire modal is replaced with the second step of the form to show the
        // form fields for each media item.
        if ($form_state::hasAnyErrors()) {
            $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $form));
            return $response;
        }
        // Check if the remove button is clicked.
        if (end($triggering_element['#parents']) === 'remove_button') {
            // When the list of added media is empty, return to the media library and
            // shift focus back to the first tabbable element (which should be the
            // source field).
            if (empty($added_media)) {
                $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $this->buildMediaLibraryUi($form_state)));
                $response->addCommand(new FocusFirstCommand('#media-library-add-form-wrapper'));
            }
            else {
                $response->addCommand(new ReplaceCommand("#{$wrapper_id}", $form));
                // Find the delta of the next media item. If there is no item with a
                // bigger delta, we automatically use the delta of the previous item and
                // shift the focus there.
                $removed_delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
                $delta_to_focus = 0;
                foreach ($added_media as $delta => $media) {
                    $delta_to_focus = $delta;
                    if ($delta > $removed_delta) {
                        break;
                    }
                }
                $response->addCommand(new InvokeCommand("[data-media-library-added-delta={$delta_to_focus}]", 'focus'));
            }
        }
        else {
            $response->addCommand(new ReplaceCommand("#{$wrapper_id}", $form));
            $response->addCommand(new InvokeCommand('.js-media-library-add-form-added-media', 'focus'));
        }
        return $response;
    }
    
    /**
     * {@inheritdoc}
     */
    public function validateForm(array &$form, FormStateInterface $form_state) {
        foreach ($this->getAddedMediaItems($form_state) as $delta => $media) {
            $this->validateMediaEntity($media, $form, $form_state, $delta);
        }
    }
    
    /**
     * Validate a created media item.
     *
     * @param \Drupal\media\MediaInterface $media
     *   The media item to validate.
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     * @param int $delta
     *   The delta of the media item.
     */
    protected function validateMediaEntity(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
        $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
        $form_display->extractFormValues($media, $form['media'][$delta]['fields'], $form_state);
        $form_display->validateFormValues($media, $form['media'][$delta]['fields'], $form_state);
    }
    
    /**
     * {@inheritdoc}
     */
    public function submitForm(array &$form, FormStateInterface $form_state) {
        foreach ($this->getAddedMediaItems($form_state) as $delta => $media) {
            EntityFormDisplay::collectRenderDisplay($media, 'media_library')->extractFormValues($media, $form['media'][$delta]['fields'], $form_state);
            $this->prepareMediaEntityForSave($media);
            $media->save();
        }
    }
    
    /**
     * AJAX callback to send the new media item(s) to the media library.
     *
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array|\Drupal\Core\Ajax\AjaxResponse
     *   The form array if there are validation errors, or an AJAX response to add
     *   the created items to the current selection.
     */
    public function updateLibrary(array &$form, FormStateInterface $form_state) {
        if ($form_state::hasAnyErrors()) {
            return $form;
        }
        $media_ids = array_map(function (MediaInterface $media) {
            return $media->id();
        }, $this->getAddedMediaItems($form_state));
        $selected_count = $this->getSelectedMediaItemCount($media_ids, $form_state);
        $response = new AjaxResponse();
        $response->addCommand(new UpdateSelectionCommand($media_ids));
        $media_id_to_focus = array_pop($media_ids);
        $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $this->buildMediaLibraryUi($form_state)));
        $response->addCommand(new InvokeCommand("#media-library-content [value={$media_id_to_focus}]", 'focus'));
        $available_slots = $this->getMediaLibraryState($form_state)
            ->getAvailableSlots();
        if ($available_slots > 0 && $selected_count > $available_slots) {
            $warning = $this->formatPlural($selected_count - $available_slots, 'There are currently @total items selected. The maximum number of items for the field is @max. Remove @count item from the selection.', 'There are currently @total items selected. The maximum number of items for the field is @max. Remove @count items from the selection.', [
                '@total' => $selected_count,
                '@max' => $available_slots,
            ]);
            $response->addCommand(new MessageCommand($warning, '#media-library-messages', [
                'type' => 'warning',
            ]));
        }
        return $response;
    }
    
    /**
     * Build the render array of the media library UI.
     *
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array
     *   The render array for the media library.
     */
    protected function buildMediaLibraryUi(FormStateInterface $form_state) {
        // Get the render array for the media library. The media library state might
        // contain the 'media_library_content' when it has been opened from a
        // vertical tab. We need to remove that to make sure the render array
        // contains the vertical tabs. Besides that, we also need to force the media
        // library to create a new instance of the media add form.
        // @see \Drupal\media_library\MediaLibraryUiBuilder::buildMediaTypeAddForm()
        $state = $this->getMediaLibraryState($form_state);
        $state->remove('media_library_content');
        $state->set('_media_library_form_rebuild', TRUE);
        return $this->libraryUiBuilder
            ->buildUi($state);
    }
    
    /**
     * AJAX callback to send the new media item(s) to the calling code.
     *
     * @param array $form
     *   The complete form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return array|\Drupal\Core\Ajax\AjaxResponse
     *   The form array when there are form errors or an AJAX response to select
     *   the created items in the media library.
     */
    public function updateWidget(array &$form, FormStateInterface $form_state) {
        if ($form_state::hasAnyErrors()) {
            return $form;
        }
        // The added media items get an ID when they are saved in ::submitForm().
        // For that reason the added media items are keyed by delta in the form
        // state and we have to do an array map to get each media ID.
        $media_ids = array_map(function (MediaInterface $media) {
            return $media->id();
        }, $this->getCurrentMediaItems($form_state));
        // Allow the opener service to respond to the selection.
        $state = $this->getMediaLibraryState($form_state);
        $selected_count = $this->getSelectedMediaItemCount($media_ids, $form_state);
        $available_slots = $this->getMediaLibraryState($form_state)
            ->getAvailableSlots();
        if ($available_slots > 0 && $selected_count > $available_slots) {
            // Return to library where we display a warning about the overage.
            return $this->updateLibrary($form, $form_state);
        }
        return $this->openerResolver
            ->get($state)
            ->getSelectionResponse($state, $media_ids)
            ->addCommand(new CloseDialogCommand());
    }
    
    /**
     * Get the number of selected media.
     *
     * @param array $media_ids
     *   Array with the media IDs.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return int
     *   The number of media currently selected.
     */
    private function getSelectedMediaItemCount(array $media_ids, FormStateInterface $form_state) : int {
        $selected_count = count($media_ids);
        if ($current_selection = $form_state->getValue('current_selection')) {
            $selected_count += count(explode(',', $current_selection));
        }
        return $selected_count;
    }
    
    /**
     * Get the media library state from the form state.
     *
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return \Drupal\media_library\MediaLibraryState
     *   The media library state.
     *
     * @throws \InvalidArgumentException
     *   If the media library state is not present in the form state.
     */
    protected function getMediaLibraryState(FormStateInterface $form_state) {
        $state = $form_state->get('media_library_state');
        if (!$state) {
            throw new \InvalidArgumentException('The media library state is not present in the form state.');
        }
        return $state;
    }
    
    /**
     * Returns the name of the source field for a media type.
     *
     * @param \Drupal\media\MediaTypeInterface $media_type
     *   The media type to get the source field name for.
     *
     * @return string
     *   The name of the media type's source field.
     */
    protected function getSourceFieldName(MediaTypeInterface $media_type) {
        return $media_type->getSource()
            ->getSourceFieldDefinition($media_type)
            ->getName();
    }
    
    /**
     * Get all pre-selected media items from the form state.
     *
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return \Drupal\media\MediaInterface[]
     *   An array containing the pre-selected media items keyed by ID.
     */
    protected function getPreSelectedMediaItems(FormStateInterface $form_state) {
        // Get the pre-selected media items from the form state.
        // @see ::processInputValues()
        return $form_state->get('current_selection') ?: [];
    }
    
    /**
     * Get all added media items from the form state.
     *
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return \Drupal\media\MediaInterface[]
     *   An array containing the added media items keyed by delta. The media items
     *   won't have an ID until they are saved in ::submitForm().
     */
    protected function getAddedMediaItems(FormStateInterface $form_state) {
        return $form_state->get('media') ?: [];
    }
    
    /**
     * Get all pre-selected and added media items from the form state.
     *
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current form state.
     *
     * @return \Drupal\media\MediaInterface[]
     *   An array containing all pre-selected and added media items with
     *   renumbered numeric keys.
     */
    protected function getCurrentMediaItems(FormStateInterface $form_state) {
        $pre_selected_media = $this->getPreSelectedMediaItems($form_state);
        $added_media = $this->getAddedMediaItems($form_state);
        // Using array_merge will renumber the numeric keys.
        return array_merge($pre_selected_media, $added_media);
    }
    
    /**
     * Determines if the "advanced UI" of the Media Library is enabled.
     *
     * This exposes additional features that are useful to power users.
     *
     * @return bool
     *   TRUE if the advanced UI is enabled, FALSE otherwise.
     *
     * @see ::buildActions()
     * @see ::buildCurrentSelectionArea()
     */
    protected function isAdvancedUi() {
        return (bool) $this->config('media_library.settings')
            ->get('advanced_ui');
    }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overriden Title Overrides
AddFormBase::$entityTypeManager protected property The entity type manager.
AddFormBase::$libraryUiBuilder protected property The media library UI builder.
AddFormBase::$mediaType protected property The type of media items being created by this form.
AddFormBase::$openerResolver protected property The opener resolver.
AddFormBase::$viewBuilder protected property The media view builder.
AddFormBase::buildActions protected function Returns an array of supported actions for the form.
AddFormBase::buildCurrentSelectionArea protected function Returns a render array containing the current selection.
AddFormBase::buildEntityFormElement protected function Builds the sub-form for setting required fields on a new media item. 1
AddFormBase::buildForm public function Form constructor. Overrides FormInterface::buildForm
AddFormBase::buildInputElement abstract protected function Builds the element for submitting source field value(s). 3
AddFormBase::buildMediaLibraryUi protected function Build the render array of the media library UI.
AddFormBase::buildSelectedItemElement protected function Returns a render array for a single pre-selected media item.
AddFormBase::create public static function Instantiates a new instance of this class. Overrides FormBase::create 2
AddFormBase::createMediaFromValue protected function Creates a new, unsaved media item from a source field value. 1
AddFormBase::getAddedMediaItems protected function Get all added media items from the form state.
AddFormBase::getBaseFormId public function Returns a string identifying the base form. Overrides BaseFormIdInterface::getBaseFormId
AddFormBase::getCurrentMediaItems protected function Get all pre-selected and added media items from the form state.
AddFormBase::getMediaLibraryState protected function Get the media library state from the form state.
AddFormBase::getMediaType protected function Get the media type from the form state. 2
AddFormBase::getPreSelectedMediaItems protected function Get all pre-selected media items from the form state.
AddFormBase::getSelectedMediaItemCount private function Get the number of selected media.
AddFormBase::getSourceFieldName protected function Returns the name of the source field for a media type.
AddFormBase::isAdvancedUi protected function Determines if the &quot;advanced UI&quot; of the Media Library is enabled.
AddFormBase::prepareMediaEntityForSave protected function Prepares a created media item to be permanently saved. 1
AddFormBase::preRenderAddedMedia public function Converts the set of newly added media into an item list for rendering.
AddFormBase::processInputValues protected function Creates media items from source field input values.
AddFormBase::removeButtonSubmit public function Submit handler for the remove button. 1
AddFormBase::submitForm public function Form submission handler. Overrides FormInterface::submitForm
AddFormBase::trustedCallbacks public static function Lists the trusted callbacks provided by the implementing class. Overrides TrustedCallbackInterface::trustedCallbacks
AddFormBase::updateFormCallback public function AJAX callback to update the entire form based on source field input.
AddFormBase::updateLibrary public function AJAX callback to send the new media item(s) to the media library.
AddFormBase::updateWidget public function AJAX callback to send the new media item(s) to the calling code.
AddFormBase::validateForm public function Form validation handler. Overrides FormBase::validateForm
AddFormBase::validateMediaEntity protected function Validate a created media item.
AddFormBase::__construct public function Constructs an AddFormBase object. 2
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FormBase::$configFactory protected property The config factory. 3
FormBase::$requestStack protected property The request stack. 1
FormBase::$routeMatch protected property The route match.
FormBase::config protected function Retrieves a configuration object.
FormBase::configFactory protected function Gets the config factory for this form. 3
FormBase::container private function Returns the service container.
FormBase::currentUser protected function Gets the current user. 2
FormBase::getRequest protected function Gets the request object.
FormBase::getRouteMatch protected function Gets the route match.
FormBase::logger protected function Gets the logger for a specific channel.
FormBase::redirect protected function Returns a redirect response object for the specified route.
FormBase::resetConfigFactory public function Resets the configuration factory.
FormBase::setConfigFactory public function Sets the config factory for this form.
FormBase::setRequestStack public function Sets the request stack object to use.
FormInterface::getFormId public function Returns a unique string identifying the form. 284
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 16
MessengerTrait::messenger public function Gets the messenger. 16
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 2
RedirectDestinationTrait::getDestinationArray protected function Prepares a &#039;destination&#039; URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TrustedCallbackInterface::THROW_EXCEPTION constant Untrusted callbacks throw exceptions.
TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION constant Untrusted callbacks trigger silenced E_USER_DEPRECATION errors.
TrustedCallbackInterface::TRIGGER_WARNING Deprecated constant Untrusted callbacks trigger E_USER_WARNING errors.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.