diff --git a/core/modules/media/src/Annotation/MediaSource.php b/core/modules/media/src/Annotation/MediaSource.php
index 2f7bdad23d4..1286601b296 100644
--- a/core/modules/media/src/Annotation/MediaSource.php
+++ b/core/modules/media/src/Annotation/MediaSource.php
@@ -57,6 +57,16 @@ class MediaSource extends Plugin {
*/
public $allowed_field_types = [];
+ /**
+ * The classes used to define media source-specific forms.
+ *
+ * An array of form class names, keyed by ID. The ID represents the operation
+ * the form is used for.
+ *
+ * @var string[]
+ */
+ public $forms = [];
+
/**
* A filename for the default thumbnail.
*
diff --git a/core/modules/media_library/css/media_library.theme.css b/core/modules/media_library/css/media_library.theme.css
index 61be1a75079..e5ca9ab6d5d 100644
--- a/core/modules/media_library/css/media_library.theme.css
+++ b/core/modules/media_library/css/media_library.theme.css
@@ -83,6 +83,19 @@
border-left: 0;
}
+.media-library-add-form--without-input {
+ margin-bottom: 1em;
+ border-bottom: 1px solid #c0c0c0;
+}
+
+.media-library-add-form--without-input .form-item {
+ margin: 0 0 1em;
+}
+
+.media-library-add-form .file-upload-help {
+ margin: 8px 0 0;
+}
+
.media-library-views-form__header .form-item {
margin-right: 8px;
}
@@ -276,31 +289,34 @@
border-color: #40b6ff;
}
-/* Style the wrappers around new media and files */
-.media-library-upload__media,
-.media-library-upload__file {
+/* Style the wrappers around new media and files. */
+.media-library-add-form__media {
display: flex;
padding: 20px 0 20px 0;
+ border-bottom: 1px solid #c0c0c0;
}
-.media-library-upload__file {
- align-items: center;
+/* Do not show the top padding for the first item. */
+.media-library-add-form__media:first-child {
+ padding-top: 0;
}
-.media-library-upload__file-label {
- margin-right: 10px;
+/* Do not show the bottom border and padding for the last item. */
+.media-library-add-form__media:last-child {
+ border-bottom: 0;
+ padding-bottom: 0;
}
/* @todo Remove in https://www.drupal.org/project/drupal/issues/2987921 */
-.media-library-upload__source-field .file,
-.media-library-upload__source-field .button,
-.media-library-upload__source-field .image-preview,
-.media-library-upload__source-field .form-type-managed-file > label,
-.media-library-upload__source-field .file-size {
+.media-library-add-form__source-field .file,
+.media-library-add-form__source-field .button,
+.media-library-add-form__source-field .image-preview,
+.media-library-add-form__source-field .form-type-managed-file > label,
+.media-library-add-form__source-field .file-size {
display: none;
}
-.media-library-upload__media-preview {
+.media-library-add-form__preview {
display: flex;
justify-content: center;
align-items: center;
@@ -308,15 +324,11 @@
margin-right: 20px;
background: #ebebeb;
}
-[dir="rtl"] .media-library-upload__media-preview {
+[dir="rtl"] .media-library-add-form__preview {
margin-right: 0;
margin-left: 20px;
}
-.media-library-upload__media-preview img {
- display: block;
-}
-
/* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
.media-library-widget .media-library-item__name a,
.media-library-view.view-display-id-widget .media-library-item__name a {
diff --git a/core/modules/media_library/js/media_library.ui.es6.js b/core/modules/media_library/js/media_library.ui.es6.js
index d0aff3baf95..f0676d10136 100644
--- a/core/modules/media_library/js/media_library.ui.es6.js
+++ b/core/modules/media_library/js/media_library.ui.es6.js
@@ -15,6 +15,26 @@
currentSelection: [],
};
+ /**
+ * Command to update the current media library selection.
+ *
+ * @param {Drupal.Ajax} [ajax]
+ * The Drupal Ajax object.
+ * @param {object} response
+ * Object holding the server response.
+ * @param {number} [status]
+ * The HTTP status code.
+ */
+ Drupal.AjaxCommands.prototype.updateMediaLibrarySelection = function(
+ ajax,
+ response,
+ status,
+ ) {
+ Object.values(response.mediaIds).forEach(value => {
+ Drupal.MediaLibrary.currentSelection.push(value);
+ });
+ };
+
/**
* Warn users when clicking outgoing links from the library or widget.
*
diff --git a/core/modules/media_library/js/media_library.ui.js b/core/modules/media_library/js/media_library.ui.js
index 163f989bdca..4d3e9169345 100644
--- a/core/modules/media_library/js/media_library.ui.js
+++ b/core/modules/media_library/js/media_library.ui.js
@@ -10,6 +10,12 @@
currentSelection: []
};
+ Drupal.AjaxCommands.prototype.updateMediaLibrarySelection = function (ajax, response, status) {
+ Object.values(response.mediaIds).forEach(function (value) {
+ Drupal.MediaLibrary.currentSelection.push(value);
+ });
+ };
+
Drupal.behaviors.MediaLibraryWidgetWarn = {
attach: function attach(context) {
$('.js-media-library-item a[href]', context).once('media-library-warn-link').on('click', function (e) {
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index b5d5d1d3c76..6533258899b 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -5,7 +5,6 @@
* Contains hook implementations for the media_library module.
*/
-use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
@@ -17,11 +16,11 @@ use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Template\Attribute;
-use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\media\MediaTypeForm;
use Drupal\media\MediaTypeInterface;
+use Drupal\media_library\Form\FileUploadForm;
use Drupal\media_library\MediaLibraryState;
use Drupal\views\Form\ViewsForm;
use Drupal\views\Plugin\views\cache\CachePluginBase;
@@ -41,6 +40,16 @@ function media_library_help($route_name, RouteMatchInterface $route_match) {
}
}
+/**
+ * Implements hook_media_source_info_alter().
+ */
+function media_library_media_source_info_alter(array &$sources) {
+ $sources['audio_file']['forms']['media_library_add'] = FileUploadForm::class;
+ $sources['file']['forms']['media_library_add'] = FileUploadForm::class;
+ $sources['image']['forms']['media_library_add'] = FileUploadForm::class;
+ $sources['video_file']['forms']['media_library_add'] = FileUploadForm::class;
+}
+
/**
* Implements hook_theme().
*/
@@ -52,36 +61,6 @@ function media_library_theme() {
];
}
-/**
- * Implements hook_preprocess_view().
- *
- * Adds a link to add media above the view.
- */
-function media_library_preprocess_views_view(&$variables) {
- $view = $variables['view'];
- if ($view->id() === 'media_library' && $view->current_display === 'widget') {
- $url = Url::fromRoute('media_library.upload');
- if ($url->access()) {
- $url->setOption('query', \Drupal::request()->query->all());
- $variables['header']['add_media'] = [
- '#type' => 'link',
- '#title' => t('Add media'),
- '#url' => $url,
- '#attributes' => [
- 'class' => ['button', 'button-action', 'button--primary', 'use-ajax'],
- 'data-dialog-type' => 'modal',
- 'data-dialog-options' => Json::encode([
- 'dialogClass' => 'media-library-widget-modal',
- 'height' => '75%',
- 'width' => '75%',
- 'title' => t('Add media'),
- ]),
- ],
- ];
- }
- }
-}
-
/**
* Implements hook_views_post_render().
*/
diff --git a/core/modules/media_library/media_library.routing.yml b/core/modules/media_library/media_library.routing.yml
index 8f0fb5f87e1..efc9836e470 100644
--- a/core/modules/media_library/media_library.routing.yml
+++ b/core/modules/media_library/media_library.routing.yml
@@ -1,9 +1,3 @@
-media_library.upload:
- path: '/admin/content/media-widget-upload'
- defaults:
- _form: '\Drupal\media_library\Form\MediaLibraryUploadForm'
- requirements:
- _custom_access: '\Drupal\media_library\Form\MediaLibraryUploadForm::access'
media_library.ui:
path: '/media-library'
defaults:
diff --git a/core/modules/media_library/media_library.services.yml b/core/modules/media_library/media_library.services.yml
index 9550f5190b9..acd28df2493 100644
--- a/core/modules/media_library/media_library.services.yml
+++ b/core/modules/media_library/media_library.services.yml
@@ -1,4 +1,4 @@
services:
media_library.ui_builder:
class: Drupal\media_library\MediaLibraryUiBuilder
- arguments: ['@entity_type.manager', '@request_stack', '@views.executable']
+ arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder']
diff --git a/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php b/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php
new file mode 100644
index 00000000000..c9f329ce6bc
--- /dev/null
+++ b/core/modules/media_library/src/Ajax/UpdateSelectionCommand.php
@@ -0,0 +1,54 @@
+mediaIds = $media_ids;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render() {
+ return [
+ 'command' => 'updateMediaLibrarySelection',
+ 'mediaIds' => $this->mediaIds,
+ ];
+ }
+
+}
diff --git a/core/modules/media_library/src/Form/AddFormBase.php b/core/modules/media_library/src/Form/AddFormBase.php
new file mode 100644
index 00000000000..da783d36ab0
--- /dev/null
+++ b/core/modules/media_library/src/Form/AddFormBase.php
@@ -0,0 +1,447 @@
+entityTypeManager = $entity_type_manager;
+ $this->libraryUiBuilder = $library_ui_builder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('media_library.ui_builder')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ 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.
+ */
+ protected function getMediaType(FormStateInterface $form_state) {
+ if ($this->mediaType) {
+ return $this->mediaType;
+ }
+
+ $state = $form_state->get('media_library_state');
+
+ if (!$state) {
+ throw new \InvalidArgumentException('The media library state is not present in the form state.');
+ }
+
+ $selected_type_id = $form_state->get('media_library_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) {
+ $form['#prefix'] = '
';
+ $form['#suffix'] = '
';
+ $form['#attached']['library'][] = 'media_library/style';
+
+ // 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'][] = 'media-library-add-form';
+ $added_media = $form_state->get('media');
+ if (empty($added_media)) {
+ $form['#attributes']['class'][] = 'media-library-add-form--without-input';
+ $form = $this->buildInputElement($form, $form_state);
+ }
+ else {
+ $form['#attributes']['class'][] = 'media-library-add-form--with-input';
+
+ $form['media'] = [
+ '#type' => 'container',
+ ];
+
+ foreach ($added_media as $delta => $media) {
+ $form['media'][$delta] = $this->buildEntityFormElement($media, $form, $form_state, $delta);
+ }
+
+ $form['actions'] = $this->buildActions($form, $form_state);
+ }
+ 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()
+ */
+ abstract protected 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) {
+ $element = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => [
+ 'media-library-add-form__media',
+ ],
+ ],
+ 'preview' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => [
+ 'media-library-add-form__preview',
+ ],
+ ],
+ ],
+ 'fields' => [
+ '#type' => 'container',
+ '#attributes' => [
+ 'class' => [
+ 'media-library-add-form__fields',
+ ],
+ ],
+ // The '#parents' are set here because the entity form display needs it
+ // to build the entity form fields.
+ '#parents' => ['media', $delta, 'fields'],
+ ],
+ ];
+ // @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);
+
+ // We hide the preview of the uploaded file in the image widget with CSS.
+ // @todo Improve hiding file widget elements in
+ // https://www.drupal.org/project/drupal/issues/2987921
+ $source_field_name = $this->getSourceFieldName($media->bundle->entity);
+ if (isset($element['fields'][$source_field_name])) {
+ $element['fields'][$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
+ }
+ // 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;
+ }
+
+ /**
+ * 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) {
+ return [
+ '#type' => 'actions',
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Save'),
+ '#ajax' => [
+ 'callback' => '::updateWidget',
+ 'wrapper' => 'media-library-add-form-wrapper',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * 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);
+ $form_state->set('media', $media)->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) {
+ return $media_storage->create([
+ 'bundle' => $media_type->id(),
+ $source_field_name => $source_field_value,
+ ]);
+ }
+
+ /**
+ * 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.
+ }
+
+ /**
+ * 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) {
+ // 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 = new AjaxResponse();
+ $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $form));
+ return $response;
+ }
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state) {
+ $added_media = $form_state->get('media') ?: [];
+ foreach ($added_media 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) {
+ $added_media = $form_state->get('media') ?: [];
+ foreach ($added_media 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 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 a 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;
+ }
+
+ $added_media = $form_state->get('media') ?: [];
+ $media_ids = array_map(function (MediaInterface $media) {
+ return $media->id();
+ }, $added_media);
+
+ // 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 = MediaLibraryState::fromRequest($this->getRequest());
+ $state->remove('media_library_content');
+ $state->set('_media_library_form_rebuild', TRUE);
+ $library_ui = $this->libraryUiBuilder->buildUi($state);
+
+ $response = new AjaxResponse();
+ $response->addCommand(new UpdateSelectionCommand($media_ids));
+ $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $library_ui));
+ return $response;
+ }
+
+ /**
+ * 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();
+ }
+
+}
diff --git a/core/modules/media_library/src/Form/FileUploadForm.php b/core/modules/media_library/src/Form/FileUploadForm.php
new file mode 100644
index 00000000000..e89b67e7d15
--- /dev/null
+++ b/core/modules/media_library/src/Form/FileUploadForm.php
@@ -0,0 +1,248 @@
+elementInfo = $element_info;
+ $this->renderer = $renderer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity_type.manager'),
+ $container->get('media_library.ui_builder'),
+ $container->get('element_info'),
+ $container->get('renderer')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getMediaType(FormStateInterface $form_state) {
+ if ($this->mediaType) {
+ return $this->mediaType;
+ }
+
+ $media_type = parent::getMediaType($form_state);
+ // The file upload form only supports media types which use a file field as
+ // a source field.
+ $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
+ if (!is_a($field_definition->getClass(), FileFieldItemList::class, TRUE)) {
+ throw new \InvalidArgumentException('Can only add media types which use a file field as a source field.');
+ }
+ return $media_type;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildInputElement(array $form, FormStateInterface $form_state) {
+ $form['#attributes']['class'][] = 'media-library-add-form-upload';
+
+ // Create a file item to get the upload validators.
+ $media_type = $this->getMediaType($form_state);
+ $item = $this->createFileItem($media_type);
+
+ /** @var \Drupal\media_library\MediaLibraryState $state */
+ $state = $form_state->get('media_library_state');
+ if (!$state->hasSlotsAvailable()) {
+ return $form;
+ }
+
+ $slots = $state->getAvailableSlots();
+
+ $process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
+ $form['upload'] = [
+ '#type' => 'managed_file',
+ '#title' => $this->formatPlural($slots, 'Add file', 'Add files'),
+ // @todo Move validation in https://www.drupal.org/node/2988215
+ '#process' => array_merge(['::validateUploadElement'], $process, ['::processUploadElement']),
+ '#upload_validators' => $item->getUploadValidators(),
+ '#multiple' => $slots > 1 || $slots === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+ '#cardinality' => $slots,
+ '#remaining_slots' => $slots,
+ ];
+
+ $file_upload_help = [
+ '#theme' => 'file_upload_help',
+ '#upload_validators' => $form['upload']['#upload_validators'],
+ '#cardinality' => $slots,
+ ];
+
+ // The file upload help needs to be rendered since the description does not
+ // accept render arrays. The FileWidget::formElement() method adds the file
+ // upload help in the same way, so any theming improvements made to file
+ // fields would also be applied to this upload field.
+ // @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formElement()
+ $form['upload']['#description'] = $this->renderer->renderPlain($file_upload_help);
+
+ return $form;
+ }
+
+ /**
+ * Validates the upload element.
+ *
+ * @param array $element
+ * The upload element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return array
+ * The processed upload element.
+ */
+ public function validateUploadElement(array $element, FormStateInterface $form_state) {
+ if ($form_state::hasAnyErrors()) {
+ // When an error occurs during uploading files, remove all files so the
+ // user can re-upload the files.
+ $element['#value'] = [];
+ }
+ $values = $form_state->getValue('upload', []);
+ if (count($values['fids']) > $element['#cardinality'] && $element['#cardinality'] !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
+ $form_state->setError($element, $this->t('A maximum of @count files can be uploaded.', [
+ '@count' => $element['#cardinality'],
+ ]));
+ $form_state->setValue('upload', []);
+ $element['#value'] = [];
+ }
+ return $element;
+ }
+
+ /**
+ * Processes an upload (managed_file) element.
+ *
+ * @param array $element
+ * The upload element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return array
+ * The processed upload element.
+ */
+ public function processUploadElement(array $element, FormStateInterface $form_state) {
+ $element['upload_button']['#submit'] = ['::uploadButtonSubmit'];
+ $element['upload_button']['#ajax'] = [
+ 'callback' => '::updateFormCallback',
+ 'wrapper' => 'media-library-wrapper',
+ ];
+ return $element;
+ }
+
+ /**
+ * Submit handler for the upload button, inside the managed_file element.
+ *
+ * @param array $form
+ * The form render array.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ */
+ public function uploadButtonSubmit(array $form, FormStateInterface $form_state) {
+ $files = $this->entityTypeManager
+ ->getStorage('file')
+ ->loadMultiple($form_state->getValue('upload', []));
+ $this->processInputValues($files, $form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $file) {
+ if (!($file instanceof FileInterface)) {
+ throw new \InvalidArgumentException('Cannot create a media item without a file entity.');
+ }
+
+ // Create a file item to get the upload location.
+ $item = $this->createFileItem($media_type);
+ $upload_location = $item->getUploadLocation();
+ if (!file_prepare_directory($upload_location, FILE_CREATE_DIRECTORY)) {
+ throw new \Exception("The destination directory '$upload_location' is not writable");
+ }
+ $file = file_move($file, $upload_location);
+ if (!$file) {
+ throw new \RuntimeException("Unable to move file to '$upload_location'");
+ }
+
+ return parent::createMediaFromValue($media_type, $media_storage, $source_field_name, $file)->setName($file->getFilename());
+ }
+
+ /**
+ * Create a file field item.
+ *
+ * @param \Drupal\media\MediaTypeInterface $media_type
+ * The media type of the media item.
+ *
+ * @return \Drupal\file\Plugin\Field\FieldType\FileItem
+ * A created file item.
+ */
+ protected function createFileItem(MediaTypeInterface $media_type) {
+ $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
+ $data_definition = FieldItemDataDefinition::create($field_definition);
+ return new FileItem($data_definition);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function prepareMediaEntityForSave(MediaInterface $media) {
+ /** @var \Drupal\file\FileInterface $file */
+ $file = $media->get($this->getSourceFieldName($media->bundle->entity))->entity;
+ $file->setPermanent();
+ $file->save();
+ }
+
+}
diff --git a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
deleted file mode 100644
index 66670ffe1bc..00000000000
--- a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
+++ /dev/null
@@ -1,639 +0,0 @@
-entityTypeManager = $entity_type_manager;
- $this->elementInfo = $element_info;
- $this->mediumStyleExists = !empty($entity_type_manager->getStorage('image_style')->load('medium'));
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('entity_type.manager'),
- $container->get('element_info')
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFormId() {
- return 'media_library_upload_form';
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildForm(array $form, FormStateInterface $form_state) {
- $form['#prefix'] = '';
- $form['#suffix'] = '
';
-
- $form['#attached']['library'][] = 'media_library/style';
-
- $form['#attributes']['class'][] = 'media-library-upload';
-
- if (empty($this->media) && empty($this->files)) {
- $process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
- $upload_validators = $this->mergeUploadValidators($this->getTypes());
- $form['upload'] = [
- '#type' => 'managed_file',
- '#title' => $this->t('Upload'),
- // @todo Move validation in https://www.drupal.org/node/2988215
- '#process' => array_merge(['::validateUploadElement'], $process, ['::processUploadElement']),
- '#upload_validators' => $upload_validators,
- ];
- $form['upload_help'] = [
- '#theme' => 'file_upload_help',
- '#description' => $this->t('Upload files here to add new media.'),
- '#upload_validators' => $upload_validators,
- ];
- $remaining = (int) $this->getRequest()->query->get('media_library_remaining');
- if ($remaining || $remaining === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
- $form['upload']['#multiple'] = $remaining > 1 || $remaining === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED;
- $form['upload']['#cardinality'] = $form['upload_help']['#cardinality'] = $remaining;
- }
- }
- else {
- $form['media'] = [
- '#type' => 'container',
- ];
- foreach ($this->media as $i => $media) {
- $source_field = $media->getSource()
- ->getSourceFieldDefinition($media->bundle->entity)
- ->getName();
-
- $element = [
- '#type' => 'container',
- '#attributes' => [
- 'class' => [
- 'media-library-upload__media',
- ],
- ],
- 'preview' => [
- '#type' => 'container',
- '#attributes' => [
- 'class' => [
- 'media-library-upload__media-preview',
- ],
- ],
- ],
- 'fields' => [
- '#type' => 'container',
- '#attributes' => [
- 'class' => [
- 'media-library-upload__media-fields',
- ],
- ],
- // Parents is set here as it is used in the form display.
- '#parents' => ['media', $i, 'fields'],
- ],
- ];
- // @todo Make this configurable in https://www.drupal.org/node/2988223
- if ($this->mediumStyleExists && $thumbnail_uri = $media->getSource()->getMetadata($media, 'thumbnail_uri')) {
- $element['preview']['thumbnail'] = [
- '#theme' => 'image_style',
- '#style_name' => 'medium',
- '#uri' => $thumbnail_uri,
- ];
- }
-
- $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
- // When the name is not added to the form as a 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);
-
- // We hide certain elements in the image widget with CSS.
- if (isset($element['fields'][$source_field])) {
- $element['fields'][$source_field]['#attributes']['class'][] = 'media-library-upload__source-field';
- }
- if (isset($element['fields']['revision_log_message'])) {
- $element['fields']['revision_log_message']['#access'] = FALSE;
- }
- $form['media'][$i] = $element;
- }
-
- $form['files'] = [
- '#type' => 'container',
- ];
- foreach ($this->files as $i => $file) {
- $types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
- $form['files'][$i] = [
- '#type' => 'container',
- '#attributes' => [
- 'class' => [
- 'media-library-upload__file',
- ],
- ],
- 'help' => [
- '#markup' => '' . $this->t('Select a media type for %filename:', [
- '%filename' => $file->getFilename(),
- ]) . '',
- ],
- ];
- foreach ($types as $type) {
- $form['files'][$i][$type->id()] = [
- '#type' => 'submit',
- '#media_library_index' => $i,
- '#media_library_type' => $type->id(),
- '#value' => $type->label(),
- '#submit' => ['::selectType'],
- '#ajax' => [
- 'callback' => '::updateFormCallback',
- 'wrapper' => 'media-library-upload-wrapper',
- ],
- '#limit_validation_errors' => [['files', $i, $type->id()]],
- ];
- }
- }
-
- $form['actions'] = [
- '#type' => 'actions',
- ];
- $form['actions']['submit'] = [
- '#type' => 'submit',
- '#value' => $this->t('Save'),
- '#ajax' => [
- 'callback' => '::updateWidget',
- 'wrapper' => 'media-library-upload-wrapper',
- ],
- ];
- }
-
- return $form;
- }
-
- /**
- * {@inheritdoc}
- */
- public function validateForm(array &$form, FormStateInterface $form_state) {
- if (count($this->files)) {
- $form_state->setError($form['files'], $this->t('Please select a media type for all files.'));
- }
- foreach ($this->media as $i => $media) {
- $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
- $form_display->extractFormValues($media, $form['media'][$i]['fields'], $form_state);
- $form_display->validateFormValues($media, $form['media'][$i]['fields'], $form_state);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
- foreach ($this->media as $i => $media) {
- EntityFormDisplay::collectRenderDisplay($media, 'media_library')
- ->extractFormValues($media, $form['media'][$i]['fields'], $form_state);
- $source_field = $media->getSource()->getSourceFieldDefinition($media->bundle->entity)->getName();
- /** @var \Drupal\file\FileInterface $file */
- $file = $media->get($source_field)->entity;
- $file->setPermanent();
- $file->save();
- $media->save();
- }
- }
-
- /**
- * AJAX callback to select a media type for a file.
- *
- * @param array $form
- * An associative array containing the structure of the form.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The current state of the form.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
- * If the triggering element is missing required properties.
- */
- public function selectType(array &$form, FormStateInterface $form_state) {
- $element = $form_state->getTriggeringElement();
- if (!isset($element['#media_library_index']) || !isset($element['#media_library_type'])) {
- throw new BadRequestHttpException('The "#media_library_index" and "#media_library_type" properties on the triggering element are required for type selection.');
- }
- $i = $element['#media_library_index'];
- $type = $element['#media_library_type'];
- $this->media[] = $this->createMediaEntity($this->files[$i], $this->getTypes()[$type]);
- unset($this->files[$i]);
- $form_state->setRebuild();
- }
-
- /**
- * AJAX callback to update the field widget.
- *
- * @param array $form
- * An associative array containing the structure of the form.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The current state of the form.
- *
- * @return \Drupal\Core\Ajax\AjaxResponse
- * A command to send the selection to the current field widget.
- */
- public function updateWidget(array &$form, FormStateInterface $form_state) {
- if ($form_state->getErrors()) {
- return $form;
- }
-
- $mids = array_map(function (MediaInterface $media) {
- return $media->id();
- }, $this->media);
-
- // Pass the selection to the field widget based on the current widget ID.
- $opener_id = MediaLibraryState::fromRequest($this->getRequest())->getOpenerId();
- if ($field_id = MediaLibraryWidget::getOpenerFieldId($opener_id)) {
- return (new AjaxResponse())
- ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$field_id\"]", 'val', [implode(',', $mids)]))
- ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$field_id\"]", 'trigger', ['mousedown']))
- ->addCommand(new CloseDialogCommand());
- }
- }
-
- /**
- * Processes an upload (managed_file) element.
- *
- * @param array $element
- * The upload element.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The form state.
- *
- * @return array
- * The processed upload element.
- */
- public function processUploadElement(array $element, FormStateInterface $form_state) {
- $element['upload_button']['#submit'] = ['::uploadButtonSubmit'];
- $element['upload_button']['#ajax'] = [
- 'callback' => '::updateFormCallback',
- 'wrapper' => 'media-library-upload-wrapper',
- ];
- return $element;
- }
-
- /**
- * Validates the upload element.
- *
- * @param array $element
- * The upload element.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The form state.
- *
- * @return array
- * The processed upload element.
- */
- public function validateUploadElement(array $element, FormStateInterface $form_state) {
- if ($form_state->getErrors()) {
- $element['#value'] = [];
- }
- $values = $form_state->getValue('upload', []);
- if (count($values['fids']) > $element['#cardinality'] && $element['#cardinality'] !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
- $form_state->setError($element, $this->t('A maximum of @count files can be uploaded.', [
- '@count' => $element['#cardinality'],
- ]));
- $form_state->setValue('upload', []);
- $element['#value'] = [];
- }
- return $element;
- }
-
- /**
- * Submit handler for the upload button, inside the managed_file element.
- *
- * @param array $form
- * The form render array.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The form state.
- */
- public function uploadButtonSubmit(array $form, FormStateInterface $form_state) {
- $fids = $form_state->getValue('upload', []);
- $files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids);
- /** @var \Drupal\file\FileInterface $file */
- foreach ($files as $file) {
- $types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
- if (!empty($types)) {
- if (count($types) === 1) {
- $this->media[] = $this->createMediaEntity($file, reset($types));
- }
- else {
- $this->files[] = $file;
- }
- }
- }
- $form_state->setRebuild();
- }
-
- /**
- * Creates a new, unsaved media entity.
- *
- * @param \Drupal\file\FileInterface $file
- * A file for the media source field.
- * @param \Drupal\media\MediaTypeInterface $type
- * A media type.
- *
- * @return \Drupal\media\MediaInterface
- * An unsaved media entity.
- *
- * @throws \Exception
- * If a file operation failed when moving the upload.
- */
- protected function createMediaEntity(FileInterface $file, MediaTypeInterface $type) {
- $media = $this->entityTypeManager->getStorage('media')->create([
- 'bundle' => $type->id(),
- 'name' => $file->getFilename(),
- ]);
- $source_field = $type->getSource()->getSourceFieldDefinition($type)->getName();
- $location = $this->getUploadLocationForType($media->bundle->entity);
- if (!file_prepare_directory($location, FILE_CREATE_DIRECTORY)) {
- throw new \Exception("The destination directory '$location' is not writable");
- }
- $file = file_move($file, $location);
- if (!$file) {
- throw new \Exception("Unable to move file to '$location'");
- }
- $media->set($source_field, $file->id());
- return $media;
- }
-
- /**
- * AJAX callback for refreshing the entire form.
- *
- * @param array $form
- * The form render array.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The form state.
- *
- * @return array
- * The form render array.
- */
- public function updateFormCallback(array &$form, FormStateInterface $form_state) {
- return $form;
- }
-
- /**
- * Access callback to check that the user can create file based media.
- *
- * @param array $allowed_types
- * (optional) The contextually allowed types.
- *
- * @return \Drupal\Core\Access\AccessResultInterface
- * The access result.
- *
- * @todo Remove $allowed_types param in https://www.drupal.org/node/2956747
- */
- public function access(array $allowed_types = NULL) {
- return AccessResultAllowed::allowedIf(count($this->getTypes($allowed_types)))->mergeCacheMaxAge(0);
- }
-
- /**
- * Returns media types which use files that the current user can create.
- *
- * @param array $allowed_types
- * (optional) The contextually allowed types.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @return \Drupal\media\MediaTypeInterface[]
- * A list of media types that are valid for this form.
- */
- protected function getTypes(array $allowed_types = NULL) {
- // Cache results if possible.
- if (!isset($this->types)) {
- $media_type_storage = $this->entityTypeManager->getStorage('media_type');
- if (!$allowed_types) {
- $types = $media_type_storage->loadMultiple(MediaLibraryState::fromRequest($this->getRequest())->getAllowedTypeIds());
- }
- else {
- $types = $media_type_storage->loadMultiple($allowed_types);
- }
- $types = $this->filterTypesWithFileSource($types);
- $types = $this->filterTypesWithCreateAccess($types);
- $this->types = $types;
- }
- return $this->types;
- }
-
- /**
- * Filters media types that accept a given file.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\file\FileInterface $file
- * A file entity.
- * @param \Drupal\media\MediaTypeInterface[] $types
- * An array of available media types.
- *
- * @return \Drupal\media\MediaTypeInterface[]
- * An array of media types that accept the file.
- */
- protected function filterTypesThatAcceptFile(FileInterface $file, array $types) {
- $types = $this->filterTypesWithFileSource($types);
- return array_filter($types, function (MediaTypeInterface $type) use ($file) {
- $validators = $this->getUploadValidatorsForType($type);
- $errors = file_validate($file, $validators);
- return empty($errors);
- });
- }
-
- /**
- * Filters an array of media types that accept file sources.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\media\MediaTypeInterface[] $types
- * An array of media types.
- *
- * @return \Drupal\media\MediaTypeInterface[]
- * An array of media types that accept file sources.
- */
- protected function filterTypesWithFileSource(array $types) {
- return array_filter($types, function (MediaTypeInterface $type) {
- return is_a($type->getSource()->getSourceFieldDefinition($type)->getClass(), FileFieldItemList::class, TRUE);
- });
- }
-
- /**
- * Merges file upload validators for an array of media types.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\media\MediaTypeInterface[] $types
- * An array of media types.
- *
- * @return array
- * An array suitable for passing to file_save_upload() or the file field
- * element's '#upload_validators' property.
- */
- protected function mergeUploadValidators(array $types) {
- $max_size = 0;
- $extensions = [];
- $types = $this->filterTypesWithFileSource($types);
- foreach ($types as $type) {
- $validators = $this->getUploadValidatorsForType($type);
- if (isset($validators['file_validate_size'])) {
- $max_size = max($max_size, $validators['file_validate_size'][0]);
- }
- if (isset($validators['file_validate_extensions'])) {
- $extensions = array_unique(array_merge($extensions, explode(' ', $validators['file_validate_extensions'][0])));
- }
- }
- // If no field defines a max size, default to the system wide setting.
- if ($max_size === 0) {
- $max_size = file_upload_max_size();
- }
- return [
- 'file_validate_extensions' => [implode(' ', $extensions)],
- 'file_validate_size' => [$max_size],
- ];
- }
-
- /**
- * Gets upload validators for a given media type.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\media\MediaTypeInterface $type
- * A media type.
- *
- * @return array
- * An array suitable for passing to file_save_upload() or the file field
- * element's '#upload_validators' property.
- */
- protected function getUploadValidatorsForType(MediaTypeInterface $type) {
- return $this->getFileItemForType($type)->getUploadValidators();
- }
-
- /**
- * Gets upload destination for a given media type.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\media\MediaTypeInterface $type
- * A media type.
- *
- * @return string
- * An unsanitized file directory URI with tokens replaced.
- */
- protected function getUploadLocationForType(MediaTypeInterface $type) {
- return $this->getFileItemForType($type)->getUploadLocation();
- }
-
- /**
- * Creates a file item for a given media type.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\media\MediaTypeInterface $type
- * A media type.
- *
- * @return \Drupal\file\Plugin\Field\FieldType\FileItem
- * The file item.
- */
- protected function getFileItemForType(MediaTypeInterface $type) {
- $source = $type->getSource();
- $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($type));
- return new FileItem($source_data_definition);
- }
-
- /**
- * Filters an array of media types that can be created by the current user.
- *
- * @todo Move in https://www.drupal.org/node/2987924
- *
- * @param \Drupal\media\MediaTypeInterface[] $types
- * An array of media types.
- *
- * @return \Drupal\media\MediaTypeInterface[]
- * An array of media types that accept file sources.
- */
- protected function filterTypesWithCreateAccess(array $types) {
- $access_handler = $this->entityTypeManager->getAccessControlHandler('media');
- return array_filter($types, function (MediaTypeInterface $type) use ($access_handler) {
- return $access_handler->createAccess($type->id());
- });
- }
-
-}
diff --git a/core/modules/media_library/src/MediaLibraryState.php b/core/modules/media_library/src/MediaLibraryState.php
index ea740dba800..018c42a74c3 100644
--- a/core/modules/media_library/src/MediaLibraryState.php
+++ b/core/modules/media_library/src/MediaLibraryState.php
@@ -29,8 +29,9 @@ use Symfony\Component\HttpFoundation\Request;
* items can be selected.
*
* @internal
- * This class is an internal part of the media library and should not be
- * instantiated or used by external code.
+ * Media Library is an experimental module and its internal code may be
+ * subject to change in minor releases. External code should not instantiate
+ * or extend this class.
*/
class MediaLibraryState extends ParameterBag {
diff --git a/core/modules/media_library/src/MediaLibraryUiBuilder.php b/core/modules/media_library/src/MediaLibraryUiBuilder.php
index c97cc33764a..4359e0b0e4e 100644
--- a/core/modules/media_library/src/MediaLibraryUiBuilder.php
+++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
@@ -3,6 +3,8 @@
namespace Drupal\media_library;
use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormState;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Session\AccountInterface;
@@ -15,13 +17,21 @@ use Symfony\Component\HttpFoundation\RequestStack;
* Service which builds the media library.
*
* @internal
- * This class is an internal part of the media library and should not be
- * instantiated or used by external code.
+ * Media Library is an experimental module and its internal code may be
+ * subject to change in minor releases. External code should not instantiate
+ * or extend this class.
*/
class MediaLibraryUiBuilder {
use StringTranslationTrait;
+ /**
+ * The form builder.
+ *
+ * @var \Drupal\Core\Form\FormBuilderInterface
+ */
+ protected $formBuilder;
+
/**
* The entity type manager.
*
@@ -52,11 +62,14 @@ class MediaLibraryUiBuilder {
* The request stack.
* @param \Drupal\views\ViewExecutableFactory $views_executable_factory
* The views executable factory.
+ * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
+ * The currently active request object.
*/
- public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory) {
+ public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder) {
$this->entityTypeManager = $entity_type_manager;
$this->request = $request_stack->getCurrentRequest();
$this->viewsExecutableFactory = $views_executable_factory;
+ $this->formBuilder = $form_builder;
}
/**
@@ -77,11 +90,17 @@ class MediaLibraryUiBuilder {
/**
* Build the media library UI.
*
+ * @param \Drupal\media_library\MediaLibraryState $state
+ * (optional) The current state of the media library, derived from the
+ * current request.
+ *
* @return array
* The render array for the media library.
*/
- public function buildUi() {
- $state = MediaLibraryState::fromRequest($this->request);
+ public function buildUi(MediaLibraryState $state = NULL) {
+ if (!$state) {
+ $state = MediaLibraryState::fromRequest($this->request);
+ }
// When navigating to a media type through the vertical tabs, we only want
// to load the changed library content. This is not only more efficient, but
// also provides a more accessible user experience for screen readers.
@@ -123,6 +142,7 @@ class MediaLibraryUiBuilder {
'class' => ['media-library-content'],
'tabindex' => -1,
],
+ 'form' => $this->buildMediaTypeAddForm($state),
'view' => $this->buildMediaLibraryView($state),
];
}
@@ -178,13 +198,15 @@ class MediaLibraryUiBuilder {
],
];
- // Get the state parameters but remove the wrapper format. Also add the
- // 'media_library_content' argument to fetch only the updated content for
- // the tab.
- // @see self::buildUi()
- $state->remove(MainContentViewSubscriber::WRAPPER_FORMAT);
- $state->add(['media_library_content' => 1]);
+ // Get the state parameters but remove the wrapper format, AJAX form and
+ // form rebuild parameters. These are internal parameters that should never
+ // be part of the vertical tab links.
$query = $state->all();
+ unset($query[MainContentViewSubscriber::WRAPPER_FORMAT], $query[FormBuilderInterface::AJAX_FORM_REQUEST], $query['_media_library_form_rebuild']);
+ // Add the 'media_library_content' parameter so the response will contain
+ // only the updated content for the tab.
+ // @see self::buildUi()
+ $query['media_library_content'] = 1;
$allowed_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple($allowed_type_ids);
@@ -216,6 +238,42 @@ class MediaLibraryUiBuilder {
return $menu;
}
+ /**
+ * Get the add form for the selected media type.
+ *
+ * @param \Drupal\media_library\MediaLibraryState $state
+ * The current state of the media library, derived from the current request.
+ *
+ * @return array
+ * The render array for the media type add form.
+ */
+ protected function buildMediaTypeAddForm(MediaLibraryState $state) {
+ $selected_type_id = $state->getSelectedTypeId();
+
+ if (!$this->entityTypeManager->getAccessControlHandler('media')->createAccess($selected_type_id)) {
+ return [];
+ }
+
+ $selected_type = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id);
+ $plugin_definition = $selected_type->getSource()->getPluginDefinition();
+
+ if (empty($plugin_definition['forms']['media_library_add'])) {
+ return [];
+ }
+
+ // After the form to add new media is submitted, we need to rebuild the
+ // media library with a new instance of the media add form. The form API
+ // allows us to do that by forcing empty user input.
+ // @see \Drupal\Core\Form\FormBuilder::doBuildForm()
+ $form_state = new FormState();
+ if ($state->get('_media_library_form_rebuild')) {
+ $form_state->setUserInput([]);
+ $state->remove('_media_library_form_rebuild');
+ }
+ $form_state->set('media_library_state', $state);
+ return $this->formBuilder->buildForm($plugin_definition['forms']['media_library_add'], $form_state);
+ }
+
/**
* Get the media library view.
*
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index 033c7da139f..3ecf3ed78e0 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -33,6 +33,9 @@ use Symfony\Component\Validator\ConstraintViolationInterface;
* )
*
* @internal
+ * Media Library is an experimental module and its internal code may be
+ * subject to change in minor releases. External code should not instantiate
+ * or extend this class.
*/
class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
index d1d6b4cad63..5660e43f4a7 100644
--- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
+++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
@@ -20,6 +20,9 @@ use Drupal\views\ResultRow;
* @ViewsField("media_library_select_form")
*
* @internal
+ * Media Library is an experimental module and its internal code may be
+ * subject to change in minor releases. External code should not instantiate
+ * or extend this class.
*/
class MediaLibrarySelectForm extends FieldPluginBase {
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
index 64f3ff59b3a..34245daec40 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -150,8 +150,8 @@ class MediaLibraryTest extends WebDriverTestBase {
$role->save();
// Create a working state.
- $allowed_types = ['type_one', 'type_two'];
- $state = MediaLibraryState::create('test', $allowed_types, 'type_two', 2);
+ $allowed_types = ['type_one', 'type_two', 'type_three', 'type_four'];
+ $state = MediaLibraryState::create('test', $allowed_types, 'type_three', 2);
$url_options = ['query' => $state->all()];
// Verify that unprivileged users can't access the widget view.
@@ -169,6 +169,18 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->elementExists('css', '.view-media-library');
$this->drupalGet('media-library', $url_options);
$assert_session->elementExists('css', '.view-media-library');
+ // Assert the user does not have access to the media add form if the user
+ // does not have the 'create media' permission.
+ $assert_session->fieldNotExists('files[upload][]');
+
+ // Assert users with the 'create media' permission can access the media add
+ // form.
+ $this->grantPermissions($role, [
+ 'create media',
+ ]);
+ $this->drupalGet('media-library', $url_options);
+ $assert_session->elementExists('css', '.view-media-library');
+ $assert_session->fieldExists('Add files');
}
/**
@@ -258,7 +270,7 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Turtle');
- $assert_session->elementExists('named', ['link', 'Type Three'])->click();
+ $page->clickLink('Type Three');
$assert_session->assertWaitOnAjaxRequest();
$assert_session->elementExists('named', ['link', 'Type Three (active tab)']);
$assert_session->pageTextNotContains('Dog');
@@ -316,9 +328,9 @@ class MediaLibraryTest extends WebDriverTestBase {
$this->assertFalse($checkboxes[3]->hasAttribute('disabled'));
// The selection should be persisted when navigating to other media types in
// the modal.
- $assert_session->elementExists('named', ['link', 'Type Three'])->click();
+ $page->clickLink('Type Three');
$assert_session->assertWaitOnAjaxRequest();
- $assert_session->elementExists('named', ['link', 'Type One'])->click();
+ $page->clickLink('Type One');
$assert_session->assertWaitOnAjaxRequest();
$checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input');
$selected_checkboxes = [];
@@ -331,7 +343,7 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', implode(',', $selected_checkboxes));
$assert_session->elementTextContains('css', '.media-library-selected-count', '1 of 2 items selected');
// Add to selection from another type.
- $assert_session->elementExists('named', ['link', 'Type Two'])->click();
+ $page->clickLink('Type Two');
$assert_session->assertWaitOnAjaxRequest();
$checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input');
$checkboxes[0]->click();
@@ -345,7 +357,7 @@ class MediaLibraryTest extends WebDriverTestBase {
$this->assertTrue($checkboxes[2]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[3]->hasAttribute('disabled'));
// Assert the checkboxes are also disabled on other pages.
- $assert_session->elementExists('named', ['link', 'Type One'])->click();
+ $page->clickLink('Type One');
$assert_session->assertWaitOnAjaxRequest();
$this->assertTrue($checkboxes[0]->hasAttribute('disabled'));
$this->assertFalse($checkboxes[1]->hasAttribute('disabled'));
@@ -473,6 +485,7 @@ class MediaLibraryTest extends WebDriverTestBase {
*/
public function testWidgetAnonymous() {
$assert_session = $this->assertSession();
+ $page = $this->getSession()->getPage();
$this->drupalLogout();
@@ -492,9 +505,7 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->assertWaitOnAjaxRequest();
// Select the first media item (should be Dog).
- $checkbox_selector = '.media-library-view .js-click-to-select-checkbox input';
- $checkboxes = $this->getSession()->getPage()->findAll('css', $checkbox_selector);
- $checkboxes[0]->click();
+ $page->find('css', '.media-library-view .js-click-to-select-checkbox input')->click();
$assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Select media');
$assert_session->assertWaitOnAjaxRequest();
@@ -533,6 +544,44 @@ class MediaLibraryTest extends WebDriverTestBase {
$this->fail('Expected test files not present.');
}
+ // Create a user that can only add media of type four.
+ $user = $this->drupalCreateUser([
+ 'access administration pages',
+ 'access content',
+ 'create basic_page content',
+ 'create type_four media',
+ 'view media',
+ ]);
+ $this->drupalLogin($user);
+
+ // Visit a node create page and open the media library.
+ $this->drupalGet('node/add/basic_page');
+ $assert_session->elementExists('css', '.media-library-open-button[href*="field_twin_media"]')->click();
+ $assert_session->assertWaitOnAjaxRequest();
+ $assert_session->pageTextContains('Media library');
+
+ // Assert the upload form is visible for type_four.
+ $page->clickLink('Type Four');
+ $assert_session->assertWaitOnAjaxRequest();
+ $assert_session->fieldExists('Add files');
+ $assert_session->pageTextContains('Maximum 2 files.');
+
+ // Assert the upload form is not visible for type_three.
+ $page->clickLink('Type Three');
+ $assert_session->assertWaitOnAjaxRequest();
+ $assert_session->fieldNotExists('files[upload][]');
+ $assert_session->pageTextNotContains('Maximum 2 files.');
+
+ // Create a user that can create media for all media types.
+ $user = $this->drupalCreateUser([
+ 'access administration pages',
+ 'access content',
+ 'create basic_page content',
+ 'create media',
+ 'view media',
+ ]);
+ $this->drupalLogin($user);
+
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
@@ -544,11 +593,19 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->elementExists('css', '.media-library-open-button[href*="field_twin_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextContains('Media library');
- $assert_session->elementExists('css', '#drupal-modal')->clickLink('Add media');
- $assert_session->assertWaitOnAjaxRequest();
- $page->attachFileToField('Upload', $this->container->get('file_system')->realpath($png_image->uri));
+ // Assert the default tab for media type one does not have an upload form.
+ $assert_session->fieldNotExists('files[upload][]');
+
+ // Assert we can upload a file to media type three.
+ $page->clickLink('Type Three');
$assert_session->assertWaitOnAjaxRequest();
+ $assert_session->elementExists('css', '.media-library-add-form--without-input');
+ $assert_session->elementNotExists('css', '.media-library-add-form--with-input');
+ $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
+ $assert_session->assertWaitOnAjaxRequest();
+ $assert_session->elementExists('css', '.media-library-add-form--with-input');
+ $assert_session->elementNotExists('css', '.media-library-add-form--without-input');
// Files are temporary until the form is saved.
$files = $file_storage->loadMultiple();
@@ -556,6 +613,11 @@ class MediaLibraryTest extends WebDriverTestBase {
$this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri()));
$this->assertTrue($file->isTemporary());
+ // Assert the revision_log_message field is not shown.
+ $upload_form = $assert_session->elementExists('css', '.media-library-add-form');
+ $assert_session->fieldNotExists('Revision log message', $upload_form);
+
+ // Assert the name field contains the filename and the alt text is required.
$this->assertSame($assert_session->fieldExists('Name')->getValue(), $png_image->filename);
$assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save');
$assert_session->assertWaitOnAjaxRequest();
@@ -569,7 +631,23 @@ class MediaLibraryTest extends WebDriverTestBase {
$file = array_pop($files);
$this->assertFalse($file->isTemporary());
- // Ensure the media item was added.
+ // Load the created media item.
+ $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
+ $media_items = $media_storage->loadMultiple();
+ $added_media = array_pop($media_items);
+
+ // Ensure the media item was saved to the library and automatically
+ // selected. The added media items should be in the first position of the
+ // add form.
+ $assert_session->pageTextContains('Media library');
+ $assert_session->pageTextContains($png_image->filename);
+ $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id());
+ $assert_session->checkboxChecked('media_library_select_form[0]');
+ $assert_session->pageTextContains('1 of 2 items selected');
+
+ // Ensure the created item is added in the widget.
+ $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Select media');
+ $assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextNotContains('Media library');
$assert_session->pageTextContains($png_image->filename);
@@ -577,52 +655,77 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->elementExists('css', '.media-library-open-button[href*="field_unlimited_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextContains('Media library');
- $assert_session->elementExists('css', '#drupal-modal')->clickLink('Add media');
+
+ // Navigate to the media type three tab first.
+ $page->clickLink('Type Three');
$assert_session->assertWaitOnAjaxRequest();
+ // Select a media item.
+ $page->find('css', '.media-library-view .js-click-to-select-checkbox input')->click();
+ $assert_session->pageTextContains('1 item selected');
+
// Multiple uploads should be allowed.
// @todo Add test when https://github.com/minkphp/Mink/issues/358 is closed
- $this->assertTrue($assert_session->fieldExists('Upload')->hasAttribute('multiple'));
+ $this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple'));
- $page->attachFileToField('Upload', $this->container->get('file_system')->realpath($png_image->uri));
+ $page->attachFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
$assert_session->assertWaitOnAjaxRequest();
$page->fillField('Name', 'Unlimited Cardinality Image');
$page->fillField('Alternative text', $this->randomString());
$assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save');
$assert_session->assertWaitOnAjaxRequest();
- // Ensure the media item was added.
+ // Load the created media item.
+ $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
+ $media_items = $media_storage->loadMultiple();
+ $added_media = array_pop($media_items);
+
+ // Ensure the media item was saved to the library and automatically
+ // selected. The added media items should be in the first position of the
+ // add form.
+ $assert_session->pageTextContains('Media library');
+ $assert_session->pageTextContains('Unlimited Cardinality Image');
+ $assert_session->fieldValueEquals('media_library_select_form[0]', $added_media->id());
+ $assert_session->checkboxChecked('media_library_select_form[0]');
+
+ // Assert the item that was selected before uploading the file is still
+ // selected.
+ $assert_session->pageTextContains('2 items selected');
+ $checkboxes = $page->findAll('css', '.media-library-view .js-click-to-select-checkbox input');
+ $selected_checkboxes = [];
+ foreach ($checkboxes as $checkbox) {
+ if ($checkbox->isChecked()) {
+ $selected_checkboxes[] = $checkbox->getValue();
+ }
+ }
+ $this->assertCount(2, $selected_checkboxes);
+
+ // Ensure the created item is added in the widget.
+ $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Select media');
+ $assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextNotContains('Media library');
$assert_session->pageTextContains('Unlimited Cardinality Image');
- // Open the browser again to test type resolution.
+ // Verify we can only upload the files allowed by the media type.
$assert_session->elementExists('css', '.media-library-open-button[href*="field_twin_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextContains('Media library');
- $assert_session->elementExists('css', '#drupal-modal')->clickLink('Add media');
+ $page->clickLink('Type Four');
$assert_session->assertWaitOnAjaxRequest();
- $page->attachFileToField('Upload', $file_system->realpath($jpg_image->uri));
+ // Assert we can now only upload one more media item.
+ $this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple'));
+ $assert_session->pageTextContains('One file only.');
+
+ // Assert media type four should only allow jpg files by trying a png file
+ // first.
+ $page->attachFileToField('Add file', $file_system->realpath($png_image->uri));
$assert_session->assertWaitOnAjaxRequest();
+ $assert_session->pageTextContains('Only files with the following extensions are allowed');
- $assert_session->pageTextContains('Select a media type for ' . $jpg_image->filename);
-
- // Before the type is determined, the file lives in the default upload
- // location (temporary://).
- $files = $file_storage->loadMultiple();
- $file = array_pop($files);
- $this->assertSame('temporary', $file_system->uriScheme($file->getFileUri()));
-
- // Both the type_three and type_four media types accept jpg images.
- $assert_session->buttonExists('Type Three');
- $assert_session->buttonExists('Type Four')->click();
+ // Assert that jpg files are accepted by type four.
+ $page->attachFileToField('Add file', $file_system->realpath($jpg_image->uri));
$assert_session->assertWaitOnAjaxRequest();
-
- // The file should have been moved when the type was selected.
- $files = $file_storage->loadMultiple();
- $file = array_pop($files);
- $this->assertSame('public://type-four-dir', $file_system->dirname($file->getFileUri()));
- $this->assertSame($assert_session->fieldExists('Name')->getValue(), $jpg_image->filename);
$page->fillField('Alternative text', $this->randomString());
// The type_four media type has another optional image field.
@@ -637,6 +740,14 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save');
$assert_session->assertWaitOnAjaxRequest();
+ // Ensure the media item was saved to the library and automatically
+ // selected.
+ $assert_session->pageTextContains('Media library');
+ $assert_session->pageTextContains($jpg_image->filename);
+
+ // Ensure the created item is added in the widget.
+ $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Select media');
+ $assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextNotContains('Media library');
$assert_session->pageTextContains($jpg_image->filename);
}
diff --git a/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php b/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php
new file mode 100644
index 00000000000..78d12f50ff3
--- /dev/null
+++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryAddFormTest.php
@@ -0,0 +1,111 @@
+installEntitySchema('user');
+ $this->installEntitySchema('file');
+ $this->installSchema('file', 'file_usage');
+ $this->installSchema('system', ['sequences', 'key_value_expire']);
+ $this->installEntitySchema('media');
+ $this->installConfig([
+ 'field',
+ 'system',
+ 'file',
+ 'image',
+ 'media',
+ 'media_library',
+ ]);
+
+ // Create an account with special UID 1.
+ $this->createUser([]);
+
+ $this->createMediaType('image', ['id' => 'image']);
+ $this->createMediaType('oembed:video', ['id' => 'remote_video']);
+ }
+
+ /**
+ * Tests the media library add form.
+ */
+ public function testMediaTypeAddForm() {
+ $entity_type_manager = \Drupal::entityTypeManager();
+ $image = $entity_type_manager->getStorage('media_type')->load('image');
+ $remote_video = $entity_type_manager->getStorage('media_type')->load('remote_video');
+ $image_source_definition = $image->getSource()->getPluginDefinition();
+ $remote_video_source_definition = $remote_video->getSource()->getPluginDefinition();
+
+ // Assert the form class is added to the media source.
+ $this->assertSame(FileUploadForm::class, $image_source_definition['forms']['media_library_add']);
+ $this->assertArrayNotHasKey('media_library_add', $remote_video_source_definition['forms']);
+
+ // Assert the media library UI does not contains the add form when the user
+ // does not have access.
+ $state = MediaLibraryState::create('test', ['image', 'remote_video'], 'image', -1);
+ $library_ui = \Drupal::service('media_library.ui_builder')->buildUi($state);
+ $this->assertEmpty($library_ui['content']['form']);
+
+ // Create a user that has access to the media add form.
+ $this->setCurrentUser($this->createUser([
+ 'create image media',
+ ]));
+ $library_ui = \Drupal::service('media_library.ui_builder')->buildUi($state);
+ $this->assertSame('managed_file', $library_ui['content']['form']['upload']['#type']);
+ }
+
+ /**
+ * Tests the validation of the library state in the media library add form.
+ */
+ public function testFormStateValidation() {
+ $form_state = new FormState();
+ $this->setExpectedException(\InvalidArgumentException::class, 'The media library state is not present in the form state.');
+ \Drupal::formBuilder()->buildForm(FileUploadForm::class, $form_state);
+ }
+
+ /**
+ * Tests the validation of the selected type in the media library add form.
+ */
+ public function testSelectedTypeValidation() {
+ $state = MediaLibraryState::create('test', ['image', 'remote_video', 'header_image'], 'header_image', -1);
+ $form_state = new FormState();
+ $form_state->set('media_library_state', $state);
+ $this->setExpectedException(\InvalidArgumentException::class, "The 'header_image' media type does not exist.");
+ \Drupal::formBuilder()->buildForm(FileUploadForm::class, $form_state);
+ }
+
+}