Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
|
@ -0,0 +1,626 @@
|
|||
<?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\FieldStorageDefinitionInterface;
|
||||
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 = (int) $this->getRequest()->query->get('media_library_remaining');
|
||||
if ($remaining || $remaining === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
|
||||
$form['upload']['#multiple'] = $remaining > 1 || $remaining === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED;
|
||||
$form['upload']['#cardinality'] = $form['upload_help']['#cardinality'] = $remaining;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form['media'] = [
|
||||
'#type' => 'container',
|
||||
];
|
||||
foreach ($this->media as $i => $media) {
|
||||
$source_field = $media->getSource()
|
||||
->getSourceFieldDefinition($media->bundle->entity)
|
||||
->getName();
|
||||
|
||||
$element = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'media-library-upload__media',
|
||||
],
|
||||
],
|
||||
'preview' => [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'media-library-upload__media-preview',
|
||||
],
|
||||
],
|
||||
],
|
||||
'fields' => [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'media-library-upload__media-fields',
|
||||
],
|
||||
],
|
||||
// Parents is set here as it is used in the form display.
|
||||
'#parents' => ['media', $i, 'fields'],
|
||||
],
|
||||
];
|
||||
// @todo Make this configurable in https://www.drupal.org/node/2988223
|
||||
if ($this->mediumStyleExists && $thumbnail_uri = $media->getSource()->getMetadata($media, 'thumbnail_uri')) {
|
||||
$element['preview']['thumbnail'] = [
|
||||
'#theme' => 'image_style',
|
||||
'#style_name' => 'medium',
|
||||
'#uri' => $thumbnail_uri,
|
||||
];
|
||||
}
|
||||
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'] && $element['#cardinality'] !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
|
||||
$form_state->setError($element, $this->t('A maximum of @count files can be uploaded.', [
|
||||
'@count' => $element['#cardinality'],
|
||||
]));
|
||||
$form_state->setValue('upload', []);
|
||||
$element['#value'] = [];
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the upload button, inside the managed_file element.
|
||||
*
|
||||
* @param array $form
|
||||
* The form render array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public function uploadButtonSubmit(array $form, FormStateInterface $form_state) {
|
||||
$fids = $form_state->getValue('upload', []);
|
||||
$files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids);
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
foreach ($files as $file) {
|
||||
$types = $this->filterTypesThatAcceptFile($file, $this->getTypes());
|
||||
if (!empty($types)) {
|
||||
if (count($types) === 1) {
|
||||
$this->media[] = $this->createMediaEntity($file, reset($types));
|
||||
}
|
||||
else {
|
||||
$this->files[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new, unsaved media entity.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* A file for the media source field.
|
||||
* @param \Drupal\media\MediaTypeInterface $type
|
||||
* A media type.
|
||||
*
|
||||
* @return \Drupal\media\MediaInterface
|
||||
* An unsaved media entity.
|
||||
*
|
||||
* @throws \Exception
|
||||
* If a file operation failed when moving the upload.
|
||||
*/
|
||||
protected function createMediaEntity(FileInterface $file, MediaTypeInterface $type) {
|
||||
$media = $this->entityTypeManager->getStorage('media')->create([
|
||||
'bundle' => $type->id(),
|
||||
'name' => $file->getFilename(),
|
||||
]);
|
||||
$source_field = $type->getSource()->getSourceFieldDefinition($type)->getName();
|
||||
$location = $this->getUploadLocationForType($media->bundle->entity);
|
||||
if (!file_prepare_directory($location, FILE_CREATE_DIRECTORY)) {
|
||||
throw new \Exception("The destination directory '$location' is not writable");
|
||||
}
|
||||
$file = file_move($file, $location);
|
||||
if (!$file) {
|
||||
throw new \Exception("Unable to move file to '$location'");
|
||||
}
|
||||
$media->set($source_field, $file->id());
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback for refreshing the entire form.
|
||||
*
|
||||
* @param array $form
|
||||
* The form render array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return array
|
||||
* The form render array.
|
||||
*/
|
||||
public function updateFormCallback(array &$form, FormStateInterface $form_state) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback to check that the user can create file based media.
|
||||
*
|
||||
* @param array $allowed_types
|
||||
* (optional) The contextually allowed types.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*
|
||||
* @todo Remove $allowed_types param in https://www.drupal.org/node/2956747
|
||||
*/
|
||||
public function access(array $allowed_types = NULL) {
|
||||
return AccessResultAllowed::allowedIf(count($this->getTypes($allowed_types)))->mergeCacheMaxAge(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns media types which use files that the current user can create.
|
||||
*
|
||||
* @param array $allowed_types
|
||||
* (optional) The contextually allowed types.
|
||||
*
|
||||
* @todo Move in https://www.drupal.org/node/2987924
|
||||
*
|
||||
* @return \Drupal\media\MediaTypeInterface[]
|
||||
* A list of media types that are valid for this form.
|
||||
*/
|
||||
protected function getTypes(array $allowed_types = NULL) {
|
||||
// Cache results if possible.
|
||||
if (!isset($this->types)) {
|
||||
$media_type_storage = $this->entityTypeManager->getStorage('media_type');
|
||||
if (!$allowed_types) {
|
||||
$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());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,539 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\media_library\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\SortArray;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\media\Entity\Media;
|
||||
use Drupal\media_library\Form\MediaLibraryUploadForm;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'media_library_widget' widget.
|
||||
*
|
||||
* @FieldWidget(
|
||||
* id = "media_library_widget",
|
||||
* label = @Translation("Media library"),
|
||||
* description = @Translation("Allows you to select items from the media library."),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* },
|
||||
* multiple_values = TRUE,
|
||||
* )
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* Entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the add button should be shown.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $addAccess = FALSE;
|
||||
|
||||
/**
|
||||
* Constructs a MediaLibraryWidget widget.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @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 \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* Entity type manager service.
|
||||
* @param bool $add_access
|
||||
* Indicates whether or not the add button should be shown.
|
||||
*/
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, $add_access) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->addAccess = $add_access;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
$settings = $configuration['field_definition']->getSettings()['handler_settings'];
|
||||
$target_bundles = isset($settings['target_bundles']) ? $settings['target_bundles'] : NULL;
|
||||
return new static(
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$configuration['field_definition'],
|
||||
$configuration['settings'],
|
||||
$configuration['third_party_settings'],
|
||||
$container->get('entity_type.manager'),
|
||||
// @todo Use URL access in https://www.drupal.org/node/2956747
|
||||
MediaLibraryUploadForm::create($container)->access($target_bundles)->isAllowed()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return $field_definition->getSetting('target_type') === 'media';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
|
||||
// Load the items for form rebuilds from the field state.
|
||||
$field_state = static::getWidgetState($form['#parents'], $this->fieldDefinition->getName(), $form_state);
|
||||
if (isset($field_state['items'])) {
|
||||
usort($field_state['items'], [SortArray::class, 'sortByWeightElement']);
|
||||
$items->setValue($field_state['items']);
|
||||
}
|
||||
|
||||
return parent::form($items, $form, $form_state, $get_delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */
|
||||
$referenced_entities = $items->referencedEntities();
|
||||
$view_builder = $this->entityTypeManager->getViewBuilder('media');
|
||||
$field_name = $this->fieldDefinition->getName();
|
||||
$parents = $form['#parents'];
|
||||
$id_suffix = '-' . implode('-', $parents);
|
||||
$wrapper_id = $field_name . '-media-library-wrapper' . $id_suffix;
|
||||
$limit_validation_errors = [array_merge($parents, [$field_name])];
|
||||
|
||||
$settings = $this->getFieldSetting('handler_settings');
|
||||
$element += [
|
||||
'#type' => 'fieldset',
|
||||
'#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
|
||||
'#target_bundles' => isset($settings['target_bundles']) ? $settings['target_bundles'] : FALSE,
|
||||
'#attributes' => [
|
||||
'id' => $wrapper_id,
|
||||
'class' => ['media-library-widget'],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => ['media_library/widget'],
|
||||
],
|
||||
];
|
||||
|
||||
$element['selection'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'js-media-library-selection',
|
||||
'media-library-selection',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (empty($referenced_entities)) {
|
||||
$element['empty_selection'] = [
|
||||
'#markup' => $this->t('<p>No media items are selected.</p>'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$element['weight_toggle'] = [
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'button',
|
||||
'#value' => $this->t('Show media item weights'),
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'link',
|
||||
'media-library-widget__toggle-weight',
|
||||
'js-media-library-widget-toggle-weight',
|
||||
],
|
||||
'title' => $this->t('Re-order media by numerical weight instead of dragging'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($referenced_entities as $delta => $media_item) {
|
||||
$element['selection'][$delta] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'media-library-item',
|
||||
'js-media-library-item',
|
||||
],
|
||||
],
|
||||
'preview' => [
|
||||
'#type' => 'container',
|
||||
// @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
|
||||
'rendered_entity' => $view_builder->view($media_item, 'media_library'),
|
||||
'remove_button' => [
|
||||
'#type' => 'submit',
|
||||
'#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
|
||||
'#value' => $this->t('Remove'),
|
||||
'#attributes' => [
|
||||
'class' => ['media-library-item__remove'],
|
||||
],
|
||||
'#ajax' => [
|
||||
'callback' => [static::class, 'updateWidget'],
|
||||
'wrapper' => $wrapper_id,
|
||||
],
|
||||
'#submit' => [[static::class, 'removeItem']],
|
||||
// Prevent errors in other widgets from preventing removal.
|
||||
'#limit_validation_errors' => $limit_validation_errors,
|
||||
],
|
||||
],
|
||||
'target_id' => [
|
||||
'#type' => 'hidden',
|
||||
'#value' => $media_item->id(),
|
||||
],
|
||||
// This hidden value can be toggled visible for accessibility.
|
||||
'weight' => [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Weight'),
|
||||
'#default_value' => $delta,
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'js-media-library-item-weight',
|
||||
'media-library-item__weight',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$remaining = $element['#cardinality'] - count($referenced_entities);
|
||||
|
||||
// Inform the user of how many items are remaining.
|
||||
if (!$cardinality_unlimited) {
|
||||
if ($remaining) {
|
||||
$cardinality_message = $this->formatPlural($remaining, 'One media item remaining.', '@count media items remaining.');
|
||||
}
|
||||
else {
|
||||
$cardinality_message = $this->t('The maximum number of media items have been selected.');
|
||||
}
|
||||
$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',
|
||||
'#title' => $this->t('Browse media'),
|
||||
'#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' => $query,
|
||||
]),
|
||||
'#attributes' => [
|
||||
'class' => ['button', 'use-ajax', 'media-library-open-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' => $cardinality_unlimited || $remaining > 0,
|
||||
];
|
||||
|
||||
$element['media_library_add_button'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Add media'),
|
||||
'#name' => $field_name . '-media-library-add-button' . $id_suffix,
|
||||
'#url' => Url::fromRoute('media_library.upload', [], [
|
||||
'query' => $query,
|
||||
]),
|
||||
'#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' => $this->addAccess && ($cardinality_unlimited || $remaining > 0),
|
||||
];
|
||||
|
||||
// This hidden field and button are used to add new items to the widget.
|
||||
$element['media_library_selection'] = [
|
||||
'#type' => 'hidden',
|
||||
'#attributes' => [
|
||||
// This is used to pass the selection from the modal to the widget.
|
||||
'data-media-library-widget-value' => $field_name . $id_suffix,
|
||||
],
|
||||
];
|
||||
|
||||
// When a selection is made this hidden button is pressed to add new media
|
||||
// items based on the "media_library_selection" value.
|
||||
$element['media_library_update_widget'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Update widget'),
|
||||
'#name' => $field_name . '-media-library-update' . $id_suffix,
|
||||
'#ajax' => [
|
||||
'callback' => [static::class, 'updateWidget'],
|
||||
'wrapper' => $wrapper_id,
|
||||
],
|
||||
'#attributes' => [
|
||||
'data-media-library-widget-update' => $field_name . $id_suffix,
|
||||
'class' => ['js-hide'],
|
||||
],
|
||||
'#validate' => [[static::class, 'validateItems']],
|
||||
'#submit' => [[static::class, 'updateItems']],
|
||||
// Prevent errors in other widgets from preventing updates.
|
||||
'#limit_validation_errors' => $limit_validation_errors,
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) {
|
||||
return isset($element['target_id']) ? $element['target_id'] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
|
||||
if (isset($values['selection'])) {
|
||||
usort($values['selection'], [SortArray::class, 'sortByWeightElement']);
|
||||
return $values['selection'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback to update the widget when the selection changes.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return array
|
||||
* An array representing the updated widget.
|
||||
*/
|
||||
public static function updateWidget(array $form, FormStateInterface $form_state) {
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
// This callback is either invoked from the remove button or the update
|
||||
// button, which have different nesting levels.
|
||||
$length = end($triggering_element['#parents']) === 'remove_button' ? -4 : -1;
|
||||
if (count($triggering_element['#array_parents']) < abs($length)) {
|
||||
throw new \LogicException('The element that triggered the widget update was at an unexpected depth. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
|
||||
}
|
||||
$parents = array_slice($triggering_element['#array_parents'], 0, $length);
|
||||
$element = NestedArray::getValue($form, $parents);
|
||||
// Always clear the textfield selection to prevent duplicate additions.
|
||||
$element['media_library_selection']['#value'] = '';
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for remove buttons.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public static function removeItem(array $form, FormStateInterface $form_state) {
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
|
||||
// Get the parents required to find the top-level widget element.
|
||||
if (count($triggering_element['#array_parents']) < 4) {
|
||||
throw new \LogicException('Expected the remove button to be more than four levels deep in the form. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
|
||||
}
|
||||
$parents = array_slice($triggering_element['#array_parents'], 0, -4);
|
||||
// Get the delta of the item being removed.
|
||||
$delta = array_slice($triggering_element['#array_parents'], -3, 1)[0];
|
||||
$element = NestedArray::getValue($form, $parents);
|
||||
|
||||
// Get the field state.
|
||||
$path = $element['#parents'];
|
||||
$values = NestedArray::getValue($form_state->getValues(), $path);
|
||||
$field_state = static::getFieldState($element, $form_state);
|
||||
|
||||
// Remove the item from the field state and update it.
|
||||
if (isset($values['selection'][$delta])) {
|
||||
array_splice($values['selection'], $delta, 1);
|
||||
$field_state['items'] = $values['selection'];
|
||||
static::setFieldState($element, $form_state, $field_state);
|
||||
}
|
||||
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that newly selected items can be added to the widget.
|
||||
*
|
||||
* Making an invalid selection from the view should not be possible, but we
|
||||
* still validate in case other selection methods (ex: upload) are valid.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public static function validateItems(array $form, FormStateInterface $form_state) {
|
||||
$button = $form_state->getTriggeringElement();
|
||||
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
|
||||
|
||||
$field_state = static::getFieldState($element, $form_state);
|
||||
$media = static::getNewMediaItems($element, $form_state);
|
||||
if (empty($media)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if more items were selected than we allow.
|
||||
$cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$selection = count($field_state['items']) + count($media);
|
||||
if (!$cardinality_unlimited && ($selection > $element['#cardinality'])) {
|
||||
$form_state->setError($element, \Drupal::translation()->formatPlural($element['#cardinality'], 'Only one item can be selected.', 'Only @count items can be selected.'));
|
||||
}
|
||||
|
||||
// Validate that each selected media is of an allowed bundle.
|
||||
$all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
|
||||
$bundle_labels = array_map(function ($bundle) use ($all_bundles) {
|
||||
return $all_bundles[$bundle]['label'];
|
||||
}, $element['#target_bundles']);
|
||||
foreach ($media as $media_item) {
|
||||
if ($element['#target_bundles'] && !in_array($media_item->bundle(), $element['#target_bundles'], TRUE)) {
|
||||
$form_state->setError($element, t('The media item "@label" is not of an accepted type. Allowed types: @types', [
|
||||
'@label' => $media_item->label(),
|
||||
'@types' => implode(', ', $bundle_labels),
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the field state and flags the form for rebuild.
|
||||
*
|
||||
* @param array $form
|
||||
* The form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public static function updateItems(array $form, FormStateInterface $form_state) {
|
||||
$button = $form_state->getTriggeringElement();
|
||||
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
|
||||
|
||||
$field_state = static::getFieldState($element, $form_state);
|
||||
|
||||
$media = static::getNewMediaItems($element, $form_state);
|
||||
if (!empty($media)) {
|
||||
$weight = count($field_state['items']);
|
||||
foreach ($media as $media_item) {
|
||||
// Any ID can be passed to the widget, so we have to check access.
|
||||
if ($media_item->access('view')) {
|
||||
$field_state['items'][] = [
|
||||
'target_id' => $media_item->id(),
|
||||
'weight' => $weight++,
|
||||
];
|
||||
}
|
||||
}
|
||||
static::setFieldState($element, $form_state, $field_state);
|
||||
}
|
||||
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets newly selected media items.
|
||||
*
|
||||
* @param array $element
|
||||
* The wrapping element for this widget.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\media\MediaInterface[]
|
||||
* An array of selected media items.
|
||||
*/
|
||||
protected static function getNewMediaItems(array $element, FormStateInterface $form_state) {
|
||||
// Get the new media IDs passed to our hidden button.
|
||||
$values = $form_state->getValues();
|
||||
$path = $element['#parents'];
|
||||
$value = NestedArray::getValue($values, $path);
|
||||
|
||||
if (!empty($value['media_library_selection'])) {
|
||||
$ids = explode(',', $value['media_library_selection']);
|
||||
$ids = array_filter($ids, 'is_numeric');
|
||||
if (!empty($ids)) {
|
||||
/** @var \Drupal\media\MediaInterface[] $media */
|
||||
return Media::loadMultiple($ids);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field state for the widget.
|
||||
*
|
||||
* @param array $element
|
||||
* The wrapping element for this widget.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return array[]
|
||||
* An array of arrays with the following key/value pairs:
|
||||
* - items: (array) An array of selections.
|
||||
* - target_id: (int) A media entity ID.
|
||||
* - weight: (int) A weight for the selection.
|
||||
*/
|
||||
protected static function getFieldState(array $element, FormStateInterface $form_state) {
|
||||
// Default to using the current selection if the form is new.
|
||||
$path = $element['#parents'];
|
||||
$values = NestedArray::getValue($form_state->getValues(), $path);
|
||||
$selection = isset($values['selection']) ? $values['selection'] : [];
|
||||
|
||||
$widget_state = static::getWidgetState($element['#field_parents'], $element['#field_name'], $form_state);
|
||||
$widget_state['items'] = isset($widget_state['items']) ? $widget_state['items'] : $selection;
|
||||
return $widget_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the field state for the widget.
|
||||
*
|
||||
* @param array $element
|
||||
* The wrapping element for this widget.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param array[] $field_state
|
||||
* An array of arrays with the following key/value pairs:
|
||||
* - items: (array) An array of selections.
|
||||
* - target_id: (int) A media entity ID.
|
||||
* - weight: (int) A weight for the selection.
|
||||
*/
|
||||
protected static function setFieldState(array $element, FormStateInterface $form_state, array $field_state) {
|
||||
static::setWidgetState($element['#field_parents'], $element['#field_name'], $form_state, $field_state);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\media_library\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\CloseDialogCommand;
|
||||
use Drupal\Core\Ajax\InvokeCommand;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
|
||||
use Drupal\views\Render\ViewsRenderPipelineMarkup;
|
||||
use Drupal\views\ResultRow;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
/**
|
||||
* Defines a field that outputs a checkbox and form for selecting media.
|
||||
*
|
||||
* @ViewsField("media_library_select_form")
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MediaLibrarySelectForm extends FieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue(ResultRow $row, $field = NULL) {
|
||||
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(ResultRow $values) {
|
||||
return ViewsRenderPipelineMarkup::create($this->getValue($values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor for the media library select form.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function viewsForm(array &$form, FormStateInterface $form_state) {
|
||||
// Only add the bulk form options and buttons if there are results.
|
||||
if (empty($this->view->result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render checkboxes for all rows.
|
||||
$form[$this->options['id']]['#tree'] = TRUE;
|
||||
foreach ($this->view->result as $row_index => $row) {
|
||||
$entity = $this->getEntity($row);
|
||||
$form[$this->options['id']][$row_index] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Select @label', [
|
||||
'@label' => $entity->label(),
|
||||
]),
|
||||
'#title_display' => 'invisible',
|
||||
'#return_value' => $entity->id(),
|
||||
];
|
||||
}
|
||||
|
||||
// @todo Remove in https://www.drupal.org/project/drupal/issues/2504115
|
||||
// Currently the default URL for all AJAX form elements is the current URL,
|
||||
// not the form action. This causes bugs when this form is rendered from an
|
||||
// AJAX path like /views/ajax, which cannot process AJAX form submits.
|
||||
$url = parse_url($form['#action'], PHP_URL_PATH);
|
||||
$query = \Drupal::request()->query->all();
|
||||
$query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
|
||||
$form['actions']['submit']['#ajax'] = [
|
||||
'url' => Url::fromUserInput($url),
|
||||
'options' => [
|
||||
'query' => $query,
|
||||
],
|
||||
'callback' => [static::class, 'updateWidget'],
|
||||
];
|
||||
|
||||
$form['actions']['submit']['#value'] = $this->t('Select media');
|
||||
$form['actions']['submit']['#field_id'] = $this->options['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the media library select form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* A command to send the selection to the current field widget.
|
||||
*/
|
||||
public static function updateWidget(array &$form, FormStateInterface $form_state) {
|
||||
$widget_id = \Drupal::request()->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.');
|
||||
}
|
||||
$field_id = $form_state->getTriggeringElement()['#field_id'];
|
||||
$selected = array_values(array_filter($form_state->getValue($field_id, [])));
|
||||
// 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(',', $selected)]))
|
||||
->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$widget_id\"]", 'trigger', ['mousedown']))
|
||||
->addCommand(new CloseDialogCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewsFormValidate(array &$form, FormStateInterface $form_state) {
|
||||
$selected = array_filter($form_state->getValue($this->options['id']));
|
||||
if (empty($selected)) {
|
||||
$form_state->setErrorByName('', $this->t('No items selected.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clickSortable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue