1c1
< diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogUrlCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogUrlCommand.php
---
> diff --git a/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
3c3
< index 0000000000..7e36a33dcd
---
> index 0000000000..e082019a82
5,198c5
< +++ b/core/lib/Drupal/Core/Ajax/OpenDialogUrlCommand.php
< @@ -0,0 +1,51 @@
< + '70%',
< + ];
< +
< + /**
< + * OpenDialogUrlCommand constructor.
< + *
< + * @param \Drupal\Core\Url|string $url
< + * The URL to open in the modal dialog.
< + * @param array $dialog_options
< + * (optional) Additional client-side options for the modal dialog.
< + */
< + public function __construct($url, array $dialog_options = []) {
< + $this->url = $url instanceof Url ? $url->toString() : $url;
< + if ($dialog_options) {
< + $this->dialogOptions = $dialog_options;
< + }
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function render() {
< + return [
< + 'command' => 'openDialogUrl',
< + 'url' => $this->url,
< + 'dialogOptions' => $this->dialogOptions,
< + ];
< + }
< +
< +}
< diff --git a/core/misc/dialog/dialog.ajax.es6.js b/core/misc/dialog/dialog.ajax.es6.js
< index 798673a1d8..6c8b44a809 100644
< --- a/core/misc/dialog/dialog.ajax.es6.js
< +++ b/core/misc/dialog/dialog.ajax.es6.js
< @@ -95,6 +95,27 @@
< };
<
< /**
< + * Command to open an arbitrary URL in a modal dialog.
< + *
< + * @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.openDialogUrl = function (ajax, response, status) {
< + new Drupal.Ajax(null, null, {
< + url: response.url,
< + dialogType: 'modal',
< + dialog: response.dialogOptions,
< + progress: {
< + type: 'throbber'
< + }
< + }).execute();
< + };
< +
< + /**
< * Command to open a dialog.
< *
< * @param {Drupal.Ajax} ajax
< diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js
< index 8277df3e51..15e11f39c4 100644
< --- a/core/misc/dialog/dialog.ajax.js
< +++ b/core/misc/dialog/dialog.ajax.js
< @@ -59,6 +59,17 @@
< }
< };
<
< + Drupal.AjaxCommands.prototype.openDialogUrl = function (ajax, response, status) {
< + new Drupal.Ajax(null, null, {
< + url: response.url,
< + dialogType: 'modal',
< + dialog: response.dialogOptions,
< + progress: {
< + type: 'throbber'
< + }
< + }).execute();
< + };
< +
< Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) {
< if (!response.selector) {
< return false;
< diff --git a/core/modules/media/config/install/core.entity_form_display.media.file.add_file.yml b/core/modules/media/config/install/core.entity_form_display.media.file.add_file.yml
< new file mode 100644
< index 0000000000..e2ed5cefbd
< --- /dev/null
< +++ b/core/modules/media/config/install/core.entity_form_display.media.file.add_file.yml
< @@ -0,0 +1,34 @@
< +langcode: en
< +status: true
< +dependencies:
< + config:
< + - core.entity_form_mode.media.add_file
< + - field.field.media.file.field_media_file
< + - media.type.file
< + module:
< + - file
< +id: media.file.add_file
< +targetEntityType: media
< +bundle: file
< +mode: add_file
< +content:
< + field_media_file:
< + settings:
< + progress_indicator: throbber
< + third_party_settings: { }
< + type: file_generic
< + weight: 1
< + 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/config/install/core.entity_form_display.media.image.add_file.yml b/core/modules/media/config/install/core.entity_form_display.media.image.add_file.yml
< new file mode 100644
< index 0000000000..75be602501
< --- /dev/null
< +++ b/core/modules/media/config/install/core.entity_form_display.media.image.add_file.yml
< @@ -0,0 +1,36 @@
< +langcode: en
< +status: true
< +dependencies:
< + config:
< + - core.entity_form_mode.media.add_file
< + - field.field.media.image.field_media_image
< + - image.style.thumbnail
< + - media.type.image
< + module:
< + - image
< +id: media.image.add_file
< +targetEntityType: media
< +bundle: image
< +mode: add_file
< +content:
< + field_media_image:
< + settings:
< + progress_indicator: throbber
< + preview_image_style: thumbnail
< + third_party_settings: { }
< + type: image_image
< + weight: 1
< + 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/config/install/core.entity_form_mode.media.add_file.yml b/core/modules/media/config/install/core.entity_form_mode.media.add_file.yml
< new file mode 100644
< index 0000000000..ff7eccdeb5
< --- /dev/null
< +++ b/core/modules/media/config/install/core.entity_form_mode.media.add_file.yml
---
> +++ b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml
205c12
< +id: media.add_file
---
> +id: media.add_inline
209,235c16,29
< diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml
< index dad518985a..1176376da8 100644
< --- a/core/modules/media/config/schema/media.schema.yml
< +++ b/core/modules/media/config/schema/media.schema.yml
< @@ -40,6 +40,14 @@ field.formatter.settings.media_thumbnail:
< type: field.formatter.settings.image
< label: 'Media thumbnail field display format settings'
<
< +field.widget.settings.media_file:
< + type: mapping
< + label: 'File widget settings'
< + mapping:
< + open:
< + type: boolean
< + label: 'Open by default'
< +
< media.source.*:
< type: mapping
< label: 'Media source settings'
< diff --git a/core/modules/media/media.module b/core/modules/media/media.module
< index dc2d898e3c..f89ae4a730 100644
< --- a/core/modules/media/media.module
< +++ b/core/modules/media/media.module
< @@ -6,12 +6,15 @@
< */
<
< use Drupal\Core\Access\AccessResult;
---
> diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_view_mode.media.full.yml
> similarity index 100%
> rename from core/modules/media/config/install/core.entity_view_mode.media.full.yml
> rename to core/modules/media/config/optional/core.entity_view_mode.media.full.yml
> diff --git a/core/modules/media/src/Element/MediaManagedFile.php b/core/modules/media/src/Element/MediaManagedFile.php
> new file mode 100644
> index 0000000000..e3fbbe8cab
> --- /dev/null
> +++ b/core/modules/media/src/Element/MediaManagedFile.php
> @@ -0,0 +1,43 @@
> + +
> +namespace Drupal\media\Element;
> +
237,251c31,32
< use Drupal\Core\Session\AccountInterface;
< use Drupal\field\FieldConfigInterface;
< use Drupal\Core\Entity\EntityInterface;
< use Drupal\Core\Render\Element;
< use Drupal\Core\Routing\RouteMatchInterface;
< use Drupal\Core\Url;
< +use Drupal\file\FileInterface;
< +use Drupal\media\Form\AddFileForm;
<
< /**
< * Implements hook_help().
< @@ -96,3 +99,39 @@ function template_preprocess_media(array &$variables) {
< $variables['content'][$key] = $variables['elements'][$key];
< }
< }
---
> +use Drupal\file\Element\ManagedFile;
> +use Drupal\media\Entity\Media;
254,260c35
< + * Validate uploaded file for media field.
< + *
< + * @param \Drupal\file\FileInterface $file
< + * File that should be validated.
< + *
< + * @return array
< + * Result of validation.
---
> + * @FormElement("media_managed_file")
262,263c37,51
< +function media_validate_file_upload(FileInterface $file) {
< + $media_types = \Drupal::service('media.type_guesser.file')->getTypesForFile($file);
---
> +class MediaManagedFile extends ManagedFile {
> +
> + public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
> + if (!empty($element['#value']['target_id'])) {
> + // We're editing an entity with a previously-saved Media entity
> + // reference(s). In order not to confuse ManagedFile, load those entities'
> + // source fids and store them where it expects them.
> + $media_entity = Media::load($element['#value']['target_id']);
> + $media_source_field = $media_entity->getSource()->getConfiguration()['source_field'];
> + $file_value = $media_entity->get($media_source_field)->getValue();
> + foreach ($file_value as $value) {
> + $element['#value']['fids'][] = $value['target_id'];
> + }
> + }
> + $element = parent::processManagedFile($element, $form_state, $complete_form);
265,269c53,62
< + if (empty($media_types)) {
< + return [
< + t('%filename could not be matched to any media types.', [
< + '%filename' => $file->getFilename(),
< + ]),
---
> + // We don't want to show the file-upload field for values that already have
> + // a file.
> + if (!empty($element['#files'])) {
> + $element['upload']['#access'] = FALSE;
> + }
> +
> + $mids = isset($element['#value']['mids']) ? $element['#value']['mids'] : [];
> + $element['mids'] = [
> + '#type' => 'hidden',
> + '#value' => $mids,
271,273d63
< + }
< + return [];
< +}
275,280c65,66
< +/**
< + * Implements hook_field_widget_WIDGET_TYPE_form_alter().
< + */
< +function media_field_widget_file_generic_form_alter(array &$element, FormStateInterface $form_state, array $context) {
< + AddFileForm::alterFileWidget($element, $form_state);
< +}
---
> + return $element;
> + }
282,286d67
< +/**
< + * Implements hook_field_widget_WIDGET_TYPE_form_alter().
< + */
< +function media_field_widget_image_image_form_alter(array &$element, FormStateInterface $form_state, array $context) {
< + AddFileForm::alterFileWidget($element, $form_state);
288,321d68
< diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml
< index 9fbadeff29..fb7a582f4c 100644
< --- a/core/modules/media/media.routing.yml
< +++ b/core/modules/media/media.routing.yml
< @@ -19,3 +19,16 @@ entity.media.revision:
< requirements:
< _access_media_revision: 'view'
< media: \d+
< +
< +entity.media.add_file:
< + path: '/media/add-file/{media_type}/{file}/{selector}'
< + defaults:
< + _entity_form: 'media.add_file'
< + options:
< + parameters:
< + media_type:
< + type: 'entity:media_type'
< + file:
< + type: 'entity:file'
< + requirements:
< + _entity_create_access: 'media'
< diff --git a/core/modules/media/media.services.yml b/core/modules/media/media.services.yml
< index f22f90a124..7896a10ce1 100644
< --- a/core/modules/media/media.services.yml
< +++ b/core/modules/media/media.services.yml
< @@ -8,3 +8,8 @@ services:
< arguments: ['@entity_type.manager']
< tags:
< - { name: access_check, applies_to: _access_media_revision }
< +
< + media.type_guesser.file:
< + class: Drupal\media\FileGuesser
< + # TODO: Add a cache backend.
< + arguments: ['@entity_type.manager']
323c70
< index 49f7283f7e..dea100180c 100644
---
> index 49f7283f7e..2c6862f8a7 100644
326,327c73
< @@ -38,6 +38,7 @@
< * "add" = "Drupal\media\MediaForm",
---
> @@ -39,6 +39,7 @@
330d75
< + * "add_file" = "Drupal\media\Form\AddFileForm",
331a77
> + * "add_inline" = "Drupal\media\Form\MediaInlineForm",
334c80,81
< diff --git a/core/modules/media/src/FileGuesser.php b/core/modules/media/src/FileGuesser.php
---
> * "route_provider" = {
> diff --git a/core/modules/media/src/Form/MediaInlineForm.php b/core/modules/media/src/Form/MediaInlineForm.php
336c83
< index 0000000000..1f69ee7f24
---
> index 0000000000..988de582a6
338,339c85,86
< +++ b/core/modules/media/src/FileGuesser.php
< @@ -0,0 +1,152 @@
---
> +++ b/core/modules/media/src/Form/MediaInlineForm.php
> @@ -0,0 +1,154 @@
342c89
< +namespace Drupal\media;
---
> +namespace Drupal\media\Form;
344,346c91,94
< +use Drupal\Component\Utility\Unicode;
< +use Drupal\Core\Cache\CacheBackendInterface;
< +use Drupal\Core\Cache\UseCacheBackendTrait;
---
> +use Drupal\Core\Entity\ContentEntityInterface;
> +use Drupal\Core\Entity\Entity\EntityFormDisplay;
> +use Drupal\Core\Entity\EntityFieldManagerInterface;
> +use Drupal\Core\Entity\EntityTypeInterface;
348,500c96,99
< +use Drupal\file\FileInterface;
< +use Drupal\file\Plugin\Field\FieldType\FileItem;
< +
< +/**
< + * Discovers media types which support file uploads.
< + */
< +class FileGuesser implements FileGuesserInterface {
< +
< + use UseCacheBackendTrait;
< +
< + /**
< + * Entity type manager service.
< + *
< + * @var \Drupal\Core\Entity\EntityTypeManagerInterface
< + */
< + protected $entityTypeManager;
< +
< + /**
< + * All media types that can be created by the current user.
< + *
< + * @var \Drupal\media\MediaTypeInterface[]
< + */
< + protected $allowedTypes;
< +
< + /**
< + * Constructs a new FileGuesser.
< + *
< + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
< + * Entity type manager.
< + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
< + * (optional) A cache backend.
< + */
< + public function __construct(EntityTypeManagerInterface $entity_type_manager, CacheBackendInterface $cache_backend = NULL) {
< + $this->entityTypeManager = $entity_type_manager;
< + $this->cacheBackend = $cache_backend;
< + $this->useCaches = isset($cache_backend);
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function guessForFile($file, array $limit = []) {
< + $media_types = $this->getTypesForFile($file, $limit);
< + return key($media_types);
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function guessForExtension($extension, array $limit = []) {
< + $media_types = $this->getTypesByExtension($extension, $limit);
< + return key($media_types);
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function getTypesForFile($file, array $limit = []) {
< + if (is_numeric($file)) {
< + $file = $this->entityTypeManager->getStorage('file')->load($file);
< + }
< +
< + if ($file instanceof FileInterface) {
< + $extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION);
< +
< + return $this->getTypesByExtension($extension, $limit);
< + }
< + else {
< + throw new \InvalidArgumentException('Expected file ID or entity');
< + }
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function getTypesByExtension($extension, array $limit = []) {
< + $cache = $this->cacheGet($extension);
< + if ($cache) {
< + return $cache->data;
< + }
< +
< + $media_types = $this->getAllowedMediaTypes();
< + if ($limit) {
< + $media_types = array_intersect_key($media_types, array_flip($limit));
< + }
< +
< + $filter = function (MediaTypeInterface $media_type) {
< + $item_class = $media_type
< + ->getSource()
< + ->getSourceFieldDefinition($media_type)
< + ->getItemDefinition()
< + ->getClass();
< +
< + return is_a($item_class, FileItem::class, TRUE);
< + };
< + $media_types = array_filter($media_types, $filter);
< +
< + $extension = Unicode::strtolower($extension);
< +
< + $matches = [];
< + /** @var \Drupal\media\MediaTypeInterface $media_type */
< + foreach ($media_types as $id => $media_type) {
< + $extensions = $media_type
< + ->getSource()
< + ->getSourceFieldDefinition($media_type)
< + ->getSetting('file_extensions');
< +
< + $allowed_extensions = preg_split('/\s+/', Unicode::strtolower($extensions));
< +
< + if (in_array($extension, $allowed_extensions)) {
< + $matches[$id] = $media_type->label();
< + }
< + }
< + $this->cacheSet($extension, $matches);
< +
< + return $matches;
< + }
< +
< + /**
< + * Returns all media types that can be created by the current user.
< + *
< + * @return \Drupal\media\MediaTypeInterface[]
< + * The media types.
< + */
< + protected function getAllowedMediaTypes() {
< + if (isset($this->allowedTypes)) {
< + return $this->allowedTypes;
< + }
< +
< + $storage = $this->entityTypeManager->getStorage('media_type');
< +
< + $media_types = array_filter(
< + $storage->getQuery()->execute(),
< + [
< + $this->entityTypeManager->getAccessControlHandler('media'),
< + 'createAccess',
< + ]
< + );
< + $this->allowedTypes = $storage->loadMultiple($media_types);
< +
< + return $this->allowedTypes;
< + }
< +
< +}
< diff --git a/core/modules/media/src/FileGuesserInterface.php b/core/modules/media/src/FileGuesserInterface.php
< new file mode 100644
< index 0000000000..fa97260506
< --- /dev/null
< +++ b/core/modules/media/src/FileGuesserInterface.php
< @@ -0,0 +1,69 @@
< + +use Drupal\Core\Extension\ModuleHandlerInterface;
> +use Drupal\Core\Form\FormStateInterface;
> +use Drupal\media\MediaInlineFormInterface;
> +use Symfony\Component\DependencyInjection\ContainerInterface;
503c102
< + * Discovers media types which support file uploads.
---
> + * Media entity inline form handler.
505c104
< +interface FileGuesserInterface {
---
> +class MediaInlineForm implements MediaInlineFormInterface {
508c107
< + * Returns the first media type ID that matches a file.
---
> + * The entity field manager.
510,517c109
< + * @param \Drupal\file\FileInterface|int $file
< + * A file entity or ID.
< + * @param string[] $limit
< + * (optional) The media type IDs to choose from. If omitted, all media types
< + * will be considered.
< + *
< + * @return string|bool
< + * A media type ID, or FALSE if no media types matched.
---
> + * @var \Drupal\Core\Entity\EntityFieldManagerInterface
519c111
< + public function guessForFile($file, array $limit = []);
---
> + protected $entityFieldManager;
522,528c114
< + * Returns the first media type ID that matches a file extension.
< + *
< + * @param string $extension
< + * A file extension, without the leading period.
< + * @param string[] $limit
< + * (optional) The media type IDs to choose from. If omitted, all media types
< + * will be considered.
---
> + * The entity type manager.
530,531c116
< + * @return string|bool
< + * A media type ID, or FALSE if no media types matched.
---
> + * @var \Drupal\Core\Entity\EntityTypeManagerInterface
533c118
< + public function guessForExtension($extension, array $limit = []);
---
> + protected $entityTypeManager;
536,542c121
< + * Returns all media types that match a file.
< + *
< + * @param \Drupal\file\FileInterface|int $file
< + * A file entity or ID.
< + * @param string[] $limit
< + * (optional) The media type IDs to choose from. If omitted, all media types
< + * will be considered.
---
> + * The entity type managed by this handler.
544,548c123
< + * @throws \InvalidArgumentException
< + * If $file is not a file entity, or the ID of one.
< + *
< + * @return \Drupal\media\MediaTypeInterface[]
< + * All matching media types.
---
> + * @var \Drupal\Core\Entity\EntityTypeInterface
550c125
< + public function getTypesForFile($file, array $limit = []);
---
> + protected $entityType;
553,559c128
< + * Returns all media types that match a file extension.
< + *
< + * @param string $extension
< + * A file extension, without the leading period.
< + * @param string[] $limit
< + * (optional) The media type IDs to choose from. If omitted, all media types
< + * will be considered.
---
> + * Module handler service.
561,562c130
< + * @return \Drupal\media\MediaTypeInterface[]
< + * All matching media types.
---
> + * @var \Drupal\Core\Extension\ModuleHandlerInterface
564,596c132
< + public function getTypesByExtension($extension, array $limit = []);
< +
< +}
< diff --git a/core/modules/media/src/Form/AddFileForm.php b/core/modules/media/src/Form/AddFileForm.php
< new file mode 100644
< index 0000000000..465866fa28
< --- /dev/null
< +++ b/core/modules/media/src/Form/AddFileForm.php
< @@ -0,0 +1,317 @@
< + + protected $moduleHandler;
599c135
< + * The file guesser service.
---
> + * Constructs the inline entity form controller.
601,615c137,144
< + * @var \Drupal\media\FileGuesserInterface
< + */
< + protected $fileGuesser;
< +
< + /**
< + * Constructs a ContentEntityForm object.
< + *
< + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
< + * The entity manager.
< + * @param \Drupal\media\FileGuesserInterface $file_guesser
< + * The file guesser service.
< + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
< + * (optional) The entity type bundle service.
< + * @param \Drupal\Component\Datetime\TimeInterface $time
< + * (optional) The time service.
---
> + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
> + * The entity field manager.
> + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
> + * The entity type manager.
> + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
> + * The module handler.
> + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
> + * The entity type.
617,619c146,150
< + public function __construct(EntityManagerInterface $entity_manager, FileGuesserInterface $file_guesser, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, TimeInterface $time = NULL) {
< + parent::__construct($entity_manager, $entity_type_bundle_info, $time);
< + $this->fileGuesser = $file_guesser;
---
> + public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EntityTypeInterface $entity_type) {
> + $this->entityFieldManager = $entity_field_manager;
> + $this->entityTypeManager = $entity_type_manager;
> + $this->moduleHandler = $module_handler;
> + $this->entityType = $entity_type;
625c156
< + public static function create(ContainerInterface $container) {
---
> + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
627,630c158,161
< + $container->get('entity.manager'),
< + $container->get('media.type_guesser.file'),
< + $container->get('entity_type.bundle.info'),
< + $container->get('datetime.time')
---
> + $container->get('entity_field.manager'),
> + $container->get('entity_type.manager'),
> + $container->get('module_handler'),
> + $entity_type
635,649d165
< + * AJAX callback associated with the 'media type' input selector.
< + *
< + * @param array $form
< + * The form.
< + * @param \Drupal\Core\Form\FormStateInterface $form_state
< + * The form state.
< + *
< + * @return array
< + * The forms 'fields' array.
< + */
< + public static function onTypeSelect(array &$form, FormStateInterface $form_state) {
< + return $form['fields'];
< + }
< +
< + /**
652,662c168,169
< + protected function init(FormStateInterface $form_state) {
< + if ($form_state->hasValue('bundle')) {
< + $entity = $this->entityTypeManager
< + ->getStorage('media')
< + ->create([
< + 'bundle' => $form_state->getValue('bundle'),
< + ]);
< +
< + $this->setEntity($entity);
< + }
< + return parent::init($form_state);
---
> + public function getEntityType() {
> + return $this->entityType;
668,701c175,196
< + protected function prepareEntity() {
< + parent::prepareEntity();
< +
< + /** @var \Drupal\media\MediaInterface $entity */
< + $entity = $this->getEntity();
< +
< + $source_field = $entity->getSource()
< + ->getSourceFieldDefinition($entity->bundle->entity)
< + ->getName();
< +
< + /** @var \Drupal\file\FileInterface $file */
< + $file = $this->getRouteMatch()->getParameter('file');
< + $entity->set($source_field, $file);
< +
< + // Move the file to the location specified by the source field.
< + /** @var \Drupal\file\Plugin\Field\FieldType\FileItem $item */
< + $item = $entity->get($source_field)->first();
< + $destination = $item->getUploadLocation();
< + file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
< + $destination .= '/' . $file->getFilename();
< + if (!file_exists($destination)) {
< + // TODO: Use an injected file_system service for this.
< + $destination = file_unmanaged_move($file->getFileUri(), $destination);
< + }
< + $file->setFileUri($destination);
< + // TODO: The file is saved on every bundle change. We might not want that.
< + $file->save();
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function validateForm(array &$form, FormStateInterface $form_state) {
< + $entity = parent::validateForm($form, $form_state);
---
> + public function entityForm(array $entity_form, FormStateInterface $form_state) {
> + // Build the media entity form.
> + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
> + $entity = $entity_form['#entity'];
> + $form_display = $this->getFormDisplay($entity, $entity_form['#form_mode']);
> + $form_display->buildForm($entity, $entity_form, $form_state);
> + $entity_form['#weight'] = 100;
> +
> + // In the service of keeping this form as user-friendly as possible in the
> + // context of a parent entity form, only show required fields.
> + /** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */
> + $entity_field_manager = \Drupal::service('entity_field.manager');
> + $field_definitions = $entity_field_manager->getFieldDefinitions('media', 'file');
> + /** @var \Drupal\Core\Field\BaseFieldDefinition $field_definition */
> + foreach ($field_definitions as $field_definition) {
> + $field_name = $field_definition->getName();
> + if (!$field_definition->isRequired()) {
> + $entity_form[$field_name]['#access'] = FALSE;
> + }
> + else {
> + $entity_form[$field_name]['#element_validate'] = [];
> + }
703,709d197
< + // If the bundle has changed, reinitialize the entity form. This will call
< + // init(), which will replace the current entity with a new one of the
< + // selected type.
< + $chosen_bundle = $form_state->getValue('bundle');
< + if ($chosen_bundle && $chosen_bundle !== $this->getEntity()->bundle()) {
< + $storage = &$form_state->getStorage();
< + unset($storage['entity_form_initialized']);
712,724c200,201
< + return $entity;
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function form(array $form, FormStateInterface $form_state) {
< + $form['fields'] = parent::form($form, $form_state);
< + $form['fields']['#prefix'] = '
';
< + $form['fields']['#suffix'] = '
';
< +
< + /** @var \Drupal\media\MediaInterface $entity */
< + $entity = $this->getEntity();
---
> + // We've already set the just-uploaded file as the value of the source
> + // field, so hide that too.
728,734c205
< + /** @var \Drupal\file\FileInterface $file */
< + $file = $entity->get($source_field)->entity;
< + $form['message'] = [
< + '#type' => 'markup',
< + '#markup' => $this->t('Choose a media type for %filename', ['%filename' => $file->getFilename()]),
< + '#weight' => -110,
< + ];
---
> + $entity_form[$source_field]['#access'] = FALSE;
736,772c207,216
< + $form['bundle'] = [
< + '#type' => 'select',
< + '#title' => $this->t('Media Type'),
< + '#required' => TRUE,
< + '#default_value' => $this->getEntity()->bundle(),
< + '#weight' => -100,
< + '#ajax' => [
< + 'wrapper' => 'entity-fields',
< + 'callback' => [static::class, 'onTypeSelect'],
< + ],
< + '#limit_validation_errors' => [
< + ['bundle'],
< + ],
< + '#submit' => [],
< + ];
< +
< + // TODO: Currently the types are fetched based on the field settings, but
< + // since every type can have it's own upload validators, we need to account
< + // for that as well. One media type might support PDF files, while another
< + // media type doesn't.
< + $query = $this->getRequest()->query;
< + $entity_type = $query->get('entity_type');
< + $bundle = $query->get('bundle');
< + $field_name = $query->get('field_name');
< + if ($entity_type && $bundle && $field_name) {
< + $field_definitions = $this->entityManager->getFieldDefinitions($entity_type, $bundle);
< +
< + if (!isset($field_definitions[$field_name])) {
< + return $form;
< + }
< +
< + $allowed_types = MediaType::loadMultiple($field_definitions[$field_name]->getSetting('handler_settings')['target_bundles']);
< + foreach ($allowed_types as $media_type) {
< + /** @var \Drupal\media\Entity\MediaType $media_type */
< + $source = $media_type->getSource();
< + if ($source->getPluginId() === 'file') {
< + $form['bundle']['#options'][$media_type->id()] = $media_type->label();
---
> + // Inline entities inherit the parent language.
> + $langcode_key = $this->entityType->getKey('langcode');
> + if ($langcode_key && isset($entity_form[$langcode_key])) {
> + $entity_form[$langcode_key]['#access'] = FALSE;
> + }
> + if (!empty($entity_form['#translating'])) {
> + // Hide the non-translatable fields.
> + foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
> + if (isset($entity_form[$field_name]) && $field_name != $langcode_key) {
> + $entity_form[$field_name]['#access'] = $definition->isTranslatable();
777,778d220
< + // Hide revision log message field.
< + $form['fields']['revision_log_message']['#access'] = FALSE;
780c222
< + return $form;
---
> + return $entity_form;
784,797c226
< + * {@inheritdoc}
< + */
< + protected function actions(array $form, FormStateInterface $form_state) {
< + $actions = parent::actions($form, $form_state);
< +
< + $actions['submit']['#ajax'] = [
< + 'callback' => '::onSave',
< + ];
< +
< + return $actions;
< + }
< +
< + /**
< + * The 'onSave' action callback.
---
> + * Gets the form display for the given entity.
799,802c228,231
< + * @param array $form
< + * The form.
< + * @param \Drupal\Core\Form\FormStateInterface $form_state
< + * The form state.
---
> + * @param \Drupal\Core\Entity\ContentEntityInterface $entity
> + * The entity.
> + * @param string $form_mode
> + * The form mode.
804,805c233,234
< + * @return \Drupal\Core\Ajax\AjaxResponse
< + * The AJAX response.
---
> + * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
> + * The form display.
807,808c236,238
< + public function onSave(array &$form, FormStateInterface $form_state) {
< + $response = new AjaxResponse();
---
> + protected function getFormDisplay(ContentEntityInterface $entity, $form_mode) {
> + return EntityFormDisplay::collectRenderDisplay($entity, $form_mode);
> + }
810,814c240,247
< + if ($form_state->getErrors()) {
< + return $response->addCommand(
< + new BeforeCommand('#entity-fields', ['#type' => 'status_messages'])
< + );
< + }
---
> +}
> diff --git a/core/modules/media/src/MediaInlineFormInterface.php b/core/modules/media/src/MediaInlineFormInterface.php
> new file mode 100644
> index 0000000000..fc6ea3ac88
> --- /dev/null
> +++ b/core/modules/media/src/MediaInlineFormInterface.php
> @@ -0,0 +1,36 @@
> +getRouteMatch();
< + $parameters = $route_match->getRawParameters()->all();
< + $query = $this->getRequest()->query;
< +
< + $media_items = $query->get('media', []);
< + array_push($media_items, $this->getEntity()->id());
< +
< + if ($query->has('files')) {
< + $query = [
< + 'files' => $query->get('files'),
< + 'media' => $media_items,
< + ];
< + $next_file_id = array_shift($query['files']);
< + $parameters['file'] = $next_file_id;
< + $parameters['media_type'] = $this->fileGuesser->guessForFile($next_file_id);
< +
< + $url = Url::fromRoute($route_match->getRouteName(), $parameters, [
< + 'query' => $query,
< + ]);
< + $response->addCommand(new OpenDialogUrlCommand($url));
< + }
< + else {
< + $response
< + ->addCommand(new InvokeCommand(
< + 'input[data-drupal-selector="' . $parameters['selector'] . '"]',
< + 'val',
< + (array) implode(',', array_map('intval', $media_items))
< + ))
< + ->addCommand(new CloseModalDialogCommand());
< + }
< + return $response;
< + }
---
> +namespace Drupal\media;
849,858c251,252
< + /**
< + * Alters the field widget by hiding the remove button.
< + *
< + * @param array $element
< + * The elements.
< + * @param \Drupal\Core\Form\FormStateInterface $form_state
< + * The form state.
< + */
< + public static function alterFileWidget(array &$element, FormStateInterface $form_state) {
< + $self = static::class;
---
> +use Drupal\Core\Entity\EntityHandlerInterface;
> +use Drupal\Core\Form\FormStateInterface;
860,863c254,257
< + if ($form_state->getFormObject() instanceof $self) {
< + $element['#process'][] = [$self, 'hideRemoveButton'];
< + }
< + }
---
> +/**
> + * Defines the interface for inline form handlers.
> + */
> +interface MediaInlineFormInterface extends EntityHandlerInterface {
866c260
< + * Hides the remove button.
---
> + * Gets the entity type managed by this handler.
868,872c262,263
< + * @param array $element
< + * An associative array containing the 'remove_button' render element.
< + *
< + * @return array
< + * An associated array containing the updated render array.
---
> + * @return \Drupal\Core\Entity\EntityTypeInterface
> + * The entity type.
874,877c265
< + public static function hideRemoveButton(array $element) {
< + $element['remove_button']['#access'] = FALSE;
< + return $element;
< + }
---
> + public function getEntityType();
880c268
< + * Checks whether the revision form fields should be added to the form.
---
> + * Builds the entity form.
882,883c270,278
< + * @return bool
< + * TRUE if the form field should be added, FALSE otherwise.
---
> + * @param array $entity_form
> + * The entity form, containing the following basic properties:
> + * - #entity: The entity for the current entity form.
> + * - #form_mode: The form mode used to display the entity form.
> + * - #parents: Identifies the position of the entity form in the overall
> + * parent form, and identifies the location where the field values are
> + * placed within $form_state->getValues().
> + * @param \Drupal\Core\Form\FormStateInterface $form_state
> + * The form state of the parent form.
885,887c280
< + protected function showRevisionUi() {
< + return FALSE;
< + }
---
> + public function entityForm(array $entity_form, FormStateInterface $form_state);
892c285
< index 0000000000..33a779d09a
---
> index 0000000000..7ac3eb3c10
895c288
< @@ -0,0 +1,269 @@
---
> @@ -0,0 +1,310 @@
900,904d292
< +use Drupal\Component\Utility\NestedArray;
< +use Drupal\Core\Ajax\OpenDialogUrlCommand;
< +use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
< +use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
< +use Drupal\Core\Field\FieldDefinitionInterface;
906d293
< +use Drupal\Core\Field\WidgetBase;
908,910c295,298
< +use Drupal\Core\Render\RendererInterface;
< +use Drupal\Core\Url;
< +use Drupal\file\Element\ManagedFile;
---
> +use Drupal\file\Entity\File;
> +use Drupal\file\Plugin\Field\FieldWidget;
> +use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
> +use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
911a300
> +use Drupal\media\Entity\Media;
913,914c302,303
< +use Symfony\Component\DependencyInjection\ContainerInterface;
< +use Symfony\Component\HttpFoundation\Request;
---
> +use Drupal\Component\Utility\NestedArray;
> +use Drupal\Core\Link;
923,925c312,313
< + * "entity_reference",
< + * },
< + * multiple_values = TRUE,
---
> + * "entity_reference"
> + * }
928c316
< +class MediaFileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
---
> +class MediaFileWidget extends FileWidget {
931,933c319
< + * An associative array describing the 'managed_file' element type.
< + *
< + * As defined by the plugin.manager.element_info service.
---
> + * {@inheritdoc}
935c321,323
< + * @var array
---
> + * Uses nearly the same code that FileWidget does here, except that we have to
> + * dig down through the target Media entity to *its* file field's upload
> + * location and upload validators.
937c325,326
< + protected $managedFileInfo;
---
> + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
> + $field_settings = $this->getFieldSettings();
939,944c328,373
< + /**
< + * The renderer service.
< + *
< + * @var \Drupal\Core\Render\RendererInterface
< + */
< + protected $renderer;
---
> + // The field settings include defaults for the field type. However, this
> + // widget is a base class for other widgets (e.g., ImageWidget) that may act
> + // on field types without these expected settings.
> + $field_settings += [
> + 'display_default' => NULL,
> + 'display_field' => NULL,
> + 'description_field' => NULL,
> + ];
> +
> + $defaults = [
> + 'mids' => [],
> + 'fids' => [],
> + 'display' => (bool) $field_settings['display_default'],
> + 'description' => '',
> + ];
> +
> + /** @var \Drupal\media\Entity\MediaType $media_type */
> + $target_bundle = array_shift($this->fieldDefinition->getSetting('handler_settings')['target_bundles']);
> + $media_type = MediaType::load($target_bundle);
> + $source = $media_type->getSource();
> + $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($media_type));
> + $file_item = new FileItem($source_data_definition);
> + $element_info = $this->elementInfo->getInfo('media_managed_file');
> +
> + $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
> +
> + $element += [
> + '#type' => 'media_managed_file',
> + '#upload_location' => $file_item->getUploadLocation(),
> + '#upload_validators' => $file_item->getUploadValidators(),
> + '#value_callback' => [get_class($this), 'value'],
> + '#process' => array_merge($element_info['#process'], [[get_class($this), 'process']]),
> + '#progress_indicator' => $this->getSetting('progress_indicator'),
> + // Allows this field to return an array instead of a single value.
> + '#extended' => TRUE,
> + // Add properties needed by value() and process() methods.
> + '#field_name' => $this->fieldDefinition->getName(),
> + // This is actually talking about the bundle ON WHICH this field is
> + // placed, not the bundle(s) TO WHICH it can refer. In fact, it seems only
> + // to be used, unnecessarily, on FileWidget::validateMultipleCount().
> + '#entity_type' => $items->getEntity()->getEntityTypeId(),
> + '#display_field' => (bool) $field_settings['display_field'],
> + '#display_default' => $field_settings['display_default'],
> + '#description_field' => $field_settings['description_field'],
> + '#cardinality' => $cardinality,
> + ];
946,968c375
< + /**
< + * Constructs a WidgetBase object.
< + *
< + * @param string $plugin_id
< + * The plugin_id for the widget.
< + * @param mixed $plugin_definition
< + * The plugin implementation definition.
< + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
< + * The definition of the field to which the widget is associated.
< + * @param array $settings
< + * The widget settings.
< + * @param array $third_party_settings
< + * Any third party settings.
< + * @param array $managed_file_info
< + * An associative array describing the 'managed_file' element.
< + * @param \Drupal\Core\Render\RendererInterface $renderer
< + * The renderer.
< + */
< + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, array $managed_file_info, RendererInterface $renderer) {
< + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
< + $this->managedFileInfo = $managed_file_info;
< + $this->renderer = $renderer;
< + }
---
> + $element['#weight'] = $delta;
970,982c377,398
< + /**
< + * {@inheritdoc}
< + */
< + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
< + return new static(
< + $plugin_id,
< + $plugin_definition,
< + $configuration['field_definition'],
< + $configuration['settings'],
< + $configuration['third_party_settings'],
< + $container->get('element_info')->getInfo('managed_file'),
< + $container->get('renderer')
< + );
---
> + // Save mid, the target_id value that this field ultimately cares about.
> + if (!isset($items[$delta]->mids) && isset($items[$delta]->target_id)) {
> + $items[$delta]->mids = [$items[$delta]->target_id];
> + }
> + $element['#default_value'] = $items[$delta]->getValue() + $defaults;
> +
> + $default_fids = $element['#extended'] ? $element['#default_value']['fids'] : $element['#default_value'];
> + if (empty($default_fids)) {
> + $file_upload_help = [
> + '#theme' => 'file_upload_help',
> + '#description' => $element['#description'],
> + '#upload_validators' => $element['#upload_validators'],
> + '#cardinality' => $cardinality,
> + ];
> + $element['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
> + $element['#multiple'] = $cardinality != 1 ? TRUE : FALSE;
> + if ($cardinality != 1 && $cardinality != -1) {
> + $element['#element_validate'][] = [get_class($this), 'validateMultipleCount'];
> + }
> + }
> +
> + return $element;
988,992c404,417
< + public static function defaultSettings() {
< + $settings = [
< + 'open' => TRUE,
< + ];
< + return $settings + parent::defaultSettings();
---
> + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
> + // Override FileWidget::massageFormValues() in order to reference Media
> + // entities instead of files.
> + $new_values = [];
> + foreach ($values as &$value) {
> + foreach ($value['mids'] as $mid) {
> + $new_value = $value;
> + $new_value['target_id'] = $mid;
> + unset($new_value['mids']);
> + $new_values[] = $new_value;
> + }
> + }
> +
> + return $new_values;
998,1001c423,424
< + public static function isApplicable(FieldDefinitionInterface $field_definition) {
< + if ($field_definition->getType() !== 'entity_reference') {
< + return FALSE;
< + }
---
> + public static function value($element, $input, FormStateInterface $form_state) {
> + $return = parent::value($element, $input, $form_state);
1003,1004c426,449
< + if ($field_definition->getFieldStorageDefinition()->getSetting('target_type') !== 'media') {
< + return FALSE;
---
> + // When the form has just been submitted with file(s) being uploaded,
> + // $input['mids'] should be empty. FileWidget::value() calls
> + // ManagedFile::valueCallback(), which populates $return['fids']. If there's
> + // info in there AND we're not doing the subsequent form rebuild, it's time
> + // to make the media.
> + if (empty($input['mids']) && !empty($return['fids']) && !$form_state->isRebuilding()) {
> + foreach ($return['fids'] as $fid) {
> + $file = File::load($fid);
> + /** @var \Drupal\media\MediaInterface $media_entity */
> + $media_entity = Media::create([
> + 'name' => $file->getFilename(),
> + 'bundle' => 'file', // TODO: generalize (note that we have to apply this to MediaInlineFileWidgetTest::setUp() too, in createdMediaType() call)
> + 'uid' => \Drupal::currentUser()->id(),
> + // TODO: figure out langcode in this context.
> + // 'langcode' => !empty($element['#langcode']) ? $element['#langcode'] : LanguageInterface::LANGCODE_DEFAULT,
> + ]);
> + $media_entity->setPublished();
> + $source_field = $media_entity->getSource()
> + ->getSourceFieldDefinition($media_entity->bundle->entity)
> + ->getName();
> + $media_entity->set($source_field, $fid);
> + $media_entity->save();
> + $return['mids'][] = $media_entity->id();
> + }
1006,1012c451,464
< +
< + $allowed_types = MediaType::loadMultiple($field_definition->getSetting('handler_settings')['target_bundles']);
< + foreach ($allowed_types as $media_type) {
< + /** @var \Drupal\media\Entity\MediaType $media_type */
< + $source = $media_type->getSource();
< + if ($source->getPluginId() === 'file') {
< + return TRUE;
---
> + else if (!empty($input['mids'])) {
> + $mid = $input['mids'];
> + $media_fields = 'media_' . $mid;
> +
> + if (!empty($input[$media_fields])) {
> + $media_entity = Media::load($mid);
> + foreach ($input[$media_fields] as $field_name => $field_value) {
> + $media_entity->set($field_name, $field_value);
> + }
> + $media_entity->save();
> + }
> + else {
> + // Warn process() that this is a preexisting value.
> + $return['media_already_saved'] = TRUE;
1013a466
> + $return['mids'] = [$input['mids']];
1016c469
< + return FALSE;
---
> + return $return;
1022,1038c475,478
< + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
< + $field_definition = $items->getFieldDefinition();
< + $field_storage_definition = $field_definition->getFieldStorageDefinition();
< +
< + $element = [
< + '#type' => 'details',
< + '#title' => $field_definition->getLabel(),
< + '#attributes' => [
< + 'title' => $field_definition->getLabel(),
< + ],
< + '#attached' => [
< + 'library' => [
< + 'core/drupal.dialog.ajax',
< + ],
< + ],
< + '#open' => $this->getSetting('open'),
< + ];
---
> + public static function process($element, FormStateInterface $form_state, $form) {
> + $element = parent::process($element, $form_state, $form);
> + $mid = FALSE;
> + $show_form = FALSE;
1040c480,491
< + $cardinality = $field_storage_definition->getCardinality();
---
> + if (!empty($element['#value']['target_id'])) {
> + // This is an existing entity ref and we're loading the edit page the
> + // first time. Just show the icon and name.
> + $mid = $element['#value']['target_id'];
> + }
> + else if (!empty($element['#value']['mids']) && count($element['#value']['mids']) === 1) {
> + // If $element['mids']['#value'] has multiple values, that means this is
> + // the first pass after someone uploaded several files; ignore. If it has
> + // only ONE, then this media entity was just created and we want to force
> + // the user to fill out all the new entity's required fields. I.e., show
> + // the form, as well as the standard icon and name.
> + $mid = $element['#value']['mids'][0];
1042,1050c493,494
< + $upload_validators = [[]];
< + $allowed_types = MediaType::loadMultiple($field_definition->getSetting('handler_settings')['target_bundles']);
< + foreach ($allowed_types as $media_type) {
< + /** @var \Drupal\media\Entity\MediaType $media_type */
< + $source = $media_type->getSource();
< + if ($source->getPluginId() === 'file') {
< + $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($media_type));
< + $file_item = new FileItem($source_data_definition);
< + $upload_validators[] = $file_item->getUploadValidators();
---
> + if (empty($element['#value']['media_already_saved'])) {
> + $show_form = TRUE;
1053d496
< + $upload_validators = array_merge(...$upload_validators);
1055,1073c498,520
< + $file_upload_help = [
< + '#theme' => 'file_upload_help',
< + '#upload_validators' => $upload_validators,
< + '#cardinality' => $cardinality,
< + ];
< +
< + $element['file'] = [
< + '#type' => 'managed_file',
< + '#multiple' => $cardinality !== 1 ? TRUE : FALSE,
< + // Hide the file element if the field is at capacity.
< + '#access' => $items->isEmpty() || ($cardinality > 0 && count($items) < $cardinality),
< + '#upload_validators' => $upload_validators + [
< + 'media_validate_file_upload' => [],
< + ],
< + '#description' => $this->renderer->renderPlain($file_upload_help),
< + '#entity_type' => $field_definition->getTargetEntityTypeId(),
< + '#bundle' => $field_definition->getTargetBundle(),
< + '#field_name' => $field_definition->getName(),
< + ];
---
> + if ($mid) {
> + // First, get rid of the file link that FileManaged has created, and
> + // replace with a media-entity link.
> + $fid = $element['#value']['fids'][0];
> + unset($element['file_' . $fid]);
> + // Under some circumstances this button said "remove selected," but since
> + // we just removed the checkbox this button should instead always just say
> + // "remove."
> + $element['remove_button']['#value'] = t('Remove');
> +
> + // Now show the media icon and name.
> + $form_element_name = 'media_' . $mid;
> + $form_element_parents = array_merge($element['#parents'], [$form_element_name]);
> + $media_entity = Media::load($mid);
> +
> + $element[$form_element_name]['media_icon'] = [
> + '#theme' => 'image_style',
> + '#style_name' => 'thumbnail',
> + '#uri' => $media_entity->getSource()->getMetadata($media_entity, 'thumbnail_uri'),
> + '#weight' => -20,
> + ];
> + $link_ra = Link::createFromRoute($media_entity->getName(), 'entity.media.canonical', ['media' => $mid])->toRenderable();
> + $element[$form_element_name]['media_name'] = $link_ra + ['weight' => -10];
1075,1078c522,532
< + // Attach a AJAX callback using '#process' information extracted from the
< + // 'managed_file' element type.
< + $element['file']['#process'] = $this->managedFileInfo['#process'];
< + $element['file']['#process'][] = [static::class, 'processFileElement'];
---
> + if ($show_form) {
> + // Create and add the media entity form.
> + $form_mode = 'add_inline';
> + $media_form = [
> + '#entity' => $media_entity,
> + '#form_mode' => $form_mode,
> + '#parents' => $form_element_parents,
> + ];
> + $inline_form_handler = \Drupal::entityTypeManager()
> + ->getHandler('media', $form_mode);
> + $media_form = $inline_form_handler->entityForm($media_form, $form_state);
1080c534,536
< + $element['media_items']['#type'] = 'hidden';
---
> + $element[$form_element_name] += $media_form;
> + }
> + }
1086c542
< + * Adds this widget to the AJAX callback of the upload_button.
---
> + * Form submission handler for upload/remove button of formElement().
1088,1089c544,545
< + * @param array $element
< + * A render array containing the upload button.
---
> + * Overrides FileWidget::submit(), performing the exact same functionality,
> + * only maintaining awareness of mids as well as fids.
1091,1092c547,548
< + * @return array
< + * An updated render array.
---
> + * @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit()
> + * @see file_managed_file_submit()
1094,1104c550,569
< + public static function processFileElement(array $element) {
< + $element['upload_button']['#ajax']['callback'][0] = static::class;
< + return $element;
< + }
< +
< + /**
< + * {@inheritdoc}
< + */
< + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
< + if (empty($values['media_items'])) {
< + return [];
---
> + public static function submit($form, FormStateInterface $form_state) {
> + // During the form rebuild, formElement() will create field item widget
> + // elements using re-indexed deltas, so clear out FormState::$input to
> + // avoid a mismatch between old and new deltas. The rebuilt elements will
> + // have #default_value set appropriately for the current state of the field,
> + // so nothing is lost in doing this.
> + $button = $form_state->getTriggeringElement();
> + $parents = array_slice($button['#parents'], 0, -2);
> + NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
> +
> + // Go one level up in the form, to the widgets container.
> + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
> + $field_name = $element['#field_name'];
> + $parents = $element['#field_parents'];
> +
> + $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2));
> + foreach ($submitted_values as $delta => $submitted_value) {
> + if (empty($submitted_value['fids'])) {
> + unset($submitted_values[$delta]);
> + }
1107,1143c572,583
< + return array_map(
< + function ($media_id) {
< + return [
< + 'target_id' => (int) $media_id,
< + ];
< + },
< + explode(',', $values['media_items'])
< + );
< + }
< +
< + /**
< + * Ajax callback for media_file upload forms.
< + *
< + * Augments the ManagedFile::uploadAjaxCallback() with a OpenDialogUrlCommand
< + * and its associated data.
< + *
< + * @param array $form
< + * The form render array.
< + * @param \Drupal\Core\Form\FormStateInterface $form_state
< + * The form state.
< + * @param \Symfony\Component\HttpFoundation\Request $request
< + * The request.
< + *
< + * @return \Drupal\Core\Ajax\AjaxResponse
< + * An updated AjaxResponse.
< + */
< + public static function uploadAjaxCallback(array &$form, FormStateInterface $form_state, Request $request) {
< + $response = ManagedFile::uploadAjaxCallback($form, $form_state, $request);
< +
< + $trigger = $form_state->getTriggeringElement();
< + $complete_form = $form_state->getCompleteForm();
< + $widget = NestedArray::getValue($complete_form, array_slice($trigger['#array_parents'], 0, -2));
< +
< + $files = $widget['file']['#value']['fids'];
< +
< + if (!$files) {
< + return $response;
---
> + // If there are more files uploaded via the same widget, we have to separate
> + // them, as we display each file in its own widget.
> + $new_values = [];
> + foreach ($submitted_values as $submitted_value) {
> + if (is_array($submitted_value['fids']) && is_array($submitted_value['mids'])) {
> + foreach ($submitted_value['fids'] as $delta => $fid) {
> + $new_value = $submitted_value;
> + $new_value['fids'] = [$fid];
> + $new_value['mids'] = [$submitted_value['mids'][$delta]];
> + $new_values[] = $new_value;
> + }
> + }
1146,1159c586,590
< + $parameters = [
< + 'file' => array_shift($files),
< + 'selector' => $widget['media_items']['#attributes']['data-drupal-selector'],
< + ];
< + $parameters['media_type'] = \Drupal::service('media.type_guesser.file')->guessForFile($parameters['file']);
< + $options = [
< + 'query' => [
< + 'files' => $files,
< + 'entity_type' => $widget['file']['#entity_type'],
< + 'bundle' => $widget['file']['#bundle'],
< + 'field_name' => $widget['file']['#field_name'],
< + ],
< + ];
< + $url = Url::fromRoute('entity.media.add_file', $parameters, $options);
---
> + // Re-index deltas after removing empty items.
> + $submitted_values = array_values($new_values);
> +
> + // Update form_state values.
> + NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values);
1161c592,595
< + return $response->addCommand(new OpenDialogUrlCommand($url));
---
> + // Update items.
> + $field_state = static::getWidgetState($parents, $field_name, $form_state);
> + $field_state['items'] = $submitted_values;
> + static::setWidgetState($parents, $field_name, $form_state, $field_state);
1165c599,603
< diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaFileWidgetTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaFileWidgetTest.php
---
> diff --git a/core/modules/media/tests/fixtures/example_1.pdf b/core/modules/media/tests/fixtures/example_1.pdf
> new file mode 100644
> index 0000000000..1bdb258c86
> Binary files /dev/null and b/core/modules/media/tests/fixtures/example_1.pdf differ
> diff --git a/core/modules/media/tests/fixtures/example_2.pdf b/core/modules/media/tests/fixtures/example_2.pdf
1167c605,609
< index 0000000000..04c8a9a80a
---
> index 0000000000..1bdb258c86
> Binary files /dev/null and b/core/modules/media/tests/fixtures/example_2.pdf differ
> diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php
> new file mode 100644
> index 0000000000..f35addbaa4
1169,1170c611,612
< +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaFileWidgetTest.php
< @@ -0,0 +1,137 @@
---
> +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php
> @@ -0,0 +1,99 @@
1177c619
< +use Drupal\media\Entity\Media;
---
> +use Drupal\Tests\field\Unit\FieldStorageConfigAccessControlHandlerTest;
1180,1181d621
< + * Tests the media_file field widget.
< + *
1184c624
< +class MediaFileWidgetTest extends MediaJavascriptTestBase {
---
> +class MediaInlineFileWidgetTest extends MediaJavascriptTestBase {
1187,1189c627
< + * A node content type.
< + *
< + * @var \Drupal\node\NodeTypeInterface
---
> + * {@inheritdoc}
1191c629,633
< + protected $nodeType;
---
> + public static $modules = ['node'];
> +
> + protected $strictConfigSchema = FALSE;
> +
> + protected $node_type;
1198,1204c640,645
< +
< + $this->nodeType = $this->drupalCreateContentType();
< +
< + $field_storage = FieldStorageConfig::create([
< + 'field_name' => 'field_media',
< + 'entity_type' => 'node',
< + 'type' => 'entity_reference',
---
> + $media_type = $this->createMediaType(['bundle' => 'file'], 'file');
> + $media_required_field_storage = FieldStorageConfig::create([
> + 'type' => 'string',
> + 'cardinality' => -1,
> + 'entity_type' => 'media',
> + 'field_name' => 'field_media_required',
1206c647
< + 'target_type' => 'media',
---
> + 'max_length' => 255,
1208d648
< + 'cardinality' => 3,
1210,1244c650,654
< + $field_storage->save();
< +
< + FieldConfig::create([
< + 'label' => 'Media',
< + 'bundle' => $this->nodeType->id(),
< + 'field_storage' => $field_storage,
< + 'settings' => [
< + 'handler' => 'default:media',
< + 'handler_settings' => [
< + 'target_bundles' => NULL,
< + ],
< + ],
< + ])->save();
< +
< + entity_get_form_display('node', $this->nodeType->id(), 'default')
< + ->setComponent('field_media', [
< + 'type' => 'media_file',
< + 'region' => 'content',
< + ])
< + ->save();
< + }
< +
< + /**
< + * Tests various feature of the MediaFileWidget through the UI.
< + *
< + * By attaching it to a node and uploading a file and inspected various
< + * stages.
< + */
< + public function testWidget() {
< + $assert = $this->assertSession();
< +
< + $account = $this->drupalCreateUser([
< + 'create media',
< + 'create ' . $this->nodeType->id() . ' content',
< + 'edit own ' . $this->nodeType->id() . ' content',
---
> + $media_required_field_storage->save();
> + $media_required_field = FieldConfig::create([
> + 'field_storage' => $media_required_field_storage,
> + 'bundle' => $media_type->id(),
> + 'required' => TRUE,
1246c656,659
< + $this->drupalLogin($account);
---
> + $media_required_field->save();
> +// $media_form_display = entity_get_form_display('media', $media_type->id(), 'add_inline');
> +// $media_form_display->setComponent('field_media_required', ['type' => 'string_textfield']);
> +// $media_form_display->save();
1248,1332c661,662
< + $this->drupalGet('/node/add/' . $this->nodeType->id());
< + $wrapper = $assert->elementExists('css', 'details[open][title = "Media"]');
< + // Assert that multiple-cardinality media reference fields produce only
< + // a single multiple-value file upload element.
< + /** @var \Behat\Mink\Element\NodeElement[] $file_elements */
< + $file_elements = $wrapper->findAll('css', 'input[type = "file"]');
< + $this->assertCount(1, $file_elements);
< + $file_element = reset($file_elements);
< + $this->assertSame('files[field_media_file][]', $file_element->getAttribute('name'));
< + $this->assertTrue($file_element->hasAttribute('multiple'));
< +
< + // Upload a file and assert that the media form shows up as expected.
< + $this->getSession()->getPage()->attachFileToField($file_element->getAttribute('name'), __DIR__ . '/../../fixtures/example_1.jpeg');
< + $this->waitUntilVisible('#drupal-modal', 30000);
< + $form_element = $assert->elementExists('css', '#drupal-modal form');
< + $assert->fieldValueEquals('Media type', 'image', $form_element);
< + $assert->fieldExists('Name', $form_element);
< + $fid = $assert->hiddenFieldExists('inner_form[field_media_image][0][fids]', $form_element)->getValue();
< + $this->assertNotEmpty($fid);
< + $this->assertTrue(is_numeric($fid));
< + $assert->buttonExists('Save', $form_element);
< +
< + $buttons = $assert->elementExists('css', '#drupal-modal + .ui-dialog-buttonpane');
< + $save_button = $assert->buttonExists('Save', $buttons);
< + $save_button->press();
< +
< + $errors_selector = '#drupal-modal form .messages--error';
< + $this->waitUntilVisible($errors_selector, 30000);
< + $assert->elementExists('named', ['content', 'Name field is required.'], $form_element);
< + $assert->elementExists('named', ['content', 'Alternative text field is required.'], $form_element);
< +
< + $this->getSession()->executeScript('document.querySelector("' . $errors_selector . '").remove()');
< + $form_element->fillField('Name', 'Pastafazoul');
< + $save_button->press();
< + $this->waitUntilVisible($errors_selector, 30000);
< + $assert->elementNotExists('named', ['content', 'Name field is required.'], $form_element);
< + $assert->elementExists('named', ['content', 'Alternative text field is required.'], $form_element);
< +
< + // Assert that the file element is hidden if the field is at capacity.
< + $node = $this->drupalCreateNode([
< + 'type' => $this->nodeType->id(),
< + 'uid' => $account->id(),
< + ]);
< + $media_type = $this->createMediaType()->id();
< + for ($i = 0; $i < 3; $i++) {
< + $media = Media::create([
< + 'bundle' => $media_type,
< + 'field_media_test' => $this->randomString(),
< + ]);
< + $media->save();
< + $node->field_media[$i] = $media;
< + }
< + $node->save();
< +
< + $this->drupalGet('/node/' . $node->id() . '/edit');
< + $wrapper = $assert->elementExists('css', 'details[open][title = "Media"]');
< + $assert->elementNotExists('css', 'input[type = "file"]', $wrapper);
< + }
< +
< +}
< diff --git a/core/modules/media/tests/src/Kernel/MediaFileWidgetTest.php b/core/modules/media/tests/src/Kernel/MediaFileWidgetTest.php
< new file mode 100644
< index 0000000000..559b3628b1
< --- /dev/null
< +++ b/core/modules/media/tests/src/Kernel/MediaFileWidgetTest.php
< @@ -0,0 +1,54 @@
< + + $node_type = $this->drupalCreateContentType();
> + $this->node_type = $node_type->id();
1334,1335d663
< + 'entity_type' => 'user',
< + 'field_name' => 'field_media',
1336a665,666
> + 'entity_type' => 'node',
> + 'field_name' => 'field_media_reference',
1342d671
< +
1344d672
< + 'bundle' => 'user',
1345a674
> + 'bundle' => $node_type->id(),
1347c676
< + 'handler' => 'default:media',
---
> + 'file_extensions' => 'pdf',
1349c678,680
< + 'target_bundles' => NULL,
---
> + 'target_bundles' => [
> + $media_type->id(),
> + ],
1353a685,688
> + $form_display = entity_get_form_display('node', $node_type->id(), 'default');
> + $form_display->setComponent('field_media_reference', ['type' => 'media_file']);
> + $form_display->save();
> + }
1355,1364c690,708
< + $this->assertTrue(MediaFileWidget::isApplicable($field));
< +
< + // Assert that the widget cannot be used on a string field, or an entity
< + // reference field that does not reference media items.
< + $fields = $this->container
< + ->get('entity_field.manager')
< + ->getBaseFieldDefinitions('user');
< +
< + $this->assertFalse(MediaFileWidget::isApplicable($fields['name']));
< + $this->assertFalse(MediaFileWidget::isApplicable($fields['roles']));
---
> + /**
> + * {@inheritdoc}
> + */
> + public function testInlineFileWidget() {
> + $this->drupalGet('/node/add/' . $this->node_type);
> + $assert = $this->assertSession();
> + $element = $assert->elementExists('css', 'input[type="file"]');
> + $filepath = \Drupal::root() . '/' . drupal_get_path('module', 'media') . '/tests/fixtures/example_1.pdf';
> + $this->getSession()->getPage()->attachFileToField($element->getAttribute('id'), $filepath);
> + // Does the new media item's "name" field exist?
> + $this->waitUntilVisible('input[value="example_1.pdf"]', 10000);
> + // Does the other required field for this media type exist?
> + $assert->elementExists('css', 'input[name*="field_media_required"]'); // fails
> + // Can I upload a second file and get a second media item's "name" field?
> + $element = $assert->elementExists('css', 'input[type="file"]'); // fails
> + $filepath = \Drupal::root() . '/' . drupal_get_path('module', 'media') . '/tests/fixtures/example_2.pdf';
> + $this->getSession()->getPage()->attachFileToField($element->getAttribute('id'), $filepath);
> + $this->waitUntilVisible('input[value="example_2.pdf"]', 10000);
> + // Next: save the node and then follow a link to a working media entity.