diff --git a/core/modules/media_library/config/install/core.entity_form_mode.media.media_library.yml b/core/modules/media_library/config/install/core.entity_form_mode.media.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..7889cd0fc174168642408c76d26cb73b69f493f2 --- /dev/null +++ b/core/modules/media_library/config/install/core.entity_form_mode.media.media_library.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + module: + - media +id: media.media_library +label: 'Media library' +targetEntityType: media +cache: true diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..56df385a2e1586b1cc44ca7d6480aa66800afe89 --- /dev/null +++ b/core/modules/media_library/config/optional/core.entity_form_display.media.audio.media_library.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.media_library + - field.field.media.audio.field_media_audio_file + - media.type.audio +id: media.audio.media_library +targetEntityType: media +bundle: audio +mode: media_library +content: + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + field_media_audio_file: true + path: true + status: true + uid: true diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.file.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.file.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..a0a5d49d903e59927d5ca1862c69658f48b690fc --- /dev/null +++ b/core/modules/media_library/config/optional/core.entity_form_display.media.file.media_library.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.media_library + - field.field.media.file.field_media_file + - media.type.file +id: media.file.media_library +targetEntityType: media +bundle: file +mode: media_library +content: + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + field_media_file: true + path: true + status: true + uid: true diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..68ca01ae64486ea441a1f5caf96fe80762c09f96 --- /dev/null +++ b/core/modules/media_library/config/optional/core.entity_form_display.media.image.media_library.yml @@ -0,0 +1,36 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.media_library + - field.field.media.image.field_media_image + - image.style.thumbnail + - media.type.image + module: + - image +id: media.image.media_library +targetEntityType: media +bundle: image +mode: media_library +content: + field_media_image: + type: image_image + weight: 1 + region: content + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + path: true + status: true + uid: true diff --git a/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml b/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..b7caf5dbfeb9f1b0d6a50a2314be0f4362d805e1 --- /dev/null +++ b/core/modules/media_library/config/optional/core.entity_form_display.media.video.media_library.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.media_library + - field.field.media.video.field_media_video_file + - media.type.video +id: media.video.media_library +targetEntityType: media +bundle: video +mode: media_library +content: + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + field_media_video_file: true + path: true + status: true + uid: true diff --git a/core/modules/media_library/css/media_library.theme.css b/core/modules/media_library/css/media_library.theme.css index 5aab3c5bbd17118340a3d677868267aabfb94505..4f04445dd5c75e35340a5f4c9e6452ddcaf6cceb 100644 --- a/core/modules/media_library/css/media_library.theme.css +++ b/core/modules/media_library/css/media_library.theme.css @@ -198,6 +198,44 @@ border-color: #40b6ff; } +/* Style the wrappers around new media and files */ +.media-library-upload__media, +.media-library-upload__file { + display: flex; + flex-wrap: wrap; + padding: 20px 0 20px 0; +} + +.media-library-upload__file { + align-items: center; +} + +.media-library-upload__file-label { + margin-right: 10px; +} + +/* @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 { + display: none; +} + +.media-library-upload__media-preview { + margin-right: 20px; + width: 220px; + background: #ebebeb; + display: flex; + align-items: center; + justify-content: center; +} + +.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/media_library.module b/core/modules/media_library/media_library.module index b72a4d47ce7c145669176863a4cdb695ec6be862..977da24ae5d7c8fa390faa2adca3b6b0dbd8e757 100644 --- a/core/modules/media_library/media_library.module +++ b/core/modules/media_library/media_library.module @@ -6,11 +6,13 @@ */ use Drupal\Component\Utility\UrlHelper; +use Drupal\Component\Serialization\Json; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Template\Attribute; +use Drupal\Core\Url; use Drupal\views\Form\ViewsForm; use Drupal\views\Plugin\views\cache\CachePluginBase; use Drupal\views\Plugin\views\query\QueryPluginBase; @@ -42,6 +44,36 @@ 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 new file mode 100644 index 0000000000000000000000000000000000000000..1724760acb1e0820bbd077c366dd70dc7999a8ad --- /dev/null +++ b/core/modules/media_library/media_library.routing.yml @@ -0,0 +1,6 @@ +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' diff --git a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php new file mode 100644 index 0000000000000000000000000000000000000000..3ed96b4c4e2c1cb7a99897d3b08be1e369e05d39 --- /dev/null +++ b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php @@ -0,0 +1,615 @@ +<?php + +namespace Drupal\media_library\Form; + +use Drupal\Core\Access\AccessResultAllowed; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\CloseDialogCommand; +use Drupal\Core\Ajax\InvokeCommand; +use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Field\TypedData\FieldItemDataDefinition; +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\file\FileInterface; +use Drupal\file\Plugin\Field\FieldType\FileFieldItemList; +use Drupal\file\Plugin\Field\FieldType\FileItem; +use Drupal\media\MediaInterface; +use Drupal\media\MediaTypeInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; + +/** + * Creates a form to create media entities from uploaded files. + * + * @internal + */ +class MediaLibraryUploadForm extends FormBase { + + /** + * The element info manager. + * + * @var \Drupal\Core\Render\ElementInfoManagerInterface + */ + protected $elementInfo; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Media types the current user has access to. + * + * @var \Drupal\media\MediaTypeInterface[] + */ + protected $types; + + /** + * The media being processed. + * + * @var \Drupal\media\MediaInterface[] + */ + protected $media = []; + + /** + * The files waiting for type selection. + * + * @var \Drupal\file\FileInterface[] + */ + protected $files = []; + + /** + * Indicates whether the 'medium' image style exists. + * + * @var bool + */ + protected $mediumStyleExists = FALSE; + + /** + * Constructs a new MediaLibraryUploadForm. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info + * The element info manager. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info) { + $this->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'] = '<div id="media-library-upload-wrapper">'; + $form['#suffix'] = '</div>'; + + $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 = $this->getRequest()->query->get('media_library_remaining'); + if ($remaining) { + $form['upload']['#multiple'] = $remaining > 1; + $form['upload']['#cardinality'] = $form['upload_help']['#cardinality'] = (int) $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, + ]; + } + EntityFormDisplay::collectRenderDisplay($media, 'media_library') + ->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' => '<strong class="media-library-upload__file-label">' . $this->t('Select a media type for %filename:', [ + '%filename' => $file->getFilename(), + ]) . '</strong>', + ], + ]; + 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. + * + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * If the "media_library_widget_id" query parameter is not present. + */ + public function updateWidget(array &$form, FormStateInterface $form_state) { + if ($form_state->getErrors()) { + return $form; + } + $widget_id = $this->getRequest()->query->get('media_library_widget_id'); + if (!$widget_id || !is_string($widget_id)) { + throw new BadRequestHttpException('The "media_library_widget_id" query parameter is required and must be a string.'); + } + $mids = array_map(function (MediaInterface $media) { + return $media->id(); + }, $this->media); + // Pass the selection to the field widget based on the current widget ID. + return (new AjaxResponse()) + ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$widget_id\"]", 'val', [implode(',', $mids)])) + ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$widget_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']) { + $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. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function access() { + return AccessResultAllowed::allowedIf(count($this->getTypes()))->mergeCacheMaxAge(0); + } + + /** + * Returns media types which use files that the current user can create. + * + * @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() { + // Cache results if possible. + if (!isset($this->types)) { + $media_type_storage = $this->entityTypeManager->getStorage('media_type'); + $allowed_types = _media_library_get_allowed_types() ?: NULL; + $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/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php index 86aa2e979539707d5db4543caff052156604b585..290800241f785cebc54cfa22c8abe56232d1aa46 100644 --- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php +++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php @@ -124,31 +124,6 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ], ]; - // @todo Remove in https://www.drupal.org/project/drupal/issues/2938116 - $allowed_bundles = !empty($element['#target_bundles']) ? $element['#target_bundles'] : []; - $add_url = _media_get_add_url($allowed_bundles); - if ($add_url) { - $element['create_help'] = [ - '#type' => 'container', - ]; - $element['create_help']['label'] = [ - '#type' => 'html_tag', - '#tag' => 'h4', - '#attributes' => [ - 'class' => ['label'], - ], - '#value' => $this->t('Create new media'), - ]; - $element['create_help']['description'] = [ - '#type' => 'html_tag', - '#tag' => 'div', - '#attributes' => [ - 'class' => ['description'], - ], - '#value' => $this->t('Create your media on the <a href=":add_page" target="_blank">media add page</a> (opens a new window), then select it in the library.', [':add_page' => $add_url]), - ]; - } - $element['selection'] = [ '#type' => 'container', '#attributes' => [ @@ -242,6 +217,18 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $element['#description'] .= '<br />' . $cardinality_message; } + $query = [ + 'media_library_widget_id' => $field_name . $id_suffix, + 'media_library_allowed_types' => $element['#target_bundles'], + 'media_library_remaining' => $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining, + ]; + $dialog_options = Json::encode([ + 'dialogClass' => 'media-library-widget-modal', + 'height' => '75%', + 'width' => '75%', + 'title' => $this->t('Media library'), + ]); + // Add a button that will load the Media library in a modal using AJAX. $element['media_library_open_button'] = [ '#type' => 'link', @@ -249,27 +236,36 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen '#name' => $field_name . '-media-library-open-button' . $id_suffix, // @todo Make the view configurable in https://www.drupal.org/project/drupal/issues/2971209 '#url' => Url::fromRoute('view.media_library.widget', [], [ - 'query' => [ - 'media_library_widget_id' => $field_name . $id_suffix, - 'media_library_allowed_types' => $element['#target_bundles'], - 'media_library_remaining' => $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining, - ], + 'query' => $query, ]), '#attributes' => [ 'class' => ['button', 'use-ajax', 'media-library-open-button'], 'data-dialog-type' => 'modal', - 'data-dialog-options' => Json::encode([ - 'dialogClass' => 'media-library-widget-modal', - 'height' => '75%', - 'width' => '75%', - 'title' => $this->t('Media library'), - ]), + 'data-dialog-options' => $dialog_options, ], // Prevent errors in other widgets from preventing addition. '#limit_validation_errors' => $limit_validation_errors, '#access' => $cardinality_unlimited || $remaining > 0, ]; + $add_url = Url::fromRoute('media_library.upload', [], [ + 'query' => $query, + ]); + $element['media_library_add_button'] = [ + '#type' => 'link', + '#title' => $this->t('Add media'), + '#name' => $field_name . '-media-library-add-button' . $id_suffix, + '#url' => $add_url, + '#attributes' => [ + 'class' => ['button', 'use-ajax', 'media-library-add-button'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => $dialog_options, + ], + // Prevent errors in other widgets from preventing addition. + '#limit_validation_errors' => $limit_validation_errors, + '#access' => $add_url->access() && ($cardinality_unlimited || $remaining > 0), + ]; + // This hidden field and button are used to add new items to the widget. $element['media_library_selection'] = [ '#type' => 'hidden', diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml new file mode 100644 index 0000000000000000000000000000000000000000..40d4d1bd31c01f5f161dc60d1ab197b998dd37d7 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.default.yml @@ -0,0 +1,69 @@ +langcode: en +status: true +dependencies: + config: + - field.field.media.type_four.field_media_test_image + - field.field.media.type_four.field_media_extra_image + - image.style.medium + - media.type.type_four + module: + - image + - path +id: media.type_four.default +targetEntityType: media +bundle: type_four +mode: default +content: + created: + type: datetime_timestamp + weight: 10 + region: content + settings: { } + third_party_settings: { } + field_media_test_image: + weight: 0 + settings: + progress_indicator: throbber + preview_image_style: medium + third_party_settings: { } + type: image_image + region: content + field_media_extra_image: + weight: 1 + settings: + progress_indicator: throbber + preview_image_style: medium + third_party_settings: { } + type: image_image + region: content + name: + type: string_textfield + weight: -5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + path: + type: path + weight: 30 + region: content + settings: { } + third_party_settings: { } + status: + type: boolean_checkbox + settings: + display_label: true + weight: 100 + region: content + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } +hidden: { } diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.media_library.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..0abef4849c1705516bad4aab95046edab29aa242 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_four.media_library.yml @@ -0,0 +1,44 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.media_library + - field.field.media.type_four.field_media_test_image + - image.style.thumbnail + - media.type.type_four + module: + - image +id: media.type_four.media_library +targetEntityType: media +bundle: type_four +mode: media_library +content: + field_media_test_image: + weight: 2 + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + type: image_image + region: content + field_media_extra_image: + weight: 1 + settings: + progress_indicator: throbber + preview_image_style: medium + third_party_settings: { } + type: image_image + region: content + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + path: true + status: true + uid: true diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml new file mode 100644 index 0000000000000000000000000000000000000000..ea7248e2aa3e180f97708e6873ccc6989f57a8f8 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.default.yml @@ -0,0 +1,60 @@ +langcode: en +status: true +dependencies: + config: + - field.field.media.type_three.field_media_test_image + - image.style.medium + - media.type.type_three + module: + - image + - path +id: media.type_three.default +targetEntityType: media +bundle: type_three +mode: default +content: + created: + type: datetime_timestamp + weight: 10 + region: content + settings: { } + third_party_settings: { } + field_media_test_image: + weight: 0 + settings: + progress_indicator: throbber + preview_image_style: medium + third_party_settings: { } + type: image_image + region: content + name: + type: string_textfield + weight: -5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + path: + type: path + weight: 30 + region: content + settings: { } + third_party_settings: { } + status: + type: boolean_checkbox + settings: + display_label: true + weight: 100 + region: content + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } +hidden: { } diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.media_library.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.media_library.yml new file mode 100644 index 0000000000000000000000000000000000000000..c7fdfc74f9b6d82bfc084d25656aec60a5a6f741 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.media.type_three.media_library.yml @@ -0,0 +1,36 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.media_library + - field.field.media.type_three.field_media_test_image + - image.style.thumbnail + - media.type.type_three + module: + - image +id: media.type_three.media_library +targetEntityType: media +bundle: type_three +mode: media_library +content: + field_media_test_image: + weight: 1 + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + type: image_image + region: content + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: + created: true + path: true + status: true + uid: true diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_three.default.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_three.default.yml new file mode 100644 index 0000000000000000000000000000000000000000..de6650e2b49d2dffd3fa7b20bc25977363e60dfb --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.media.type_three.default.yml @@ -0,0 +1,52 @@ +langcode: en +status: true +dependencies: + config: + - field.field.media.type_three.field_media_test_image + - image.style.thumbnail + - media.type.type_three + module: + - image + - user +id: media.type_three.default +targetEntityType: media +bundle: type_three +mode: default +content: + created: + label: hidden + type: timestamp + weight: 0 + region: content + settings: + date_format: medium + custom_date_format: '' + timezone: '' + third_party_settings: { } + field_media_test_image: + weight: 6 + label: above + settings: + image_style: '' + image_link: '' + third_party_settings: { } + type: image + region: content + thumbnail: + type: image + weight: 5 + label: hidden + settings: + image_style: thumbnail + image_link: '' + region: content + third_party_settings: { } + uid: + label: hidden + type: author + weight: 0 + region: content + settings: { } + third_party_settings: { } +hidden: + name: true diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_extra_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_extra_image.yml new file mode 100644 index 0000000000000000000000000000000000000000..3bd38cb4d20161fe8d362c05cb7cf8f5b075f02d --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_extra_image.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.media.field_media_extra_image + - media.type.type_four + module: + - image +id: media.type_three.field_media_extra_image +field_name: field_media_extra_image +entity_type: media +bundle: type_four +label: Extra Image +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_extensions: 'jpg' + alt_field: false + alt_field_required: false + title_field: false + title_field_required: false + max_resolution: '' + min_resolution: '' + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + file_directory: 'type-four-extra-dir' + max_filesize: '' + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_test_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_test_image.yml new file mode 100644 index 0000000000000000000000000000000000000000..e1d4e5194de4d4bd222e47b91a975d25fec31872 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_four.field_media_test_image.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.media.field_media_test_image + - media.type.type_four + module: + - image +id: media.type_three.field_media_test_image +field_name: field_media_test_image +entity_type: media +bundle: type_four +label: Image +description: '' +required: true +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_extensions: 'jpg' + alt_field: true + alt_field_required: true + title_field: false + title_field_required: false + max_resolution: '' + min_resolution: '' + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + file_directory: 'type-four-dir' + max_filesize: '' + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_three.field_media_test_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_three.field_media_test_image.yml new file mode 100644 index 0000000000000000000000000000000000000000..27bce7e4780655ba088cb29b3409de529a8a0468 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.media.type_three.field_media_test_image.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.media.field_media_test_image + - media.type.type_three + module: + - image +id: media.type_three.field_media_test_image +field_name: field_media_test_image +entity_type: media +bundle: type_three +label: Image +description: '' +required: true +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_extensions: 'png gif jpg jpeg' + alt_field: true + alt_field_required: true + title_field: false + title_field_required: false + max_resolution: '' + min_resolution: '' + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + file_directory: 'type-three-dir' + max_filesize: '' + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml index 7d4d8cdc0a01ed74c83abb8bfda043759b2eec36..5c874b736f63dd75614be42fcbccf01c14175ad5 100644 --- a/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.basic_page.field_twin_media.yml @@ -22,6 +22,8 @@ settings: target_bundles: type_one: type_one type_two: type_two + type_three: type_three + type_four: type_four sort: field: _none auto_create: false diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_extra_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_extra_image.yml new file mode 100644 index 0000000000000000000000000000000000000000..a81f94ab23c8bd5a45eca95a33cf629e889c00f0 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_extra_image.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - media +id: media.field_media_extra_image +field_name: field_media_extra_image +entity_type: media +type: image +settings: + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false + uri_scheme: public +module: image +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_image.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_image.yml new file mode 100644 index 0000000000000000000000000000000000000000..db94e9312ab1255465a3c5a24914f0c26315b33b --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.media.field_media_test_image.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - media +id: media.field_media_test_image +field_name: field_media_test_image +entity_type: media +type: image +settings: + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false + uri_scheme: public +module: image +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_four.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_four.yml new file mode 100644 index 0000000000000000000000000000000000000000..087c34ed063b882c11d21d6b2be1080b14bba4d2 --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_four.yml @@ -0,0 +1,12 @@ +langcode: en +status: true +dependencies: { } +id: type_four +label: 'Type Four' +description: '' +source: image +queue_thumbnail_downloads: false +new_revision: false +source_configuration: + source_field: field_media_test_image +field_map: { } diff --git a/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_three.yml b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_three.yml new file mode 100644 index 0000000000000000000000000000000000000000..536de34492a24f547962520e908ac0913d9cfa5a --- /dev/null +++ b/core/modules/media_library/tests/modules/media_library_test/config/install/media.type.type_three.yml @@ -0,0 +1,12 @@ +langcode: en +status: true +dependencies: { } +id: type_three +label: 'Type Three' +description: '' +source: image +queue_thumbnail_downloads: false +new_revision: false +source_configuration: + source_field: field_media_test_image +field_map: { } diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml index 41979c8eed148d2a7ccaf1a6b201b88e23534263..78582f204fa173cb6288bac79682eef7ddd78b71 100644 --- a/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml +++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml @@ -4,6 +4,7 @@ description: 'Test module for Media library.' package: Testing core: 8.x dependencies: + - drupal:image - drupal:media_library - drupal:media_test_source - drupal:menu_ui diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php index ab6e8a55f8a14faed001fad6d146d0efbca8f326..b92a42e3d4015347f7d80dbc269dba4eb201c9a2 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php @@ -4,6 +4,7 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\media\Entity\Media; +use Drupal\Tests\TestFileCreationTrait; use Drupal\user\Entity\Role; use Drupal\user\RoleInterface; @@ -14,6 +15,8 @@ */ class MediaLibraryTest extends WebDriverTestBase { + use TestFileCreationTrait; + /** * {@inheritdoc} */ @@ -286,4 +289,110 @@ public function testWidgetAnonymous() { $assert_session->pageTextContains('Dog'); } + /** + * Tests that uploads in the Media library's widget works as expected. + */ + public function testWidgetUpload() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + foreach ($this->getTestFiles('image') as $image) { + $extension = pathinfo($image->filename, PATHINFO_EXTENSION); + if ($extension === 'png') { + $png_image = $image; + } + elseif ($extension === 'jpg') { + $jpg_image = $image; + } + } + + if (!isset($png_image) || !isset($jpg_image)) { + $this->fail('Expected test files not present.'); + } + + // Visit a node create page. + $this->drupalGet('node/add/basic_page'); + + $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $this->container->get('file_system'); + + // Add to the twin media field using the add button directly on the widget. + $unlimited_button = $assert_session->elementExists('css', '.media-library-add-button[href*="field_twin_media"]'); + $unlimited_button->click(); + $assert_session->assertWaitOnAjaxRequest(); + + $page->attachFileToField('Upload', $this->container->get('file_system')->realpath($png_image->uri)); + $assert_session->assertWaitOnAjaxRequest(); + + // Files are temporary until the form is saved. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri())); + $this->assertTrue($file->isTemporary()); + + $this->assertSame($assert_session->fieldExists('Name')->getValue(), $png_image->filename); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextContains('Alternative text field is required'); + $page->fillField('Alternative text', $this->randomString()); + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save'); + $assert_session->assertWaitOnAjaxRequest(); + + // The file should be permanent now. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertFalse($file->isTemporary()); + + // Ensure the media item was added. + $assert_session->pageTextNotContains('Media library'); + $assert_session->pageTextContains($png_image->filename); + + // Open the browser again to test type resolution. + $unlimited_button = $assert_session->elementExists('css', '.media-library-open-button[href*="field_twin_media"]'); + $unlimited_button->click(); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextContains('Media library'); + $assert_session->elementExists('css', '#drupal-modal')->clickLink('Add media'); + $assert_session->assertWaitOnAjaxRequest(); + + $page->attachFileToField('Upload', $file_system->realpath($jpg_image->uri)); + $assert_session->assertWaitOnAjaxRequest(); + + $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_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. + $assert_session->pageTextContains('Extra Image'); + $page->attachFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_image->uri)); + $assert_session->assertWaitOnAjaxRequest(); + // Ensure that the extra image was uploaded to the correct directory. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri())); + + $assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save'); + $assert_session->assertWaitOnAjaxRequest(); + + $assert_session->pageTextNotContains('Media library'); + $assert_session->pageTextContains($jpg_image->filename); + } + }