Initial Drupal 11 with DDEV setup
This commit is contained in:
		@ -0,0 +1,13 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
  enforced:
 | 
			
		||||
    module:
 | 
			
		||||
      - media_library
 | 
			
		||||
id: media.media_library
 | 
			
		||||
label: 'Media library'
 | 
			
		||||
description: null
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
cache: true
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
  enforced:
 | 
			
		||||
    module:
 | 
			
		||||
      - media_library
 | 
			
		||||
id: media.media_library
 | 
			
		||||
label: 'Media library'
 | 
			
		||||
description: ''
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
cache: true
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  enforced:
 | 
			
		||||
    module:
 | 
			
		||||
      - media_library
 | 
			
		||||
name: media_library
 | 
			
		||||
label: 'Media Library thumbnail (220×220)'
 | 
			
		||||
effects:
 | 
			
		||||
  75b076a8-1234-4b42-85db-bf377c4d8d5f:
 | 
			
		||||
    uuid: 75b076a8-1234-4b42-85db-bf377c4d8d5f
 | 
			
		||||
    id: image_scale
 | 
			
		||||
    weight: 0
 | 
			
		||||
    data:
 | 
			
		||||
      width: 220
 | 
			
		||||
      height: 220
 | 
			
		||||
      upscale: false
 | 
			
		||||
  1021da71-fc2a-43d0-be5d-efaf1c79e2ea:
 | 
			
		||||
    uuid: 1021da71-fc2a-43d0-be5d-efaf1c79e2ea
 | 
			
		||||
    id: image_convert_avif
 | 
			
		||||
    weight: 2
 | 
			
		||||
    data:
 | 
			
		||||
      extension: webp
 | 
			
		||||
@ -0,0 +1 @@
 | 
			
		||||
advanced_ui: false
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
field.widget.settings.media_library_widget:
 | 
			
		||||
  type: mapping
 | 
			
		||||
  label: 'Media library widget settings'
 | 
			
		||||
  mapping:
 | 
			
		||||
    media_types:
 | 
			
		||||
      type: sequence
 | 
			
		||||
      label: 'Allowed media types, in display order'
 | 
			
		||||
      sequence:
 | 
			
		||||
        type: string
 | 
			
		||||
        label: 'Media type ID'
 | 
			
		||||
 | 
			
		||||
media_library.settings:
 | 
			
		||||
  type: config_object
 | 
			
		||||
  label: 'Media library settings'
 | 
			
		||||
  mapping:
 | 
			
		||||
    advanced_ui:
 | 
			
		||||
      type: boolean
 | 
			
		||||
      label: 'Enable advanced UI'
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file media_library.click_to_select.js
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
(($, Drupal) => {
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows users to select an element which checks a hidden checkbox.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior for selecting media library item.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.ClickToSelect = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      $(
 | 
			
		||||
        once(
 | 
			
		||||
          'media-library-click-to-select',
 | 
			
		||||
          '.js-click-to-select-trigger',
 | 
			
		||||
          context,
 | 
			
		||||
        ),
 | 
			
		||||
      ).on('click', (event) => {
 | 
			
		||||
        // Links inside the trigger should not be click-able.
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        // Click the hidden checkbox when the trigger is clicked.
 | 
			
		||||
        const $input = $(event.currentTarget)
 | 
			
		||||
          .closest('.js-click-to-select')
 | 
			
		||||
          .find('.js-click-to-select-checkbox input');
 | 
			
		||||
        $input.prop('checked', !$input.prop('checked')).trigger('change');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      $(
 | 
			
		||||
        once(
 | 
			
		||||
          'media-library-click-to-select',
 | 
			
		||||
          '.js-click-to-select-checkbox input',
 | 
			
		||||
          context,
 | 
			
		||||
        ),
 | 
			
		||||
      )
 | 
			
		||||
        .on('change', ({ currentTarget }) => {
 | 
			
		||||
          $(currentTarget)
 | 
			
		||||
            .closest('.js-click-to-select')
 | 
			
		||||
            .toggleClass('checked', $(currentTarget).prop('checked'));
 | 
			
		||||
        })
 | 
			
		||||
        // Adds is-focus class to the click-to-select element.
 | 
			
		||||
        .on('focus blur', ({ currentTarget, type }) => {
 | 
			
		||||
          $(currentTarget)
 | 
			
		||||
            .closest('.js-click-to-select')
 | 
			
		||||
            .toggleClass('is-focus', type === 'focus');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      // Adds hover class to the click-to-select element.
 | 
			
		||||
      $(
 | 
			
		||||
        once(
 | 
			
		||||
          'media-library-click-to-select-hover',
 | 
			
		||||
          '.js-click-to-select-trigger, .js-click-to-select-checkbox',
 | 
			
		||||
          context,
 | 
			
		||||
        ),
 | 
			
		||||
      ).on('mouseover mouseout', ({ currentTarget, type }) => {
 | 
			
		||||
        $(currentTarget)
 | 
			
		||||
          .closest('.js-click-to-select')
 | 
			
		||||
          .toggleClass('is-hover', type === 'mouseover');
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
})(jQuery, Drupal);
 | 
			
		||||
							
								
								
									
										429
									
								
								web/core/modules/media_library/js/media_library.ui.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								web/core/modules/media_library/js/media_library.ui.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,429 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file media_library.ui.js
 | 
			
		||||
 */
 | 
			
		||||
(($, Drupal, window, { tabbable }) => {
 | 
			
		||||
  /**
 | 
			
		||||
   * Wrapper object for the current state of the media library.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.MediaLibrary = {
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 tabs.
 | 
			
		||||
     */
 | 
			
		||||
    currentSelection: [],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Command to update the current media library selection.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Drupal.Ajax} [ajax]
 | 
			
		||||
   *   The Drupal Ajax object.
 | 
			
		||||
   * @param {object} response
 | 
			
		||||
   *   Object holding the server response.
 | 
			
		||||
   * @param {number} [status]
 | 
			
		||||
   *   The HTTP status code.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.AjaxCommands.prototype.updateMediaLibrarySelection = function (
 | 
			
		||||
    ajax,
 | 
			
		||||
    response,
 | 
			
		||||
    status,
 | 
			
		||||
  ) {
 | 
			
		||||
    Object.values(response.mediaIds).forEach((value) => {
 | 
			
		||||
      Drupal.MediaLibrary.currentSelection.push(value);
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Load media library content through AJAX.
 | 
			
		||||
   *
 | 
			
		||||
   * Standard AJAX links (using the 'use-ajax' class) replace the entire library
 | 
			
		||||
   * dialog. When navigating to a media type through the vertical tabs, we only
 | 
			
		||||
   * want to load the changed library content. This is not only more efficient,
 | 
			
		||||
   * but also provides a more accessible user experience for screen readers.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to vertical tabs in the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Remove when the AJAX system adds support for replacing a specific
 | 
			
		||||
   *   selector via a link.
 | 
			
		||||
   *   https://www.drupal.org/project/drupal/issues/3026636
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryTabs = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      const $menu = $('.js-media-library-menu');
 | 
			
		||||
      $(once('media-library-menu-item', $menu.find('a')))
 | 
			
		||||
        .on('keypress', (e) => {
 | 
			
		||||
          // The AJAX link has the button role, so we need to make sure the link
 | 
			
		||||
          // is also triggered when pressing the space bar.
 | 
			
		||||
          if (e.which === 32) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
            $(e.currentTarget).trigger('click');
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .on('click', (e) => {
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
          // Replace the library content.
 | 
			
		||||
          const ajaxObject = Drupal.ajax({
 | 
			
		||||
            wrapper: 'media-library-content',
 | 
			
		||||
            url: e.currentTarget.href,
 | 
			
		||||
            dialogType: 'ajax',
 | 
			
		||||
            progress: {
 | 
			
		||||
              type: 'fullscreen',
 | 
			
		||||
              message: Drupal.t('Processing...'),
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          // Override the AJAX success callback to shift focus to the media
 | 
			
		||||
          // library content.
 | 
			
		||||
          ajaxObject.success = function (response, status) {
 | 
			
		||||
            return Promise.resolve(
 | 
			
		||||
              Drupal.Ajax.prototype.success.call(ajaxObject, response, status),
 | 
			
		||||
            ).then(() => {
 | 
			
		||||
              // Set focus to the first tabbable element in the media library
 | 
			
		||||
              // content.
 | 
			
		||||
              const mediaLibraryContent = document.getElementById(
 | 
			
		||||
                'media-library-content',
 | 
			
		||||
              );
 | 
			
		||||
              if (mediaLibraryContent) {
 | 
			
		||||
                const tabbableContent = tabbable(mediaLibraryContent);
 | 
			
		||||
                if (tabbableContent.length) {
 | 
			
		||||
                  tabbableContent[0].focus();
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          };
 | 
			
		||||
          ajaxObject.execute();
 | 
			
		||||
 | 
			
		||||
          // Set the selected tab.
 | 
			
		||||
          $menu.find('.active-tab').remove();
 | 
			
		||||
          $menu.find('a').removeClass('active');
 | 
			
		||||
          $(e.currentTarget)
 | 
			
		||||
            .addClass('active')
 | 
			
		||||
            .html(
 | 
			
		||||
              Drupal.t(
 | 
			
		||||
                '<span class="visually-hidden">Show </span>@title<span class="visually-hidden"> media</span><span class="active-tab visually-hidden"> (selected)</span>',
 | 
			
		||||
                { '@title': $(e.currentTarget).data('title') },
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
          // Announce the updated content.
 | 
			
		||||
          Drupal.announce(
 | 
			
		||||
            Drupal.t('Showing @title media.', {
 | 
			
		||||
              '@title': $(e.currentTarget).data('title'),
 | 
			
		||||
            }),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Load media library displays through AJAX.
 | 
			
		||||
   *
 | 
			
		||||
   * Standard AJAX links (using the 'use-ajax' class) replace the entire library
 | 
			
		||||
   * dialog. When navigating to a media library views display, we only want to
 | 
			
		||||
   * load the changed views display content. This is not only more efficient,
 | 
			
		||||
   * but also provides a more accessible user experience for screen readers.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to vertical tabs in the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Remove when the AJAX system adds support for replacing a specific
 | 
			
		||||
   *   selector via a link.
 | 
			
		||||
   *   https://www.drupal.org/project/drupal/issues/3026636
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryViewsDisplay = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      const $view = $(context).hasClass('.js-media-library-view')
 | 
			
		||||
        ? $(context)
 | 
			
		||||
        : $('.js-media-library-view', context);
 | 
			
		||||
 | 
			
		||||
      // Add a class to the view to allow it to be replaced via AJAX.
 | 
			
		||||
      // @todo Remove the custom ID when the AJAX system allows replacing
 | 
			
		||||
      //    elements by selector.
 | 
			
		||||
      //    https://www.drupal.org/project/drupal/issues/2821793
 | 
			
		||||
      $view
 | 
			
		||||
        .closest('.views-element-container')
 | 
			
		||||
        .attr('id', 'media-library-view');
 | 
			
		||||
 | 
			
		||||
      // We would ideally use a generic JavaScript specific class to detect the
 | 
			
		||||
      // display links. Since we have no good way of altering display links yet,
 | 
			
		||||
      // this is the best we can do for now.
 | 
			
		||||
      // @todo Add media library specific classes and data attributes to the
 | 
			
		||||
      //    media library display links when we can alter display links.
 | 
			
		||||
      //    https://www.drupal.org/project/drupal/issues/3036694
 | 
			
		||||
      $(
 | 
			
		||||
        once(
 | 
			
		||||
          'media-library-views-display-link',
 | 
			
		||||
          '.views-display-link-widget, .views-display-link-widget_table',
 | 
			
		||||
          context,
 | 
			
		||||
        ),
 | 
			
		||||
      ).on('click', (e) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        const $link = $(e.currentTarget);
 | 
			
		||||
 | 
			
		||||
        // Add a loading and display announcement for screen reader users.
 | 
			
		||||
        let loadingAnnouncement = '';
 | 
			
		||||
        let displayAnnouncement = '';
 | 
			
		||||
        let focusSelector = '';
 | 
			
		||||
        if ($link.hasClass('views-display-link-widget')) {
 | 
			
		||||
          loadingAnnouncement = Drupal.t('Loading grid view.');
 | 
			
		||||
          displayAnnouncement = Drupal.t('Changed to grid view.');
 | 
			
		||||
          focusSelector = '.views-display-link-widget';
 | 
			
		||||
        } else if ($link.hasClass('views-display-link-widget_table')) {
 | 
			
		||||
          loadingAnnouncement = Drupal.t('Loading table view.');
 | 
			
		||||
          displayAnnouncement = Drupal.t('Changed to table view.');
 | 
			
		||||
          focusSelector = '.views-display-link-widget_table';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Replace the library view.
 | 
			
		||||
        const ajaxObject = Drupal.ajax({
 | 
			
		||||
          wrapper: 'media-library-view',
 | 
			
		||||
          url: e.currentTarget.href,
 | 
			
		||||
          dialogType: 'ajax',
 | 
			
		||||
          progress: {
 | 
			
		||||
            type: 'fullscreen',
 | 
			
		||||
            message: loadingAnnouncement || Drupal.t('Processing...'),
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Override the AJAX success callback to announce the updated content
 | 
			
		||||
        // to screen readers.
 | 
			
		||||
        if (displayAnnouncement || focusSelector) {
 | 
			
		||||
          const success = ajaxObject.success;
 | 
			
		||||
          ajaxObject.success = function (response, status) {
 | 
			
		||||
            success.bind(this)(response, status);
 | 
			
		||||
            // The AJAX link replaces the whole view, including the clicked
 | 
			
		||||
            // link. Move the focus back to the clicked link when the view is
 | 
			
		||||
            // replaced.
 | 
			
		||||
            if (focusSelector) {
 | 
			
		||||
              $(focusSelector).focus();
 | 
			
		||||
            }
 | 
			
		||||
            // Announce the new view is loaded to screen readers.
 | 
			
		||||
            if (displayAnnouncement) {
 | 
			
		||||
              Drupal.announce(displayAnnouncement);
 | 
			
		||||
            }
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ajaxObject.execute();
 | 
			
		||||
 | 
			
		||||
        // Announce the new view is being loaded to screen readers.
 | 
			
		||||
        // @todo Replace custom announcement when
 | 
			
		||||
        //   https://www.drupal.org/project/drupal/issues/2973140 is in.
 | 
			
		||||
        if (loadingAnnouncement) {
 | 
			
		||||
          Drupal.announce(loadingAnnouncement);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update the media library selection when loaded or media items are selected.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to select media items.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryItemSelection = {
 | 
			
		||||
    attach(context, settings) {
 | 
			
		||||
      const $form = $(
 | 
			
		||||
        '.js-media-library-views-form, .js-media-library-add-form',
 | 
			
		||||
        context,
 | 
			
		||||
      );
 | 
			
		||||
      const currentSelection = Drupal.MediaLibrary.currentSelection;
 | 
			
		||||
 | 
			
		||||
      if (!$form.length) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const $mediaItems = $(
 | 
			
		||||
        '.js-media-library-item input[type="checkbox"]',
 | 
			
		||||
        $form,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * Disable media items.
 | 
			
		||||
       *
 | 
			
		||||
       * @param {jQuery} $items
 | 
			
		||||
       *   A jQuery object representing the media items that should be disabled.
 | 
			
		||||
       */
 | 
			
		||||
      function disableItems($items) {
 | 
			
		||||
        $items
 | 
			
		||||
          .prop('disabled', true)
 | 
			
		||||
          .closest('.js-media-library-item')
 | 
			
		||||
          .addClass('media-library-item--disabled');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * Enable media items.
 | 
			
		||||
       *
 | 
			
		||||
       * @param {jQuery} $items
 | 
			
		||||
       *   A jQuery object representing the media items that should be enabled.
 | 
			
		||||
       */
 | 
			
		||||
      function enableItems($items) {
 | 
			
		||||
        $items
 | 
			
		||||
          .prop('disabled', false)
 | 
			
		||||
          .closest('.js-media-library-item')
 | 
			
		||||
          .removeClass('media-library-item--disabled');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * Update the number of selected items in the button pane.
 | 
			
		||||
       *
 | 
			
		||||
       * @param {number} remaining
 | 
			
		||||
       *   The number of remaining slots.
 | 
			
		||||
       */
 | 
			
		||||
      function updateSelectionCount(remaining) {
 | 
			
		||||
        // When the remaining number of items is a negative number, we allow an
 | 
			
		||||
        // unlimited number of items. In that case we don't want to show the
 | 
			
		||||
        // number of remaining slots.
 | 
			
		||||
        const selectItemsText =
 | 
			
		||||
          remaining < 0
 | 
			
		||||
            ? Drupal.formatPlural(
 | 
			
		||||
                currentSelection.length,
 | 
			
		||||
                '1 item selected',
 | 
			
		||||
                '@count items selected',
 | 
			
		||||
              )
 | 
			
		||||
            : Drupal.formatPlural(
 | 
			
		||||
                remaining,
 | 
			
		||||
                '@selected of @count item selected',
 | 
			
		||||
                '@selected of @count items selected',
 | 
			
		||||
                {
 | 
			
		||||
                  '@selected': currentSelection.length,
 | 
			
		||||
                },
 | 
			
		||||
              );
 | 
			
		||||
        // The selected count div could have been created outside of the
 | 
			
		||||
        // context, so we unfortunately can't use context here.
 | 
			
		||||
        $('.js-media-library-selected-count').html(selectItemsText);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function checkEnabled() {
 | 
			
		||||
        updateSelectionCount(settings.media_library.selection_remaining);
 | 
			
		||||
        if (
 | 
			
		||||
          currentSelection.length === settings.media_library.selection_remaining
 | 
			
		||||
        ) {
 | 
			
		||||
          disableItems($mediaItems.not(':checked'));
 | 
			
		||||
          enableItems($mediaItems.filter(':checked'));
 | 
			
		||||
        } else {
 | 
			
		||||
          enableItems($mediaItems);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Update the selection array and the hidden form field when a media item
 | 
			
		||||
      // is selected.
 | 
			
		||||
      $(once('media-item-change', $mediaItems)).on('change', (e) => {
 | 
			
		||||
        const id = e.currentTarget.value;
 | 
			
		||||
 | 
			
		||||
        // Update the selection.
 | 
			
		||||
        if (e.currentTarget.checked) {
 | 
			
		||||
          // Check if the ID is not already in the selection and add if needed.
 | 
			
		||||
          if (!currentSelection.includes(id)) {
 | 
			
		||||
            currentSelection.push(id);
 | 
			
		||||
          }
 | 
			
		||||
        } else if (currentSelection.includes(id)) {
 | 
			
		||||
          // Remove the ID when it is in the current selection.
 | 
			
		||||
          currentSelection.splice(currentSelection.indexOf(id), 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const mediaLibraryModalSelection = document.querySelector(
 | 
			
		||||
          '#media-library-modal-selection',
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (mediaLibraryModalSelection) {
 | 
			
		||||
          // Set the selection in the hidden form element.
 | 
			
		||||
          mediaLibraryModalSelection.value = currentSelection.join();
 | 
			
		||||
          $(mediaLibraryModalSelection).trigger('change');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set the selection in the media library add form. Since the form is
 | 
			
		||||
        // not necessarily loaded within the same context, we can't use the
 | 
			
		||||
        // context here.
 | 
			
		||||
        document
 | 
			
		||||
          .querySelectorAll('.js-media-library-add-form-current-selection')
 | 
			
		||||
          .forEach((item) => {
 | 
			
		||||
            item.value = currentSelection.join();
 | 
			
		||||
          });
 | 
			
		||||
      });
 | 
			
		||||
      checkEnabled();
 | 
			
		||||
      // The hidden selection form field changes when the selection is updated.
 | 
			
		||||
      $(
 | 
			
		||||
        once(
 | 
			
		||||
          'media-library-selection-change',
 | 
			
		||||
          $form.find('#media-library-modal-selection'),
 | 
			
		||||
        ),
 | 
			
		||||
      ).on('change', (e) => {
 | 
			
		||||
        checkEnabled();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // Apply the current selection to the media library view. Changing the
 | 
			
		||||
      // checkbox values triggers the change event for the media items. The
 | 
			
		||||
      // change event handles updating the hidden selection field for the form.
 | 
			
		||||
      currentSelection.forEach((value) => {
 | 
			
		||||
        $form
 | 
			
		||||
          .find(`input[type="checkbox"][value="${value}"]`)
 | 
			
		||||
          .prop('checked', true)
 | 
			
		||||
          .trigger('change');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // Add the selection count to the button pane when a media library dialog
 | 
			
		||||
      // is created.
 | 
			
		||||
      if (!once('media-library-selection-info', 'html').length) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      window.addEventListener('dialog:aftercreate', () => {
 | 
			
		||||
        // Since the dialog HTML is not part of the context, we can't use
 | 
			
		||||
        // context here.
 | 
			
		||||
        const $buttonPane = $(
 | 
			
		||||
          '.media-library-widget-modal .ui-dialog-buttonpane',
 | 
			
		||||
        );
 | 
			
		||||
        if (!$buttonPane.length) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        $buttonPane.append(Drupal.theme('mediaLibrarySelectionCount'));
 | 
			
		||||
        updateSelectionCount(settings.media_library.selection_remaining);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Clear the current selection.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to clear the selection when the library modal closes.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryModalClearSelection = {
 | 
			
		||||
    attach() {
 | 
			
		||||
      if (!once('media-library-clear-selection', 'html').length) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      window.addEventListener('dialog:afterclose', () => {
 | 
			
		||||
        // This empty the array while keeping the existing array reference,
 | 
			
		||||
        // to keep event listeners working.
 | 
			
		||||
        Drupal.MediaLibrary.currentSelection.length = 0;
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Theme function for the selection count.
 | 
			
		||||
   *
 | 
			
		||||
   * @return {string}
 | 
			
		||||
   *   The corresponding HTML.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.theme.mediaLibrarySelectionCount = function () {
 | 
			
		||||
    return `<div class="media-library-selected-count js-media-library-selected-count" role="status" aria-live="polite" aria-atomic="true"></div>`;
 | 
			
		||||
  };
 | 
			
		||||
})(jQuery, Drupal, window, window.tabbable);
 | 
			
		||||
							
								
								
									
										49
									
								
								web/core/modules/media_library/js/media_library.view.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								web/core/modules/media_library/js/media_library.view.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file media_library.view.js
 | 
			
		||||
 */
 | 
			
		||||
(($, Drupal) => {
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds checkbox to select all items in the library.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to select all media items.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibrarySelectAll = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      const $view = $(
 | 
			
		||||
        once(
 | 
			
		||||
          'media-library-select-all',
 | 
			
		||||
          '.js-media-library-view[data-view-display-id="page"]',
 | 
			
		||||
          context,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      if ($view.length && $view.find('.js-media-library-item').length) {
 | 
			
		||||
        const $checkbox = $(Drupal.theme('checkbox')).on(
 | 
			
		||||
          'click',
 | 
			
		||||
          ({ currentTarget }) => {
 | 
			
		||||
            // Toggle all checkboxes.
 | 
			
		||||
            const $checkboxes = $(currentTarget)
 | 
			
		||||
              .closest('.js-media-library-view')
 | 
			
		||||
              .find('.js-media-library-item input[type="checkbox"]');
 | 
			
		||||
            $checkboxes
 | 
			
		||||
              .prop('checked', $(currentTarget).prop('checked'))
 | 
			
		||||
              .trigger('change');
 | 
			
		||||
            // Announce the selection.
 | 
			
		||||
            const announcement = $(currentTarget).prop('checked')
 | 
			
		||||
              ? Drupal.t('All @count items selected', {
 | 
			
		||||
                  '@count': $checkboxes.length,
 | 
			
		||||
                })
 | 
			
		||||
              : Drupal.t('Zero items selected');
 | 
			
		||||
            Drupal.announce(announcement);
 | 
			
		||||
          },
 | 
			
		||||
        );
 | 
			
		||||
        const $label = $('<label class="media-library-select-all"></label>');
 | 
			
		||||
        $label[0].textContent = Drupal.t('Select all media');
 | 
			
		||||
        $label.prepend($checkbox);
 | 
			
		||||
        $view.find('.js-media-library-item').first().before($label);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
})(jQuery, Drupal);
 | 
			
		||||
							
								
								
									
										106
									
								
								web/core/modules/media_library/js/media_library.widget.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								web/core/modules/media_library/js/media_library.widget.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @file media_library.widget.js
 | 
			
		||||
 */
 | 
			
		||||
(($, Drupal, Sortable) => {
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows users to re-order their selection with drag+drop.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to re-order selected media items.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryWidgetSortable = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      // Allow media items to be re-sorted with drag+drop in the widget.
 | 
			
		||||
      const selection = context.querySelectorAll('.js-media-library-selection');
 | 
			
		||||
      selection.forEach((widget) => {
 | 
			
		||||
        Sortable.create(widget, {
 | 
			
		||||
          draggable: '.js-media-library-item',
 | 
			
		||||
          handle: '.js-media-library-item-preview',
 | 
			
		||||
          onEnd: () => {
 | 
			
		||||
            $(widget)
 | 
			
		||||
              .children()
 | 
			
		||||
              .each((index, child) => {
 | 
			
		||||
                $(child).find('.js-media-library-item-weight')[0].value = index;
 | 
			
		||||
              });
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Allows selection order to be set without drag+drop for accessibility.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to toggle the weight field for media items.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryWidgetToggleWeight = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      const strings = {
 | 
			
		||||
        show: Drupal.t('Show media item weights'),
 | 
			
		||||
        hide: Drupal.t('Hide media item weights'),
 | 
			
		||||
      };
 | 
			
		||||
      const mediaLibraryToggle = once(
 | 
			
		||||
        'media-library-toggle',
 | 
			
		||||
        '.js-media-library-widget-toggle-weight',
 | 
			
		||||
        context,
 | 
			
		||||
      );
 | 
			
		||||
      $(mediaLibraryToggle).on('click', (e) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        const $target = $(e.currentTarget);
 | 
			
		||||
        e.currentTarget.textContent = $target.hasClass('active')
 | 
			
		||||
          ? strings.show
 | 
			
		||||
          : strings.hide;
 | 
			
		||||
        $target
 | 
			
		||||
          .toggleClass('active')
 | 
			
		||||
          .closest('.js-media-library-widget')
 | 
			
		||||
          .find('.js-media-library-item-weight')
 | 
			
		||||
          .parent()
 | 
			
		||||
          .toggle();
 | 
			
		||||
      });
 | 
			
		||||
      mediaLibraryToggle.forEach((item) => {
 | 
			
		||||
        item.textContent = strings.show;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      $(once('media-library-toggle', '.js-media-library-item-weight', context))
 | 
			
		||||
        .parent()
 | 
			
		||||
        .hide();
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Disable the open button when the user is not allowed to add more items.
 | 
			
		||||
   *
 | 
			
		||||
   * @type {Drupal~behavior}
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {Drupal~behaviorAttach} attach
 | 
			
		||||
   *   Attaches behavior to disable the media library open button.
 | 
			
		||||
   */
 | 
			
		||||
  Drupal.behaviors.MediaLibraryWidgetDisableButton = {
 | 
			
		||||
    attach(context) {
 | 
			
		||||
      // When the user returns from the modal to the widget, we want to shift
 | 
			
		||||
      // the focus back to the open button. If the user is not allowed to add
 | 
			
		||||
      // more items, the button needs to be disabled. Since we can't shift the
 | 
			
		||||
      // focus to disabled elements, the focus is set back to the open button
 | 
			
		||||
      // via JavaScript by adding the 'data-disabled-focus' attribute.
 | 
			
		||||
      once(
 | 
			
		||||
        'media-library-disable',
 | 
			
		||||
        '.js-media-library-open-button[data-disabled-focus="true"]',
 | 
			
		||||
        context,
 | 
			
		||||
      ).forEach((button) => {
 | 
			
		||||
        $(button).focus();
 | 
			
		||||
 | 
			
		||||
        // There is a small delay between the focus set by the browser and the
 | 
			
		||||
        // focus of screen readers. We need to give screen readers time to shift
 | 
			
		||||
        // the focus as well before the button is disabled.
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          $(button).attr('disabled', 'disabled');
 | 
			
		||||
        }, 50);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
})(jQuery, Drupal, Sortable);
 | 
			
		||||
							
								
								
									
										92
									
								
								web/core/modules/media_library/media_library.api.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								web/core/modules/media_library/media_library.api.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Documentation related to Media Library.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @defgroup media_library_architecture Media Library Architecture
 | 
			
		||||
 * @{
 | 
			
		||||
 *
 | 
			
		||||
 * Media Library is a UI for the core Media module. It provides a visual
 | 
			
		||||
 * interface for users to manage media in their site, and it allows authors to
 | 
			
		||||
 * visually select media for use in entity reference and text fields, using a
 | 
			
		||||
 * modal dialog.
 | 
			
		||||
 *
 | 
			
		||||
 * In order to provide a consistent user experience, Media Library is
 | 
			
		||||
 * intentionally opinionated, with few extension points and no hooks. Most of
 | 
			
		||||
 * its code is internal and should not be extended or instantiated by external
 | 
			
		||||
 * code.
 | 
			
		||||
 *
 | 
			
		||||
 * @section openers Openers
 | 
			
		||||
 * Interaction with the modal media library dialog is mediated by "opener"
 | 
			
		||||
 * services. All openers must implement
 | 
			
		||||
 * \Drupal\media_library\MediaLibraryOpenerInterface.
 | 
			
		||||
 *
 | 
			
		||||
 * Openers are responsible for determining access to the media library, and for
 | 
			
		||||
 * generating an AJAX response when the user has finished selecting media items
 | 
			
		||||
 * in the library. An opener is a "bridge" between the opinionated media library
 | 
			
		||||
 * modal dialog and whatever is consuming it, allowing the dialog to be
 | 
			
		||||
 * triggered in a way that makes sense for that particular consumer. Examples in
 | 
			
		||||
 * Drupal core include entity reference fields and text editors.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryOpenerInterface
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryEditorOpener
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryFieldWidgetOpener
 | 
			
		||||
 *
 | 
			
		||||
 * @section state Modal dialog state
 | 
			
		||||
 * When the media library modal is used, its configuration and state (such as
 | 
			
		||||
 * how many items are currently selected, the maximum number that can be
 | 
			
		||||
 * selected, which media types the user is allowed to see, and so forth) are
 | 
			
		||||
 * stored in an instance of \Drupal\media_library\MediaLibraryState. The state
 | 
			
		||||
 * object also stores the service ID of the opener being used, as well as any
 | 
			
		||||
 * additional parameters or data that are specific to that opener.
 | 
			
		||||
 *
 | 
			
		||||
 * The media library state is passed between the user and the server in the
 | 
			
		||||
 * URL's query parameters. Therefore, the state is also protected by a hash in
 | 
			
		||||
 * order to prevent tampering.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryState
 | 
			
		||||
 *
 | 
			
		||||
 * @section add_form Adding media in the dialog
 | 
			
		||||
 * Users with appropriate permissions can add media to the library from directly
 | 
			
		||||
 * within the modal dialog.
 | 
			
		||||
 *
 | 
			
		||||
 * This interaction is implemented using forms, and is customizable by modules.
 | 
			
		||||
 * Since the media library is segmented by media type, each media type can
 | 
			
		||||
 * expose a different form for adding media of that type; the type's source
 | 
			
		||||
 * plugin specifies the actual form class to use. Here is an example of a media
 | 
			
		||||
 * source plugin definition which provides an add form for the media library:
 | 
			
		||||
 *
 | 
			
		||||
 * @code
 | 
			
		||||
 * #[MediaSource(
 | 
			
		||||
 *   id: "file",
 | 
			
		||||
 *   label: new TranslatableMarkup("File"),
 | 
			
		||||
 *   description: new TranslatableMarkup("Use local files for reusable media."),
 | 
			
		||||
 *   allowed_field_types: ["file"],
 | 
			
		||||
 *   forms = [
 | 
			
		||||
 *     "media_library_add" => "\Drupal\media_library\Form\FileUploadForm",
 | 
			
		||||
 *   ]
 | 
			
		||||
 * )]
 | 
			
		||||
 * @endcode
 | 
			
		||||
 *
 | 
			
		||||
 * This can also be done in hook_media_source_info_alter(). For example:
 | 
			
		||||
 *
 | 
			
		||||
 * @code
 | 
			
		||||
 * function example_media_source_info_alter(array &$sources) {
 | 
			
		||||
 *   $sources['file']['forms']['media_library_add'] = "\Drupal\media_library\Form\FileUploadForm";
 | 
			
		||||
 * }
 | 
			
		||||
 * @endcode
 | 
			
		||||
 *
 | 
			
		||||
 * The add form is a standard form class, and can be altered by modules and
 | 
			
		||||
 * themes just like any other form. For easier implementation, it is recommended
 | 
			
		||||
 * that modules extend \Drupal\media_library\Form\AddFormBase when providing add
 | 
			
		||||
 * forms.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\media_library\Form\AddFormBase
 | 
			
		||||
 * @see \Drupal\media_library\Form\FileUploadForm
 | 
			
		||||
 * @see \Drupal\media_library\Form\OEmbedForm
 | 
			
		||||
 *
 | 
			
		||||
 * @}
 | 
			
		||||
 */
 | 
			
		||||
							
								
								
									
										10
									
								
								web/core/modules/media_library/media_library.info.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								web/core/modules/media_library/media_library.info.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
name: 'Media Library'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Enhances the media list with additional features to more easily find and use existing media items.'
 | 
			
		||||
package: Core
 | 
			
		||||
version: VERSION
 | 
			
		||||
configure: media_library.settings
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:media
 | 
			
		||||
  - drupal:views
 | 
			
		||||
  - drupal:user
 | 
			
		||||
							
								
								
									
										27
									
								
								web/core/modules/media_library/media_library.install
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/core/modules/media_library/media_library.install
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Install, update and uninstall functions for the media_library module.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use Drupal\media\Entity\MediaType;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_install().
 | 
			
		||||
 */
 | 
			
		||||
function media_library_install($is_syncing): void {
 | 
			
		||||
  if (!$is_syncing) {
 | 
			
		||||
    foreach (MediaType::loadMultiple() as $type) {
 | 
			
		||||
      _media_library_configure_form_display($type);
 | 
			
		||||
      _media_library_configure_view_display($type);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_update_last_removed().
 | 
			
		||||
 */
 | 
			
		||||
function media_library_update_last_removed(): int {
 | 
			
		||||
  return 8704;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								web/core/modules/media_library/media_library.libraries.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								web/core/modules/media_library/media_library.libraries.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
click_to_select:
 | 
			
		||||
  version: VERSION
 | 
			
		||||
  js:
 | 
			
		||||
    js/media_library.click_to_select.js: {}
 | 
			
		||||
  dependencies:
 | 
			
		||||
    - core/drupal
 | 
			
		||||
    - core/once
 | 
			
		||||
 | 
			
		||||
view:
 | 
			
		||||
  version: VERSION
 | 
			
		||||
  js:
 | 
			
		||||
    js/media_library.view.js: {}
 | 
			
		||||
  dependencies:
 | 
			
		||||
    - core/drupal.announce
 | 
			
		||||
    - core/drupal.checkbox
 | 
			
		||||
    - media_library/click_to_select
 | 
			
		||||
 | 
			
		||||
widget:
 | 
			
		||||
  version: VERSION
 | 
			
		||||
  js:
 | 
			
		||||
    js/media_library.widget.js: {}
 | 
			
		||||
  dependencies:
 | 
			
		||||
    - core/drupal.dialog.ajax
 | 
			
		||||
    - core/once
 | 
			
		||||
    - core/sortable
 | 
			
		||||
 | 
			
		||||
ui:
 | 
			
		||||
  version: VERSION
 | 
			
		||||
  js:
 | 
			
		||||
    js/media_library.ui.js: {}
 | 
			
		||||
  dependencies:
 | 
			
		||||
    - core/drupal.ajax
 | 
			
		||||
    - core/drupal.announce
 | 
			
		||||
    - core/once
 | 
			
		||||
    - core/jquery
 | 
			
		||||
    - media_library/view
 | 
			
		||||
    - core/tabbable
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
media_library.add:
 | 
			
		||||
  route_name: entity.media.add_page
 | 
			
		||||
  title: 'Add media'
 | 
			
		||||
  appears_on:
 | 
			
		||||
    - view.media_library.page
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
media_library.settings:
 | 
			
		||||
  title: 'Media Library settings'
 | 
			
		||||
  parent: system.admin_config_media
 | 
			
		||||
  description: 'Manage Media Library settings.'
 | 
			
		||||
  route_name: media_library.settings
 | 
			
		||||
							
								
								
									
										10
									
								
								web/core/modules/media_library/media_library.links.task.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								web/core/modules/media_library/media_library.links.task.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
media_library.table:
 | 
			
		||||
  title: 'Table'
 | 
			
		||||
  parent_id: entity.media.collection
 | 
			
		||||
  route_name: entity.media.collection
 | 
			
		||||
  weight: 10
 | 
			
		||||
media_library.grid:
 | 
			
		||||
  title: 'Grid'
 | 
			
		||||
  parent_id: entity.media.collection
 | 
			
		||||
  route_name: view.media_library.page
 | 
			
		||||
  weight: 20
 | 
			
		||||
							
								
								
									
										215
									
								
								web/core/modules/media_library/media_library.module
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								web/core/modules/media_library/media_library.module
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,215 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\Template\Attribute;
 | 
			
		||||
use Drupal\image\Entity\ImageStyle;
 | 
			
		||||
use Drupal\image\Plugin\Field\FieldType\ImageItem;
 | 
			
		||||
use Drupal\media\MediaTypeInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Prepares variables for the media library modal dialog.
 | 
			
		||||
 *
 | 
			
		||||
 * Default template: media-library-wrapper.html.twig.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $variables
 | 
			
		||||
 *   An associative array containing:
 | 
			
		||||
 *   - element: An associative array containing the properties of the element.
 | 
			
		||||
 *     Properties used: #menu, #content.
 | 
			
		||||
 */
 | 
			
		||||
function template_preprocess_media_library_wrapper(array &$variables): void {
 | 
			
		||||
  $variables['menu'] = &$variables['element']['menu'];
 | 
			
		||||
  $variables['content'] = &$variables['element']['content'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Prepares variables for a selected media item.
 | 
			
		||||
 *
 | 
			
		||||
 * Default template: media-library-item.html.twig.
 | 
			
		||||
 *
 | 
			
		||||
 * @param array $variables
 | 
			
		||||
 *   An associative array containing:
 | 
			
		||||
 *   - element: An associative array containing the properties and children of
 | 
			
		||||
 *     the element.
 | 
			
		||||
 */
 | 
			
		||||
function template_preprocess_media_library_item(array &$variables): void {
 | 
			
		||||
  $element = &$variables['element'];
 | 
			
		||||
  foreach (Element::children($element) as $key) {
 | 
			
		||||
    $variables['content'][$key] = $element[$key];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_preprocess_media().
 | 
			
		||||
 */
 | 
			
		||||
function media_library_preprocess_media(&$variables): void {
 | 
			
		||||
  if ($variables['view_mode'] === 'media_library') {
 | 
			
		||||
    /** @var \Drupal\media\MediaInterface $media */
 | 
			
		||||
    $media = $variables['media'];
 | 
			
		||||
    $variables['#cache']['contexts'][] = 'user.permissions';
 | 
			
		||||
    $rel = $media->access('edit') ? 'edit-form' : 'canonical';
 | 
			
		||||
    $variables['url'] = $media->toUrl($rel, [
 | 
			
		||||
      'language' => $media->language(),
 | 
			
		||||
    ]);
 | 
			
		||||
    $variables += [
 | 
			
		||||
      'preview_attributes' => new Attribute(),
 | 
			
		||||
      'metadata_attributes' => new Attribute(),
 | 
			
		||||
    ];
 | 
			
		||||
    $variables['status'] = $media->isPublished();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_preprocess_views_view() for the 'media_library' view.
 | 
			
		||||
 */
 | 
			
		||||
function media_library_preprocess_views_view__media_library(array &$variables): void {
 | 
			
		||||
  $variables['attributes']['data-view-display-id'] = $variables['view']->current_display;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_preprocess_views_view_fields().
 | 
			
		||||
 */
 | 
			
		||||
function media_library_preprocess_views_view_fields(&$variables): void {
 | 
			
		||||
  // Add classes to media rendered entity field so it can be targeted for
 | 
			
		||||
  // JavaScript mouseover and click events.
 | 
			
		||||
  if ($variables['view']->id() === 'media_library' && isset($variables['fields']['rendered_entity'])) {
 | 
			
		||||
    if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
 | 
			
		||||
      $variables['fields']['rendered_entity']->wrapper_attributes->addClass('js-click-to-select-trigger');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Form #after_build callback for media_library view's exposed filters form.
 | 
			
		||||
 */
 | 
			
		||||
function _media_library_views_form_media_library_after_build(array $form, FormStateInterface $form_state) {
 | 
			
		||||
  // Remove .form-actions from the view's exposed filter actions. This prevents
 | 
			
		||||
  // the "Apply filters" submit button from being moved into the dialog's
 | 
			
		||||
  // button area.
 | 
			
		||||
  // @see \Drupal\Core\Render\Element\Actions::processActions
 | 
			
		||||
  // @see Drupal.behaviors.dialog.prepareDialogButtons
 | 
			
		||||
  // @todo Remove this after
 | 
			
		||||
  //   https://www.drupal.org/project/drupal/issues/3089751 is fixed.
 | 
			
		||||
  if (($key = array_search('form-actions', $form['actions']['#attributes']['class'])) !== FALSE) {
 | 
			
		||||
    unset($form['actions']['#attributes']['class'][$key]);
 | 
			
		||||
  }
 | 
			
		||||
  return $form;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Submit callback for media type form.
 | 
			
		||||
 */
 | 
			
		||||
function _media_library_media_type_form_submit(array &$form, FormStateInterface $form_state): void {
 | 
			
		||||
  $form_object = $form_state->getFormObject();
 | 
			
		||||
  if ($form_object->getOperation() === 'add') {
 | 
			
		||||
    $type = $form_object->getEntity();
 | 
			
		||||
    $form_display_created = _media_library_configure_form_display($type);
 | 
			
		||||
    $view_display_created = _media_library_configure_view_display($type);
 | 
			
		||||
    if ($form_display_created || $view_display_created) {
 | 
			
		||||
      \Drupal::messenger()->addStatus(t('Media Library form and view displays have been created for the %type media type.', [
 | 
			
		||||
        '%type' => $type->label(),
 | 
			
		||||
      ]));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ensures that the given media type has a media_library form display.
 | 
			
		||||
 *
 | 
			
		||||
 * @param \Drupal\media\MediaTypeInterface $type
 | 
			
		||||
 *   The media type to configure.
 | 
			
		||||
 *
 | 
			
		||||
 * @return bool
 | 
			
		||||
 *   Whether a form display has been created or not.
 | 
			
		||||
 *
 | 
			
		||||
 * @throws \Drupal\Core\Entity\EntityStorageException
 | 
			
		||||
 */
 | 
			
		||||
function _media_library_configure_form_display(MediaTypeInterface $type) {
 | 
			
		||||
  $display = EntityFormDisplay::load('media.' . $type->id() . '.media_library');
 | 
			
		||||
 | 
			
		||||
  if ($display) {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $values = [
 | 
			
		||||
    'targetEntityType' => 'media',
 | 
			
		||||
    'bundle' => $type->id(),
 | 
			
		||||
    'mode' => 'media_library',
 | 
			
		||||
    'status' => TRUE,
 | 
			
		||||
  ];
 | 
			
		||||
  $display = EntityFormDisplay::create($values);
 | 
			
		||||
  // Remove all default components.
 | 
			
		||||
  foreach (array_keys($display->getComponents()) as $name) {
 | 
			
		||||
    $display->removeComponent($name);
 | 
			
		||||
  }
 | 
			
		||||
  // Expose the name field when it is not mapped.
 | 
			
		||||
  if (!in_array('name', $type->getFieldMap(), TRUE)) {
 | 
			
		||||
    $display->setComponent('name', [
 | 
			
		||||
      'type' => 'string_textfield',
 | 
			
		||||
      'settings' => [
 | 
			
		||||
        'size' => 60,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
  // If the source field is an image field, expose it so that users can set alt
 | 
			
		||||
  // and title text.
 | 
			
		||||
  $source_field = $type->getSource()->getSourceFieldDefinition($type);
 | 
			
		||||
  if ($source_field->isDisplayConfigurable('form') && is_a($source_field->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
 | 
			
		||||
    $type->getSource()->prepareFormDisplay($type, $display);
 | 
			
		||||
  }
 | 
			
		||||
  return (bool) $display->save();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ensures that the given media type has a media_library view display.
 | 
			
		||||
 *
 | 
			
		||||
 * @param \Drupal\media\MediaTypeInterface $type
 | 
			
		||||
 *   The media type to configure.
 | 
			
		||||
 *
 | 
			
		||||
 * @return bool
 | 
			
		||||
 *   Whether a view display has been created or not.
 | 
			
		||||
 *
 | 
			
		||||
 * @throws \Drupal\Core\Entity\EntityStorageException
 | 
			
		||||
 */
 | 
			
		||||
function _media_library_configure_view_display(MediaTypeInterface $type) {
 | 
			
		||||
  $display = EntityViewDisplay::load('media.' . $type->id() . '.media_library');
 | 
			
		||||
 | 
			
		||||
  if ($display) {
 | 
			
		||||
    return FALSE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $values = [
 | 
			
		||||
    'targetEntityType' => 'media',
 | 
			
		||||
    'bundle' => $type->id(),
 | 
			
		||||
    'mode' => 'media_library',
 | 
			
		||||
    'status' => TRUE,
 | 
			
		||||
  ];
 | 
			
		||||
  $display = EntityViewDisplay::create($values);
 | 
			
		||||
  // Remove all default components.
 | 
			
		||||
  foreach (array_keys($display->getComponents()) as $name) {
 | 
			
		||||
    $display->removeComponent($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // @todo Remove dependency on 'medium' and 'thumbnail' image styles from
 | 
			
		||||
  //   media and media library modules.
 | 
			
		||||
  //   https://www.drupal.org/project/drupal/issues/3030437
 | 
			
		||||
  $image_style = ImageStyle::load('medium');
 | 
			
		||||
 | 
			
		||||
  // Expose the thumbnail component. If the medium image style doesn't exist,
 | 
			
		||||
  // use the fallback 'media_library' image style.
 | 
			
		||||
  $display->setComponent('thumbnail', [
 | 
			
		||||
    'type' => 'image',
 | 
			
		||||
    'label' => 'hidden',
 | 
			
		||||
    'settings' => [
 | 
			
		||||
      'image_style' => $image_style ? $image_style->id() : 'media_library',
 | 
			
		||||
      'image_link' => '',
 | 
			
		||||
    ],
 | 
			
		||||
  ]);
 | 
			
		||||
  return (bool) $display->save();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								web/core/modules/media_library/media_library.post_update.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								web/core/modules/media_library/media_library.post_update.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Post update functions for Media Library.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implements hook_removed_post_updates().
 | 
			
		||||
 */
 | 
			
		||||
function media_library_removed_post_updates(): array {
 | 
			
		||||
  return [
 | 
			
		||||
    'media_library_post_update_display_modes' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_table_display' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_add_media_library_image_style' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_add_status_extra_filter' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_add_buttons_to_page_view' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_update_8001_checkbox_classes' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_default_administrative_list_to_table_display' => '9.0.0',
 | 
			
		||||
    'media_library_post_update_add_langcode_filters' => '9.0.0',
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								web/core/modules/media_library/media_library.routing.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								web/core/modules/media_library/media_library.routing.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
media_library.ui:
 | 
			
		||||
  path: '/media-library'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: 'media_library.ui_builder:buildUi'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _custom_access: 'media_library.ui_builder:checkAccess'
 | 
			
		||||
 | 
			
		||||
media_library.settings:
 | 
			
		||||
  path: '/admin/config/media/media-library'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _form: '\Drupal\media_library\Form\SettingsForm'
 | 
			
		||||
    _title: 'Media Library settings'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _permission: 'administer media'
 | 
			
		||||
							
								
								
									
										23
									
								
								web/core/modules/media_library/media_library.services.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/core/modules/media_library/media_library.services.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
parameters:
 | 
			
		||||
  media_library.skip_procedural_hook_scan: false
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  _defaults:
 | 
			
		||||
    autoconfigure: true
 | 
			
		||||
  media_library.ui_builder:
 | 
			
		||||
    class: Drupal\media_library\MediaLibraryUiBuilder
 | 
			
		||||
    arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder', '@media_library.opener_resolver']
 | 
			
		||||
  Drupal\media_library\MediaLibraryUiBuilder: '@media_library.ui_builder'
 | 
			
		||||
  media_library.route_subscriber:
 | 
			
		||||
    class: Drupal\media_library\Routing\RouteSubscriber
 | 
			
		||||
  media_library.opener_resolver:
 | 
			
		||||
    class: Drupal\media_library\OpenerResolver
 | 
			
		||||
    tags:
 | 
			
		||||
      - { name: service_collector, tag: media_library.opener, call: addOpener }
 | 
			
		||||
  Drupal\media_library\OpenerResolverInterface: '@media_library.opener_resolver'
 | 
			
		||||
  media_library.opener.field_widget:
 | 
			
		||||
    class: Drupal\media_library\MediaLibraryFieldWidgetOpener
 | 
			
		||||
    arguments: ['@entity_type.manager']
 | 
			
		||||
  media_library.opener.editor:
 | 
			
		||||
    class: Drupal\media_library\MediaLibraryEditorOpener
 | 
			
		||||
    arguments: ['@entity_type.manager']
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Ajax;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\CommandInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AJAX command for adding media items to the media library selection.
 | 
			
		||||
 *
 | 
			
		||||
 * This command instructs the client to add the given media item IDs to the
 | 
			
		||||
 * current selection of the media library stored in
 | 
			
		||||
 * Drupal.MediaLibrary.currentSelection.
 | 
			
		||||
 *
 | 
			
		||||
 * This command is implemented by
 | 
			
		||||
 * Drupal.AjaxCommands.prototype.updateMediaLibrarySelection() defined in
 | 
			
		||||
 * media_library.ui.js.
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup ajax
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This is an internal part of Media Library and may be subject to change in
 | 
			
		||||
 *   minor releases. External code should not instantiate or extend this class.
 | 
			
		||||
 */
 | 
			
		||||
class UpdateSelectionCommand implements CommandInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * An array of media IDs to add to the current selection.
 | 
			
		||||
   *
 | 
			
		||||
   * @var int[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $mediaIds;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs an UpdateSelectionCommand object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int[] $media_ids
 | 
			
		||||
   *   An array of media IDs to add to the current selection.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $media_ids) {
 | 
			
		||||
    $this->mediaIds = $media_ids;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function render() {
 | 
			
		||||
    return [
 | 
			
		||||
      'command' => 'updateMediaLibrarySelection',
 | 
			
		||||
      'mediaIds' => $this->mediaIds,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										900
									
								
								web/core/modules/media_library/src/Form/AddFormBase.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										900
									
								
								web/core/modules/media_library/src/Form/AddFormBase.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,900 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxResponse;
 | 
			
		||||
use Drupal\Core\Ajax\CloseDialogCommand;
 | 
			
		||||
use Drupal\Core\Ajax\FocusFirstCommand;
 | 
			
		||||
use Drupal\Core\Ajax\InvokeCommand;
 | 
			
		||||
use Drupal\Core\Ajax\MessageCommand;
 | 
			
		||||
use Drupal\Core\Ajax\ReplaceCommand;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
 | 
			
		||||
use Drupal\Core\Entity\EntityStorageInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Form\BaseFormIdInterface;
 | 
			
		||||
use Drupal\Core\Form\FormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
use Drupal\Core\Security\TrustedCallbackInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\media\MediaInterface;
 | 
			
		||||
use Drupal\media\MediaTypeInterface;
 | 
			
		||||
use Drupal\media_library\Ajax\UpdateSelectionCommand;
 | 
			
		||||
use Drupal\media_library\MediaLibraryUiBuilder;
 | 
			
		||||
use Drupal\media_library\OpenerResolverInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a base class for creating media items from within the media library.
 | 
			
		||||
 */
 | 
			
		||||
abstract class AddFormBase extends FormBase implements BaseFormIdInterface, TrustedCallbackInterface, WorkspaceSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The media library UI builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\media_library\MediaLibraryUiBuilder
 | 
			
		||||
   */
 | 
			
		||||
  protected $libraryUiBuilder;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The type of media items being created by this form.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\media\MediaTypeInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $mediaType;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The media view builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityViewBuilderInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $viewBuilder;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The opener resolver.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\media_library\OpenerResolverInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $openerResolver;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs an AddFormBase object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryUiBuilder $library_ui_builder
 | 
			
		||||
   *   The media library UI builder.
 | 
			
		||||
   * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
 | 
			
		||||
   *   The opener resolver.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, MediaLibraryUiBuilder $library_ui_builder, OpenerResolverInterface $opener_resolver) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->libraryUiBuilder = $library_ui_builder;
 | 
			
		||||
    $this->viewBuilder = $this->entityTypeManager->getViewBuilder('media');
 | 
			
		||||
    $this->openerResolver = $opener_resolver;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('media_library.ui_builder'),
 | 
			
		||||
      $container->get('media_library.opener_resolver')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getBaseFormId() {
 | 
			
		||||
    return 'media_library_add_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the media type from the form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media\MediaTypeInterface
 | 
			
		||||
   *   The media type.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   If the selected media type does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getMediaType(FormStateInterface $form_state) {
 | 
			
		||||
    if ($this->mediaType) {
 | 
			
		||||
      return $this->mediaType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $state = $this->getMediaLibraryState($form_state);
 | 
			
		||||
    $selected_type_id = $state->getSelectedTypeId();
 | 
			
		||||
    $this->mediaType = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id);
 | 
			
		||||
 | 
			
		||||
    if (!$this->mediaType) {
 | 
			
		||||
      throw new \InvalidArgumentException("The '$selected_type_id' media type does not exist.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->mediaType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // @todo Remove the ID when we can use selectors to replace content via
 | 
			
		||||
    //   AJAX in https://www.drupal.org/project/drupal/issues/2821793.
 | 
			
		||||
    $form['#prefix'] = '<div id="media-library-add-form-wrapper">';
 | 
			
		||||
    $form['#suffix'] = '</div>';
 | 
			
		||||
 | 
			
		||||
    // The media library is loaded via AJAX, which means that the form action
 | 
			
		||||
    // URL defaults to the current URL. However, to add media, we always need to
 | 
			
		||||
    // submit the form to the media library URL, not whatever the current URL
 | 
			
		||||
    // may be.
 | 
			
		||||
    $form['#action'] = Url::fromRoute('media_library.ui', [], [
 | 
			
		||||
      'query' => $this->getMediaLibraryState($form_state)->all(),
 | 
			
		||||
    ])->toString();
 | 
			
		||||
 | 
			
		||||
    // The form is posted via AJAX. When there are messages set during the
 | 
			
		||||
    // validation or submission of the form, the messages need to be shown to
 | 
			
		||||
    // the user.
 | 
			
		||||
    $form['status_messages'] = [
 | 
			
		||||
      '#type' => 'status_messages',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['#attributes']['class'] = [
 | 
			
		||||
      'js-media-library-add-form',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $added_media = $this->getAddedMediaItems($form_state);
 | 
			
		||||
    if (empty($added_media)) {
 | 
			
		||||
      $form = $this->buildInputElement($form, $form_state);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $form['#attributes']['data-input'] = 'true';
 | 
			
		||||
 | 
			
		||||
      // This deserves to be themeable, but it doesn't need to be its own "real"
 | 
			
		||||
      // template.
 | 
			
		||||
      $form['description'] = [
 | 
			
		||||
        '#type' => 'inline_template',
 | 
			
		||||
        '#template' => '<p>{{ text }}</p>',
 | 
			
		||||
        '#context' => [
 | 
			
		||||
          'text' => $this->formatPlural(count($added_media), 'The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.', 'The media items have been created but have not yet been saved. Fill in any required fields and save to add them to the media library.'),
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      $form['media'] = [
 | 
			
		||||
        '#pre_render' => [
 | 
			
		||||
          [$this, 'preRenderAddedMedia'],
 | 
			
		||||
        ],
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'class' => [
 | 
			
		||||
            // This needs to be focus-able by an AJAX response.
 | 
			
		||||
            // @see ::updateFormCallback()
 | 
			
		||||
            'js-media-library-add-form-added-media',
 | 
			
		||||
          ],
 | 
			
		||||
          'aria-label' => $this->t('Added media items'),
 | 
			
		||||
          // Add the tabindex '-1' to allow the focus to be shifted to the added
 | 
			
		||||
          // media wrapper when items are added. We set focus to the container
 | 
			
		||||
          // because a media item does not necessarily have required fields and
 | 
			
		||||
          // we do not want to set focus to the remove button automatically.
 | 
			
		||||
          // @see ::updateFormCallback()
 | 
			
		||||
          'tabindex' => '-1',
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      foreach ($added_media as $delta => $media) {
 | 
			
		||||
        $form['media'][$delta] = $this->buildEntityFormElement($media, $form, $form_state, $delta);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $form['selection'] = $this->buildCurrentSelectionArea($form, $form_state);
 | 
			
		||||
      $form['actions'] = $this->buildActions($form, $form_state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Allow the current selection to be set in a hidden field so the selection
 | 
			
		||||
    // can be passed between different states of the form. This field is filled
 | 
			
		||||
    // via JavaScript so the default value should be empty.
 | 
			
		||||
    // @see Drupal.behaviors.MediaLibraryItemSelection
 | 
			
		||||
    $form['current_selection'] = [
 | 
			
		||||
      '#type' => 'hidden',
 | 
			
		||||
      '#default_value' => '',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => [
 | 
			
		||||
          'js-media-library-add-form-current-selection',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the element for submitting source field value(s).
 | 
			
		||||
   *
 | 
			
		||||
   * The input element needs to have a submit handler to create media items from
 | 
			
		||||
   * the user input and store them in the form state using
 | 
			
		||||
   * ::processInputValues().
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The complete form, with the element added.
 | 
			
		||||
   *
 | 
			
		||||
   * @see ::processInputValues()
 | 
			
		||||
   */
 | 
			
		||||
  abstract protected function buildInputElement(array $form, FormStateInterface $form_state);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the sub-form for setting required fields on a new media item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaInterface $media
 | 
			
		||||
   *   A new, unsaved media item.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the media item.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The element containing the required fields sub-form.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildEntityFormElement(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
 | 
			
		||||
    // We need to make sure each button has a unique name attribute. The default
 | 
			
		||||
    // name for button elements is 'op'. If the name is not unique, the
 | 
			
		||||
    // triggering element is not set correctly and the wrong media item is
 | 
			
		||||
    // removed.
 | 
			
		||||
    // @see ::removeButtonSubmit()
 | 
			
		||||
    $parents = $form['#parents'] ?? [];
 | 
			
		||||
    $id_suffix = $parents ? '-' . implode('-', $parents) : '';
 | 
			
		||||
 | 
			
		||||
    $element = [
 | 
			
		||||
      '#wrapper_attributes' => [
 | 
			
		||||
        'aria-label' => $media->getName(),
 | 
			
		||||
        // Add the tabindex '-1' to allow the focus to be shifted to the next
 | 
			
		||||
        // media item when an item is removed. We set focus to the container
 | 
			
		||||
        // because a media item does not necessarily have required fields and we
 | 
			
		||||
        // do not want to set focus to the remove button automatically.
 | 
			
		||||
        // @see ::updateFormCallback()
 | 
			
		||||
        'tabindex' => '-1',
 | 
			
		||||
        // Add a data attribute containing the delta to allow us to easily shift
 | 
			
		||||
        // the focus to a specific media item.
 | 
			
		||||
        // @see ::updateFormCallback()
 | 
			
		||||
        'data-media-library-added-delta' => $delta,
 | 
			
		||||
      ],
 | 
			
		||||
      'preview' => [
 | 
			
		||||
        '#type' => 'container',
 | 
			
		||||
        '#weight' => 10,
 | 
			
		||||
      ],
 | 
			
		||||
      'fields' => [
 | 
			
		||||
        '#type' => 'container',
 | 
			
		||||
        '#weight' => 20,
 | 
			
		||||
        // The '#parents' are set here because the entity form display needs it
 | 
			
		||||
        // to build the entity form fields.
 | 
			
		||||
        '#parents' => ['media', $delta, 'fields'],
 | 
			
		||||
      ],
 | 
			
		||||
      'remove_button' => [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#value' => $this->t('Remove'),
 | 
			
		||||
        '#name' => 'media-' . $delta . '-remove-button' . $id_suffix,
 | 
			
		||||
        '#weight' => 30,
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'aria-label' => $this->t('Remove @label', ['@label' => $media->getName()]),
 | 
			
		||||
        ],
 | 
			
		||||
        '#ajax' => [
 | 
			
		||||
          'callback' => '::updateFormCallback',
 | 
			
		||||
          'wrapper' => 'media-library-add-form-wrapper',
 | 
			
		||||
          'message' => $this->t('Removing @label.', ['@label' => $media->getName()]),
 | 
			
		||||
        ],
 | 
			
		||||
        '#submit' => ['::removeButtonSubmit'],
 | 
			
		||||
        // Ensure errors in other media items do not prevent removal.
 | 
			
		||||
        '#limit_validation_errors' => [],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    // @todo Make the image style configurable in
 | 
			
		||||
    //   https://www.drupal.org/node/2988223
 | 
			
		||||
    $source = $media->getSource();
 | 
			
		||||
    $plugin_definition = $source->getPluginDefinition();
 | 
			
		||||
    if ($thumbnail_uri = $source->getMetadata($media, $plugin_definition['thumbnail_uri_metadata_attribute'])) {
 | 
			
		||||
      $element['preview']['thumbnail'] = [
 | 
			
		||||
        '#theme' => 'image_style',
 | 
			
		||||
        '#style_name' => 'media_library',
 | 
			
		||||
        '#uri' => $thumbnail_uri,
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
 | 
			
		||||
    // When the name is not added to the form as an editable field, output
 | 
			
		||||
    // the name as a fixed element to confirm the right file was uploaded.
 | 
			
		||||
    if (!$form_display->getComponent('name')) {
 | 
			
		||||
      $element['fields']['name'] = [
 | 
			
		||||
        '#type' => 'item',
 | 
			
		||||
        '#title' => $this->t('Name'),
 | 
			
		||||
        '#markup' => $media->getName(),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    $form_display->buildForm($media, $element['fields'], $form_state);
 | 
			
		||||
 | 
			
		||||
    // Add source field name so that it can be identified in form alter and
 | 
			
		||||
    // widget alter hooks.
 | 
			
		||||
    $element['fields']['#source_field_name'] = $this->getSourceFieldName($media->bundle->entity);
 | 
			
		||||
 | 
			
		||||
    // The revision log field is currently not configurable from the form
 | 
			
		||||
    // display, so hide it by changing the access.
 | 
			
		||||
    // @todo Make the revision_log_message field configurable in
 | 
			
		||||
    //   https://www.drupal.org/project/drupal/issues/2696555
 | 
			
		||||
    if (isset($element['fields']['revision_log_message'])) {
 | 
			
		||||
      $element['fields']['revision_log_message']['#access'] = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function trustedCallbacks() {
 | 
			
		||||
    return ['preRenderAddedMedia'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Converts the set of newly added media into an item list for rendering.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element
 | 
			
		||||
   *   The render element to transform.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The transformed render element.
 | 
			
		||||
   */
 | 
			
		||||
  public function preRenderAddedMedia(array $element) {
 | 
			
		||||
    // Transform the element into an item list for rendering.
 | 
			
		||||
    $element['#theme'] = 'item_list__media_library_add_form_media_list';
 | 
			
		||||
    $element['#list_type'] = 'ul';
 | 
			
		||||
 | 
			
		||||
    foreach (Element::children($element) as $delta) {
 | 
			
		||||
      $element['#items'][$delta] = $element[$delta];
 | 
			
		||||
      unset($element[$delta]);
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a render array containing the current selection.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array containing the current selection.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildCurrentSelectionArea(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $pre_selected_items = $this->getPreSelectedMediaItems($form_state);
 | 
			
		||||
 | 
			
		||||
    if (!$pre_selected_items || !$this->isAdvancedUi()) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $selection = [
 | 
			
		||||
      '#type' => 'details',
 | 
			
		||||
      '#theme_wrappers' => [
 | 
			
		||||
        'details__media_library_add_form_selected_media',
 | 
			
		||||
      ],
 | 
			
		||||
      '#open' => FALSE,
 | 
			
		||||
      '#title' => $this->t('Additional selected media'),
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($pre_selected_items as $media_id => $media) {
 | 
			
		||||
      $selection[$media_id] = $this->buildSelectedItemElement($media, $form, $form_state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $selection;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a render array for a single pre-selected media item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaInterface $media
 | 
			
		||||
   *   The media item.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   A render array of a pre-selected media item.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildSelectedItemElement(MediaInterface $media, array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return [
 | 
			
		||||
      '#theme' => 'media_library_item__small',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => [
 | 
			
		||||
          'js-media-library-item',
 | 
			
		||||
          'js-click-to-select',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'select' => [
 | 
			
		||||
        '#type' => 'container',
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'class' => [
 | 
			
		||||
            'js-click-to-select-checkbox',
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
        'select_checkbox' => [
 | 
			
		||||
          '#type' => 'checkbox',
 | 
			
		||||
          '#title' => $this->t('Select @name', ['@name' => $media->label()]),
 | 
			
		||||
          '#title_display' => 'invisible',
 | 
			
		||||
          '#return_value' => $media->id(),
 | 
			
		||||
          // The checkbox's value is never processed by this form. It is present
 | 
			
		||||
          // for usability and accessibility reasons, and only used by
 | 
			
		||||
          // JavaScript to track whether or not this media item is selected. The
 | 
			
		||||
          // hidden 'current_selection' field is used to store the actual IDs of
 | 
			
		||||
          // selected media items.
 | 
			
		||||
          '#value' => FALSE,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      'rendered_entity' => $this->viewBuilder->view($media, 'media_library'),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns an array of supported actions for the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An actions element containing the actions of the form.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildActions(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $actions = [
 | 
			
		||||
      '#type' => 'actions',
 | 
			
		||||
      'save_select' => [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#button_type' => 'primary',
 | 
			
		||||
        '#value' => $this->t('Save'),
 | 
			
		||||
        '#ajax' => [
 | 
			
		||||
          'callback' => '::updateLibrary',
 | 
			
		||||
          'wrapper' => 'media-library-add-form-wrapper',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    if ($this->isAdvancedUi()) {
 | 
			
		||||
      $actions['save_select']['#value'] = $this->t('Save and select');
 | 
			
		||||
      $actions['save_insert'] = [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#value' => $this->t('Save and insert'),
 | 
			
		||||
        '#ajax' => [
 | 
			
		||||
          'callback' => '::updateWidget',
 | 
			
		||||
          'wrapper' => 'media-library-add-form-wrapper',
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    return $actions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates media items from source field input values.
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed[] $source_field_values
 | 
			
		||||
   *   The values for source fields of the media items.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   */
 | 
			
		||||
  protected function processInputValues(array $source_field_values, array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $media_type = $this->getMediaType($form_state);
 | 
			
		||||
    $media_storage = $this->entityTypeManager->getStorage('media');
 | 
			
		||||
    $source_field_name = $this->getSourceFieldName($media_type);
 | 
			
		||||
    $media = array_map(function ($source_field_value) use ($media_type, $media_storage, $source_field_name) {
 | 
			
		||||
      return $this->createMediaFromValue($media_type, $media_storage, $source_field_name, $source_field_value);
 | 
			
		||||
    }, $source_field_values);
 | 
			
		||||
    // Re-key the media items before setting them in the form state.
 | 
			
		||||
    $form_state->set('media', array_values($media));
 | 
			
		||||
    // Save the selected items in the form state so they are remembered when an
 | 
			
		||||
    // item is removed.
 | 
			
		||||
    $media = $this->entityTypeManager->getStorage('media')
 | 
			
		||||
      ->loadMultiple(explode(',', $form_state->getValue('current_selection')));
 | 
			
		||||
    // Any ID can be passed to the form, so we have to check access.
 | 
			
		||||
    $form_state->set('current_selection', array_filter($media, function ($media_item) {
 | 
			
		||||
      return $media_item->access('view');
 | 
			
		||||
    }));
 | 
			
		||||
    $form_state->setRebuild();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new, unsaved media item from a source field value.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaTypeInterface $media_type
 | 
			
		||||
   *   The media type of the media item.
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityStorageInterface $media_storage
 | 
			
		||||
   *   The media storage.
 | 
			
		||||
   * @param string $source_field_name
 | 
			
		||||
   *   The name of the media type's source field.
 | 
			
		||||
   * @param mixed $source_field_value
 | 
			
		||||
   *   The value for the source field of the media item.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media\MediaInterface
 | 
			
		||||
   *   An unsaved media entity.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $source_field_value) {
 | 
			
		||||
    $media = $media_storage->create([
 | 
			
		||||
      'bundle' => $media_type->id(),
 | 
			
		||||
      $source_field_name => $source_field_value,
 | 
			
		||||
    ]);
 | 
			
		||||
    $media->setName($media->getName());
 | 
			
		||||
    return $media;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Prepares a created media item to be permanently saved.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaInterface $media
 | 
			
		||||
   *   The unsaved media item.
 | 
			
		||||
   */
 | 
			
		||||
  protected function prepareMediaEntityForSave(MediaInterface $media) {
 | 
			
		||||
    // Intentionally empty by default.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit handler for the remove button.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form render array.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function removeButtonSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // Retrieve the delta of the media item from the parents of the remove
 | 
			
		||||
    // button.
 | 
			
		||||
    $triggering_element = $form_state->getTriggeringElement();
 | 
			
		||||
    $delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
 | 
			
		||||
 | 
			
		||||
    $added_media = $form_state->get('media');
 | 
			
		||||
    $removed_media = $added_media[$delta];
 | 
			
		||||
 | 
			
		||||
    // Update the list of added media items in the form state.
 | 
			
		||||
    unset($added_media[$delta]);
 | 
			
		||||
 | 
			
		||||
    // Update the media items in the form state.
 | 
			
		||||
    $form_state->set('media', $added_media)->setRebuild();
 | 
			
		||||
 | 
			
		||||
    // Show a message to the user to confirm the media is removed.
 | 
			
		||||
    $this->messenger()->addStatus($this->t('The media item %label has been removed.', ['%label' => $removed_media->label()]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * AJAX callback to update the entire form based on source field input.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Ajax\AjaxResponse|array
 | 
			
		||||
   *   The form render array or an AJAX response object.
 | 
			
		||||
   */
 | 
			
		||||
  public function updateFormCallback(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $triggering_element = $form_state->getTriggeringElement();
 | 
			
		||||
    $wrapper_id = $triggering_element['#ajax']['wrapper'];
 | 
			
		||||
    $added_media = $form_state->get('media');
 | 
			
		||||
 | 
			
		||||
    $response = new AjaxResponse();
 | 
			
		||||
 | 
			
		||||
    // When the source field input contains errors, replace the existing form to
 | 
			
		||||
    // let the user change the source field input. If the user input is valid,
 | 
			
		||||
    // the entire modal is replaced with the second step of the form to show the
 | 
			
		||||
    // form fields for each media item.
 | 
			
		||||
    if ($form_state::hasAnyErrors()) {
 | 
			
		||||
      $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $form));
 | 
			
		||||
      return $response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if the remove button is clicked.
 | 
			
		||||
    if (end($triggering_element['#parents']) === 'remove_button') {
 | 
			
		||||
      // When the list of added media is empty, return to the media library and
 | 
			
		||||
      // shift focus back to the first tabbable element (which should be the
 | 
			
		||||
      // source field).
 | 
			
		||||
      if (empty($added_media)) {
 | 
			
		||||
        $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $this->buildMediaLibraryUi($form_state)));
 | 
			
		||||
        $response->addCommand(new FocusFirstCommand('#media-library-add-form-wrapper'));
 | 
			
		||||
      }
 | 
			
		||||
      // When there are still more items, update the form and shift the focus to
 | 
			
		||||
      // the next media item. If the last list item is removed, shift focus to
 | 
			
		||||
      // the previous item.
 | 
			
		||||
      else {
 | 
			
		||||
        $response->addCommand(new ReplaceCommand("#$wrapper_id", $form));
 | 
			
		||||
 | 
			
		||||
        // Find the delta of the next media item. If there is no item with a
 | 
			
		||||
        // bigger delta, we automatically use the delta of the previous item and
 | 
			
		||||
        // shift the focus there.
 | 
			
		||||
        $removed_delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
 | 
			
		||||
        $delta_to_focus = 0;
 | 
			
		||||
        foreach ($added_media as $delta => $media) {
 | 
			
		||||
          $delta_to_focus = $delta;
 | 
			
		||||
          if ($delta > $removed_delta) {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        $response->addCommand(new InvokeCommand("[data-media-library-added-delta=$delta_to_focus]", 'focus'));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Update the form and shift focus to the added media items.
 | 
			
		||||
    else {
 | 
			
		||||
      $response->addCommand(new ReplaceCommand("#$wrapper_id", $form));
 | 
			
		||||
      $response->addCommand(new InvokeCommand('.js-media-library-add-form-added-media', 'focus'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    foreach ($this->getAddedMediaItems($form_state) as $delta => $media) {
 | 
			
		||||
      $this->validateMediaEntity($media, $form, $form_state, $delta);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validate a created media item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaInterface $media
 | 
			
		||||
   *   The media item to validate.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   * @param int $delta
 | 
			
		||||
   *   The delta of the media item.
 | 
			
		||||
   */
 | 
			
		||||
  protected function validateMediaEntity(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
 | 
			
		||||
    $form_display = EntityFormDisplay::collectRenderDisplay($media, 'media_library');
 | 
			
		||||
    $form_display->extractFormValues($media, $form['media'][$delta]['fields'], $form_state);
 | 
			
		||||
    $form_display->validateFormValues($media, $form['media'][$delta]['fields'], $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    foreach ($this->getAddedMediaItems($form_state) as $delta => $media) {
 | 
			
		||||
      EntityFormDisplay::collectRenderDisplay($media, 'media_library')
 | 
			
		||||
        ->extractFormValues($media, $form['media'][$delta]['fields'], $form_state);
 | 
			
		||||
      $this->prepareMediaEntityForSave($media);
 | 
			
		||||
      $media->save();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * AJAX callback to send the new media item(s) to the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|\Drupal\Core\Ajax\AjaxResponse
 | 
			
		||||
   *   The form array if there are validation errors, or an AJAX response to add
 | 
			
		||||
   *   the created items to the current selection.
 | 
			
		||||
   */
 | 
			
		||||
  public function updateLibrary(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    if ($form_state::hasAnyErrors()) {
 | 
			
		||||
      return $form;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $media_ids = array_map(function (MediaInterface $media) {
 | 
			
		||||
      return $media->id();
 | 
			
		||||
    }, $this->getAddedMediaItems($form_state));
 | 
			
		||||
 | 
			
		||||
    $selected_count = $this->getSelectedMediaItemCount($media_ids, $form_state);
 | 
			
		||||
 | 
			
		||||
    $response = new AjaxResponse();
 | 
			
		||||
    $response->addCommand(new UpdateSelectionCommand($media_ids));
 | 
			
		||||
    $media_id_to_focus = array_pop($media_ids);
 | 
			
		||||
    $response->addCommand(new ReplaceCommand('#media-library-add-form-wrapper', $this->buildMediaLibraryUi($form_state)));
 | 
			
		||||
    $response->addCommand(new InvokeCommand("#media-library-content [value=$media_id_to_focus]", 'focus'));
 | 
			
		||||
    $available_slots = $this->getMediaLibraryState($form_state)->getAvailableSlots();
 | 
			
		||||
    if ($available_slots > 0 && $selected_count > $available_slots) {
 | 
			
		||||
      $warning = $this->formatPlural($selected_count - $available_slots, 'There are currently @total items selected. The maximum number of items for the field is @max. Remove @count item from the selection.', 'There are currently @total items selected. The maximum number of items for the field is @max. Remove @count items from the selection.', [
 | 
			
		||||
        '@total' => $selected_count,
 | 
			
		||||
        '@max' => $available_slots,
 | 
			
		||||
      ]);
 | 
			
		||||
      $response->addCommand(new MessageCommand($warning, '#media-library-messages', ['type' => 'warning']));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Build the render array of the media library UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the media library.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMediaLibraryUi(FormStateInterface $form_state) {
 | 
			
		||||
    // Get the render array for the media library. The media library state might
 | 
			
		||||
    // contain the 'media_library_content' when it has been opened from a
 | 
			
		||||
    // vertical tab. We need to remove that to make sure the render array
 | 
			
		||||
    // contains the vertical tabs. Besides that, we also need to force the media
 | 
			
		||||
    // library to create a new instance of the media add form.
 | 
			
		||||
    // @see \Drupal\media_library\MediaLibraryUiBuilder::buildMediaTypeAddForm()
 | 
			
		||||
    $state = $this->getMediaLibraryState($form_state);
 | 
			
		||||
    $state->remove('media_library_content');
 | 
			
		||||
    $state->set('_media_library_form_rebuild', TRUE);
 | 
			
		||||
    return $this->libraryUiBuilder->buildUi($state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * AJAX callback to send the new media item(s) to the calling code.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array|\Drupal\Core\Ajax\AjaxResponse
 | 
			
		||||
   *   The form array when there are form errors or an AJAX response to select
 | 
			
		||||
   *   the created items in the media library.
 | 
			
		||||
   */
 | 
			
		||||
  public function updateWidget(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    if ($form_state::hasAnyErrors()) {
 | 
			
		||||
      return $form;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The added media items get an ID when they are saved in ::submitForm().
 | 
			
		||||
    // For that reason the added media items are keyed by delta in the form
 | 
			
		||||
    // state and we have to do an array map to get each media ID.
 | 
			
		||||
    $media_ids = array_map(function (MediaInterface $media) {
 | 
			
		||||
      return $media->id();
 | 
			
		||||
    }, $this->getCurrentMediaItems($form_state));
 | 
			
		||||
 | 
			
		||||
    // Allow the opener service to respond to the selection.
 | 
			
		||||
    $state = $this->getMediaLibraryState($form_state);
 | 
			
		||||
    $selected_count = $this->getSelectedMediaItemCount($media_ids, $form_state);
 | 
			
		||||
 | 
			
		||||
    $available_slots = $this->getMediaLibraryState($form_state)->getAvailableSlots();
 | 
			
		||||
    if ($available_slots > 0 && $selected_count > $available_slots) {
 | 
			
		||||
      // Return to library where we display a warning about the overage.
 | 
			
		||||
      return $this->updateLibrary($form, $form_state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->openerResolver->get($state)
 | 
			
		||||
      ->getSelectionResponse($state, $media_ids)
 | 
			
		||||
      ->addCommand(new CloseDialogCommand());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the number of selected media.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $media_ids
 | 
			
		||||
   *   Array with the media IDs.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The number of media currently selected.
 | 
			
		||||
   */
 | 
			
		||||
  private function getSelectedMediaItemCount(array $media_ids, FormStateInterface $form_state): int {
 | 
			
		||||
    $selected_count = count($media_ids);
 | 
			
		||||
    if ($current_selection = $form_state->getValue('current_selection')) {
 | 
			
		||||
      $selected_count += count(explode(',', $current_selection));
 | 
			
		||||
    }
 | 
			
		||||
    return $selected_count;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the media library state from the form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media_library\MediaLibraryState
 | 
			
		||||
   *   The media library state.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   If the media library state is not present in the form state.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getMediaLibraryState(FormStateInterface $form_state) {
 | 
			
		||||
    $state = $form_state->get('media_library_state');
 | 
			
		||||
    if (!$state) {
 | 
			
		||||
      throw new \InvalidArgumentException('The media library state is not present in the form state.');
 | 
			
		||||
    }
 | 
			
		||||
    return $state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the name of the source field for a media type.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaTypeInterface $media_type
 | 
			
		||||
   *   The media type to get the source field name for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The name of the media type's source field.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getSourceFieldName(MediaTypeInterface $media_type) {
 | 
			
		||||
    return $media_type->getSource()
 | 
			
		||||
      ->getSourceFieldDefinition($media_type)
 | 
			
		||||
      ->getName();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get all pre-selected media items from the form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media\MediaInterface[]
 | 
			
		||||
   *   An array containing the pre-selected media items keyed by ID.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getPreSelectedMediaItems(FormStateInterface $form_state) {
 | 
			
		||||
    // Get the pre-selected media items from the form state.
 | 
			
		||||
    // @see ::processInputValues()
 | 
			
		||||
    return $form_state->get('current_selection') ?: [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get all added media items from the form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media\MediaInterface[]
 | 
			
		||||
   *   An array containing the added media items keyed by delta. The media items
 | 
			
		||||
   *   won't have an ID until they are saved in ::submitForm().
 | 
			
		||||
   */
 | 
			
		||||
  protected function getAddedMediaItems(FormStateInterface $form_state) {
 | 
			
		||||
    return $form_state->get('media') ?: [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get all pre-selected and added media items from the form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media\MediaInterface[]
 | 
			
		||||
   *   An array containing all pre-selected and added media items with
 | 
			
		||||
   *   renumbered numeric keys.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getCurrentMediaItems(FormStateInterface $form_state) {
 | 
			
		||||
    $pre_selected_media = $this->getPreSelectedMediaItems($form_state);
 | 
			
		||||
    $added_media = $this->getAddedMediaItems($form_state);
 | 
			
		||||
    // Using array_merge will renumber the numeric keys.
 | 
			
		||||
    return array_merge($pre_selected_media, $added_media);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Determines if the "advanced UI" of the Media Library is enabled.
 | 
			
		||||
   *
 | 
			
		||||
   * This exposes additional features that are useful to power users.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool
 | 
			
		||||
   *   TRUE if the advanced UI is enabled, FALSE otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @see ::buildActions()
 | 
			
		||||
   * @see ::buildCurrentSelectionArea()
 | 
			
		||||
   */
 | 
			
		||||
  protected function isAdvancedUi() {
 | 
			
		||||
    return (bool) $this->config('media_library.settings')->get('advanced_ui');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										393
									
								
								web/core/modules/media_library/src/Form/FileUploadForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								web/core/modules/media_library/src/Form/FileUploadForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,393 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityStorageInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
 | 
			
		||||
use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
 | 
			
		||||
use Drupal\Core\File\Exception\FileWriteException;
 | 
			
		||||
use Drupal\Core\File\FileSystemInterface;
 | 
			
		||||
use Drupal\Core\Form\FormBuilderInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Render\ElementInfoManagerInterface;
 | 
			
		||||
use Drupal\Core\Render\RendererInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\file\FileRepositoryInterface;
 | 
			
		||||
use Drupal\file\FileInterface;
 | 
			
		||||
use Drupal\file\FileUsage\FileUsageInterface;
 | 
			
		||||
use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
 | 
			
		||||
use Drupal\file\Plugin\Field\FieldType\FileItem;
 | 
			
		||||
use Drupal\media\MediaInterface;
 | 
			
		||||
use Drupal\media\MediaTypeInterface;
 | 
			
		||||
use Drupal\media_library\MediaLibraryUiBuilder;
 | 
			
		||||
use Drupal\media_library\OpenerResolverInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a form to create media entities from uploaded files.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class FileUploadForm extends AddFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The element info manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Render\ElementInfoManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $elementInfo;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The renderer service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Render\ElementInfoManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $renderer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file system service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\File\FileSystemInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileSystem;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file usage service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\file\FileUsage\FileUsageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileUsage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The file repository service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\file\FileRepositoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $fileRepository;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new FileUploadForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryUiBuilder $library_ui_builder
 | 
			
		||||
   *   The media library UI builder.
 | 
			
		||||
   * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
 | 
			
		||||
   *   The element info manager.
 | 
			
		||||
   * @param \Drupal\Core\Render\RendererInterface $renderer
 | 
			
		||||
   *   The renderer service.
 | 
			
		||||
   * @param \Drupal\Core\File\FileSystemInterface $file_system
 | 
			
		||||
   *   The file system service.
 | 
			
		||||
   * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
 | 
			
		||||
   *   The opener resolver.
 | 
			
		||||
   * @param \Drupal\file\FileUsage\FileUsageInterface $file_usage
 | 
			
		||||
   *   The file usage service.
 | 
			
		||||
   * @param \Drupal\file\FileRepositoryInterface $file_repository
 | 
			
		||||
   *   The file repository service.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, MediaLibraryUiBuilder $library_ui_builder, ElementInfoManagerInterface $element_info, RendererInterface $renderer, FileSystemInterface $file_system, OpenerResolverInterface $opener_resolver, FileUsageInterface $file_usage, FileRepositoryInterface $file_repository) {
 | 
			
		||||
    parent::__construct($entity_type_manager, $library_ui_builder, $opener_resolver);
 | 
			
		||||
    $this->elementInfo = $element_info;
 | 
			
		||||
    $this->renderer = $renderer;
 | 
			
		||||
    $this->fileSystem = $file_system;
 | 
			
		||||
    $this->fileUsage = $file_usage;
 | 
			
		||||
    $this->fileRepository = $file_repository;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('media_library.ui_builder'),
 | 
			
		||||
      $container->get('element_info'),
 | 
			
		||||
      $container->get('renderer'),
 | 
			
		||||
      $container->get('file_system'),
 | 
			
		||||
      $container->get('media_library.opener_resolver'),
 | 
			
		||||
      $container->get('file.usage'),
 | 
			
		||||
      $container->get('file.repository')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return $this->getBaseFormId() . '_upload';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getMediaType(FormStateInterface $form_state) {
 | 
			
		||||
    if ($this->mediaType) {
 | 
			
		||||
      return $this->mediaType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $media_type = parent::getMediaType($form_state);
 | 
			
		||||
    // The file upload form only supports media types which use a file field as
 | 
			
		||||
    // a source field.
 | 
			
		||||
    $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
 | 
			
		||||
    if (!is_a($field_definition->getClass(), FileFieldItemList::class, TRUE)) {
 | 
			
		||||
      throw new \InvalidArgumentException('Can only add media types which use a file field as a source field.');
 | 
			
		||||
    }
 | 
			
		||||
    return $media_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildInputElement(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // Create a file item to get the upload validators.
 | 
			
		||||
    $media_type = $this->getMediaType($form_state);
 | 
			
		||||
    $item = $this->createFileItem($media_type);
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\media_library\MediaLibraryState $state */
 | 
			
		||||
    $state = $this->getMediaLibraryState($form_state);
 | 
			
		||||
    if (!$state->hasSlotsAvailable()) {
 | 
			
		||||
      return $form;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $slots = $state->getAvailableSlots();
 | 
			
		||||
 | 
			
		||||
    // Add a container to group the input elements for styling purposes.
 | 
			
		||||
    $form['container'] = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
 | 
			
		||||
    $form['container']['upload'] = [
 | 
			
		||||
      '#type' => 'managed_file',
 | 
			
		||||
      '#title' => $this->formatPlural($slots, 'Add file', 'Add files'),
 | 
			
		||||
      // @todo Move validation in https://www.drupal.org/node/2988215
 | 
			
		||||
      '#process' => array_merge(['::validateUploadElement'], $process, ['::processUploadElement']),
 | 
			
		||||
      '#upload_validators' => $item->getUploadValidators(),
 | 
			
		||||
      // Set multiple to true only if available slots is not exactly one
 | 
			
		||||
      // to ensure correct language (singular or plural) in UI
 | 
			
		||||
      '#multiple' => $slots != 1 ? TRUE : FALSE,
 | 
			
		||||
      // Do not limit the number uploaded. There is validation based on the
 | 
			
		||||
      // number selected in the media library that prevents overages.
 | 
			
		||||
      // @see Drupal\media_library\Form\AddFormBase::updateLibrary()
 | 
			
		||||
      '#cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
 | 
			
		||||
      '#remaining_slots' => $slots,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $file_upload_help = [
 | 
			
		||||
      '#theme' => 'file_upload_help',
 | 
			
		||||
      '#upload_validators' => $form['container']['upload']['#upload_validators'],
 | 
			
		||||
      '#cardinality' => $slots,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // The file upload help needs to be rendered since the description does not
 | 
			
		||||
    // accept render arrays. The FileWidget::formElement() method adds the file
 | 
			
		||||
    // upload help in the same way, so any theming improvements made to file
 | 
			
		||||
    // fields would also be applied to this upload field.
 | 
			
		||||
    // @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formElement()
 | 
			
		||||
    $form['container']['upload']['#description'] = $this->renderer->renderInIsolation($file_upload_help);
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates the upload element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element
 | 
			
		||||
   *   The upload element.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The processed upload element.
 | 
			
		||||
   */
 | 
			
		||||
  public function validateUploadElement(array $element, FormStateInterface $form_state) {
 | 
			
		||||
    if ($form_state::hasAnyErrors()) {
 | 
			
		||||
      // When an error occurs during uploading files, remove all files so the
 | 
			
		||||
      // user can re-upload the files.
 | 
			
		||||
      $element['#value'] = [];
 | 
			
		||||
    }
 | 
			
		||||
    $values = $form_state->getValue('upload', []);
 | 
			
		||||
    if (count($values['fids']) > $element['#cardinality'] && $element['#cardinality'] !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
 | 
			
		||||
      $form_state->setError($element, $this->t('A maximum of @count files can be uploaded.', [
 | 
			
		||||
        '@count' => $element['#cardinality'],
 | 
			
		||||
      ]));
 | 
			
		||||
      $form_state->setValue('upload', []);
 | 
			
		||||
      $element['#value'] = [];
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Processes an upload (managed_file) element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element
 | 
			
		||||
   *   The upload element.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The processed upload element.
 | 
			
		||||
   */
 | 
			
		||||
  public function processUploadElement(array $element, FormStateInterface $form_state) {
 | 
			
		||||
    $element['upload_button']['#submit'] = ['::uploadButtonSubmit'];
 | 
			
		||||
    // Limit the validation errors to make sure
 | 
			
		||||
    // FormValidator::handleErrorsWithLimitedValidation doesn't remove the
 | 
			
		||||
    // current selection from the form state.
 | 
			
		||||
    // @see Drupal\Core\Form\FormValidator::handleErrorsWithLimitedValidation()
 | 
			
		||||
    $element['upload_button']['#limit_validation_errors'] = [
 | 
			
		||||
      ['upload'],
 | 
			
		||||
      ['current_selection'],
 | 
			
		||||
    ];
 | 
			
		||||
    $element['upload_button']['#ajax'] = [
 | 
			
		||||
      'callback' => '::updateFormCallback',
 | 
			
		||||
      'wrapper' => 'media-library-wrapper',
 | 
			
		||||
      // Add a fixed URL to post the form since AJAX forms are automatically
 | 
			
		||||
      // posted to <current> instead of $form['#action'].
 | 
			
		||||
      // @todo Remove when https://www.drupal.org/project/drupal/issues/2504115
 | 
			
		||||
      //   is fixed.
 | 
			
		||||
      'url' => Url::fromRoute('media_library.ui'),
 | 
			
		||||
      'options' => [
 | 
			
		||||
        'query' => $this->getMediaLibraryState($form_state)->all() + [
 | 
			
		||||
          FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildEntityFormElement(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
 | 
			
		||||
    $element = parent::buildEntityFormElement($media, $form, $form_state, $delta);
 | 
			
		||||
    $source_field = $this->getSourceFieldName($media->bundle->entity);
 | 
			
		||||
    if (isset($element['fields'][$source_field])) {
 | 
			
		||||
      $element['fields'][$source_field]['widget'][0]['#process'][] = [static::class, 'hideExtraSourceFieldComponents'];
 | 
			
		||||
    }
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Processes an image or file source field element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $element
 | 
			
		||||
   *   The entity form source field element.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The processed form element.
 | 
			
		||||
   */
 | 
			
		||||
  public static function hideExtraSourceFieldComponents($element, FormStateInterface $form_state, $form) {
 | 
			
		||||
    // Remove original button added by ManagedFile::processManagedFile().
 | 
			
		||||
    if (!empty($element['remove_button'])) {
 | 
			
		||||
      $element['remove_button']['#access'] = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
    // Remove preview added by ImageWidget::process().
 | 
			
		||||
    if (!empty($element['preview'])) {
 | 
			
		||||
      $element['preview']['#access'] = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $element['#title_display'] = 'none';
 | 
			
		||||
    $element['#description_display'] = 'none';
 | 
			
		||||
 | 
			
		||||
    // Remove the filename display.
 | 
			
		||||
    foreach ($element['#files'] as $file) {
 | 
			
		||||
      $element['file_' . $file->id()]['filename']['#access'] = FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit handler for the upload button, inside the managed_file element.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form render array.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function uploadButtonSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $files = $this->entityTypeManager
 | 
			
		||||
      ->getStorage('file')
 | 
			
		||||
      ->loadMultiple($form_state->getValue('upload', []));
 | 
			
		||||
    $this->processInputValues($files, $form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $file) {
 | 
			
		||||
    if (!($file instanceof FileInterface)) {
 | 
			
		||||
      throw new \InvalidArgumentException('Cannot create a media item without a file entity.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a file item to get the upload location.
 | 
			
		||||
    $item = $this->createFileItem($media_type);
 | 
			
		||||
    $upload_location = $item->getUploadLocation();
 | 
			
		||||
    if (!$this->fileSystem->prepareDirectory($upload_location, FileSystemInterface::CREATE_DIRECTORY)) {
 | 
			
		||||
      throw new FileWriteException("The destination directory '$upload_location' is not writable");
 | 
			
		||||
    }
 | 
			
		||||
    $file = $this->fileRepository->move($file, $upload_location);
 | 
			
		||||
    if (!$file) {
 | 
			
		||||
      throw new \RuntimeException("Unable to move file to '$upload_location'");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parent::createMediaFromValue($media_type, $media_storage, $source_field_name, $file);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a file field item.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media\MediaTypeInterface $media_type
 | 
			
		||||
   *   The media type of the media item.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\file\Plugin\Field\FieldType\FileItem
 | 
			
		||||
   *   A created file item.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createFileItem(MediaTypeInterface $media_type) {
 | 
			
		||||
    $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
 | 
			
		||||
    $data_definition = FieldItemDataDefinition::create($field_definition);
 | 
			
		||||
    return new FileItem($data_definition);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function prepareMediaEntityForSave(MediaInterface $media) {
 | 
			
		||||
    /** @var \Drupal\file\FileInterface $file */
 | 
			
		||||
    $file = $media->get($this->getSourceFieldName($media->bundle->entity))->entity;
 | 
			
		||||
    $file->setPermanent();
 | 
			
		||||
    $file->save();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit handler for the remove button.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form render array.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function removeButtonSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    // Retrieve the delta of the media item from the parents of the remove
 | 
			
		||||
    // button.
 | 
			
		||||
    $triggering_element = $form_state->getTriggeringElement();
 | 
			
		||||
    $delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\media\MediaInterface $removed_media */
 | 
			
		||||
    $removed_media = $form_state->get(['media', $delta]);
 | 
			
		||||
 | 
			
		||||
    $file = $removed_media->get($this->getSourceFieldName($removed_media->bundle->entity))->entity;
 | 
			
		||||
    if ($file instanceof FileInterface && empty($this->fileUsage->listUsage($file))) {
 | 
			
		||||
      $file->delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parent::removeButtonSubmit($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										178
									
								
								web/core/modules/media_library/src/Form/OEmbedForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								web/core/modules/media_library/src/Form/OEmbedForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,178 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Form\FormBuilderInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\media\OEmbed\ResourceException;
 | 
			
		||||
use Drupal\media\OEmbed\ResourceFetcherInterface;
 | 
			
		||||
use Drupal\media\OEmbed\UrlResolverInterface;
 | 
			
		||||
use Drupal\media\Plugin\media\Source\OEmbedInterface;
 | 
			
		||||
use Drupal\media_library\MediaLibraryUiBuilder;
 | 
			
		||||
use Drupal\media_library\OpenerResolverInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a form to create media entities from oEmbed URLs.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class OEmbedForm extends AddFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The oEmbed URL resolver service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\media\OEmbed\UrlResolverInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $urlResolver;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The oEmbed resource fetcher service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\media\OEmbed\ResourceFetcherInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $resourceFetcher;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new OEmbedForm.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryUiBuilder $library_ui_builder
 | 
			
		||||
   *   The media library UI builder.
 | 
			
		||||
   * @param \Drupal\media\OEmbed\UrlResolverInterface $url_resolver
 | 
			
		||||
   *   The oEmbed URL resolver service.
 | 
			
		||||
   * @param \Drupal\media\OEmbed\ResourceFetcherInterface $resource_fetcher
 | 
			
		||||
   *   The oEmbed resource fetcher service.
 | 
			
		||||
   * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
 | 
			
		||||
   *   The opener resolver.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, MediaLibraryUiBuilder $library_ui_builder, UrlResolverInterface $url_resolver, ResourceFetcherInterface $resource_fetcher, ?OpenerResolverInterface $opener_resolver = NULL) {
 | 
			
		||||
    parent::__construct($entity_type_manager, $library_ui_builder, $opener_resolver);
 | 
			
		||||
    $this->urlResolver = $url_resolver;
 | 
			
		||||
    $this->resourceFetcher = $resource_fetcher;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    return new static(
 | 
			
		||||
      $container->get('entity_type.manager'),
 | 
			
		||||
      $container->get('media_library.ui_builder'),
 | 
			
		||||
      $container->get('media.oembed.url_resolver'),
 | 
			
		||||
      $container->get('media.oembed.resource_fetcher'),
 | 
			
		||||
      $container->get('media_library.opener_resolver')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return $this->getBaseFormId() . '_oembed';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getMediaType(FormStateInterface $form_state) {
 | 
			
		||||
    if ($this->mediaType) {
 | 
			
		||||
      return $this->mediaType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $media_type = parent::getMediaType($form_state);
 | 
			
		||||
    if (!$media_type->getSource() instanceof OEmbedInterface) {
 | 
			
		||||
      throw new \InvalidArgumentException('Can only add media types which use an oEmbed source plugin.');
 | 
			
		||||
    }
 | 
			
		||||
    return $media_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildInputElement(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $media_type = $this->getMediaType($form_state);
 | 
			
		||||
    $providers = $media_type->getSource()->getProviders();
 | 
			
		||||
 | 
			
		||||
    // Add a container to group the input elements for styling purposes.
 | 
			
		||||
    $form['container'] = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['container']['url'] = [
 | 
			
		||||
      '#type' => 'url',
 | 
			
		||||
      '#title' => $this->t('Add @type via URL', [
 | 
			
		||||
        '@type' => $this->getMediaType($form_state)->label(),
 | 
			
		||||
      ]),
 | 
			
		||||
      '#description' => $this->t('Allowed providers: @providers.', [
 | 
			
		||||
        '@providers' => implode(', ', $providers),
 | 
			
		||||
      ]),
 | 
			
		||||
      '#required' => TRUE,
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'placeholder' => 'https://',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['container']['submit'] = [
 | 
			
		||||
      '#type' => 'submit',
 | 
			
		||||
      '#value' => $this->t('Add'),
 | 
			
		||||
      '#button_type' => 'primary',
 | 
			
		||||
      '#validate' => ['::validateUrl'],
 | 
			
		||||
      '#submit' => ['::addButtonSubmit'],
 | 
			
		||||
      // @todo Move validation in https://www.drupal.org/node/2988215
 | 
			
		||||
      '#ajax' => [
 | 
			
		||||
        'callback' => '::updateFormCallback',
 | 
			
		||||
        'wrapper' => 'media-library-wrapper',
 | 
			
		||||
        // Add a fixed URL to post the form since AJAX forms are automatically
 | 
			
		||||
        // posted to <current> instead of $form['#action'].
 | 
			
		||||
        // @todo Remove when https://www.drupal.org/project/drupal/issues/2504115
 | 
			
		||||
        //   is fixed.
 | 
			
		||||
        'url' => Url::fromRoute('media_library.ui'),
 | 
			
		||||
        'options' => [
 | 
			
		||||
          'query' => $this->getMediaLibraryState($form_state)->all() + [
 | 
			
		||||
            FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates the oEmbed URL.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The complete form.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The current form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function validateUrl(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $url = $form_state->getValue('url');
 | 
			
		||||
    if ($url) {
 | 
			
		||||
      try {
 | 
			
		||||
        $resource_url = $this->urlResolver->getResourceUrl($url);
 | 
			
		||||
        $this->resourceFetcher->fetchResource($resource_url);
 | 
			
		||||
      }
 | 
			
		||||
      catch (ResourceException $e) {
 | 
			
		||||
        $form_state->setErrorByName('url', $e->getMessage());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Submit handler for the add button.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $form
 | 
			
		||||
   *   The form render array.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormStateInterface $form_state
 | 
			
		||||
   *   The form state.
 | 
			
		||||
   */
 | 
			
		||||
  public function addButtonSubmit(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->processInputValues([$form_state->getValue('url')], $form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								web/core/modules/media_library/src/Form/SettingsForm.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								web/core/modules/media_library/src/Form/SettingsForm.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\ConfigFormBase;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a form for configuring the Media Library module.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Form classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
class SettingsForm extends ConfigFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getEditableConfigNames() {
 | 
			
		||||
    return ['media_library.settings'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'media_library_settings_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $form['advanced_ui'] = [
 | 
			
		||||
      '#type' => 'checkbox',
 | 
			
		||||
      '#title' => $this->t('Enable advanced UI'),
 | 
			
		||||
      '#default_value' => $this->config('media_library.settings')->get('advanced_ui'),
 | 
			
		||||
      '#description' => $this->t('If checked, users creating new media items in the media library will see a summary of their selected media items, and they will be able to insert their selection directly into the media field or text editor.'),
 | 
			
		||||
    ];
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->config('media_library.settings')
 | 
			
		||||
      ->set('advanced_ui', (bool) $form_state->getValue('advanced_ui'))
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    parent::submitForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								web/core/modules/media_library/src/Hook/MediaLibraryHooks.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								web/core/modules/media_library/src/Hook/MediaLibraryHooks.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,283 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Access\AccessResultInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityInterface;
 | 
			
		||||
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\media\MediaTypeForm;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Component\Utility\UrlHelper;
 | 
			
		||||
use Drupal\media_library\MediaLibraryState;
 | 
			
		||||
use Drupal\views\Plugin\views\cache\CachePluginBase;
 | 
			
		||||
use Drupal\views\ViewExecutable;
 | 
			
		||||
use Drupal\media_library\Form\OEmbedForm;
 | 
			
		||||
use Drupal\media_library\Form\FileUploadForm;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\Core\Render\Element;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for media_library.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryHooks {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_help().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('help')]
 | 
			
		||||
  public function help($route_name, RouteMatchInterface $route_match): ?string {
 | 
			
		||||
    switch ($route_name) {
 | 
			
		||||
      case 'help.page.media_library':
 | 
			
		||||
        $output = '<h2>' . $this->t('About') . '</h2>';
 | 
			
		||||
        $output .= '<p>' . $this->t('The Media Library module provides a rich, visual interface for managing media, and allows media to be reused in entity reference fields or embedded into text content. It overrides the <a href=":media-collection">media administration page</a>, allowing users to toggle between the existing table-style interface and a new grid-style interface for browsing and performing administrative operations on media.', [
 | 
			
		||||
          ':media-collection' => Url::fromRoute('entity.media.collection')->toString(),
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
        $output .= '<p>' . $this->t('To learn more about media management, begin by reviewing the <a href=":media-help">documentation for the Media module</a>. For more information about the media library and related functionality, see the <a href=":media-library-handbook">online documentation for the Media Library module</a>.', [
 | 
			
		||||
          ':media-help' => Url::fromRoute('help.page', [
 | 
			
		||||
            'name' => 'media',
 | 
			
		||||
          ])->toString(),
 | 
			
		||||
          ':media-library-handbook' => 'https://www.drupal.org/docs/8/core/modules/media-library-module',
 | 
			
		||||
        ]) . '</p>';
 | 
			
		||||
        $output .= '<h2>' . $this->t('Selection dialog') . '</h2>';
 | 
			
		||||
        $output .= '<p>' . $this->t('When selecting media for an entity reference field or a text editor, Media Library opens a modal dialog to help users easily find and select media. The modal dialog can toggle between a grid-style and table-style interface, and new media items can be uploaded directly into it.') . '</p>';
 | 
			
		||||
        $output .= '<p>' . $this->t('Within the dialog, media items are divided up by type. If more than one media type can be selected by the user, the available types will be displayed as a set of vertical tabs. To users who have appropriate permissions, each media type may also present a short form allowing you to upload or create new media items of that type.') . '</p>';
 | 
			
		||||
        $output .= '<h2>' . $this->t('Uses') . '</h2>';
 | 
			
		||||
        $output .= '<dl>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Grid-style vs. table-style interface') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('The Media Library module provides a new grid-style interface for the media administration page that displays media as thumbnails, with minimal textual information, allowing users to visually browse media in their site. The existing table-style interface is better suited to displaying additional information about media items, in addition to being more accessible to users with assistive technology.') . '</dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Reusing media in entity reference fields') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('Any entity reference field that references media can use the media library. To enable, configure the form display for the field to use the "Media library" widget.') . '</dd>';
 | 
			
		||||
        $output .= '<dt>' . $this->t('Embedding media in text content') . '</dt>';
 | 
			
		||||
        $output .= '<dd>' . $this->t('To use the media library within CKEditor, you must add the "Insert from Media Library" button to the CKEditor toolbar, and enable the "Embed media" filter in the text format associated with the text editor.') . '</dd>';
 | 
			
		||||
        $output .= '</dl>';
 | 
			
		||||
        $output .= '<h2>' . $this->t('Customize') . '</h2>';
 | 
			
		||||
        $output .= '<ul>';
 | 
			
		||||
        $output .= '<li>';
 | 
			
		||||
        if (\Drupal::moduleHandler()->moduleExists('views_ui') && \Drupal::currentUser()->hasPermission('administer views')) {
 | 
			
		||||
          $output .= $this->t('Both the table-style and grid-style interfaces are regular views and can be customized via the <a href=":views-ui">Views UI</a>, including sorting and filtering. This is the case for both the administration page and the modal dialog.', [':views_ui' => Url::fromRoute('entity.view.collection')->toString()]);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          $output .= $this->t('Both the table-style and grid-style interfaces are regular views and can be customized via the Views UI, including sorting and filtering. This is the case for both the administration page and the modal dialog.');
 | 
			
		||||
        }
 | 
			
		||||
        $output .= '</li>';
 | 
			
		||||
        $output .= '<li>' . $this->t('In the grid-style interface, the fields that are displayed (including which image style is used for images) can be customized by configuring the "Media library" view mode for each of your <a href=":media-types">media types</a>. The thumbnail images in the grid-style interface can be customized by configuring the "Media Library thumbnail (220×220)" image style.', [
 | 
			
		||||
          ':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
 | 
			
		||||
        ]) . '</li>';
 | 
			
		||||
        $output .= '<li>' . $this->t('When adding new media items within the modal dialog, the fields that are displayed can be customized by configuring the "Media library" form mode for each of your <a href=":media-types">media types</a>.', [
 | 
			
		||||
          ':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
 | 
			
		||||
        ]) . '</li>';
 | 
			
		||||
        $output .= '</ul>';
 | 
			
		||||
        return $output;
 | 
			
		||||
    }
 | 
			
		||||
    return NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_media_source_info_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('media_source_info_alter')]
 | 
			
		||||
  public function mediaSourceInfoAlter(array &$sources): void {
 | 
			
		||||
    if (empty($sources['audio_file']['forms']['media_library_add'])) {
 | 
			
		||||
      $sources['audio_file']['forms']['media_library_add'] = FileUploadForm::class;
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($sources['file']['forms']['media_library_add'])) {
 | 
			
		||||
      $sources['file']['forms']['media_library_add'] = FileUploadForm::class;
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($sources['image']['forms']['media_library_add'])) {
 | 
			
		||||
      $sources['image']['forms']['media_library_add'] = FileUploadForm::class;
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($sources['video_file']['forms']['media_library_add'])) {
 | 
			
		||||
      $sources['video_file']['forms']['media_library_add'] = FileUploadForm::class;
 | 
			
		||||
    }
 | 
			
		||||
    if (empty($sources['oembed:video']['forms']['media_library_add'])) {
 | 
			
		||||
      $sources['oembed:video']['forms']['media_library_add'] = OEmbedForm::class;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_theme().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('theme')]
 | 
			
		||||
  public function theme() : array {
 | 
			
		||||
    return [
 | 
			
		||||
      'media__media_library' => [
 | 
			
		||||
        'base hook' => 'media',
 | 
			
		||||
      ],
 | 
			
		||||
      'media_library_wrapper' => [
 | 
			
		||||
        'render element' => 'element',
 | 
			
		||||
      ],
 | 
			
		||||
      'media_library_item' => [
 | 
			
		||||
        'render element' => 'element',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_views_pre_render().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('views_pre_render')]
 | 
			
		||||
  public function viewsPreRender(ViewExecutable $view): void {
 | 
			
		||||
    $add_classes = function (&$option, array $classes_to_add) {
 | 
			
		||||
      $classes = $option ? preg_split('/\s+/', trim($option)) : [];
 | 
			
		||||
      $classes = array_filter($classes);
 | 
			
		||||
      $classes = array_merge($classes, $classes_to_add);
 | 
			
		||||
      $option = implode(' ', array_unique($classes));
 | 
			
		||||
    };
 | 
			
		||||
    if ($view->id() === 'media_library') {
 | 
			
		||||
      if ($view->current_display === 'page') {
 | 
			
		||||
        $add_classes($view->style_plugin->options['row_class'], ['js-media-library-item', 'js-click-to-select']);
 | 
			
		||||
        if (array_key_exists('media_bulk_form', $view->field)) {
 | 
			
		||||
          $add_classes($view->field['media_bulk_form']->options['element_class'], ['js-click-to-select-checkbox']);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      elseif (str_starts_with($view->current_display, 'widget')) {
 | 
			
		||||
        if (array_key_exists('media_library_select_form', $view->field)) {
 | 
			
		||||
          $add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], ['js-click-to-select-checkbox']);
 | 
			
		||||
        }
 | 
			
		||||
        $add_classes($view->display_handler->options['css_class'], ['js-media-library-view']);
 | 
			
		||||
      }
 | 
			
		||||
      $add_classes($view->style_plugin->options['row_class'], ['js-media-library-item', 'js-click-to-select']);
 | 
			
		||||
      if ($view->display_handler->options['defaults']['css_class']) {
 | 
			
		||||
        $add_classes($view->displayHandlers->get('default')->options['css_class'], ['js-media-library-view']);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $add_classes($view->display_handler->options['css_class'], ['js-media-library-view']);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_views_post_render().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('views_post_render')]
 | 
			
		||||
  public function viewsPostRender(ViewExecutable $view, &$output, CachePluginBase $cache): void {
 | 
			
		||||
    if ($view->id() === 'media_library') {
 | 
			
		||||
      $output['#attached']['library'][] = 'media_library/view';
 | 
			
		||||
      if (str_starts_with($view->current_display, 'widget')) {
 | 
			
		||||
        try {
 | 
			
		||||
          $query = MediaLibraryState::fromRequest($view->getRequest())->all();
 | 
			
		||||
        }
 | 
			
		||||
        catch (\InvalidArgumentException $e) {
 | 
			
		||||
          // MediaLibraryState::fromRequest() will throw an exception if the
 | 
			
		||||
          // view is being previewed, since not all required query parameters
 | 
			
		||||
          // will be present. In a preview, however, this can be omitted since
 | 
			
		||||
          // we're merely previewing.
 | 
			
		||||
          // @todo Use the views API for checking for the preview mode when it
 | 
			
		||||
          //   lands. https://www.drupal.org/project/drupal/issues/3060855
 | 
			
		||||
          if (empty($view->preview) && empty($view->live_preview)) {
 | 
			
		||||
            throw $e;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        // 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
 | 
			
		||||
        // parameters are prefixed and should not interfere with any other
 | 
			
		||||
        // views.
 | 
			
		||||
        // @todo Rework or remove this in https://www.drupal.org/node/2983451
 | 
			
		||||
        if (!empty($query)) {
 | 
			
		||||
          $ajax_path =& $output['#attached']['drupalSettings']['views']['ajax_path'];
 | 
			
		||||
          $parsed_url = UrlHelper::parse($ajax_path);
 | 
			
		||||
          $query = array_merge($query, $parsed_url['query']);
 | 
			
		||||
          // Reset the pager so that the user starts on the first page.
 | 
			
		||||
          unset($query['page']);
 | 
			
		||||
          $ajax_path = $parsed_url['path'] . '?' . UrlHelper::buildQuery($query);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_alter')]
 | 
			
		||||
  public function formAlter(array &$form, FormStateInterface $form_state, $form_id) : void {
 | 
			
		||||
    // Add a process callback to ensure that the media library view's exposed
 | 
			
		||||
    // filters submit button is not moved to the modal dialog's button area.
 | 
			
		||||
    if ($form_id === 'views_exposed_form' && str_starts_with($form['#id'], 'views-exposed-form-media-library-widget')) {
 | 
			
		||||
      $form['#after_build'][] = '_media_library_views_form_media_library_after_build';
 | 
			
		||||
    }
 | 
			
		||||
    // Configures media_library displays when a type is submitted.
 | 
			
		||||
    if ($form_state->getFormObject() instanceof MediaTypeForm) {
 | 
			
		||||
      $form['actions']['submit']['#submit'][] = '_media_library_media_type_form_submit';
 | 
			
		||||
      // @see field_ui_form_alter()
 | 
			
		||||
      if (isset($form['actions']['save_continue'])) {
 | 
			
		||||
        $form['actions']['save_continue']['#submit'][] = '_media_library_media_type_form_submit';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_form_FORM_ID_alter().
 | 
			
		||||
   *
 | 
			
		||||
   * Alter the bulk form to add a more accessible label.
 | 
			
		||||
   *
 | 
			
		||||
   * @todo Remove in https://www.drupal.org/node/2983454
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('form_views_form_media_library_page_alter')]
 | 
			
		||||
  public function formViewsFormMediaLibraryPageAlter(array &$form, FormStateInterface $form_state, $form_id) : void {
 | 
			
		||||
    if (isset($form['media_bulk_form']) && isset($form['output'])) {
 | 
			
		||||
      /** @var \Drupal\views\ViewExecutable $view */
 | 
			
		||||
      $view = $form['output'][0]['#view'];
 | 
			
		||||
      foreach (Element::getVisibleChildren($form['media_bulk_form']) as $key) {
 | 
			
		||||
        if (isset($view->result[$key])) {
 | 
			
		||||
          $media = $view->field['media_bulk_form']->getEntity($view->result[$key]);
 | 
			
		||||
          $form['media_bulk_form'][$key]['#title'] = $media ? $this->t('Select @label', ['@label' => $media->label()]) : '';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_field_ui_preconfigured_options_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('field_ui_preconfigured_options_alter')]
 | 
			
		||||
  public function fieldUiPreconfiguredOptionsAlter(array &$options, $field_type): void {
 | 
			
		||||
    // If the field is not an "entity_reference"-based field, bail out.
 | 
			
		||||
    $class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_type);
 | 
			
		||||
    if (!is_a($class, EntityReferenceItem::class, TRUE)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // Set the default field widget for media to be the Media library.
 | 
			
		||||
    if (!empty($options['media'])) {
 | 
			
		||||
      $options['media']['entity_form_display']['type'] = 'media_library_widget';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_local_tasks_alter().
 | 
			
		||||
   *
 | 
			
		||||
   * Removes tasks for the Media library if the view display no longer exists.
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('local_tasks_alter')]
 | 
			
		||||
  public function localTasksAlter(&$local_tasks): void {
 | 
			
		||||
    /** @var \Symfony\Component\Routing\RouteCollection $route_collection */
 | 
			
		||||
    $route_collection = \Drupal::service('router')->getRouteCollection();
 | 
			
		||||
    foreach (['media_library.grid', 'media_library.table'] as $key) {
 | 
			
		||||
      if (isset($local_tasks[$key]) && !$route_collection->get($local_tasks[$key]['route_name'])) {
 | 
			
		||||
        unset($local_tasks[$key]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_access().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('image_style_access')]
 | 
			
		||||
  public function imageStyleAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
 | 
			
		||||
    // Prevent the fallback 'media_library' image style from being deleted.
 | 
			
		||||
    // @todo Lock the image style instead of preventing delete access.
 | 
			
		||||
    //   https://www.drupal.org/project/drupal/issues/2247293
 | 
			
		||||
    if ($operation === 'delete' && $entity->id() === 'media_library') {
 | 
			
		||||
      return AccessResult::forbidden();
 | 
			
		||||
    }
 | 
			
		||||
    return AccessResult::neutral();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for media_library.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryViewsHooks {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_views_data().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('views_data')]
 | 
			
		||||
  public function viewsData(): array {
 | 
			
		||||
    $data = [];
 | 
			
		||||
    $data['media']['media_library_select_form'] = [
 | 
			
		||||
      'title' => $this->t('Select media'),
 | 
			
		||||
      'help' => $this->t('Provides a field for selecting media entities in our media library view'),
 | 
			
		||||
      'real field' => 'mid',
 | 
			
		||||
      'field' => [
 | 
			
		||||
        'id' => 'media_library_select_form',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,78 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Ajax\AjaxResponse;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\editor\Ajax\EditorDialogSave;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The media library opener for text editors.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This is an internal part of Media Library's text editor integration.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryEditorOpener implements MediaLibraryOpenerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The text format entity storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $filterStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The media storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $mediaStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The MediaLibraryEditorOpener constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->filterStorage = $entity_type_manager->getStorage('filter_format');
 | 
			
		||||
    $this->mediaStorage = $entity_type_manager->getStorage('media');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function checkAccess(MediaLibraryState $state, AccountInterface $account) {
 | 
			
		||||
    $filter_format_id = $state->getOpenerParameters()['filter_format_id'];
 | 
			
		||||
    $filter_format = $this->filterStorage->load($filter_format_id);
 | 
			
		||||
    if (empty($filter_format)) {
 | 
			
		||||
      return AccessResult::forbidden()
 | 
			
		||||
        ->addCacheTags(['filter_format_list'])
 | 
			
		||||
        ->setReason("The text format '$filter_format_id' could not be loaded.");
 | 
			
		||||
    }
 | 
			
		||||
    $filters = $filter_format->filters();
 | 
			
		||||
    return $filter_format->access('use', $account, TRUE)
 | 
			
		||||
      ->andIf(AccessResult::allowedIf($filters->has('media_embed') && $filters->get('media_embed')->status === TRUE));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSelectionResponse(MediaLibraryState $state, array $selected_ids) {
 | 
			
		||||
    $selected_media = $this->mediaStorage->load(reset($selected_ids));
 | 
			
		||||
 | 
			
		||||
    $response = new AjaxResponse();
 | 
			
		||||
    $values = [
 | 
			
		||||
      'attributes' => [
 | 
			
		||||
        'data-entity-type' => 'media',
 | 
			
		||||
        'data-entity-uuid' => $selected_media->uuid(),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $response->addCommand(new EditorDialogSave($values));
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,140 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Ajax\AjaxResponse;
 | 
			
		||||
use Drupal\Core\Ajax\InvokeCommand;
 | 
			
		||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Entity\FieldableEntityInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\Field\EntityReferenceFieldItemList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The media library opener for field widgets.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This service is an internal part of Media Library's field widget.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryFieldWidgetOpener implements MediaLibraryOpenerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * MediaLibraryFieldWidgetOpener constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function checkAccess(MediaLibraryState $state, AccountInterface $account) {
 | 
			
		||||
    $parameters = $state->getOpenerParameters() + ['entity_id' => NULL];
 | 
			
		||||
 | 
			
		||||
    // Forbid access if any of the required parameters are missing.
 | 
			
		||||
    foreach (['entity_type_id', 'bundle', 'field_name'] as $key) {
 | 
			
		||||
      if (empty($parameters[$key])) {
 | 
			
		||||
        return AccessResult::forbidden("$key parameter is missing.")->addCacheableDependency($state);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $entity_type_id = $parameters['entity_type_id'];
 | 
			
		||||
    $bundle = $parameters['bundle'];
 | 
			
		||||
    $field_name = $parameters['field_name'];
 | 
			
		||||
 | 
			
		||||
    // Since we defer to a field to determine access, ensure we are dealing with
 | 
			
		||||
    // a fieldable entity type.
 | 
			
		||||
    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
 | 
			
		||||
    if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
 | 
			
		||||
      throw new \LogicException("The media library can only be opened by fieldable entities.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
 | 
			
		||||
    $storage = $this->entityTypeManager->getStorage($entity_type_id);
 | 
			
		||||
    $access_handler = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
 | 
			
		||||
 | 
			
		||||
    if (!empty($parameters['revision_id'])) {
 | 
			
		||||
      $entity = $storage->loadRevision($parameters['revision_id']);
 | 
			
		||||
      $entity_access = $access_handler->access($entity, 'update', $account, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
    elseif ($parameters['entity_id']) {
 | 
			
		||||
      $entity = $storage->load($parameters['entity_id']);
 | 
			
		||||
      $entity_access = $access_handler->access($entity, 'update', $account, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      $entity_access = $access_handler->createAccess($bundle, $account, [], TRUE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If entity-level access is denied, there's no point in continuing.
 | 
			
		||||
    if (!$entity_access->isAllowed()) {
 | 
			
		||||
      if ($entity_access instanceof RefinableCacheableDependencyInterface) {
 | 
			
		||||
        $entity_access->addCacheableDependency($state);
 | 
			
		||||
      }
 | 
			
		||||
      return $entity_access;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the entity has not been loaded, create it in memory now.
 | 
			
		||||
    if (!isset($entity)) {
 | 
			
		||||
      $values = [];
 | 
			
		||||
      if ($bundle_key = $entity_type->getKey('bundle')) {
 | 
			
		||||
        $values[$bundle_key] = $bundle;
 | 
			
		||||
      }
 | 
			
		||||
      /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
 | 
			
		||||
      $entity = $storage->create($values);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $items = $entity->get($field_name);
 | 
			
		||||
    $field_definition = $items->getFieldDefinition();
 | 
			
		||||
 | 
			
		||||
    // Check that the field is an entity reference, or subclass of it, since we
 | 
			
		||||
    // need to check the target_type setting.
 | 
			
		||||
    if (!$items instanceof EntityReferenceFieldItemList) {
 | 
			
		||||
      throw new \LogicException('Expected the media library to be opened by an entity reference field.');
 | 
			
		||||
    }
 | 
			
		||||
    if ($field_definition->getFieldStorageDefinition()->getSetting('target_type') !== 'media') {
 | 
			
		||||
      throw new \LogicException('Expected the media library to be opened by an entity reference field that target media items.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $field_access = $access_handler->fieldAccess('edit', $field_definition, $account, $items, TRUE);
 | 
			
		||||
    $access = $entity_access->andIf($field_access);
 | 
			
		||||
    if ($access instanceof RefinableCacheableDependencyInterface) {
 | 
			
		||||
      $access->addCacheableDependency($state);
 | 
			
		||||
    }
 | 
			
		||||
    return $access;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getSelectionResponse(MediaLibraryState $state, array $selected_ids) {
 | 
			
		||||
    $response = new AjaxResponse();
 | 
			
		||||
 | 
			
		||||
    $parameters = $state->getOpenerParameters();
 | 
			
		||||
    if (empty($parameters['field_widget_id'])) {
 | 
			
		||||
      throw new \InvalidArgumentException('field_widget_id parameter is missing.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a comma-separated list of media IDs, insert them in the hidden
 | 
			
		||||
    // field of the widget, and trigger the field update via the hidden submit
 | 
			
		||||
    // button.
 | 
			
		||||
    $widget_id = $parameters['field_widget_id'];
 | 
			
		||||
    $ids = implode(',', $selected_ids);
 | 
			
		||||
    $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']));
 | 
			
		||||
 | 
			
		||||
    return $response;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an interface for media library openers.
 | 
			
		||||
 *
 | 
			
		||||
 * Media library opener services allow modules to check access to the media
 | 
			
		||||
 * library selection dialog and respond to selections. Example use cases that
 | 
			
		||||
 * require different handling:
 | 
			
		||||
 * - when used in an entity reference field widget;
 | 
			
		||||
 * - when used in a text editor.
 | 
			
		||||
 *
 | 
			
		||||
 * Openers that require additional parameters or metadata should retrieve them
 | 
			
		||||
 * from the MediaLibraryState object.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryState
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryState::getOpenerParameters()
 | 
			
		||||
 */
 | 
			
		||||
interface MediaLibraryOpenerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks media library access.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   The media library.
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $account
 | 
			
		||||
   *   The user for which to check access.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Access\AccessResultInterface
 | 
			
		||||
   *   The access result.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/project/drupal/issues/3038254
 | 
			
		||||
   */
 | 
			
		||||
  public function checkAccess(MediaLibraryState $state, AccountInterface $account);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generates a response after selecting media items in the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   The state the media library was in at the time of selection, allowing the
 | 
			
		||||
   *   response to be customized based on that state.
 | 
			
		||||
   * @param int[] $selected_ids
 | 
			
		||||
   *   The IDs of the selected media items.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Ajax\AjaxResponse
 | 
			
		||||
   *   The response to update the page after selecting media.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSelectionResponse(MediaLibraryState $state, array $selected_ids);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service provider for media library services.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryServiceProvider implements ServiceProviderInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container) {
 | 
			
		||||
    $container->registerForAutoconfiguration(MediaLibraryOpenerInterface::class)
 | 
			
		||||
      ->addTag('media_library.opener');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										298
									
								
								web/core/modules/media_library/src/MediaLibraryState.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								web/core/modules/media_library/src/MediaLibraryState.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,298 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Crypt;
 | 
			
		||||
use Drupal\Core\Cache\Cache;
 | 
			
		||||
use Drupal\Core\Cache\CacheableDependencyInterface;
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
use Symfony\Component\HttpFoundation\ParameterBag;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A value object for the media library state.
 | 
			
		||||
 *
 | 
			
		||||
 * When the media library is opened it needs several parameters to work
 | 
			
		||||
 * properly. These parameters are normally extracted from the current URL, then
 | 
			
		||||
 * retrieved from and managed by the MediaLibraryState value object. The
 | 
			
		||||
 * following parameters are required in order to open the media library:
 | 
			
		||||
 * - media_library_opener_id: The ID of a container service which implements
 | 
			
		||||
 *   \Drupal\media_library\MediaLibraryOpenerInterface and is responsible for
 | 
			
		||||
 *   interacting with the media library on behalf of the "thing" (e.g., a field
 | 
			
		||||
 *   widget or text editor button) which opened it.
 | 
			
		||||
 * - media_library_allowed_types: The media types available in the library can
 | 
			
		||||
 *   be restricted to a list of allowed types. This should be an array of media
 | 
			
		||||
 *   type IDs.
 | 
			
		||||
 * - media_library_selected_type: The media library contains tabs to navigate
 | 
			
		||||
 *   between the different media types. The selected type contains the ID of the
 | 
			
		||||
 *   media type whose tab that should be opened.
 | 
			
		||||
 * - media_library_remaining: When the opener wants to limit the amount of media
 | 
			
		||||
 *   items that can be selected, it can pass the number of remaining slots. When
 | 
			
		||||
 *   the number of remaining slots is a negative number, an unlimited amount of
 | 
			
		||||
 *   items can be selected.
 | 
			
		||||
 *
 | 
			
		||||
 * This object can also carry an optional opener-specific array of arbitrary
 | 
			
		||||
 * values, under the media_library_opener_parameters key. These values are
 | 
			
		||||
 * included in the hash generated by ::getHash(), so the end user cannot tamper
 | 
			
		||||
 * with them either.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\media_library\MediaLibraryOpenerInterface
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryState extends ParameterBag implements CacheableDependencyInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(array $parameters = []) {
 | 
			
		||||
    $this->validateRequiredParameters($parameters['media_library_opener_id'], $parameters['media_library_allowed_types'], $parameters['media_library_selected_type'], $parameters['media_library_remaining']);
 | 
			
		||||
    $parameters += [
 | 
			
		||||
      'media_library_opener_parameters' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    parent::__construct($parameters);
 | 
			
		||||
    $this->set('hash', $this->getHash());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a new MediaLibraryState object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $opener_id
 | 
			
		||||
   *   The opener ID.
 | 
			
		||||
   * @param string[] $allowed_media_type_ids
 | 
			
		||||
   *   The allowed media type IDs.
 | 
			
		||||
   * @param string $selected_type_id
 | 
			
		||||
   *   The selected media type ID.
 | 
			
		||||
   * @param int $remaining_slots
 | 
			
		||||
   *   The number of remaining items the user is allowed to select or add in the
 | 
			
		||||
   *   library.
 | 
			
		||||
   * @param array $opener_parameters
 | 
			
		||||
   *   (optional) Any additional opener-specific parameter values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return static
 | 
			
		||||
   *   A state object.
 | 
			
		||||
   */
 | 
			
		||||
  public static function create($opener_id, array $allowed_media_type_ids, $selected_type_id, $remaining_slots, array $opener_parameters = []) {
 | 
			
		||||
    $state = new static([
 | 
			
		||||
      'media_library_opener_id' => $opener_id,
 | 
			
		||||
      'media_library_allowed_types' => $allowed_media_type_ids,
 | 
			
		||||
      'media_library_selected_type' => $selected_type_id,
 | 
			
		||||
      'media_library_remaining' => $remaining_slots,
 | 
			
		||||
      'media_library_opener_parameters' => $opener_parameters,
 | 
			
		||||
    ]);
 | 
			
		||||
    return $state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the media library state from a request.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return static
 | 
			
		||||
   *   A state object.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
 | 
			
		||||
   *   Thrown when the hash query parameter is invalid.
 | 
			
		||||
   */
 | 
			
		||||
  public static function fromRequest(Request $request) {
 | 
			
		||||
    $query = $request->query;
 | 
			
		||||
 | 
			
		||||
    // Create a MediaLibraryState object through the create method to make sure
 | 
			
		||||
    // all validation runs.
 | 
			
		||||
    $state = static::create(
 | 
			
		||||
      $query->get('media_library_opener_id'),
 | 
			
		||||
      $query->all('media_library_allowed_types'),
 | 
			
		||||
      $query->get('media_library_selected_type'),
 | 
			
		||||
      $query->get('media_library_remaining'),
 | 
			
		||||
      $query->all('media_library_opener_parameters')
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // The request parameters need to contain a valid hash to prevent a
 | 
			
		||||
    // malicious user modifying the query string to attempt to access
 | 
			
		||||
    // inaccessible information.
 | 
			
		||||
    if (!$state->isValidHash($query->get('hash'))) {
 | 
			
		||||
      throw new BadRequestHttpException("Invalid media library parameters specified.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @todo Review parameters passed and remove irrelevant ones in
 | 
			
		||||
    //   https://www.drupal.org/i/3396650
 | 
			
		||||
 | 
			
		||||
    // Once we have validated the required parameters, we restore the parameters
 | 
			
		||||
    // from the request since there might be additional values.
 | 
			
		||||
    $state->replace($query->all());
 | 
			
		||||
    return $state;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validates the required parameters for a new MediaLibraryState object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $opener_id
 | 
			
		||||
   *   The media library opener service ID.
 | 
			
		||||
   * @param string[] $allowed_media_type_ids
 | 
			
		||||
   *   The allowed media type IDs.
 | 
			
		||||
   * @param string $selected_type_id
 | 
			
		||||
   *   The selected media type ID.
 | 
			
		||||
   * @param int $remaining_slots
 | 
			
		||||
   *   The number of remaining items the user is allowed to select or add in the
 | 
			
		||||
   *   library.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \InvalidArgumentException
 | 
			
		||||
   *   If one of the passed arguments is missing or does not pass the
 | 
			
		||||
   *   validation.
 | 
			
		||||
   */
 | 
			
		||||
  protected function validateRequiredParameters($opener_id, array $allowed_media_type_ids, $selected_type_id, $remaining_slots) {
 | 
			
		||||
    // The opener ID must be a non-empty string.
 | 
			
		||||
    if (!is_string($opener_id) || empty(trim($opener_id))) {
 | 
			
		||||
      throw new \InvalidArgumentException('The opener ID parameter is required and must be a string.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The allowed media type IDs must be an array of non-empty strings.
 | 
			
		||||
    if (empty($allowed_media_type_ids) || !is_array($allowed_media_type_ids)) {
 | 
			
		||||
      throw new \InvalidArgumentException('The allowed types parameter is required and must be an array of strings.');
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($allowed_media_type_ids as $allowed_media_type_id) {
 | 
			
		||||
      if (!is_string($allowed_media_type_id) || empty(trim($allowed_media_type_id))) {
 | 
			
		||||
        throw new \InvalidArgumentException('The allowed types parameter is required and must be an array of strings.');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The selected type ID must be a non-empty string.
 | 
			
		||||
    if (!is_string($selected_type_id) || empty(trim($selected_type_id))) {
 | 
			
		||||
      throw new \InvalidArgumentException('The selected type parameter is required and must be a string.');
 | 
			
		||||
    }
 | 
			
		||||
    // The selected type ID must be present in the list of allowed types.
 | 
			
		||||
    if (!in_array($selected_type_id, $allowed_media_type_ids, TRUE)) {
 | 
			
		||||
      throw new \InvalidArgumentException('The selected type parameter must be present in the list of allowed types.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The remaining slots must be numeric.
 | 
			
		||||
    if (!is_numeric($remaining_slots)) {
 | 
			
		||||
      throw new \InvalidArgumentException('The remaining slots parameter is required and must be numeric.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the hash for the state object.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The hashed parameters.
 | 
			
		||||
   */
 | 
			
		||||
  public function getHash() {
 | 
			
		||||
    // Create a hash from the required state parameters and the serialized
 | 
			
		||||
    // optional opener-specific parameters. Sort the allowed types and
 | 
			
		||||
    // opener parameters so that differences in order do not result in
 | 
			
		||||
    // different hashes.
 | 
			
		||||
    $allowed_media_type_ids = array_values($this->getAllowedTypeIds());
 | 
			
		||||
    sort($allowed_media_type_ids);
 | 
			
		||||
    $opener_parameters = $this->getOpenerParameters();
 | 
			
		||||
    ksort($opener_parameters);
 | 
			
		||||
    $hash = implode(':', [
 | 
			
		||||
      $this->getOpenerId(),
 | 
			
		||||
      implode(':', $allowed_media_type_ids),
 | 
			
		||||
      $this->getSelectedTypeId(),
 | 
			
		||||
      $this->getAvailableSlots(),
 | 
			
		||||
      serialize($opener_parameters),
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    return Crypt::hmacBase64($hash, \Drupal::service('private_key')->get() . Settings::getHashSalt());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Validate a hash for the state object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $hash
 | 
			
		||||
   *   The hash to validate.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The hashed parameters.
 | 
			
		||||
   */
 | 
			
		||||
  public function isValidHash($hash) {
 | 
			
		||||
    return hash_equals($this->getHash(), $hash);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the ID of the media library opener service.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The media library opener service ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function getOpenerId() {
 | 
			
		||||
    return $this->get('media_library_opener_id');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the media type IDs which can be selected.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[]
 | 
			
		||||
   *   The media type IDs.
 | 
			
		||||
   */
 | 
			
		||||
  public function getAllowedTypeIds() {
 | 
			
		||||
    return $this->all('media_library_allowed_types');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the selected media type.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The selected media type.
 | 
			
		||||
   */
 | 
			
		||||
  public function getSelectedTypeId() {
 | 
			
		||||
    return $this->get('media_library_selected_type');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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.
 | 
			
		||||
   *
 | 
			
		||||
   * When the value is not available in the URL the default is 0. When a
 | 
			
		||||
   * negative integer is passed, an unlimited amount of media items can be
 | 
			
		||||
   * selected.
 | 
			
		||||
   *
 | 
			
		||||
   * @return int
 | 
			
		||||
   *   The number of additional media items that can be selected.
 | 
			
		||||
   */
 | 
			
		||||
  public function getAvailableSlots() {
 | 
			
		||||
    return $this->getInt('media_library_remaining');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all opener-specific parameter values.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   An associative array of all opener-specific parameter values.
 | 
			
		||||
   */
 | 
			
		||||
  public function getOpenerParameters() {
 | 
			
		||||
    return $this->all('media_library_opener_parameters');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheContexts() {
 | 
			
		||||
    return ['url.query_args'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheMaxAge() {
 | 
			
		||||
    return Cache::PERMANENT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCacheTags() {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										345
									
								
								web/core/modules/media_library/src/MediaLibraryUiBuilder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								web/core/modules/media_library/src/MediaLibraryUiBuilder.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,345 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Form\FormBuilderInterface;
 | 
			
		||||
use Drupal\Core\Form\FormState;
 | 
			
		||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\views\ViewExecutableFactory;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service which builds the media library.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This service is an internal part of the modal media library dialog and
 | 
			
		||||
 *   does not provide any extension points.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryUiBuilder {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The form builder.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Form\FormBuilderInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $formBuilder;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The entity type manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $entityTypeManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The currently active request object.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\HttpFoundation\Request
 | 
			
		||||
   */
 | 
			
		||||
  protected $request;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The views executable factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\views\ViewExecutableFactory
 | 
			
		||||
   */
 | 
			
		||||
  protected $viewsExecutableFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The media library opener resolver.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\media_library\OpenerResolverInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $openerResolver;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a MediaLibraryUiBuilder instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
 | 
			
		||||
   *   The entity type manager.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
 | 
			
		||||
   *   The request stack.
 | 
			
		||||
   * @param \Drupal\views\ViewExecutableFactory $views_executable_factory
 | 
			
		||||
   *   The views executable factory.
 | 
			
		||||
   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
 | 
			
		||||
   *   The currently active request object.
 | 
			
		||||
   * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
 | 
			
		||||
   *   The opener resolver.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder, OpenerResolverInterface $opener_resolver) {
 | 
			
		||||
    $this->entityTypeManager = $entity_type_manager;
 | 
			
		||||
    $this->request = $request_stack->getCurrentRequest();
 | 
			
		||||
    $this->viewsExecutableFactory = $views_executable_factory;
 | 
			
		||||
    $this->formBuilder = $form_builder;
 | 
			
		||||
    $this->openerResolver = $opener_resolver;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get media library dialog options.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The media library dialog options.
 | 
			
		||||
   */
 | 
			
		||||
  public static function dialogOptions() {
 | 
			
		||||
    return [
 | 
			
		||||
      'classes' => [
 | 
			
		||||
        'ui-dialog' => 'media-library-widget-modal',
 | 
			
		||||
      ],
 | 
			
		||||
      'title' => t('Add or select media'),
 | 
			
		||||
      'height' => '75%',
 | 
			
		||||
      'width' => '75%',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Build the media library UI.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   (optional) The current state of the media library, derived from the
 | 
			
		||||
   *   current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the media library.
 | 
			
		||||
   */
 | 
			
		||||
  public function buildUi(?MediaLibraryState $state = NULL) {
 | 
			
		||||
    if (!$state) {
 | 
			
		||||
      $state = MediaLibraryState::fromRequest($this->request);
 | 
			
		||||
    }
 | 
			
		||||
    // When navigating to a media type through the vertical tabs, we only want
 | 
			
		||||
    // to load the changed library content. This is not only more efficient, but
 | 
			
		||||
    // also provides a more accessible user experience for screen readers.
 | 
			
		||||
    if ($state->get('media_library_content') === '1') {
 | 
			
		||||
      return $this->buildLibraryContent($state);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return [
 | 
			
		||||
        '#theme' => 'media_library_wrapper',
 | 
			
		||||
        '#attributes' => [
 | 
			
		||||
          'id' => 'media-library-wrapper',
 | 
			
		||||
        ],
 | 
			
		||||
        'menu' => $this->buildMediaTypeMenu($state),
 | 
			
		||||
        'content' => $this->buildLibraryContent($state),
 | 
			
		||||
        // Attach the JavaScript for the media library UI. The number of
 | 
			
		||||
        // available slots needs to be added to make sure users can't select
 | 
			
		||||
        // more items than allowed.
 | 
			
		||||
        '#attached' => [
 | 
			
		||||
          'library' => ['media_library/ui'],
 | 
			
		||||
          'drupalSettings' => [
 | 
			
		||||
            'media_library' => [
 | 
			
		||||
              'selection_remaining' => $state->getAvailableSlots(),
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Build the media library content area.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   The current state of the media library, derived from the current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the media library.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildLibraryContent(MediaLibraryState $state) {
 | 
			
		||||
    return [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#theme_wrappers' => [
 | 
			
		||||
        'container__media_library_content',
 | 
			
		||||
      ],
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'id' => 'media-library-content',
 | 
			
		||||
      ],
 | 
			
		||||
      'form' => $this->buildMediaTypeAddForm($state),
 | 
			
		||||
      'view' => $this->buildMediaLibraryView($state),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check access to the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Session\AccountInterface $account
 | 
			
		||||
   *   Run access checks for this account.
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   (optional) The current state of the media library, derived from the
 | 
			
		||||
   *   current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Access\AccessResult
 | 
			
		||||
   *   The access result.
 | 
			
		||||
   */
 | 
			
		||||
  public function checkAccess(AccountInterface $account, ?MediaLibraryState $state = NULL) {
 | 
			
		||||
    if (!$state) {
 | 
			
		||||
      try {
 | 
			
		||||
        $state = MediaLibraryState::fromRequest($this->request);
 | 
			
		||||
      }
 | 
			
		||||
      catch (BadRequestHttpException $e) {
 | 
			
		||||
        return AccessResult::forbidden($e->getMessage());
 | 
			
		||||
      }
 | 
			
		||||
      catch (\InvalidArgumentException $e) {
 | 
			
		||||
        return AccessResult::forbidden($e->getMessage());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Deny access if the view or display are removed.
 | 
			
		||||
    $view = $this->entityTypeManager->getStorage('view')->load('media_library');
 | 
			
		||||
    if (!$view) {
 | 
			
		||||
      return AccessResult::forbidden('The media library view does not exist.')
 | 
			
		||||
        ->setCacheMaxAge(0);
 | 
			
		||||
    }
 | 
			
		||||
    if (!$view->getDisplay('widget')) {
 | 
			
		||||
      return AccessResult::forbidden('The media library widget display does not exist.')
 | 
			
		||||
        ->addCacheableDependency($view);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The user must at least be able to view media in order to access the media
 | 
			
		||||
    // library.
 | 
			
		||||
    $can_view_media = AccessResult::allowedIfHasPermission($account, 'view media')
 | 
			
		||||
      ->addCacheableDependency($view);
 | 
			
		||||
 | 
			
		||||
    // Delegate any further access checking to the opener service nominated by
 | 
			
		||||
    // the media library state.
 | 
			
		||||
    return $this->openerResolver->get($state)->checkAccess($state, $account)
 | 
			
		||||
      ->andIf($can_view_media);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the media type menu for the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   The current state of the media library, derived from the current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the media type menu.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMediaTypeMenu(MediaLibraryState $state) {
 | 
			
		||||
    // Add the menu for each type if we have more than 1 media type enabled for
 | 
			
		||||
    // the field.
 | 
			
		||||
    $allowed_type_ids = $state->getAllowedTypeIds();
 | 
			
		||||
    if (count($allowed_type_ids) <= 1) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @todo Add a class to the li element.
 | 
			
		||||
    //   https://www.drupal.org/project/drupal/issues/3029227
 | 
			
		||||
    $menu = [
 | 
			
		||||
      '#theme' => 'links__media_library_menu',
 | 
			
		||||
      '#links' => [],
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'class' => ['js-media-library-menu'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $allowed_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple($allowed_type_ids);
 | 
			
		||||
 | 
			
		||||
    $selected_type_id = $state->getSelectedTypeId();
 | 
			
		||||
    foreach ($allowed_types as $allowed_type_id => $allowed_type) {
 | 
			
		||||
      $link_state = MediaLibraryState::create($state->getOpenerId(), $state->getAllowedTypeIds(), $allowed_type_id, $state->getAvailableSlots(), $state->getOpenerParameters());
 | 
			
		||||
      // Add the 'media_library_content' parameter so the response will contain
 | 
			
		||||
      // only the updated content for the tab.
 | 
			
		||||
      // @see self::buildUi()
 | 
			
		||||
      $link_state->set('media_library_content', 1);
 | 
			
		||||
 | 
			
		||||
      $title = $allowed_type->label();
 | 
			
		||||
      $display_title = $this->t('<span class="visually-hidden">Show </span>@title<span class="visually-hidden"> media</span>', ['@title' => $title]);
 | 
			
		||||
      if ($allowed_type_id === $selected_type_id) {
 | 
			
		||||
        $display_title = $this->t('<span class="visually-hidden">Show </span>@title<span class="visually-hidden"> media</span><span class="active-tab visually-hidden"> (selected)</span>', ['@title' => $title]);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $menu['#links']['media-library-menu-' . $allowed_type_id] = [
 | 
			
		||||
        'title' => $display_title,
 | 
			
		||||
        'url' => Url::fromRoute('media_library.ui', [], [
 | 
			
		||||
          'query' => $link_state->all(),
 | 
			
		||||
        ]),
 | 
			
		||||
        'attributes' => [
 | 
			
		||||
          'role' => 'button',
 | 
			
		||||
          'data-title' => $title,
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set the active menu item.
 | 
			
		||||
    $menu['#links']['media-library-menu-' . $selected_type_id]['attributes']['class'][] = 'active';
 | 
			
		||||
 | 
			
		||||
    return $menu;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the add form for the selected media type.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   The current state of the media library, derived from the current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the media type add form.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMediaTypeAddForm(MediaLibraryState $state) {
 | 
			
		||||
    $selected_type_id = $state->getSelectedTypeId();
 | 
			
		||||
 | 
			
		||||
    $access_handler = $this->entityTypeManager->getAccessControlHandler('media');
 | 
			
		||||
    $context = [
 | 
			
		||||
      'media_library_state' => $state,
 | 
			
		||||
    ];
 | 
			
		||||
    if (!$access_handler->createAccess($selected_type_id, NULL, $context)) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $selected_type = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id);
 | 
			
		||||
    $plugin_definition = $selected_type->getSource()->getPluginDefinition();
 | 
			
		||||
 | 
			
		||||
    if (empty($plugin_definition['forms']['media_library_add'])) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // After the form to add new media is submitted, we need to rebuild the
 | 
			
		||||
    // media library with a new instance of the media add form. The form API
 | 
			
		||||
    // allows us to do that by forcing empty user input.
 | 
			
		||||
    // @see \Drupal\Core\Form\FormBuilder::doBuildForm()
 | 
			
		||||
    $form_state = new FormState();
 | 
			
		||||
    if ($state->get('_media_library_form_rebuild')) {
 | 
			
		||||
      $form_state->setUserInput([]);
 | 
			
		||||
      $state->remove('_media_library_form_rebuild');
 | 
			
		||||
    }
 | 
			
		||||
    $form_state->set('media_library_state', $state);
 | 
			
		||||
    return $this->formBuilder->buildForm($plugin_definition['forms']['media_library_add'], $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the media library view.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   The current state of the media library, derived from the current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The render array for the media library view.
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildMediaLibraryView(MediaLibraryState $state) {
 | 
			
		||||
    // @todo Make the view configurable in
 | 
			
		||||
    //   https://www.drupal.org/project/drupal/issues/2971209
 | 
			
		||||
    $view = $this->entityTypeManager->getStorage('view')->load('media_library');
 | 
			
		||||
    $view_executable = $this->viewsExecutableFactory->get($view);
 | 
			
		||||
    $display_id = $state->get('views_display_id', 'widget');
 | 
			
		||||
 | 
			
		||||
    // Make sure the state parameters are set in the request so the view can
 | 
			
		||||
    // pass the parameters along in the pager, filters etc.
 | 
			
		||||
    $view_request = $view_executable->getRequest();
 | 
			
		||||
    $view_request->query->add($state->all());
 | 
			
		||||
    $view_executable->setRequest($view_request);
 | 
			
		||||
 | 
			
		||||
    $args = [$state->getSelectedTypeId()];
 | 
			
		||||
 | 
			
		||||
    $view_executable->setDisplay($display_id);
 | 
			
		||||
    $view_executable->preExecute($args);
 | 
			
		||||
    $view_executable->execute($display_id);
 | 
			
		||||
 | 
			
		||||
    return $view_executable->buildRenderable($display_id, $args, FALSE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								web/core/modules/media_library/src/OpenerResolver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								web/core/modules/media_library/src/OpenerResolver.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a class to resolve media library openers.
 | 
			
		||||
 *
 | 
			
		||||
 * This is intended to be a very thin interface-verifying wrapper around
 | 
			
		||||
 * services which implement \Drupal\media_library\MediaLibraryOpenerInterface.
 | 
			
		||||
 * It is not an API and should not be extended or used by code that does not
 | 
			
		||||
 * interact with the Media Library module.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This service is an internal part of the modal media library dialog and
 | 
			
		||||
 *   does not provide any extension points or public API.
 | 
			
		||||
 */
 | 
			
		||||
class OpenerResolver implements OpenerResolverInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var \Drupal\media_library\MediaLibraryOpenerInterface[]
 | 
			
		||||
   */
 | 
			
		||||
  protected array $openers = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Registers an opener.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryOpenerInterface $opener
 | 
			
		||||
   *   The opener.
 | 
			
		||||
   * @param string $id
 | 
			
		||||
   *   The service ID.
 | 
			
		||||
   */
 | 
			
		||||
  public function addOpener(MediaLibraryOpenerInterface $opener, string $id): void {
 | 
			
		||||
    $this->openers[$id] = $opener;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function get(MediaLibraryState $state) {
 | 
			
		||||
    $service_id = $state->getOpenerId();
 | 
			
		||||
 | 
			
		||||
    $service = $this->openers[$service_id] ?? NULL;
 | 
			
		||||
    if ($service instanceof MediaLibraryOpenerInterface) {
 | 
			
		||||
      return $service;
 | 
			
		||||
    }
 | 
			
		||||
    throw new \RuntimeException("$service_id must be an instance of " . MediaLibraryOpenerInterface::class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines an interface to get a media library opener from the container.
 | 
			
		||||
 *
 | 
			
		||||
 * This is intended to be a very thin interface-verifying wrapper around
 | 
			
		||||
 * services which implement \Drupal\media_library\MediaLibraryOpenerInterface.
 | 
			
		||||
 * It is not an API and should not be extended or used by code that does not
 | 
			
		||||
 * interact with the Media Library module.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This interface is an internal part of the modal media library dialog and
 | 
			
		||||
 *   is only implemented by \Drupal\media_library\OpenerResolver. It is not a
 | 
			
		||||
 *   public API.
 | 
			
		||||
 */
 | 
			
		||||
interface OpenerResolverInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a media library opener service from the container.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\media_library\MediaLibraryState $state
 | 
			
		||||
   *   A value object representing the state of the media library.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\media_library\MediaLibraryOpenerInterface
 | 
			
		||||
   *   The media library opener service.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \RuntimeException
 | 
			
		||||
   *   If the requested opener service does not implement
 | 
			
		||||
   *   \Drupal\media_library\MediaLibraryOpenerInterface.
 | 
			
		||||
   */
 | 
			
		||||
  public function get(MediaLibraryState $state);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,199 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Plugin\views\field;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Ajax\AjaxResponse;
 | 
			
		||||
use Drupal\Core\Ajax\CloseDialogCommand;
 | 
			
		||||
use Drupal\Core\Ajax\MessageCommand;
 | 
			
		||||
use Drupal\Core\Form\FormBuilderInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
 | 
			
		||||
use Drupal\Core\Url;
 | 
			
		||||
use Drupal\media_library\MediaLibraryState;
 | 
			
		||||
use Drupal\views\Attribute\ViewsField;
 | 
			
		||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
 | 
			
		||||
use Drupal\views\Render\ViewsRenderPipelineMarkup;
 | 
			
		||||
use Drupal\views\ResultRow;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a field that outputs a checkbox and form for selecting media.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Plugin classes are internal.
 | 
			
		||||
 */
 | 
			
		||||
#[ViewsField("media_library_select_form")]
 | 
			
		||||
class MediaLibrarySelectForm extends FieldPluginBase implements WorkspaceSafeFormInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getValue(ResultRow $row, $field = NULL) {
 | 
			
		||||
    return '<!--form-item-' . $this->options['id'] . '--' . $row->mid . '-->';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return the name of a form field.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\views\Form\ViewsFormMainForm
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The form field name.
 | 
			
		||||
   */
 | 
			
		||||
  public function form_element_name(): string {
 | 
			
		||||
    return $this->field;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return a media entity ID from a views result row.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $row_id
 | 
			
		||||
   *   The index of a views result row.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string
 | 
			
		||||
   *   The ID of a media entity.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\views\Form\ViewsFormMainForm
 | 
			
		||||
   */
 | 
			
		||||
  public function form_element_row_id(int $row_id): string {
 | 
			
		||||
    return $this->view->result[$row_id]->mid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@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) {
 | 
			
		||||
    $form['#attributes']['class'] = ['js-media-library-views-form'];
 | 
			
		||||
    // Add target for AJAX messages.
 | 
			
		||||
    $form['media_library_messages'] = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'id' => 'media-library-messages',
 | 
			
		||||
      ],
 | 
			
		||||
      '#weight' => -10,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Add an attribute that identifies the media type displayed in the form.
 | 
			
		||||
    if (isset($this->view->args[0])) {
 | 
			
		||||
      $form['#attributes']['data-drupal-media-type'] = $this->view->args[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Render checkboxes for all rows.
 | 
			
		||||
    $form[$this->options['id']]['#tree'] = TRUE;
 | 
			
		||||
    foreach ($this->view->result as $row_index => $row) {
 | 
			
		||||
      $entity = $this->getEntity($row);
 | 
			
		||||
      if (!$entity) {
 | 
			
		||||
        $form[$this->options['id']][$row_index] = [];
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      $form[$this->options['id']][$row->mid] = [
 | 
			
		||||
        '#type' => 'checkbox',
 | 
			
		||||
        '#title' => $this->t('Select @label', [
 | 
			
		||||
          '@label' => $entity->label(),
 | 
			
		||||
        ]),
 | 
			
		||||
        '#title_display' => 'invisible',
 | 
			
		||||
        '#return_value' => $entity->id(),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The selection is persistent across different pages in the media library
 | 
			
		||||
    // and populated via JavaScript.
 | 
			
		||||
    $selection_field_id = $this->options['id'] . '_selection';
 | 
			
		||||
    $form[$selection_field_id] = [
 | 
			
		||||
      '#type' => 'hidden',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        // This is used to identify the hidden field in the form via JavaScript.
 | 
			
		||||
        'id' => 'media-library-modal-selection',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // @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.
 | 
			
		||||
    $query = $this->view->getRequest()->query->all();
 | 
			
		||||
    $query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
 | 
			
		||||
    $query['views_display_id'] = $this->view->getDisplay()->display['id'];
 | 
			
		||||
    $form['actions']['submit']['#ajax'] = [
 | 
			
		||||
      'url' => Url::fromRoute('media_library.ui'),
 | 
			
		||||
      'options' => [
 | 
			
		||||
        'query' => $query,
 | 
			
		||||
      ],
 | 
			
		||||
      'callback' => [static::class, 'updateWidget'],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['actions']['submit']['#value'] = $this->t('Insert selected');
 | 
			
		||||
    $form['actions']['submit']['#button_type'] = 'primary';
 | 
			
		||||
    $form['actions']['submit']['#field_id'] = $selection_field_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.
 | 
			
		||||
   * @param \Symfony\Component\HttpFoundation\Request $request
 | 
			
		||||
   *   The current request.
 | 
			
		||||
   *
 | 
			
		||||
   * @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, Request $request) {
 | 
			
		||||
    $field_id = $form_state->getTriggeringElement()['#field_id'];
 | 
			
		||||
    $selected_ids = $form_state->getValue($field_id);
 | 
			
		||||
    $selected_ids = $selected_ids ? array_filter(explode(',', $selected_ids)) : [];
 | 
			
		||||
 | 
			
		||||
    // Allow the opener service to handle the selection.
 | 
			
		||||
    $state = MediaLibraryState::fromRequest($request);
 | 
			
		||||
 | 
			
		||||
    $current_selection = $form_state->getValue('media_library_select_form_selection');
 | 
			
		||||
    $available_slots = $state->getAvailableSlots();
 | 
			
		||||
    $selected_count = count(explode(',', $current_selection));
 | 
			
		||||
    if ($available_slots > 0 && $selected_count > $available_slots) {
 | 
			
		||||
      $response = new AjaxResponse();
 | 
			
		||||
      $error = \Drupal::translation()->formatPlural($selected_count - $available_slots, 'There are currently @total items selected. The maximum number of items for the field is @max. Remove @count item from the selection.', 'There are currently @total items selected. The maximum number of items for the field is @max. Remove @count items from the selection.', [
 | 
			
		||||
        '@total' => $selected_count,
 | 
			
		||||
        '@max' => $available_slots,
 | 
			
		||||
      ]);
 | 
			
		||||
      $response->addCommand(new MessageCommand($error, '#media-library-messages', ['type' => 'error']));
 | 
			
		||||
      return $response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return \Drupal::service('media_library.opener_resolver')
 | 
			
		||||
      ->get($state)
 | 
			
		||||
      ->getSelectionResponse($state, $selected_ids)
 | 
			
		||||
      ->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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,30 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library\Routing;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Routing\RouteSubscriberBase;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Subscriber for media library routes.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   Tagged services are internal.
 | 
			
		||||
 */
 | 
			
		||||
class RouteSubscriber extends RouteSubscriberBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function alterRoutes(RouteCollection $collection) {
 | 
			
		||||
    // Add the media library UI access checks to the widget displays of the
 | 
			
		||||
    // media library view.
 | 
			
		||||
    if ($route = $collection->get('view.media_library.widget')) {
 | 
			
		||||
      $route->addRequirements(['_custom_access' => 'media_library.ui_builder:checkAccess']);
 | 
			
		||||
    }
 | 
			
		||||
    if ($route = $collection->get('view.media_library.widget_table')) {
 | 
			
		||||
      $route->addRequirements(['_custom_access' => 'media_library.ui_builder:checkAccess']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
{#
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Default theme implementation to present a media entity in the media library.
 | 
			
		||||
 *
 | 
			
		||||
 * Available variables:
 | 
			
		||||
 * - media: The entity with limited access to object properties and methods.
 | 
			
		||||
 *   Only method names starting with "get", "has", or "is" and a few common
 | 
			
		||||
 *   methods such as "id", "label", and "bundle" are available. For example:
 | 
			
		||||
 *   - entity.getEntityTypeId() will return the entity type ID.
 | 
			
		||||
 *   - entity.hasField('field_example') returns TRUE if the entity includes
 | 
			
		||||
 *     field_example. (This does not indicate the presence of a value in this
 | 
			
		||||
 *     field.)
 | 
			
		||||
 *   Calling other methods, such as entity.delete(), will result in an exception.
 | 
			
		||||
 *   See \Drupal\Core\Entity\EntityInterface for a full list of methods.
 | 
			
		||||
 * - name: Name of the media.
 | 
			
		||||
 * - content: Media content.
 | 
			
		||||
 * - title_prefix: Additional output populated by modules, intended to be
 | 
			
		||||
 *   displayed in front of the main title tag that appears in the template.
 | 
			
		||||
 * - title_suffix: Additional output populated by modules, intended to be
 | 
			
		||||
 *   displayed after the main title tag that appears in the template.
 | 
			
		||||
 * - view_mode: View mode; for example, "teaser" or "full".
 | 
			
		||||
 * - attributes: HTML attributes for the containing element.
 | 
			
		||||
 * - title_attributes: Same as attributes, except applied to the main title
 | 
			
		||||
 *   tag that appears in the template.
 | 
			
		||||
 * - url: Direct URL of the media.
 | 
			
		||||
 * - preview_attributes: HTML attributes for the preview wrapper.
 | 
			
		||||
 * - metadata_attributes: HTML attributes for the expandable metadata area.
 | 
			
		||||
 * - status: Whether or not the Media is published.
 | 
			
		||||
 *
 | 
			
		||||
 * @see template_preprocess_media()
 | 
			
		||||
 * @see media_library_preprocess_media()
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup themeable
 | 
			
		||||
 */
 | 
			
		||||
#}
 | 
			
		||||
<article{{ attributes }}>
 | 
			
		||||
  {% if content %}
 | 
			
		||||
    <div{{ preview_attributes.addClass('js-media-library-item-preview') }}>
 | 
			
		||||
      {{ content|without('name') }}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% if not status %}
 | 
			
		||||
      {{ "unpublished"|t }}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <div{{ metadata_attributes }}>
 | 
			
		||||
      {{ name }}
 | 
			
		||||
    </div>
 | 
			
		||||
  {% endif %}
 | 
			
		||||
</article>
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
{#
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Default theme implementation of a media library item.
 | 
			
		||||
 *
 | 
			
		||||
 * This is used when displaying selected media items, either in the field
 | 
			
		||||
 * widget or in the "Additional selected media" area when adding new
 | 
			
		||||
 * media items in the media library modal dialog.
 | 
			
		||||
 *
 | 
			
		||||
 * Available variables:
 | 
			
		||||
 * - attributes: HTML attributes for the containing element.
 | 
			
		||||
 * - content: The content of the media library item, plus any additional
 | 
			
		||||
 *   fields or elements surrounding it.
 | 
			
		||||
 *
 | 
			
		||||
 * @see template_preprocess_media_library_item()
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup themeable
 | 
			
		||||
 */
 | 
			
		||||
#}
 | 
			
		||||
<div{{ attributes }}>
 | 
			
		||||
  {{ content }}
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
{#
 | 
			
		||||
/**
 | 
			
		||||
 * @file
 | 
			
		||||
 * Default theme implementation of a container used to wrap the media library's
 | 
			
		||||
 * modal dialog interface.
 | 
			
		||||
 *
 | 
			
		||||
 * Available variables:
 | 
			
		||||
 * - attributes: HTML attributes for the containing element.
 | 
			
		||||
 * - menu: The menu of available media types to choose from.
 | 
			
		||||
 * - content: The form to add new media items, followed by the grid or table of
 | 
			
		||||
 *   existing media items to choose from.
 | 
			
		||||
 *
 | 
			
		||||
 * @see template_preprocess_media_library_wrapper()
 | 
			
		||||
 *
 | 
			
		||||
 * @ingroup themeable
 | 
			
		||||
 */
 | 
			
		||||
#}
 | 
			
		||||
<div{{ attributes }}>
 | 
			
		||||
  {{ menu }}
 | 
			
		||||
  {{ content }}
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
name: 'Media Library Form Overwrite test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Test module for Media Library.'
 | 
			
		||||
package: Testing
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:image
 | 
			
		||||
  - drupal:media_library
 | 
			
		||||
  - drupal:menu_ui
 | 
			
		||||
  - drupal:node
 | 
			
		||||
  - drupal:path
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library_form_overwrite_test\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\media_library\Form\AddFormBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test add form.
 | 
			
		||||
 */
 | 
			
		||||
class TestAddForm extends AddFormBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildInputElement(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId() {
 | 
			
		||||
    return 'test_add_form';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library_form_overwrite_test\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
use Drupal\media_library_form_overwrite_test\Form\TestAddForm;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for media_library_form_overwrite_test.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryFormOverwriteTestHooks {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_media_source_info_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('media_source_info_alter')]
 | 
			
		||||
  public function mediaSourceInfoAlter(array &$sources): void {
 | 
			
		||||
    $sources['image']['forms']['media_library_add'] = TestAddForm::class;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,59 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_five.field_media_test_oembed_video
 | 
			
		||||
    - media.type.type_five
 | 
			
		||||
  module:
 | 
			
		||||
    - path
 | 
			
		||||
id: media.type_five.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_five
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: datetime_timestamp
 | 
			
		||||
    weight: 10
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_test_oembed_video:
 | 
			
		||||
    type: oembed_textfield
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: -5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  path:
 | 
			
		||||
    type: path
 | 
			
		||||
    weight: 30
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  status:
 | 
			
		||||
    type: boolean_checkbox
 | 
			
		||||
    weight: 100
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      display_label: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  uid:
 | 
			
		||||
    type: entity_reference_autocomplete
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      match_operator: CONTAINS
 | 
			
		||||
      match_limit: 10
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - core.entity_form_mode.media.media_library
 | 
			
		||||
    - field.field.media.type_five.field_media_test_oembed_video
 | 
			
		||||
    - media.type.type_five
 | 
			
		||||
id: media.type_five.media_library
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_five
 | 
			
		||||
mode: media_library
 | 
			
		||||
content:
 | 
			
		||||
  field_media_test_oembed_video:
 | 
			
		||||
    type: oembed_textfield
 | 
			
		||||
    weight: 1
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden:
 | 
			
		||||
  created: true
 | 
			
		||||
  path: true
 | 
			
		||||
  status: true
 | 
			
		||||
  uid: true
 | 
			
		||||
@ -0,0 +1,70 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_four.field_media_test_image
 | 
			
		||||
    - field.field.media.type_four.field_media_extra_image
 | 
			
		||||
    - image.style.medium
 | 
			
		||||
    - media.type.type_four
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - path
 | 
			
		||||
id: media.type_four.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_four
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: datetime_timestamp
 | 
			
		||||
    weight: 10
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_test_image:
 | 
			
		||||
    type: image_image
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      progress_indicator: throbber
 | 
			
		||||
      preview_image_style: medium
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_extra_image:
 | 
			
		||||
    type: image_image
 | 
			
		||||
    weight: 1
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      progress_indicator: throbber
 | 
			
		||||
      preview_image_style: medium
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: -5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  path:
 | 
			
		||||
    type: path
 | 
			
		||||
    weight: 30
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  status:
 | 
			
		||||
    type: boolean_checkbox
 | 
			
		||||
    weight: 100
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      display_label: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  uid:
 | 
			
		||||
    type: entity_reference_autocomplete
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      match_operator: CONTAINS
 | 
			
		||||
      match_limit: 10
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - core.entity_form_mode.media.media_library
 | 
			
		||||
    - field.field.media.type_four.field_media_test_image
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_four
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
id: media.type_four.media_library
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_four
 | 
			
		||||
mode: media_library
 | 
			
		||||
content:
 | 
			
		||||
  field_media_test_image:
 | 
			
		||||
    type: image_image
 | 
			
		||||
    weight: 2
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      progress_indicator: throbber
 | 
			
		||||
      preview_image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_extra_image:
 | 
			
		||||
    type: image_image
 | 
			
		||||
    weight: 1
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      progress_indicator: throbber
 | 
			
		||||
      preview_image_style: medium
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden:
 | 
			
		||||
  created: true
 | 
			
		||||
  path: true
 | 
			
		||||
  status: true
 | 
			
		||||
  uid: true
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_one.field_media_test
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
id: media.type_one.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_one
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: datetime_timestamp
 | 
			
		||||
    weight: 10
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_test:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: 11
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: -5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  uid:
 | 
			
		||||
    type: entity_reference_autocomplete
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      match_operator: CONTAINS
 | 
			
		||||
      match_limit: 10
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_three.field_media_test_image
 | 
			
		||||
    - image.style.medium
 | 
			
		||||
    - media.type.type_three
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - path
 | 
			
		||||
id: media.type_three.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_three
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: datetime_timestamp
 | 
			
		||||
    weight: 10
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_test_image:
 | 
			
		||||
    type: image_image
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      progress_indicator: throbber
 | 
			
		||||
      preview_image_style: medium
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: -5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  path:
 | 
			
		||||
    type: path
 | 
			
		||||
    weight: 30
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  status:
 | 
			
		||||
    type: boolean_checkbox
 | 
			
		||||
    weight: 100
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      display_label: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  uid:
 | 
			
		||||
    type: entity_reference_autocomplete
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      match_operator: CONTAINS
 | 
			
		||||
      match_limit: 10
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - core.entity_form_mode.media.media_library
 | 
			
		||||
    - field.field.media.type_three.field_media_test_image
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_three
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
id: media.type_three.media_library
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_three
 | 
			
		||||
mode: media_library
 | 
			
		||||
content:
 | 
			
		||||
  field_media_test_image:
 | 
			
		||||
    type: image_image
 | 
			
		||||
    weight: 1
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      progress_indicator: throbber
 | 
			
		||||
      preview_image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden:
 | 
			
		||||
  created: true
 | 
			
		||||
  path: true
 | 
			
		||||
  status: true
 | 
			
		||||
  uid: true
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_two.field_media_test_1
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
id: media.type_two.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_two
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: datetime_timestamp
 | 
			
		||||
    weight: 10
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_media_test_1:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: 11
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  name:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: -5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  uid:
 | 
			
		||||
    type: entity_reference_autocomplete
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      match_operator: CONTAINS
 | 
			
		||||
      match_limit: 10
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,103 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.node.basic_page.field_twin_media
 | 
			
		||||
    - field.field.node.basic_page.field_single_media_type
 | 
			
		||||
    - field.field.node.basic_page.field_unlimited_media
 | 
			
		||||
    - field.field.node.basic_page.field_no_add_media
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
  module:
 | 
			
		||||
    - media_library
 | 
			
		||||
id: node.basic_page.default
 | 
			
		||||
targetEntityType: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: datetime_timestamp
 | 
			
		||||
    weight: 10
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_twin_media:
 | 
			
		||||
    type: media_library_widget
 | 
			
		||||
    weight: 122
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      media_types:
 | 
			
		||||
        - type_three
 | 
			
		||||
        - type_one
 | 
			
		||||
        - type_two
 | 
			
		||||
        - type_four
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_single_media_type:
 | 
			
		||||
    type: media_library_widget
 | 
			
		||||
    weight: 124
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_unlimited_media:
 | 
			
		||||
    type: media_library_widget
 | 
			
		||||
    weight: 121
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_no_add_media:
 | 
			
		||||
    type: media_library_widget
 | 
			
		||||
    weight: 123
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_null_types_media:
 | 
			
		||||
    type: media_library_widget
 | 
			
		||||
    weight: 125
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  field_empty_types_media:
 | 
			
		||||
    type: media_library_widget
 | 
			
		||||
    weight: 126
 | 
			
		||||
    region: content
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  promote:
 | 
			
		||||
    type: boolean_checkbox
 | 
			
		||||
    weight: 15
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      display_label: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  status:
 | 
			
		||||
    type: boolean_checkbox
 | 
			
		||||
    weight: 120
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      display_label: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  sticky:
 | 
			
		||||
    type: boolean_checkbox
 | 
			
		||||
    weight: 16
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      display_label: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  title:
 | 
			
		||||
    type: string_textfield
 | 
			
		||||
    weight: -5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
  uid:
 | 
			
		||||
    type: entity_reference_autocomplete
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
    settings:
 | 
			
		||||
      match_operator: CONTAINS
 | 
			
		||||
      match_limit: 10
 | 
			
		||||
      size: 60
 | 
			
		||||
      placeholder: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_five.field_media_test_oembed_video
 | 
			
		||||
    - media.type.type_five
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
id: media.type_five.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_five
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: timestamp
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      date_format: medium
 | 
			
		||||
      custom_date_format: ''
 | 
			
		||||
      timezone: ''
 | 
			
		||||
      tooltip:
 | 
			
		||||
        date_format: long
 | 
			
		||||
        custom_date_format: ''
 | 
			
		||||
      time_diff:
 | 
			
		||||
        enabled: false
 | 
			
		||||
        future_format: '@interval hence'
 | 
			
		||||
        past_format: '@interval ago'
 | 
			
		||||
        granularity: 2
 | 
			
		||||
        refresh: 60
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
  field_media_test_oembed_video:
 | 
			
		||||
    type: oembed
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      max_width: 0
 | 
			
		||||
      max_height: 0
 | 
			
		||||
      loading:
 | 
			
		||||
        attribute: lazy
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 6
 | 
			
		||||
    region: content
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
  uid:
 | 
			
		||||
    type: author
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
hidden:
 | 
			
		||||
  name: true
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_five.field_media_test_oembed_video
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_five
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
id: media.type_five.media_library
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_five
 | 
			
		||||
mode: media_library
 | 
			
		||||
content:
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    weight: 5
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
      image_link: ''
 | 
			
		||||
    region: content
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
hidden:
 | 
			
		||||
  created: true
 | 
			
		||||
  field_media_test_oembed_video: true
 | 
			
		||||
  name: true
 | 
			
		||||
  uid: true
 | 
			
		||||
@ -0,0 +1,59 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_one.field_media_test
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - user
 | 
			
		||||
id: media.type_one.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_one
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: timestamp
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      date_format: medium
 | 
			
		||||
      custom_date_format: ''
 | 
			
		||||
      timezone: ''
 | 
			
		||||
      tooltip:
 | 
			
		||||
        date_format: long
 | 
			
		||||
        custom_date_format: ''
 | 
			
		||||
      time_diff:
 | 
			
		||||
        enabled: false
 | 
			
		||||
        future_format: '@interval hence'
 | 
			
		||||
        past_format: '@interval ago'
 | 
			
		||||
        granularity: 2
 | 
			
		||||
        refresh: 60
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
  field_media_test:
 | 
			
		||||
    type: string
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      link_to_entity: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 6
 | 
			
		||||
    region: content
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
  uid:
 | 
			
		||||
    type: author
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_one.field_media_test
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - user
 | 
			
		||||
id: media.type_one.media_library
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_one
 | 
			
		||||
mode: media_library
 | 
			
		||||
content:
 | 
			
		||||
  field_media_test:
 | 
			
		||||
    type: string
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      link_to_entity: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 6
 | 
			
		||||
    region: content
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
hidden:
 | 
			
		||||
  created: true
 | 
			
		||||
  name: true
 | 
			
		||||
  uid: true
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_three.field_media_test_image
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_three
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - user
 | 
			
		||||
id: media.type_three.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_three
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: timestamp
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      date_format: medium
 | 
			
		||||
      custom_date_format: ''
 | 
			
		||||
      timezone: ''
 | 
			
		||||
      tooltip:
 | 
			
		||||
        date_format: long
 | 
			
		||||
        custom_date_format: ''
 | 
			
		||||
      time_diff:
 | 
			
		||||
        enabled: false
 | 
			
		||||
        future_format: '@interval hence'
 | 
			
		||||
        past_format: '@interval ago'
 | 
			
		||||
        granularity: 2
 | 
			
		||||
        refresh: 60
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
  field_media_test_image:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: ''
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 6
 | 
			
		||||
    region: content
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
  uid:
 | 
			
		||||
    type: author
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
hidden:
 | 
			
		||||
  name: true
 | 
			
		||||
@ -0,0 +1,59 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_two.field_media_test_1
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - user
 | 
			
		||||
id: media.type_two.default
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_two
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  created:
 | 
			
		||||
    type: timestamp
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      date_format: medium
 | 
			
		||||
      custom_date_format: ''
 | 
			
		||||
      timezone: ''
 | 
			
		||||
      tooltip:
 | 
			
		||||
        date_format: long
 | 
			
		||||
        custom_date_format: ''
 | 
			
		||||
      time_diff:
 | 
			
		||||
        enabled: false
 | 
			
		||||
        future_format: '@interval hence'
 | 
			
		||||
        past_format: '@interval ago'
 | 
			
		||||
        granularity: 2
 | 
			
		||||
        refresh: 60
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
  field_media_test_1:
 | 
			
		||||
    type: string
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      link_to_entity: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 6
 | 
			
		||||
    region: content
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
  uid:
 | 
			
		||||
    type: author
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 0
 | 
			
		||||
    region: content
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.media.type_two.field_media_test_1
 | 
			
		||||
    - image.style.thumbnail
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
    - user
 | 
			
		||||
id: media.type_two.media_library
 | 
			
		||||
targetEntityType: media
 | 
			
		||||
bundle: type_two
 | 
			
		||||
mode: media_library
 | 
			
		||||
content:
 | 
			
		||||
  field_media_test_1:
 | 
			
		||||
    type: string
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      link_to_entity: true
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 6
 | 
			
		||||
    region: content
 | 
			
		||||
  thumbnail:
 | 
			
		||||
    type: image
 | 
			
		||||
    label: hidden
 | 
			
		||||
    settings:
 | 
			
		||||
      image_link: ''
 | 
			
		||||
      image_style: thumbnail
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 5
 | 
			
		||||
    region: content
 | 
			
		||||
hidden:
 | 
			
		||||
  created: true
 | 
			
		||||
  name: true
 | 
			
		||||
  uid: true
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.field.node.basic_page.field_twin_media
 | 
			
		||||
    - field.field.node.basic_page.field_unlimited_media
 | 
			
		||||
    - field.field.node.basic_page.field_no_add_media
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
  module:
 | 
			
		||||
    - user
 | 
			
		||||
id: node.basic_page.default
 | 
			
		||||
targetEntityType: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
mode: default
 | 
			
		||||
content:
 | 
			
		||||
  field_twin_media:
 | 
			
		||||
    type: entity_reference_entity_view
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      view_mode: default
 | 
			
		||||
      link: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 102
 | 
			
		||||
    region: content
 | 
			
		||||
  field_single_media_type:
 | 
			
		||||
    type: entity_reference_entity_view
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      view_mode: default
 | 
			
		||||
      link: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 101
 | 
			
		||||
    region: content
 | 
			
		||||
  field_unlimited_media:
 | 
			
		||||
    type: entity_reference_entity_view
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      view_mode: default
 | 
			
		||||
      link: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 101
 | 
			
		||||
    region: content
 | 
			
		||||
  field_no_add_media:
 | 
			
		||||
    type: entity_reference_entity_view
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      view_mode: default
 | 
			
		||||
      link: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 103
 | 
			
		||||
    region: content
 | 
			
		||||
  field_empty_types_media:
 | 
			
		||||
    type: entity_reference_entity_view
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      view_mode: default
 | 
			
		||||
      link: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 104
 | 
			
		||||
    region: content
 | 
			
		||||
  field_null_types_media:
 | 
			
		||||
    type: entity_reference_entity_view
 | 
			
		||||
    label: above
 | 
			
		||||
    settings:
 | 
			
		||||
      view_mode: default
 | 
			
		||||
      link: false
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 105
 | 
			
		||||
    region: content
 | 
			
		||||
  links:
 | 
			
		||||
    settings: {  }
 | 
			
		||||
    third_party_settings: {  }
 | 
			
		||||
    weight: 100
 | 
			
		||||
    region: content
 | 
			
		||||
hidden: {  }
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.media.field_media_test_oembed_video
 | 
			
		||||
    - media.type.type_five
 | 
			
		||||
id: media.type_five.field_media_test_oembed_video
 | 
			
		||||
field_name: field_media_test_oembed_video
 | 
			
		||||
entity_type: media
 | 
			
		||||
bundle: type_five
 | 
			
		||||
label: 'Video URL'
 | 
			
		||||
description: ''
 | 
			
		||||
required: true
 | 
			
		||||
translatable: true
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings: {  }
 | 
			
		||||
field_type: string
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.media.field_media_extra_image
 | 
			
		||||
    - media.type.type_four
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
id: media.type_three.field_media_extra_image
 | 
			
		||||
field_name: field_media_extra_image
 | 
			
		||||
entity_type: media
 | 
			
		||||
bundle: type_four
 | 
			
		||||
label: 'Extra Image'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: true
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:file'
 | 
			
		||||
  handler_settings: {  }
 | 
			
		||||
  file_directory: type-four-extra-dir
 | 
			
		||||
  file_extensions: jpg
 | 
			
		||||
  max_filesize: ''
 | 
			
		||||
  max_resolution: ''
 | 
			
		||||
  min_resolution: ''
 | 
			
		||||
  alt_field: false
 | 
			
		||||
  alt_field_required: false
 | 
			
		||||
  title_field: false
 | 
			
		||||
  title_field_required: false
 | 
			
		||||
  default_image:
 | 
			
		||||
    uuid: null
 | 
			
		||||
    alt: ''
 | 
			
		||||
    title: ''
 | 
			
		||||
    width: null
 | 
			
		||||
    height: null
 | 
			
		||||
field_type: image
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.media.field_media_test_image
 | 
			
		||||
    - media.type.type_four
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
id: media.type_three.field_media_test_image
 | 
			
		||||
field_name: field_media_test_image
 | 
			
		||||
entity_type: media
 | 
			
		||||
bundle: type_four
 | 
			
		||||
label: Image
 | 
			
		||||
description: ''
 | 
			
		||||
required: true
 | 
			
		||||
translatable: true
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:file'
 | 
			
		||||
  handler_settings: {  }
 | 
			
		||||
  file_directory: type-four-dir
 | 
			
		||||
  file_extensions: jpg
 | 
			
		||||
  max_filesize: ''
 | 
			
		||||
  max_resolution: ''
 | 
			
		||||
  min_resolution: ''
 | 
			
		||||
  alt_field: true
 | 
			
		||||
  alt_field_required: true
 | 
			
		||||
  title_field: false
 | 
			
		||||
  title_field_required: false
 | 
			
		||||
  default_image:
 | 
			
		||||
    uuid: null
 | 
			
		||||
    alt: ''
 | 
			
		||||
    title: ''
 | 
			
		||||
    width: null
 | 
			
		||||
    height: null
 | 
			
		||||
field_type: image
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.media.field_media_test
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
id: media.type_one.field_media_test
 | 
			
		||||
field_name: field_media_test
 | 
			
		||||
entity_type: media
 | 
			
		||||
bundle: type_one
 | 
			
		||||
label: field_media_test
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: true
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings: {  }
 | 
			
		||||
field_type: string
 | 
			
		||||
@ -0,0 +1,37 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.media.field_media_test_image
 | 
			
		||||
    - media.type.type_three
 | 
			
		||||
  module:
 | 
			
		||||
    - image
 | 
			
		||||
id: media.type_three.field_media_test_image
 | 
			
		||||
field_name: field_media_test_image
 | 
			
		||||
entity_type: media
 | 
			
		||||
bundle: type_three
 | 
			
		||||
label: Image
 | 
			
		||||
description: ''
 | 
			
		||||
required: true
 | 
			
		||||
translatable: true
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:file'
 | 
			
		||||
  handler_settings: {  }
 | 
			
		||||
  file_directory: type-three-dir
 | 
			
		||||
  file_extensions: 'png gif jpg jpeg webp'
 | 
			
		||||
  max_filesize: ''
 | 
			
		||||
  max_resolution: ''
 | 
			
		||||
  min_resolution: ''
 | 
			
		||||
  alt_field: true
 | 
			
		||||
  alt_field_required: true
 | 
			
		||||
  title_field: false
 | 
			
		||||
  title_field_required: false
 | 
			
		||||
  default_image:
 | 
			
		||||
    uuid: null
 | 
			
		||||
    alt: ''
 | 
			
		||||
    title: ''
 | 
			
		||||
    width: null
 | 
			
		||||
    height: null
 | 
			
		||||
field_type: image
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.media.field_media_test_1
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
id: media.type_two.field_media_test_1
 | 
			
		||||
field_name: field_media_test_1
 | 
			
		||||
entity_type: media
 | 
			
		||||
bundle: type_two
 | 
			
		||||
label: field_media_test_1
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: true
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings: {  }
 | 
			
		||||
field_type: string
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.node.field_empty_types_media
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
id: node.basic_page.field_empty_types_media
 | 
			
		||||
field_name: field_empty_types_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
label: 'Empty types media'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: false
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:media'
 | 
			
		||||
  handler_settings:
 | 
			
		||||
    target_bundles: {  }
 | 
			
		||||
    sort:
 | 
			
		||||
      field: _none
 | 
			
		||||
    auto_create: false
 | 
			
		||||
    auto_create_bundle: file
 | 
			
		||||
field_type: entity_reference
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.node.field_no_add_media
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
id: node.basic_page.field_no_add_media
 | 
			
		||||
field_name: field_no_add_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
label: 'No add media'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: false
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:media'
 | 
			
		||||
  handler_settings:
 | 
			
		||||
    target_bundles:
 | 
			
		||||
      type_one: type_one
 | 
			
		||||
      type_two: type_two
 | 
			
		||||
    sort:
 | 
			
		||||
      field: _none
 | 
			
		||||
    auto_create: false
 | 
			
		||||
    auto_create_bundle: file
 | 
			
		||||
field_type: entity_reference
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.node.field_null_types_media
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
id: node.basic_page.field_null_types_media
 | 
			
		||||
field_name: field_null_types_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
label: 'Null types media'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: false
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:media'
 | 
			
		||||
  handler_settings:
 | 
			
		||||
    target_bundles: null
 | 
			
		||||
    sort:
 | 
			
		||||
      field: _none
 | 
			
		||||
    auto_create: false
 | 
			
		||||
    auto_create_bundle: file
 | 
			
		||||
field_type: entity_reference
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.node.field_single_media_type
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
id: node.basic_page.field_single_media_type
 | 
			
		||||
field_name: field_single_media_type
 | 
			
		||||
entity_type: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
label: 'Single media type'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: false
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:media'
 | 
			
		||||
  handler_settings:
 | 
			
		||||
    target_bundles:
 | 
			
		||||
      type_one: type_one
 | 
			
		||||
    sort:
 | 
			
		||||
      field: _none
 | 
			
		||||
    auto_create: false
 | 
			
		||||
    auto_create_bundle: file
 | 
			
		||||
field_type: entity_reference
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.node.field_twin_media
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
    - media.type.type_two
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
id: node.basic_page.field_twin_media
 | 
			
		||||
field_name: field_twin_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
label: 'Twin media'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: false
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:media'
 | 
			
		||||
  handler_settings:
 | 
			
		||||
    target_bundles:
 | 
			
		||||
      type_one: type_one
 | 
			
		||||
      type_two: type_two
 | 
			
		||||
      type_three: type_three
 | 
			
		||||
      type_four: type_four
 | 
			
		||||
    sort:
 | 
			
		||||
      field: _none
 | 
			
		||||
    auto_create: false
 | 
			
		||||
    auto_create_bundle: file
 | 
			
		||||
field_type: entity_reference
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  config:
 | 
			
		||||
    - field.storage.node.field_unlimited_media
 | 
			
		||||
    - media.type.type_one
 | 
			
		||||
    - node.type.basic_page
 | 
			
		||||
id: node.basic_page.field_unlimited_media
 | 
			
		||||
field_name: field_unlimited_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
bundle: basic_page
 | 
			
		||||
label: 'Unlimited media'
 | 
			
		||||
description: ''
 | 
			
		||||
required: false
 | 
			
		||||
translatable: false
 | 
			
		||||
default_value: {  }
 | 
			
		||||
default_value_callback: ''
 | 
			
		||||
settings:
 | 
			
		||||
  handler: 'default:media'
 | 
			
		||||
  handler_settings:
 | 
			
		||||
    target_bundles:
 | 
			
		||||
      type_one: type_one
 | 
			
		||||
      type_three: type_three
 | 
			
		||||
      type_five: type_five
 | 
			
		||||
    sort:
 | 
			
		||||
      field: _none
 | 
			
		||||
    auto_create: false
 | 
			
		||||
    auto_create_bundle: audio
 | 
			
		||||
field_type: entity_reference
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - file
 | 
			
		||||
    - image
 | 
			
		||||
    - media
 | 
			
		||||
id: media.field_media_extra_image
 | 
			
		||||
field_name: field_media_extra_image
 | 
			
		||||
entity_type: media
 | 
			
		||||
type: image
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: file
 | 
			
		||||
  display_field: false
 | 
			
		||||
  display_default: false
 | 
			
		||||
  uri_scheme: public
 | 
			
		||||
  default_image:
 | 
			
		||||
    uuid: null
 | 
			
		||||
    alt: ''
 | 
			
		||||
    title: ''
 | 
			
		||||
    width: null
 | 
			
		||||
    height: null
 | 
			
		||||
module: image
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
id: media.field_media_test
 | 
			
		||||
field_name: field_media_test
 | 
			
		||||
entity_type: media
 | 
			
		||||
type: string
 | 
			
		||||
settings:
 | 
			
		||||
  max_length: 255
 | 
			
		||||
  case_sensitive: false
 | 
			
		||||
  is_ascii: false
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
id: media.field_media_test_1
 | 
			
		||||
field_name: field_media_test_1
 | 
			
		||||
entity_type: media
 | 
			
		||||
type: string
 | 
			
		||||
settings:
 | 
			
		||||
  max_length: 255
 | 
			
		||||
  case_sensitive: false
 | 
			
		||||
  is_ascii: false
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - file
 | 
			
		||||
    - image
 | 
			
		||||
    - media
 | 
			
		||||
id: media.field_media_test_image
 | 
			
		||||
field_name: field_media_test_image
 | 
			
		||||
entity_type: media
 | 
			
		||||
type: image
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: file
 | 
			
		||||
  display_field: false
 | 
			
		||||
  display_default: false
 | 
			
		||||
  uri_scheme: public
 | 
			
		||||
  default_image:
 | 
			
		||||
    uuid: null
 | 
			
		||||
    alt: ''
 | 
			
		||||
    title: ''
 | 
			
		||||
    width: null
 | 
			
		||||
    height: null
 | 
			
		||||
module: image
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
id: media.field_media_test_oembed_video
 | 
			
		||||
field_name: field_media_test_oembed_video
 | 
			
		||||
entity_type: media
 | 
			
		||||
type: string
 | 
			
		||||
settings:
 | 
			
		||||
  max_length: 255
 | 
			
		||||
  case_sensitive: false
 | 
			
		||||
  is_ascii: false
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - node
 | 
			
		||||
id: node.field_empty_types_media
 | 
			
		||||
field_name: field_empty_types_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
type: entity_reference
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: media
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: -1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - node
 | 
			
		||||
id: node.field_no_add_media
 | 
			
		||||
field_name: field_no_add_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
type: entity_reference
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: media
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: -1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - node
 | 
			
		||||
id: node.field_null_types_media
 | 
			
		||||
field_name: field_null_types_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
type: entity_reference
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: media
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: -1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - node
 | 
			
		||||
id: node.field_single_media_type
 | 
			
		||||
field_name: field_single_media_type
 | 
			
		||||
entity_type: node
 | 
			
		||||
type: entity_reference
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: media
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - node
 | 
			
		||||
id: node.field_twin_media
 | 
			
		||||
field_name: field_twin_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
type: entity_reference
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: media
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: 2
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - node
 | 
			
		||||
id: node.field_unlimited_media
 | 
			
		||||
field_name: field_unlimited_media
 | 
			
		||||
entity_type: node
 | 
			
		||||
type: entity_reference
 | 
			
		||||
settings:
 | 
			
		||||
  target_type: media
 | 
			
		||||
module: core
 | 
			
		||||
locked: false
 | 
			
		||||
cardinality: -1
 | 
			
		||||
translatable: true
 | 
			
		||||
indexes: {  }
 | 
			
		||||
persist_with_no_fields: false
 | 
			
		||||
custom_storage: false
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies: {  }
 | 
			
		||||
id: type_five
 | 
			
		||||
label: 'Type Five'
 | 
			
		||||
description: ''
 | 
			
		||||
source: 'oembed:video'
 | 
			
		||||
queue_thumbnail_downloads: false
 | 
			
		||||
new_revision: false
 | 
			
		||||
source_configuration:
 | 
			
		||||
  source_field: field_media_test_oembed_video
 | 
			
		||||
  thumbnails_directory: 'public://oembed_thumbnails'
 | 
			
		||||
  providers:
 | 
			
		||||
    - YouTube
 | 
			
		||||
    - Vimeo
 | 
			
		||||
field_map: {  }
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies: {  }
 | 
			
		||||
id: type_four
 | 
			
		||||
label: 'Type Four'
 | 
			
		||||
description: ''
 | 
			
		||||
source: image
 | 
			
		||||
queue_thumbnail_downloads: false
 | 
			
		||||
new_revision: false
 | 
			
		||||
source_configuration:
 | 
			
		||||
  source_field: field_media_test_image
 | 
			
		||||
field_map: {  }
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - media_test_source
 | 
			
		||||
id: type_one
 | 
			
		||||
label: 'Type One'
 | 
			
		||||
description: ''
 | 
			
		||||
source: test
 | 
			
		||||
queue_thumbnail_downloads: false
 | 
			
		||||
new_revision: false
 | 
			
		||||
source_configuration:
 | 
			
		||||
  source_field: field_media_test
 | 
			
		||||
  test_config_value: 'This is default value.'
 | 
			
		||||
field_map: {  }
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies: {  }
 | 
			
		||||
id: type_three
 | 
			
		||||
label: 'Type Three'
 | 
			
		||||
description: ''
 | 
			
		||||
source: image
 | 
			
		||||
queue_thumbnail_downloads: false
 | 
			
		||||
new_revision: false
 | 
			
		||||
source_configuration:
 | 
			
		||||
  source_field: field_media_test_image
 | 
			
		||||
field_map: {  }
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
dependencies:
 | 
			
		||||
  module:
 | 
			
		||||
    - media
 | 
			
		||||
    - media_test_source
 | 
			
		||||
id: type_two
 | 
			
		||||
label: 'Type Two'
 | 
			
		||||
description: ''
 | 
			
		||||
source: test
 | 
			
		||||
queue_thumbnail_downloads: false
 | 
			
		||||
new_revision: false
 | 
			
		||||
source_configuration:
 | 
			
		||||
  source_field: field_media_test_1
 | 
			
		||||
  test_config_value: 'This is default value.'
 | 
			
		||||
field_map: {  }
 | 
			
		||||
@ -0,0 +1,9 @@
 | 
			
		||||
langcode: en
 | 
			
		||||
status: true
 | 
			
		||||
name: 'Basic Page'
 | 
			
		||||
type: basic_page
 | 
			
		||||
description: null
 | 
			
		||||
help: null
 | 
			
		||||
new_revision: true
 | 
			
		||||
preview_mode: 1
 | 
			
		||||
display_submitted: true
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
field.storage_settings.entity_reference_subclass:
 | 
			
		||||
  type: base_entity_reference_field_settings
 | 
			
		||||
  label: 'Entity reference subclass field storage settings'
 | 
			
		||||
 | 
			
		||||
field.field_settings.entity_reference_subclass:
 | 
			
		||||
  type: field.field_settings.entity_reference
 | 
			
		||||
  label: 'Entity reference subclass field settings'
 | 
			
		||||
@ -0,0 +1,11 @@
 | 
			
		||||
name: 'Media Library test'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Test module for Media Library.'
 | 
			
		||||
package: Testing
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:image
 | 
			
		||||
  - drupal:media_library
 | 
			
		||||
  - drupal:media_test_source
 | 
			
		||||
  - drupal:menu_ui
 | 
			
		||||
  - drupal:node
 | 
			
		||||
  - drupal:path
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library_test\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\node\Form\NodeForm;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Override NodeForm to test media library form submission semantics.
 | 
			
		||||
 */
 | 
			
		||||
class TestNodeFormOverride extends NodeForm {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    $triggering_element = $form_state->getTriggeringElement();
 | 
			
		||||
    if (in_array('open_button', $triggering_element['#parents'], TRUE)) {
 | 
			
		||||
      throw new \Exception('The media library widget open_button element should not trigger form submit.');
 | 
			
		||||
    }
 | 
			
		||||
    parent::submitForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,63 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library_test\Hook;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Access\AccessResultInterface;
 | 
			
		||||
use Drupal\media_library_test\Form\TestNodeFormOverride;
 | 
			
		||||
use Drupal\Core\Field\FieldItemListInterface;
 | 
			
		||||
use Drupal\Core\Field\FieldDefinitionInterface;
 | 
			
		||||
use Drupal\Core\Access\AccessResult;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\Hook\Attribute\Hook;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hook implementations for media_library_test.
 | 
			
		||||
 */
 | 
			
		||||
class MediaLibraryTestHooks {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_ENTITY_TYPE_create_access().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('media_create_access')]
 | 
			
		||||
  public function mediaCreateAccess(AccountInterface $account, array $context, $entity_bundle): AccessResultInterface {
 | 
			
		||||
    if (isset($context['media_library_state'])) {
 | 
			
		||||
      /** @var \Drupal\media_library\MediaLibraryState $state */
 | 
			
		||||
      $state = $context['media_library_state'];
 | 
			
		||||
      return AccessResult::forbiddenIf($state->getSelectedTypeId() === 'deny_access');
 | 
			
		||||
    }
 | 
			
		||||
    return AccessResult::neutral();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_entity_field_access().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_field_access')]
 | 
			
		||||
  public function entityFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL): AccessResultInterface {
 | 
			
		||||
    $deny_fields = \Drupal::state()->get('media_library_test_entity_field_access_deny_fields', []);
 | 
			
		||||
    // Always deny the field_media_no_access field.
 | 
			
		||||
    $deny_fields[] = 'field_media_no_access';
 | 
			
		||||
    return AccessResult::forbiddenIf(in_array($field_definition->getName(), $deny_fields, TRUE), 'Field access denied by test module');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_entity_type_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('entity_type_alter')]
 | 
			
		||||
  public function entityTypeAlter(array &$entity_types) : void {
 | 
			
		||||
    if (isset($entity_types['node'])) {
 | 
			
		||||
      $entity_types['node']->setFormClass('default', TestNodeFormOverride::class);
 | 
			
		||||
      $entity_types['node']->setFormClass('edit', TestNodeFormOverride::class);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Implements hook_field_widget_info_alter().
 | 
			
		||||
   */
 | 
			
		||||
  #[Hook('field_widget_info_alter')]
 | 
			
		||||
  public function fieldWidgetInfoAlter(array &$info): void {
 | 
			
		||||
    $info['media_library_widget']['field_types'][] = 'entity_reference_subclass';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library_test\Plugin\Field\FieldType;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Field\Attribute\FieldType;
 | 
			
		||||
use Drupal\Core\Field\EntityReferenceFieldItemList;
 | 
			
		||||
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin implementation of the 'entity_reference_subclass' field type.
 | 
			
		||||
 */
 | 
			
		||||
#[FieldType(
 | 
			
		||||
  id: "entity_reference_subclass",
 | 
			
		||||
  label: new TranslatableMarkup("Entity reference subclass"),
 | 
			
		||||
  description: new TranslatableMarkup("An entity field containing an entity reference."),
 | 
			
		||||
  category: "reference",
 | 
			
		||||
  default_widget: "entity_reference_autocomplete",
 | 
			
		||||
  default_formatter: "entity_reference_label",
 | 
			
		||||
  list_class: EntityReferenceFieldItemList::class,
 | 
			
		||||
)]
 | 
			
		||||
class EntityReferenceItemSubclass extends EntityReferenceItem {
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
field.widget.settings.media_library_inception_widget:
 | 
			
		||||
  type: mapping
 | 
			
		||||
  label: 'Media library inception widget settings'
 | 
			
		||||
  mapping:
 | 
			
		||||
    media_types:
 | 
			
		||||
      type: sequence
 | 
			
		||||
      label: 'Allowed media types, in display order'
 | 
			
		||||
      sequence:
 | 
			
		||||
        type: string
 | 
			
		||||
        label: 'Media type ID'
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
name: 'Media Library test widget'
 | 
			
		||||
type: module
 | 
			
		||||
description: 'Test widget that has a nested media library widget'
 | 
			
		||||
package: Testing
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:image
 | 
			
		||||
  - drupal:media_library
 | 
			
		||||
  - drupal:media_test_source
 | 
			
		||||
@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\media_library_test_widget\Plugin\Field\FieldWidget;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Field\Attribute\FieldWidget;
 | 
			
		||||
use Drupal\Core\Field\FieldItemListInterface;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Plugin implementation of the 'media_library_inception_widget' widget.
 | 
			
		||||
 *
 | 
			
		||||
 * This widget is used to simulate the media library widget nested inside
 | 
			
		||||
 * another widget that performs validation of required fields before there is
 | 
			
		||||
 * an opportunity to add media.
 | 
			
		||||
 */
 | 
			
		||||
#[FieldWidget(
 | 
			
		||||
  id: 'media_library_inception_widget',
 | 
			
		||||
  label: new TranslatableMarkup('Media library inception widget'),
 | 
			
		||||
  description: new TranslatableMarkup('Puts a widget in a widget for testing purposes.'),
 | 
			
		||||
  field_types: ['entity_reference'],
 | 
			
		||||
  multiple_values: TRUE,
 | 
			
		||||
)]
 | 
			
		||||
class MediaLibraryInceptionWidget extends MediaLibraryWidget {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
 | 
			
		||||
    if (empty($element['#element_validate'])) {
 | 
			
		||||
      $element['#element_validate'] = [];
 | 
			
		||||
    }
 | 
			
		||||
    $element['#element_validate'][] = [$this, 'elementValidate'];
 | 
			
		||||
    return parent::formElement($items, $delta, $element, $form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function elementValidate($element, FormStateInterface $form_state, $form) {
 | 
			
		||||
    $field_name = $element['#field_name'];
 | 
			
		||||
    $entity = $form_state->getFormObject()->getEntity();
 | 
			
		||||
    $input = $form_state->getUserInput();
 | 
			
		||||
    if (!empty($input['_triggering_element_name']) && str_contains($input['_triggering_element_name'], 'media-library-update')) {
 | 
			
		||||
      // This will validate a required field before an upload is completed.
 | 
			
		||||
      $display = EntityFormDisplay::collectRenderDisplay($entity, 'edit');
 | 
			
		||||
      $display->extractFormValues($entity, $form, $form_state);
 | 
			
		||||
      $display->validateFormValues($entity, $form, $form_state);
 | 
			
		||||
    }
 | 
			
		||||
    $form_value = $form_state->getValue($field_name);
 | 
			
		||||
    if (!empty($form_value['media_library_selection'])) {
 | 
			
		||||
      $entity->set($field_name, $form_value['media_library_selection']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\media_library\Functional;
 | 
			
		||||
 | 
			
		||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generic module test for media_library.
 | 
			
		||||
 *
 | 
			
		||||
 * @group media_library
 | 
			
		||||
 */
 | 
			
		||||
class GenericTest extends GenericModuleTestBase {}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user