diff --git a/core/modules/media_library/config/schema/media_library.schema.yml b/core/modules/media_library/config/schema/media_library.schema.yml
index 5a6813995b..ce46c91e74 100644
--- a/core/modules/media_library/config/schema/media_library.schema.yml
+++ b/core/modules/media_library/config/schema/media_library.schema.yml
@@ -4,11 +4,7 @@ field.widget.settings.media_library_widget:
mapping:
media_types:
type: sequence
- label: 'Media types display order'
+ label: 'Media types tab order'
sequence:
- type: mapping
- label: 'Media type weight'
- mapping:
- weight:
- type: integer
- label: 'Weight'
+ type: integer
+ label: 'Weight'
diff --git a/core/modules/media_library/js/media_library.widget.es6.js b/core/modules/media_library/js/media_library.widget.es6.js
index a4c0984c1a..0242569202 100644
--- a/core/modules/media_library/js/media_library.widget.es6.js
+++ b/core/modules/media_library/js/media_library.widget.es6.js
@@ -2,8 +2,55 @@
* @file media_library.widget.js
*/
(($, Drupal, window) => {
- Drupal.media_library = {
+ /**
+ * Store the media library selection.
+ *
+ * When a user interacts with the media library we want the selection to
+ * persist as long as the media library modal is opened. We temporarily store
+ * the selected items while the user filters the media library view or
+ * navigates to different views pages and tabs.
+ */
+ Drupal.mediaLibrarySelection = {
selection: [],
+ /**
+ * Add a media item ID to the selection.
+ *
+ * @param {number} id
+ * The ID of the item we want to add.
+ */
+ add(id) {
+ const position = this.selection.indexOf(id);
+ if (position === -1) {
+ this.selection.push(id);
+ }
+ },
+ /**
+ * Remove a media item ID from the selection.
+ *
+ * @param {number} id
+ * The ID of the item we want to remove.
+ */
+ remove(id) {
+ const position = this.selection.indexOf(id);
+ if (position !== -1) {
+ this.selection.splice(position, 1);
+ }
+ },
+ /**
+ * Get the selected media items IDs.
+ *
+ * @return {Array}
+ * An array of selected media item IDs.
+ */
+ get() {
+ return this.selection;
+ },
+ /**
+ * Reset the selected media items IDs.
+ */
+ reset() {
+ this.selection = [];
+ },
};
/**
@@ -84,6 +131,36 @@
},
};
+ /**
+ * Update the select button and number of selected items in the button pane.
+ */
+ function updateButtonPane() {
+ const $buttonPane = $('.media-library-widget-modal .ui-dialog-buttonpane');
+ if (!$buttonPane.length) {
+ return;
+ }
+
+ const count = Drupal.mediaLibrarySelection.get().length;
+ const $toggleElements = $buttonPane.find(
+ '.media-library-select, .media-library-selected-count',
+ );
+
+ if (count === 0) {
+ // Hide the select button and selection count when nothing is
+ // selected.
+ $toggleElements.hide();
+ } else {
+ // Add the selection count and show the select button.
+ const $wrapper = $buttonPane.find('.media-library-selected-count');
+ if ($wrapper.length) {
+ $wrapper.replaceWith(Drupal.theme('mediaLibrarySelectionCount', count));
+ } else {
+ $buttonPane.append(Drupal.theme('mediaLibrarySelectionCount', count));
+ }
+ $toggleElements.fadeIn('fast');
+ }
+ }
+
/**
* Change the selection when a media item is checked/unchecked.
*/
@@ -93,82 +170,54 @@
'.js-media-library-item input[type="checkbox"]',
context,
);
- if ($mediaItems.length) {
- $mediaItems.once('media-item-change').on('change', e => {
- const $form = $(e.currentTarget).parents('.media-library-views-form');
- const position = Drupal.media_library.selection.indexOf(e.currentTarget.value);
- const checked = $(e.currentTarget).is(':checked');
- if (checked && position === -1) {
- Drupal.media_library.selection.push(e.currentTarget.value);
- } else if (!checked && position !== -1) {
- Drupal.media_library.selection.splice(position, 1);
- }
- // Set the selection in the hidden form element.
- $form
- .find('input#media-library-modal-selection')
- .val(Drupal.media_library.selection.join());
+ if (!$mediaItems.length) {
+ return;
+ }
- // Prevent users from selecting more items than allowed in the view.
- if (
- settings.media_library &&
- settings.media_library.selection_remaining
- ) {
- if (
- Drupal.media_library.selection.length ===
- settings.media_library.selection_remaining
- ) {
- $mediaItems
- .not(':checked')
- .prop('disabled', true)
- .closest('.js-media-library-item')
- .addClass('media-library-item--disabled');
- $mediaItems
- .filter(':checked')
- .prop('disabled', false)
- .closest('.js-media-library-item')
- .removeClass('media-library-item--disabled');
- } else {
- $mediaItems
- .prop('disabled', false)
- .closest('.js-media-library-item')
- .removeClass('media-library-item--disabled');
- }
- }
+ function disableItems($items) {
+ $items
+ .prop('disabled', true)
+ .closest('.js-media-library-item')
+ .addClass('media-library-item--disabled');
+ }
- // Hide selection button if nothing is selected. We can't use the
- // context here because the dialog copies the select button.
- const $buttonPane = $(
- '.media-library-widget-modal .ui-dialog-buttonpane',
- );
- if (Drupal.media_library.selection.length === 0) {
- $buttonPane
- .find('.media-library-select, .media-library-selected-count')
- .fadeOut('fast');
- }
- else {
- const selectItemsText = Drupal.formatPlural(
- Drupal.media_library.selection.length,
- '1 item selected',
- '@count items selected',
- );
- const $selectedItems = $buttonPane.find(
- '.media-library-selected-count',
- );
- if ($selectedItems.length) {
- $selectedItems.html(selectItemsText);
- }
- else {
- $buttonPane.append(
- `
${selectItemsText}
`,
- );
- }
- $buttonPane
- .find('.media-library-select, .media-library-selected-count')
- .fadeIn('fast');
- }
- });
+ function enableItems($items) {
+ $items
+ .prop('disabled', false)
+ .closest('.js-media-library-item')
+ .removeClass('media-library-item--disabled');
}
+
+ $mediaItems.once('media-item-change').on('change', e => {
+ const $form = $(e.currentTarget).parents('.media-library-views-form');
+
+ // Update the selection.
+ if ($(e.currentTarget).is(':checked')) {
+ Drupal.mediaLibrarySelection.add(e.currentTarget.value);
+ } else {
+ Drupal.mediaLibrarySelection.remove(e.currentTarget.value);
+ }
+
+ // Set the selection in the hidden form element.
+ $form
+ .find('input#media-library-modal-selection')
+ .val(Drupal.mediaLibrarySelection.get().join());
+
+ // Once the selection is update, update the button pane.
+ updateButtonPane();
+
+ // Prevent users from selecting more items than allowed in the view.
+ if (
+ Drupal.mediaLibrarySelection.get().length ===
+ settings.media_library.selection_remaining
+ ) {
+ disableItems($mediaItems.not(':checked'));
+ enableItems($mediaItems.filter(':checked'));
+ } else {
+ enableItems($mediaItems);
+ }
+ });
},
};
@@ -176,77 +225,28 @@
* Apply the current selection when loading the media library view.
*/
Drupal.behaviors.MediaLibraryModalApplySelection = {
- attach(context, settings) {
+ attach(context) {
const $form = $('.media-library-views-form', context);
- if ($form.length) {
- if ($form.find('.js-media-library-item').length) {
- // Select the items in the view.
- Drupal.media_library.selection.forEach(value => {
- $form
- .find(
- `.js-media-library-item input[type="checkbox"][value="${value}"]`,
- )
- .prop('checked', true)
- .trigger('change');
- });
- // Set the selection in the hidden form element.
- $form
- .find('input#media-library-modal-selection')
- .val(Drupal.media_library.selection.join());
-
- // Prevent users from selecting more items than allowed in the view.
- if (
- settings.media_library &&
- settings.media_library.selection_remaining &&
- Drupal.media_library.selection.length ===
- settings.media_library.selection_remaining
- ) {
- $form
- .find(
- '.js-media-library-item input[type="checkbox"]',
- )
- .not(':checked')
- .prop('disabled', true)
- .closest('.js-media-library-item')
- .addClass('media-library-item--disabled');
- }
- }
+ if (!$form.length) {
+ return;
+ }
- // Hide selection button if nothing is selected. We can't use the
- // context here because the dialog copies the select button.
- $(window)
- .once('media-library-toggle-buttons')
- .on({
- 'dialog:aftercreate': () => {
- const $buttonPane = $(
- '.media-library-widget-modal .ui-dialog-buttonpane',
- );
- if (Drupal.media_library.selection.length === 0) {
- $buttonPane
- .find('.media-library-select, .media-library-selected-count')
- .hide();
- } else {
- const selectItemsText = Drupal.formatPlural(
- Drupal.media_library.selection.length,
- '1 item selected',
- '@count items selected',
- );
- const $selectedItems = $buttonPane.find(
- '.media-library-selected-count',
- );
- if ($selectedItems.length) {
- $selectedItems.html(selectItemsText);
- }
- else {
- $buttonPane.append(
- `${selectItemsText}
`,
- );
- }
- }
- },
- });
+ if ($form.find('.js-media-library-item').length) {
+ // Select the items in the view.
+ Drupal.mediaLibrarySelection.get().forEach(value => {
+ $form
+ .find(`input[type="checkbox"][value="${value}"]`)
+ .prop('checked', true)
+ .trigger('change');
+ });
}
+
+ // Hide selection button if nothing is selected. We can't use the
+ // context here because the dialog copies the select button.
+ $(window)
+ .once('media-library-toggle-buttons')
+ .on('dialog:aftercreate', updateButtonPane);
},
};
@@ -257,11 +257,27 @@
attach() {
$(window)
.once('media-library-clear-selection')
- .on({
- 'dialog:afterclose': () => {
- Drupal.media_library.selection = [];
- },
+ .on('dialog:afterclose', () => {
+ Drupal.mediaLibrarySelection.reset();
});
},
};
+
+ /**
+ * Theme function for the selection count.
+ *
+ * @param {number} count
+ * The number of selected items.
+ *
+ * @return {string}
+ * The corresponding HTML.
+ */
+ Drupal.theme.mediaLibrarySelectionCount = function(count) {
+ const selectItemsText = Drupal.formatPlural(
+ count,
+ '1 item selected',
+ '@count items selected',
+ );
+ return `${selectItemsText}
`;
+ };
})(jQuery, Drupal, window);
diff --git a/core/modules/media_library/js/media_library.widget.js b/core/modules/media_library/js/media_library.widget.js
index 3707c3a598..d85ff3cae4 100644
--- a/core/modules/media_library/js/media_library.widget.js
+++ b/core/modules/media_library/js/media_library.widget.js
@@ -6,8 +6,26 @@
**/
(function ($, Drupal, window) {
- Drupal.media_library = {
- selection: []
+ Drupal.mediaLibrarySelection = {
+ selection: [],
+ add: function add(id) {
+ var position = this.selection.indexOf(id);
+ if (position === -1) {
+ this.selection.push(id);
+ }
+ },
+ remove: function remove(id) {
+ var position = this.selection.indexOf(id);
+ if (position !== -1) {
+ this.selection.splice(position, 1);
+ }
+ },
+ get: function get() {
+ return this.selection;
+ },
+ reset: function reset() {
+ this.selection = [];
+ }
};
Drupal.behaviors.MediaLibraryWidgetSortable = {
@@ -53,92 +71,95 @@
}
};
+ function updateButtonPane() {
+ var $buttonPane = $('.media-library-widget-modal .ui-dialog-buttonpane');
+ if (!$buttonPane.length) {
+ return;
+ }
+
+ var count = Drupal.mediaLibrarySelection.get().length;
+ var $toggleElements = $buttonPane.find('.media-library-select, .media-library-selected-count');
+
+ if (count === 0) {
+ $toggleElements.hide();
+ } else {
+ var $wrapper = $buttonPane.find('.media-library-selected-count');
+ if ($wrapper.length) {
+ $wrapper.replaceWith(Drupal.theme('mediaLibrarySelectionCount', count));
+ } else {
+ $buttonPane.append(Drupal.theme('mediaLibrarySelectionCount', count));
+ }
+ $toggleElements.fadeIn('fast');
+ }
+ }
+
Drupal.behaviors.MediaLibraryModalChangeSelection = {
attach: function attach(context, settings) {
var $mediaItems = $('.js-media-library-item input[type="checkbox"]', context);
- if ($mediaItems.length) {
- $mediaItems.once('media-item-change').on('change', function (e) {
- var $form = $(e.currentTarget).parents('.media-library-views-form');
- var position = Drupal.media_library.selection.indexOf(e.currentTarget.value);
- var checked = $(e.currentTarget).is(':checked');
- if (checked && position === -1) {
- Drupal.media_library.selection.push(e.currentTarget.value);
- } else if (!checked && position !== -1) {
- Drupal.media_library.selection.splice(position, 1);
- }
-
- $form.find('input#media-library-modal-selection').val(Drupal.media_library.selection.join());
-
- if (settings.media_library && settings.media_library.selection_remaining) {
- if (Drupal.media_library.selection.length === settings.media_library.selection_remaining) {
- $mediaItems.not(':checked').prop('disabled', true).closest('.js-media-library-item').addClass('media-library-item--disabled');
- $mediaItems.filter(':checked').prop('disabled', false).closest('.js-media-library-item').removeClass('media-library-item--disabled');
- } else {
- $mediaItems.prop('disabled', false).closest('.js-media-library-item').removeClass('media-library-item--disabled');
- }
- }
-
- var $buttonPane = $('.media-library-widget-modal .ui-dialog-buttonpane');
- if (Drupal.media_library.selection.length === 0) {
- $buttonPane.find('.media-library-select, .media-library-selected-count').fadeOut('fast');
- } else {
- var selectItemsText = Drupal.formatPlural(Drupal.media_library.selection.length, '1 item selected', '@count items selected');
- var $selectedItems = $buttonPane.find('.media-library-selected-count');
- if ($selectedItems.length) {
- $selectedItems.html(selectItemsText);
- } else {
- $buttonPane.append('' + selectItemsText + '
');
- }
- $buttonPane.find('.media-library-select, .media-library-selected-count').fadeIn('fast');
- }
- });
+
+ if (!$mediaItems.length) {
+ return;
+ }
+
+ function disableItems($items) {
+ $items.prop('disabled', true).closest('.js-media-library-item').addClass('media-library-item--disabled');
}
+
+ function enableItems($items) {
+ $items.prop('disabled', false).closest('.js-media-library-item').removeClass('media-library-item--disabled');
+ }
+
+ $mediaItems.once('media-item-change').on('change', function (e) {
+ var $form = $(e.currentTarget).parents('.media-library-views-form');
+
+ if ($(e.currentTarget).is(':checked')) {
+ Drupal.mediaLibrarySelection.add(e.currentTarget.value);
+ } else {
+ Drupal.mediaLibrarySelection.remove(e.currentTarget.value);
+ }
+
+ $form.find('input#media-library-modal-selection').val(Drupal.mediaLibrarySelection.get().join());
+
+ updateButtonPane();
+
+ if (Drupal.mediaLibrarySelection.get().length === settings.media_library.selection_remaining) {
+ disableItems($mediaItems.not(':checked'));
+ enableItems($mediaItems.filter(':checked'));
+ } else {
+ enableItems($mediaItems);
+ }
+ });
}
};
Drupal.behaviors.MediaLibraryModalApplySelection = {
- attach: function attach(context, settings) {
+ attach: function attach(context) {
var $form = $('.media-library-views-form', context);
- if ($form.length) {
- if ($form.find('.js-media-library-item').length) {
- Drupal.media_library.selection.forEach(function (value) {
- $form.find('.js-media-library-item input[type="checkbox"][value="' + value + '"]').prop('checked', true).trigger('change');
- });
-
- $form.find('input#media-library-modal-selection').val(Drupal.media_library.selection.join());
- if (settings.media_library && settings.media_library.selection_remaining && Drupal.media_library.selection.length === settings.media_library.selection_remaining) {
- $form.find('.js-media-library-item input[type="checkbox"]').not(':checked').prop('disabled', true).closest('.js-media-library-item').addClass('media-library-item--disabled');
- }
- }
+ if (!$form.length) {
+ return;
+ }
- $(window).once('media-library-toggle-buttons').on({
- 'dialog:aftercreate': function dialogAftercreate() {
- var $buttonPane = $('.media-library-widget-modal .ui-dialog-buttonpane');
- if (Drupal.media_library.selection.length === 0) {
- $buttonPane.find('.media-library-select, .media-library-selected-count').hide();
- } else {
- var selectItemsText = Drupal.formatPlural(Drupal.media_library.selection.length, '1 item selected', '@count items selected');
- var $selectedItems = $buttonPane.find('.media-library-selected-count');
- if ($selectedItems.length) {
- $selectedItems.html(selectItemsText);
- } else {
- $buttonPane.append('' + selectItemsText + '
');
- }
- }
- }
+ if ($form.find('.js-media-library-item').length) {
+ Drupal.mediaLibrarySelection.get().forEach(function (value) {
+ $form.find('input[type="checkbox"][value="' + value + '"]').prop('checked', true).trigger('change');
});
}
+
+ $(window).once('media-library-toggle-buttons').on('dialog:aftercreate', updateButtonPane);
}
};
Drupal.behaviors.MediaLibraryModalClearSelection = {
attach: function attach() {
- $(window).once('media-library-clear-selection').on({
- 'dialog:afterclose': function dialogAfterclose() {
- Drupal.media_library.selection = [];
- }
+ $(window).once('media-library-clear-selection').on('dialog:afterclose', function () {
+ Drupal.mediaLibrarySelection.reset();
});
}
};
+
+ Drupal.theme.mediaLibrarySelectionCount = function (count) {
+ var selectItemsText = Drupal.formatPlural(count, '1 item selected', '@count items selected');
+ return '' + selectItemsText + '
';
+ };
})(jQuery, Drupal, window);
\ No newline at end of file
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index 0a8df053d5..ae853827ed 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -13,6 +13,7 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
+use Drupal\media_library\MediaLibraryState;
use Drupal\views\Form\ViewsForm;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\ViewExecutable;
@@ -79,11 +80,8 @@ function media_library_views_post_render(ViewExecutable $view, &$output, CachePl
if ($view->id() === 'media_library') {
$output['#attached']['library'][] = 'media_library/view';
if ($view->current_display === 'widget') {
- $query = array_intersect_key(\Drupal::request()->query->all(), array_flip([
- 'media_library_widget_id',
- 'media_library_allowed_types',
- 'media_library_remaining',
- ]));
+ $state = MediaLibraryState::fromRequest();
+ $query = $state->all();
// If the current query contains any parameters we use to contextually
// filter the view, ensure they persist across AJAX rebuilds.
// The ajax_path is shared for all AJAX views on the page, but our query
diff --git a/core/modules/media_library/media_library.routing.yml b/core/modules/media_library/media_library.routing.yml
index b53cea8d28..787b236078 100644
--- a/core/modules/media_library/media_library.routing.yml
+++ b/core/modules/media_library/media_library.routing.yml
@@ -9,4 +9,4 @@ media_library.modal:
defaults:
_controller: 'media_library.modal:build'
requirements:
- _permission: 'view media'
+ _custom_access: 'media_library.modal:access'
diff --git a/core/modules/media_library/media_library.services.yml b/core/modules/media_library/media_library.services.yml
index 645544938a..2c50cda166 100644
--- a/core/modules/media_library/media_library.services.yml
+++ b/core/modules/media_library/media_library.services.yml
@@ -1,4 +1,4 @@
services:
media_library.modal:
class: Drupal\media_library\MediaLibraryModal
- arguments: ['@request_stack']
+ arguments: ['@logger.factory']
diff --git a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
index 9cab9f7ee4..5f8c047f70 100644
--- a/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
+++ b/core/modules/media_library/src/Form/MediaLibraryUploadForm.php
@@ -18,7 +18,7 @@
use Drupal\file\Plugin\Field\FieldType\FileItem;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
-use Drupal\media_library\MediaLibraryConfiguration;
+use Drupal\media_library\MediaLibraryState;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -315,24 +315,31 @@ public function selectType(array &$form, FormStateInterface $form_state) {
* 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.
+ * If the "media_library_state_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.');
+
+ $state = MediaLibraryState::fromRequest();
+ $field_id = $state->getFieldId();
+ $field_type = $state->getFieldType();
+ if (!$field_id || !$field_type) {
+ throw new BadRequestHttpException('The media library state ID 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());
+ if ($field_type === 'entity_reference') {
+ return (new AjaxResponse())
+ ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$field_id\"]", 'val', [implode(',', $mids)]))
+ ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$field_id\"]", 'trigger', ['mousedown']))
+ ->addCommand(new CloseDialogCommand());
+ }
}
/**
@@ -481,12 +488,12 @@ public function access(array $allowed_types = NULL) {
* A list of media types that are valid for this form.
*/
protected function getTypes(array $allowed_types = NULL) {
- $media_library_configuration = MediaLibraryConfiguration::fromRequest();
+ $state = MediaLibraryState::fromRequest();
// Cache results if possible.
if (!isset($this->types)) {
$media_type_storage = $this->entityTypeManager->getStorage('media_type');
if (!$allowed_types) {
- $types = $media_library_configuration->getAllowedTypes();
+ $types = $state->getAllowedTypes();
}
else {
$types = $media_type_storage->loadMultiple($allowed_types);
diff --git a/core/modules/media_library/src/MediaLibraryConfiguration.php b/core/modules/media_library/src/MediaLibraryConfiguration.php
deleted file mode 100644
index f9ddb5429e..0000000000
--- a/core/modules/media_library/src/MediaLibraryConfiguration.php
+++ /dev/null
@@ -1,99 +0,0 @@
-get('media_library_widget_id');
- }
-
- /**
- * Returns the selected media type.
- *
- * @return \Drupal\media\Entity\MediaType
- * The media type.
- */
- public function getSelectedType() {
- return MediaType::load($this->get('media_library_selected_type'));
- }
-
- /**
- * Returns the media types which can be selected.
- *
- * @return \Drupal\media\Entity\MediaType[]
- * The media types.
- */
- public function getAllowedTypes() {
- $media_types = $this->get('media_library_allowed_types');
- return MediaType::loadMultiple($media_types);
- }
-
- /**
- * Determines if additional media items can be selected.
- *
- * @return bool
- * TRUE if additional items can be selected, otherwise FALSE.
- */
- public function hasSlotsAvailable() {
- return $this->getAvailableSlots() !== 0;
- }
-
- /**
- * Returns the number of additional media items that can be selected.
- *
- * @return int
- * The number of additional media items that can be selected.
- */
- public function getAvailableSlots() {
- return $this->getInt('media_library_remaining');
- }
-
- /**
- * Get media library configuration from a request.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- * (optional) The request. If not given, the current request will be used.
- *
- * @return \Drupal\media_library\MediaLibraryConfiguration
- * A selection object.
- */
- public static function fromRequest(Request $request = NULL) {
- $request = $request ?: \Drupal::request();
- return new static($request->query->all());
- }
-
- /**
- * Get media library dialog options.
- *
- * @return array
- * The media library dialog options.
- */
- public static function dialogOptions() {
- return [
- 'dialogClass' => 'media-library-widget-modal',
- 'title' => t('Media library'),
- 'height' => '75%',
- 'width' => '75%',
- ];
- }
-
-}
diff --git a/core/modules/media_library/src/MediaLibraryModal.php b/core/modules/media_library/src/MediaLibraryModal.php
index 929419f0ca..8e15adbfd0 100644
--- a/core/modules/media_library/src/MediaLibraryModal.php
+++ b/core/modules/media_library/src/MediaLibraryModal.php
@@ -3,34 +3,48 @@
namespace Drupal\media_library;
use Drupal\Component\Serialization\Json;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\media\MediaTypeInterface;
use Drupal\views\Views;
-use Symfony\Component\HttpFoundation\RequestStack;
/**
- * Controller which renders the media library modal.
+ * Service which renders the media library modal.
*/
class MediaLibraryModal {
- use StringTranslationTrait;
+ /**
+ * The logger service.
+ *
+ * @var \Psr\Log\LoggerInterface
+ */
+ protected $logger;
/**
- * The currently active request object.
+ * Constructs a MediaLibraryModal instance.
*
- * @var \Symfony\Component\HttpFoundation\Request
+ * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
+ * The logger factory service.
*/
- protected $request;
+ public function __construct(LoggerChannelFactoryInterface $logger_factory) {
+ $this->logger = $logger_factory->get('media_library');
+ }
/**
- * Constructs a MediaLibraryController instance.
+ * Get media library dialog options.
*
- * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
- * The currently active request object.
+ * @return array
+ * The media library dialog options.
*/
- public function __construct(RequestStack $request_stack) {
- $this->request = $request_stack->getCurrentRequest();
+ public static function dialogOptions() {
+ return [
+ 'dialogClass' => 'media-library-widget-modal',
+ 'title' => t('Media library'),
+ 'height' => '75%',
+ 'width' => '75%',
+ ];
}
/**
@@ -40,7 +54,7 @@ public function __construct(RequestStack $request_stack) {
* The render array for the media library.
*/
public function build() {
- $configuration = MediaLibraryConfiguration::fromRequest();
+ $state = MediaLibraryState::fromRequest();
return [
'wrapper' => [
'#type' => 'html_tag',
@@ -48,37 +62,61 @@ public function build() {
'#attributes' => [
'class' => ['media-library-wrapper'],
],
- 'menu' => $this->getMediaTypeMenu($configuration),
+ 'menu' => $this->getMediaTypeMenu($state),
'content' => [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'class' => ['media-library-content'],
],
- 'view' => $this->getMediaLibraryView($configuration->getSelectedType()),
+ 'view' => $this->getMediaLibraryView($state->getSelectedType()),
],
],
];
}
+ /**
+ * Check access to the media library.
+ *
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * Run access checks for this account.
+ *
+ * @return \Drupal\Core\Access\AccessResult
+ * The access result.
+ */
+ public function access(AccountInterface $account) {
+ // Deny access if the view or display are removed.
+ $view = Views::getView('media_library');
+ if (!$view) {
+ $this->logger->error('The media library view does not exist.');
+ return AccessResult::forbidden('The media library view does not exist.');
+ }
+ if (!$view->storage->getDisplay('widget')) {
+ $this->logger->error('The media library view does not exist.');
+ return AccessResult::forbidden('The media library widget display does not exist.');
+ }
+
+ return AccessResult::allowedIfHasPermission($account, 'view media');
+ }
+
/**
* Get the media type menu for the media library.
*
- * @param \Drupal\media_library\MediaLibraryConfiguration $configuration
+ * @param \Drupal\media_library\MediaLibraryState $state
* The media library configuration.
*
* @return array
* The render array for the media type menu.
*/
- protected function getMediaTypeMenu(MediaLibraryConfiguration $configuration) {
- $selected_type = $configuration->getSelectedType();
- $allowed_types = $configuration->getAllowedTypes();
+ protected function getMediaTypeMenu(MediaLibraryState $state) {
+ $selected_type = $state->getSelectedType();
+ $allowed_types = $state->getAllowedTypes();
- $dialog_options = Json::encode(MediaLibraryConfiguration::dialogOptions());
+ $dialog_options = Json::encode(static::dialogOptions());
// Add the menu for each type if we have more than 1 media type enabled for
// the field.
- if (count($allowed_types) <= 1) {
+ if (count($allowed_types) === 1) {
return [];
}
@@ -87,18 +125,10 @@ protected function getMediaTypeMenu(MediaLibraryConfiguration $configuration) {
'#links' => [],
'#attributes' => [
'class' => ['media-library-menu'],
- ],
- '#attached' =>[
- 'library' => ['core/drupal.vertical-tabs'],
]
];
- $query = [
- 'media_library_widget_id' => $configuration->getWidgetId(),
- 'media_library_allowed_types' => array_keys($allowed_types),
- 'media_library_remaining' => $configuration->getAvailableSlots(),
- ];
-
+ $query = $state->all();
foreach ($allowed_types as $allowed_type_id => $allowed_type) {
$query['media_library_selected_type'] = $allowed_type_id;
$menu['#links'][$allowed_type_id] = [
@@ -133,13 +163,6 @@ protected function getMediaLibraryView(MediaTypeInterface $media_type) {
$view = Views::getView('media_library');
$display_id = 'widget';
- // Add an extra check because the view could have been deleted.
- if (!is_object($view)) {
- // @todo: throw error when view doesn't exist?
- return [];
- }
-
- // @todo: throw error when display doesn't exist?
$media_type_id = $media_type->id();
$view->setDisplay($display_id);
$view->preExecute([$media_type_id]);
diff --git a/core/modules/media_library/src/MediaLibraryState.php b/core/modules/media_library/src/MediaLibraryState.php
new file mode 100644
index 0000000000..8b16098915
--- /dev/null
+++ b/core/modules/media_library/src/MediaLibraryState.php
@@ -0,0 +1,128 @@
+get('media_library_field_id');
+ }
+
+ /**
+ * Returns the field type of the field that opened the modal.
+ *
+ * @return string
+ * The field type.
+ */
+ public function getFieldType() {
+ return $this->get('media_library_field_type');
+ }
+
+ /**
+ * Returns the selected media type.
+ *
+ * @return \Drupal\media\Entity\MediaType
+ * The media type.
+ */
+ public function getSelectedType() {
+ return MediaType::load($this->get('media_library_selected_type'));
+ }
+
+ /**
+ * Returns the media types which can be selected.
+ *
+ * @return \Drupal\media\Entity\MediaType[]
+ * The media types.
+ */
+ public function getAllowedTypes() {
+ // When no media types are passed, we load all media types since that is the
+ // default behaviour of the entity reference field target bundles.
+ $media_types = $this->get('media_library_allowed_types') ?: NULL;
+ return MediaType::loadMultiple($media_types);
+ }
+
+ /**
+ * Determines if additional media items can be selected.
+ *
+ * @return bool
+ * TRUE if additional items can be selected, otherwise FALSE.
+ */
+ public function hasSlotsAvailable() {
+ return $this->getAvailableSlots() !== 0;
+ }
+
+ /**
+ * Returns the number of additional media items that can be selected.
+ *
+ * @return int
+ * The number of additional media items that can be selected.
+ */
+ public function getAvailableSlots() {
+ return $this->getInt('media_library_remaining');
+ }
+
+ /**
+ * Create a new media library URL with state parameters.
+ *
+ * @param string $field_id
+ * The field ID.
+ * @param string $field_type
+ * The field type.
+ * @param string $selected_type_id
+ * The selected media type ID.
+ * @param string[] $allowed_media_type_ids
+ * The allowed media type IDs.
+ * @param int $remaining
+ * The number of remaining items the user is allowed to select or add in the
+ * library.
+ *
+ * @return \Drupal\Core\Url
+ * A new Url object for the media library.
+ */
+ public static function createUrl($field_id, $field_type, $selected_type_id, array $allowed_media_type_ids, $remaining) {
+ $query = [
+ 'media_library_field_id' => $field_id,
+ 'media_library_field_type' => $field_type,
+ 'media_library_selected_type' => $selected_type_id,
+ 'media_library_allowed_types' => $allowed_media_type_ids,
+ 'media_library_remaining' => $remaining,
+ ];
+ return Url::fromRoute('media_library.modal', [], [
+ 'query' => $query,
+ ]);
+ }
+
+ /**
+ * Get the media library state from a request.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * (optional) The request. If not given, the current request will be used.
+ *
+ * @return \Drupal\media_library\MediaLibraryState
+ * A selection object.
+ */
+ public static function fromRequest(Request $request = NULL) {
+ $request = $request ?: \Drupal::request();
+ return new static($request->query->all());
+ }
+
+}
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index 6c721486d2..f963290d1e 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -12,10 +12,12 @@
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\media\Entity\Media;
use Drupal\media_library\Form\MediaLibraryUploadForm;
-use Drupal\media_library\MediaLibraryConfiguration;
+use Drupal\media_library\MediaLibraryModal;
+use Drupal\media_library\MediaLibraryState;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
@@ -117,15 +119,24 @@ public static function defaultSettings() {
protected function getEnabledMediaTypeIdsSorted() {
$media_types_setting = $this->getSetting('media_types');
$configured_media_type_ids = array_keys($this->getFieldSetting('handler_settings')['target_bundles']);
+
if (empty($media_types_setting)) {
return $configured_media_type_ids;
}
- uasort($media_types_setting, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
+
+ asort($media_types_setting);
$sorted_media_type_ids = array_keys($media_types_setting);
- // Add new media types.
- $sorted_media_type_ids = array_merge($sorted_media_type_ids, array_diff($configured_media_type_ids, $sorted_media_type_ids));
- // Remove deletes types.
- return array_intersect($sorted_media_type_ids, $configured_media_type_ids);
+
+ // There could have been added or removed media types in the field storage.
+ // We need to make sure new media types are added to the list and remove
+ // media types that are no longer available for the field.
+ $new_media_type_ids = array_diff($configured_media_type_ids, $sorted_media_type_ids);
+ // Add new media type IDs to the list.
+ $sorted_media_type_ids = array_merge($sorted_media_type_ids, $new_media_type_ids);
+ // Remove media types that are no longer available.
+ $sorted_media_type_ids = array_intersect($sorted_media_type_ids, $configured_media_type_ids);
+
+ return $sorted_media_type_ids;
}
/**
@@ -133,11 +144,11 @@ protected function getEnabledMediaTypeIdsSorted() {
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$media_type_ids = $this->getEnabledMediaTypeIdsSorted();
- if (count($media_type_ids) > 1) {
+ if (count($media_type_ids) !== 1) {
$form['media_types'] = [
'#type' => 'table',
'#header' => [
- $this->t('Sort media types'),
+ $this->t('Tab order'),
$this->t('Weight'),
],
'#tabledrag' => [
@@ -164,6 +175,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
],
'#weight' => $delta,
'#attributes' => ['class' => ['draggable']],
+ '#process' => [[static::class, 'processMediaTypeParents']],
];
$delta++;
}
@@ -171,19 +183,50 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
return $form;
}
+ /**
+ * Process callback to optimize the way the media type weights are stored.
+ *
+ * The tabledrag functionality needs a specific weight field, but we don't
+ * this extra weight field in our settings. To remove this, we need to change
+ * the #parents array of the weight field. We also need to change this from
+ * the wrapper element, since the form builder handles input before processing
+ * an element.
+ *
+ * @param array $element
+ * The media type weight element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ *
+ * @return array
+ * The changed element.
+ *
+ * @see \Drupal\Core\Form\FormBuilder::doBuildForm()
+ */
+ public static function processMediaTypeParents(array $element, FormStateInterface $form_state) {
+ foreach (Element::children($element) as $key) {
+ if (!isset($element[$key]['#tree'])) {
+ $element[$key]['#tree'] = $element['#tree'];
+ }
+ if (!isset($element[$key]['#parents'])) {
+ $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? $element['#parents'] : [$key];
+ }
+ }
+ return $element;
+ }
+
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
-
$media_type_labels = [];
$media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple($this->getEnabledMediaTypeIdsSorted());
- foreach ($media_types as $media_type) {
- $media_type_labels[] = $media_type->label();
+ if ($media_types !== 1) {
+ foreach ($media_types as $media_type) {
+ $media_type_labels[] = $media_type->label();
+ }
+ $summary[] = t('Tab order: @order', ['@order' => implode(', ', $media_type_labels)]);
}
- $summary[] = t('Media type order: @order', ['@order' => implode(', ', $media_type_labels)]);
-
return $summary;
}
@@ -321,15 +364,14 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$element['#description'] .= '
' . $cardinality_message;
}
+ // Create a new media library URL with the correct state parameters.
$media_type_ids = $this->getEnabledMediaTypeIdsSorted();
$primary_media_type_id = array_slice($media_type_ids, 0, 1, TRUE);
- $query = [
- 'media_library_widget_id' => $field_name . $id_suffix,
- 'media_library_selected_type' => reset($primary_media_type_id),
- 'media_library_allowed_types' => $media_type_ids,
- 'media_library_remaining' => $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining,
- ];
- $dialog_options = Json::encode(MediaLibraryConfiguration::dialogOptions());
+ $selected_type = reset($primary_media_type_id);
+ $remaining = $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining;
+ $url = MediaLibraryState::createUrl($field_name . $id_suffix, 'entity_reference', $selected_type, $media_type_ids, $remaining);
+
+ $dialog_options = Json::encode(MediaLibraryModal::dialogOptions());
// Add a button that will load the Media library in a modal using AJAX.
$element['media_library_open_button'] = [
@@ -337,9 +379,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
'#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('media_library.modal', [], [
- 'query' => $query,
- ]),
+ '#url' => $url,
'#attributes' => [
'class' => ['button', 'use-ajax', 'media-library-open-button'],
'data-dialog-type' => 'modal',
diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
index d7e94b27c1..10379f5dcd 100644
--- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
+++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php
@@ -8,10 +8,11 @@
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
-use Drupal\media_library\MediaLibraryConfiguration;
+use Drupal\media_library\MediaLibraryState;
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.
@@ -107,19 +108,23 @@ public static function updateWidget(array &$form, FormStateInterface $form_state
$field_id = $form_state->getTriggeringElement()['#field_id'];
$selected = array_filter(explode(',', $form_state->getValue($field_id, [])));
- $media_library_configuration = MediaLibraryConfiguration::fromRequest();
-
$response = new AjaxResponse();
$response->addCommand(new CloseDialogCommand());
- $widget_id = $media_library_configuration->getWidgetId();
+ $state = MediaLibraryState::fromRequest();
+ $field_id = $state->getFieldId();
+ $field_type = $state->getFieldType();
+ if (!$field_id || !$field_type) {
+ throw new BadRequestHttpException('The media library field ID and field type are required and must be a string.');
+ }
$ids = implode(',', $selected);
- if ($widget_id && is_string($widget_id)) {
+ if ($field_type === 'entity_reference') {
$response
- ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$widget_id\"]", 'val', [$ids]))
- ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$widget_id\"]", 'trigger', ['mousedown']));
+ ->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$field_id\"]", 'val', [$ids]))
+ ->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$field_id\"]", 'trigger', ['mousedown']));
}
+
return $response;
}
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
index 9f4d1ad5d8..9866810781 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -198,10 +198,10 @@ public function testWidget() {
$assert_session->assertWaitOnAjaxRequest();
$page->find('css', '.tabledrag-toggle-weight')->click();
$edit = [
- 'fields[field_twin_media][settings_edit_form][settings][media_types][type_one][weight]' => 0,
- 'fields[field_twin_media][settings_edit_form][settings][media_types][type_three][weight]' => 1,
- 'fields[field_twin_media][settings_edit_form][settings][media_types][type_four][weight]' => 2,
- 'fields[field_twin_media][settings_edit_form][settings][media_types][type_two][weight]' => 3,
+ 'fields[field_twin_media][settings_edit_form][settings][media_types][type_one]' => 0,
+ 'fields[field_twin_media][settings_edit_form][settings][media_types][type_three]' => 1,
+ 'fields[field_twin_media][settings_edit_form][settings][media_types][type_four]' => 2,
+ 'fields[field_twin_media][settings_edit_form][settings][media_types][type_two]' => 3,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$page->find('css', '.tabledrag-toggle-weight')->click();