diff -u b/core/composer.json b/core/composer.json --- b/core/composer.json +++ b/core/composer.json @@ -120,7 +120,6 @@ "drupal/locale": "self.version", "drupal/minimal": "self.version", "drupal/media": "self.version", - "drupal/media_library": "self.version", "drupal/menu_link_content": "self.version", "drupal/menu_ui": "self.version", "drupal/migrate": "self.version", @@ -134,6 +133,7 @@ "drupal/locale": "self.version", "drupal/minimal": "self.version", "drupal/media": "self.version", + "drupal/media_library": "self.version", "drupal/menu_link_content": "self.version", "drupal/menu_ui": "self.version", "drupal/migrate": "self.version", diff -u b/core/modules/media_library/config/install/views.view.media_library.yml b/core/modules/media_library/config/install/views.view.media_library.yml --- b/core/modules/media_library/config/install/views.view.media_library.yml +++ b/core/modules/media_library/config/install/views.view.media_library.yml @@ -8,7 +8,7 @@ - media_library - user id: media_library -label: Media +label: Media Library module: views description: '' tag: '' @@ -25,7 +25,7 @@ access: type: perm options: - perm: 'view media' + perm: 'access media overview' cache: type: tag options: { } @@ -330,7 +330,7 @@ entity_type: media entity_field: created plugin_id: date - title: Media + title: 'Media' header: { } footer: { } empty: { } @@ -390,153 +390,9 @@ tags: - 'config:core.entity_view_display.media.audio.default' - 'config:core.entity_view_display.media.file.default' + - 'config:core.entity_view_display.media.file.media_library' - 'config:core.entity_view_display.media.image.default' - - 'config:core.entity_view_display.media.video.default' - bundle_page: - display_plugin: page - id: bundle_page - display_title: 'Bundle Page' - position: 2 - display_options: - display_extenders: { } - path: admin/content/media/%bundle - display_description: '' - cache_metadata: - max-age: 0 - contexts: - - 'languages:language_interface' - - url - - url.query_args - - 'url.query_args:sort_by' - - user.permissions - tags: - - 'config:core.entity_view_display.media.audio.default' - - 'config:core.entity_view_display.media.file.default' - - 'config:core.entity_view_display.media.image.default' - - 'config:core.entity_view_display.media.video.default' - embed: - display_plugin: embed - id: embed - display_title: Embed - position: 4 - display_options: - display_extenders: { } - fields: - media_select_media: - id: media_select_media - table: media - field: media_select_media - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: media-library-item-checkbox - element_label_type: '' - element_label_class: '' - element_label_colon: false - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - entity_type: media - plugin_id: select_media - rendered_entity: - id: rendered_entity - table: media - field: rendered_entity - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: media-library-item-content - element_label_type: '' - element_label_class: '' - element_label_colon: false - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - view_mode: media_library - entity_type: media - plugin_id: rendered_entity - defaults: - fields: false - cache_metadata: - max-age: 0 - contexts: - - 'languages:language_interface' - - url - - url.query_args - - 'url.query_args:sort_by' - - user.permissions - tags: - - 'config:core.entity_view_display.media.audio.default' - - 'config:core.entity_view_display.media.file.default' - - 'config:core.entity_view_display.media.image.default' + - 'config:core.entity_view_display.media.image.media_library' - 'config:core.entity_view_display.media.video.default' page: display_plugin: page @@ -568,144 +424,5 @@ - 'config:core.entity_view_display.media.file.default' + - 'config:core.entity_view_display.media.file.media_library' - 'config:core.entity_view_display.media.image.default' - - 'config:core.entity_view_display.media.video.default' - widget: - display_plugin: page - id: widget - display_title: Widget - position: 4 - display_options: - display_extenders: { } - fields: - media_select_media: - id: media_select_media - table: media - field: media_select_media - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: media-library-item-checkbox - element_label_type: '' - element_label_class: '' - element_label_colon: false - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - entity_type: media - plugin_id: select_media - rendered_entity: - id: rendered_entity - table: media - field: rendered_entity - relationship: none - group_type: group - admin_label: '' - label: '' - exclude: false - alter: - alter_text: false - text: '' - make_link: false - path: '' - absolute: false - external: false - replace_spaces: false - path_case: none - trim_whitespace: false - alt: '' - rel: '' - link_class: '' - prefix: '' - suffix: '' - target: '' - nl2br: false - max_length: 0 - word_boundary: true - ellipsis: true - more_link: false - more_link_text: '' - more_link_path: '' - strip_tags: false - trim: false - preserve_tags: '' - html: false - element_type: '' - element_class: media-library-item-content - element_label_type: '' - element_label_class: '' - element_label_colon: false - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_empty: false - empty_zero: false - hide_alter_empty: true - view_mode: media_library - entity_type: media - plugin_id: rendered_entity - defaults: - fields: false - header: false - display_description: '' - path: admin/media_field_widget - header: - area: - id: area - table: views - field: area - relationship: none - group_type: group - admin_label: '' - empty: false - tokenize: false - content: - value: 'Add New Media' - format: full_html - plugin_id: text - cache_metadata: - max-age: 0 - contexts: - - 'languages:language_interface' - - url - - url.query_args - - 'url.query_args:sort_by' - - user.permissions - tags: - - 'config:core.entity_view_display.media.audio.default' - - 'config:core.entity_view_display.media.file.default' - - 'config:core.entity_view_display.media.image.default' + - 'config:core.entity_view_display.media.image.media_library' - 'config:core.entity_view_display.media.video.default' diff -u b/core/modules/media_library/css/media_library.css b/core/modules/media_library/css/media_library.css --- b/core/modules/media_library/css/media_library.css +++ b/core/modules/media_library/css/media_library.css @@ -2,7 +2,7 @@ * @file media_library.view.css */ -#views-form-media-library-page, *[id^='views-form-media-library-widget-'], .media-library-selection { +#views-form-media-library-page { display: flex; flex-wrap: wrap; align-content: space-between; @@ -31,21 +31,21 @@ transition: border-color .2s, outline-color .2s; } -.media-library-item .field { - display: none; -} - .media-library-item .field--name-thumbnail { - display: block; + background-color: #989898; + border-bottom: 2px solid #ebebeb; } .media-library-item .field--name-thumbnail img { - width: 100%; - height: auto; + min-width: 180px; + min-height: 180px; + object-fit: cover; } -.media-library-item.media-library-item-in-widget, .media-library-item.is-hover.media-library-item-in-widget { - cursor: move; +.media-library-item .media-library-item-preview { + width: 180px; + height: 180px; + overflow: hidden; } .media-library-item.checked { @@ -124,21 +124,6 @@ display: inline-block; } -.media-library-item .field--name-thumbnail { - background-color: #989898; - border-bottom: 2px solid #ebebeb; - height: 180px; - width: 180px; - overflow: hidden; -} - -.media-library-item .field--name-thumbnail img { - height: auto; - min-height: 180px; - min-width: 180px; - width: auto; -} - .media-library-item-name { font-size: 14px; text-overflow: ellipsis; @@ -160,33 +144,0 @@ - -.media-library-item .media-library-item-remove { - position: absolute; - z-index: 1; - top: 0; - right: 0; - width: 24px; - height: 24px; - margin: 5px; - padding: 0; - background: url('../../../misc/icons/787878/ex.svg') #ffffff center no-repeat; - background-size: 16px 16px; - border: 1px solid #ccc; - border-radius: 20px; - color: transparent; - text-shadow: none; - opacity: 0; - transition: opacity .2s; -} - -.media-library-item .media-library-item-remove:hover, -.media-library-item .media-library-item-remove:focus, -.media-library-item .media-library-item-remove.button:disabled { - background: url('../../../misc/icons/787878/ex.svg') #ffffff center no-repeat; - background-size: 16px 16px; - color: transparent; - opacity: 1; - text-shadow: none; -} - -.media-library-open-button { - margin-top: 10px; -} diff -u b/core/modules/media_library/js/media_library.view.js b/core/modules/media_library/js/media_library.view.js --- b/core/modules/media_library/js/media_library.view.js +++ b/core/modules/media_library/js/media_library.view.js @@ -1,42 +1,31 @@ /** - * @file media_library.view.js - */ +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ (function ($, Drupal) { "use strict"; - // Locally-scoped variable that stores the current View selection. We track - // this in a variable as users may click a media item and then re-load the - // View with AJAX (filters, pagers, sorts, etc.), which will lose what is - // currently selected in the bulk form. This could likely be back-ported to - // core. - var MediaLibrarySelectedEntities = {}; - - /** - * Allows users to click on hidden Views select checkboxes by clicking on the - * media item (which is also a Views row). - */ Drupal.behaviors.MediaLibraryCheckboxes = { - attach: function (context) { - $('.media-library-item .field--name-thumbnail', context).once('media-library-checkbox').on('click', function (event) { - // Links inside the rendered media item should not be click-able. + attach: function attach(context) { + $('.media-library-item .media-library-item-preview', context).once('media-library-checkbox').on('click', function (event) { event.preventDefault(); - // Click the hidden checkbox when the media item is clicked. + var $input = $(this).parents('.media-library-item').find('.media-library-item-checkbox input'); $input.prop('checked', !$input.prop('checked')); if ($input.prop('checked')) { $(this).parents('.media-library-item').addClass('checked'); - } - else { + } else { $(this).parents('.media-library-item').removeClass('checked'); } if ($('.media-library-item.checked').length) { $('#edit-header').show(); $('.view-content #edit-actions').show(); - } - else { + } else { $('#edit-header').hide(); $('.view-content #edit-actions').hide(); } @@ -49,111 +38,23 @@ - /** - * Allows users to toggle metadata on media items. - */ Drupal.behaviors.MediaLibraryMetadataToggle = { - attach: function (context) { - // Add the toggle when javascript is available + attach: function attach(context) { $('.media-library-item-attributes', context).after(''); - $('.media-library-item-toggle').on('click', function () { + $('.media-library-item-toggle').once('media-library-toggle').on('click', function () { if ($(this).parents('.media-library-item').hasClass('expanded')) { $(this).parents('.media-library-item').removeClass('expanded'); - } - else { + } else { $(this).parents('.media-library-item').addClass('expanded'); } }); } }; - /** - * Adds hover effect to media items. - */ Drupal.behaviors.MediaLibraryHover = { - attach: function (context) { - $('.media-library-item .field--name-thumbnail', context).once('media-library-item').on('mouseover', function (){ + attach: function attach(context) { + $('.media-library-item .media-library-item-preview', context).once('media-library-item').on('mouseover', function () { $(this).parents('.media-library-item').addClass('is-hover'); - }).on('mouseout', function(){ + }).on('mouseout', function () { $(this).parents('.media-library-item').removeClass('is-hover'); }); } }; - - /** - * Persists bulk and select form selections across AJAX View re-renders. - */ - Drupal.behaviors.MediaLibraryPersistentSelection = { - attach: function (context) { - // For each View on the page, store the dom-id as a data attribute so - // that we can easily access it later. This is normally only stored in a - // class. - $('[class*="js-view-dom-id-"]', context).once('set-dom-id-data-attribute').each(function () { - var element = $(this); - var classes = $(this).attr('class').split(/\s+/); - $(classes).each(function (i, class_name) { - var dom_id = class_name.match(new RegExp('^js-view-dom-id-(.*)$')); - if (dom_id) { - element.attr('data-views-dom-id', dom_id[1]); - if (!MediaLibrarySelectedEntities[dom_id[1]]) { - MediaLibrarySelectedEntities[dom_id[1]] = []; - } - } - }); - }); - - // Check/uncheck checkboxes in the view based on the selected media. - $('.media-library-item-checkbox input', context).once('data-media-select').each(function (i, element) { - $(this).on('change', function () { - var checked = $(this).prop('checked'); - var views_dom_id = $(this).closest('[data-views-dom-id]').data('views-dom-id'); - var entity = $(this).val(); - if (checked) { - MediaLibrarySelectedEntities[views_dom_id].push(entity); - } - else { - MediaLibrarySelectedEntities[views_dom_id] = $(MediaLibrarySelectedEntities[views_dom_id]).filter(function (i, value) { - return !(entity == value); - }); - } - }); - }); - } - }; - - /** - * Attaches extra POST data when the Views bulk form is submitted, to ensure - * that selections that may be off-screen persist during the submit process. - */ - Drupal.behaviors.MediaLibraryViewsSubmit = { - attach: function (context, settings) { - $('[data-views-dom-id]', context).once('media-library-checkbox-submit').each(function () { - var $view = $(this); - var views_dom_id = $(this).data('views-dom-id'); - $.each(MediaLibrarySelectedEntities[views_dom_id], function(index, items) { - $view.find('input[value="' + items + '"]').prop('checked', true); - $view.find('input[value="' + items + '"]').closest('.media-library-item').addClass('checked'); - }); - - // Propagate all selected entities to the form submission. - $view.find('[data-media-select-submit]').each(function () { - var button = $(this); - $.each(Drupal.ajax.instances, function () { - if (this && $(this.element).is(button)) { - if (settings && settings.views && settings.views.ajaxViews) { - var ajaxViews = settings.views.ajaxViews; - for (var i in ajaxViews) { - var ajaxViewsInstance = Drupal.views.instances[i]; - if (ajaxViewsInstance.settings.view_dom_id == views_dom_id) { - this.submit = $.extend(this.submit, ajaxViewsInstance.settings, { - selected_entities: MediaLibrarySelectedEntities[views_dom_id], - media_library_input: settings.media_library.media_library_inputs[views_dom_id] - }); - } - } - } - } - }); - }); - }); - } - }; - -})(jQuery, Drupal); +})(jQuery, Drupal); \ No newline at end of file reverted: --- b/core/modules/media_library/js/media_library.widget.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @file media_library.widget.js - */ - -(function ($, Drupal) { - - "use strict"; - - /** - * Provides custom JS behaviors related to the media library field widget. - */ - Drupal.behaviors.MediaLibraryWidget = { - attach: function (context) { - $('.media-library-selection', context).sortable({ - tolerance: 'pointer', - stop: function(event, ui) { - // Update all the hidden "weight" fields. - $(event.target).children().each(function (index) { - $(this).find('.media-library-item-weight').val(index); - }); - } - }); - } - }; - -})(jQuery, Drupal); diff -u b/core/modules/media_library/media_library.info.yml b/core/modules/media_library/media_library.info.yml --- b/core/modules/media_library/media_library.info.yml +++ b/core/modules/media_library/media_library.info.yml @@ -1,6 +1,6 @@ name: 'Media library' type: module -description: 'Provides a library for adding and re-using Media Items.' +description: 'Provides a library for re-using Media Items.' package: Core (Experimental) version: VERSION core: 8.x diff -u b/core/modules/media_library/media_library.libraries.yml b/core/modules/media_library/media_library.libraries.yml --- b/core/modules/media_library/media_library.libraries.yml +++ b/core/modules/media_library/media_library.libraries.yml @@ -16,13 +16,2 @@ - - core/drupal.dialog.ajax - core/jquery - core/jquery.once - -widget: - version: VERSION - js: - js/media_library.widget.js: {} - dependencies: - - core/drupal - - core/jquery - - core/jquery.ui.sortable - - media_library/style diff -u b/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module --- b/core/modules/media_library/media_library.module +++ b/core/modules/media_library/media_library.module @@ -35,15 +35,12 @@ if ($variables['view_mode'] === 'media_library') { /** @var \Drupal\media\MediaInterface $media */ $media = $variables['media']; - /** @var \Drupal\user\UserInterface $user */ - if ($user = $media->getRevisionUser()) { - $variables['author_info'] = [ - '#markup' => t('Created by %author', ['%author' => $user->getDisplayName()]), - ]; - } $variables['created_ago'] = [ '#markup' => _media_library_pretty_time($media->getCreatedTime()), ]; + $variables['url'] = $media->toUrl('canonical', [ + 'language' => $media->language(), + ]); } } reverted: --- b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php +++ /dev/null @@ -1,366 +0,0 @@ -entityTypeManager = $entity_type_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $plugin_id, - $plugin_definition, - $configuration['field_definition'], - $configuration['settings'], - $configuration['third_party_settings'], - $container->get('entity_type.manager') - ); - } - - /** - * {@inheritdoc} - */ - public static function isApplicable(FieldDefinitionInterface $field_definition) { - return $field_definition->getSetting('target_type') === 'media'; - } - - /** - * {@inheritdoc} - */ - public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) { - $field_name = $this->fieldDefinition->getName(); - $parents = $form['#parents']; - - // Load the items for form rebuilds from the field state as they might not be - // in $form_state['values'] because of validation limitations. - $field_state = static::getWidgetState($parents, $field_name, $form_state); - if ($field_state && isset($field_state['items'])) { - usort($field_state['items'], [SortArray::class, 'sortByWeightElement']); - $items->setValue($field_state['items']); - } - - $build = parent::form($items, $form, $form_state, $get_delta); - - return $build; - } - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - /** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */ - $referenced_entities = $items->referencedEntities(); - $view_builder = $this->entityTypeManager->getViewBuilder($this->getFieldSetting('target_type')); - $field_name = $this->fieldDefinition->getName(); - $parents = $form['#parents']; - $id_suffix = '-' . implode('-', $parents); - $wrapper_id = $field_name . '-media-library-wrapper' . $id_suffix; - - $element += [ - '#type' => 'fieldset', - '#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(), - 'selection' => [ - '#type' => 'container', - '#attributes' => [ - 'class' => 'media-library-selection', - ], - ], - '#attributes' => [ - 'id' => $wrapper_id, - 'class' => ['media-library-wrapper'], - ], - ]; - - if (empty($referenced_entities)) { - $element['empty_selection'] = [ - '#markup' => $this->t('

No media selected.

'), - ]; - } - else { - foreach ($referenced_entities as $delta => $media_item) { - $element['selection'][$delta] = [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['media-library-item', 'media-library-item-in-widget'], - ], - 'preview' => [ - '#type' => 'container', - 'view' => $view_builder->view($media_item, 'media_library'), - '#attached' => [ - 'library' => ['media_library/widget'], - ], - 'remove_button' => [ - '#type' => 'submit', - '#media_library_remove_delta' => $delta, - '#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix, - '#value' => $this->t('Remove'), - '#attributes' => [ - 'class' => ['media-library-item-remove'], - ], - '#ajax' => [ - 'callback' => [static::class, 'updateWidget'], - 'wrapper' => $wrapper_id, - ], - '#submit' => [[static::class, 'removeItem']], - '#limit_validation_errors' => [array_merge($form['#parents'], [$field_name])], - ], - ], - 'target_id' => [ - '#type' => 'hidden', - '#value' => $media_item->id(), - ], - // This hidden value is set by JS when items are re-sorted. - 'weight' => [ - '#type' => 'hidden', - '#default_value' => $delta, - '#attributes' => [ - 'class' => ['media-library-item-weight'], - ], - ], - ]; - } - } - - // @todo We use a use-ajax link here as it is the only way we've seen - // contextual filters work when using a view in a modal. Unfortunately - // this only works the first time you open the modal - opening it twice - // in one page load breaks Views AJAX. - $element['media_library_open_container'] = [ - '#type' => 'container', - 'media_library_open_button' => [ - '#type' => 'link', - '#title' => $this->t('Add Media'), - '#name' => $field_name . '-media-library-open-button' . $id_suffix, - '#url' => Url::fromRoute('view.media_library.widget', [], [ - 'query' => ['media_library_input' => $field_name . $id_suffix], - ]), - '#attributes' => [ - 'class' => ['button', 'use-ajax', 'media-library-open-button'], - 'data-dialog-type' => 'modal', - 'data-dialog-options' => json_encode([ - 'height' => '75%', - 'width' => '75%', - 'title' => $this->t('Media library'), - ]), - ], - '#limit_validation_errors' => [array_merge($parents, [$field_name])], - ], - ]; - - // Add a hidden textfield to the form, which is used by the select_media - // Views field to pass values from the modal to the widget. This is similar - // to how the EntityReferenceAutocompleteWidget stores values. - $element['media_library_selection'] = [ - '#type' => 'textfield', - '#attributes' => [ - 'data-media-library-input-id' => $field_name . $id_suffix, - 'class' => ['visually-hidden'], - ], - ]; - - $element['media_library_update_widget'] = [ - '#type' => 'submit', - '#value' => $this->t('Update widget'), - '#name' => $field_name . '-media-library-update-button' . $id_suffix, - '#ajax' => [ - 'callback' => [static::class, 'updateWidget'], - 'wrapper' => $wrapper_id, - ], - '#attributes' => [ - 'data-media-library-submit-id' => $field_name . $id_suffix, - 'class' => ['visually-hidden', 'media-library-update-button'], - ], - '#submit' => [[static::class, 'updateItems']], - '#limit_validation_errors' => [array_merge($parents, [$field_name])], - ]; - - return $element; - } - - /** - * {@inheritdoc} - */ - public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) { - return isset($element['target_id']) ? $element['target_id'] : FALSE; - } - - /** - * {@inheritdoc} - */ - public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { - if (isset($values['selection'])) { - usort($values['selection'], [SortArray::class, 'sortByWeightElement']); - return $values['selection']; - } - else { - return []; - } - } - - /** - * AJAX callback to update the widget when the selection changes. - * - * @param array $form - * The form array. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. - * - * @return array - * An array representing the updated widget. - */ - public static function updateWidget($form, FormStateInterface $form_state) { - $triggering_element = $form_state->getTriggeringElement(); - $length = isset($triggering_element['#media_library_remove_delta']) ? -4 : -1; - $parents = $triggering_element['#array_parents']; - $parents = array_slice($parents, 0, $length); - $element = NestedArray::getValue($form, $parents); - // Always clear the textfield selection to prevent duplicate additions. - $element['media_library_selection']['#value'] = ''; - return $element; - } - - /** - * Submit callback for remove buttons. - * - * @param array $form - * The form array. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. - */ - public static function removeItem($form, FormStateInterface $form_state) { - $triggering_element = $form_state->getTriggeringElement(); - - $parents = $triggering_element['#array_parents']; - $parents = array_slice($parents, 0, -4); - $element = NestedArray::getValue($form, $parents); - $field_name = $element['#field_name']; - $parents = $element['#field_parents']; - $delta = $triggering_element['#media_library_remove_delta']; - - // Find and remove correct entity. - $path = $element['#parents']; - $values = NestedArray::getValue($form_state->getValues(), $path); - $field_state = static::getWidgetState($parents, $field_name, $form_state); - if (isset($values['selection'])) { - if (isset($values['selection'][$delta])) { - array_splice($values['selection'], $delta, 1); - $field_state['items'] = $values['selection']; - $field_state['items_count'] = count($field_state['items']); - } - } - $form_state->setValue($field_name, $values); - static::setWidgetState($parents, $field_name, $form_state, $field_state); - - // Rebuild form. - $form_state->setRebuild(); - } - - /** - * Updates the field state based on the form state and sets the form rebuild. - * - * @param array $form - * The form array. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. - */ - public static function updateItems($form, FormStateInterface $form_state) { - $button = $form_state->getTriggeringElement(); - - // Go one level up in the form, to the widgets container. - $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); - $field_name = $element['#field_name']; - $parents = $element['#field_parents']; - $field_state = static::getWidgetState($parents, $field_name, $form_state); - - // Get the new media ids passed to our hidden button. - $values = $form_state->getValues(); - $path = $element['#parents']; - $value = NestedArray::getValue($values, $path); - $selection = isset($value['selection']) ? $value['selection'] : []; - - $field_state['items'] = $selection; - - if (!empty($value['media_library_selection'])) { - $ids = explode(',', $value['media_library_selection']); - /** @var \Drupal\media\MediaInterface[] $media */ - $media = Media::loadMultiple($ids); - - // Append the provided Media to the existing selection. - $cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - foreach ($media as $media_item) { - if ($cardinality_unlimited || (count($field_state['items']) < $element['#cardinality'])) { - if ($media && $media_item->access('view')) { - $field_state['items'][] = [ - 'target_id' => $media_item->id(), - ]; - } - } - } - } - - $field_state['items_count'] = count($field_state['items']); - static::setWidgetState($parents, $field_name, $form_state, $field_state); - - $form_state->setRebuild(); - } - -} reverted: --- b/core/modules/media_library/src/Plugin/views/field/SelectMedia.php +++ /dev/null @@ -1,91 +0,0 @@ -options['id']])) { - foreach (Element::children($form[$this->options['id']]) as $index) { - $item = &$form[$this->options['id']][$index]; - $item['#attributes']['data-media-select'] = TRUE; - } - } - if (isset($form['actions']['submit'])) { - $form['actions']['submit']['#attributes']['data-media-select-submit'] = TRUE; - $form['actions']['submit']['#ajax']['url'] = Url::fromRoute(''); - $form['actions']['submit']['#ajax']['callback'] = function ($form, FormStateInterface $form_state) { - $response = new AjaxResponse(); - $response->addCommand(new CloseModalDialogCommand()); - $form_state->setResponse($response); - }; - } - if ($media_library_input = \Drupal::request()->get('media_library_input')) { - $form['#attached']['drupalSettings']['media_library']['media_library_inputs'][$this->view->dom_id] = $media_library_input; - } - } - - /** - * Submit handler for the bulk 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. - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * Thrown when the user tried to access an action without access to it. - */ - public function viewsFormSubmit(&$form, FormStateInterface $form_state) { - if ($form_state->get('step') == 'views_form_views_form') { - // Prefer the js provided selection, because it contains selected entities - // that aren't currently on screen and tracks the order of the selection. - if (\Drupal::request()->request->has('selected_entities')) { - $selected = \Drupal::request()->request->get('selected_entities'); - } - else { - // Filter only selected checkboxes. - $selected = array_filter($form_state->getValue($this->options['id'])); - } - - $entities = []; - - foreach ($selected as $bulk_form_key) { - $entity = $this->loadEntityFromBulkFormKey($bulk_form_key); - $entities[$bulk_form_key] = $entity; - } - - $ids = []; - foreach ($entities as $entity) { - $ids[] = $entity->id(); - } - - $response = new AjaxResponse(); - $library_input = \Drupal::request()->get('media_library_input'); - $input_selector = '[data-media-library-input-id="' . $library_input . '"]'; - $submit_selector = '[data-media-library-submit-id="' . $library_input . '"]'; - $response->addCommand(new InvokeCommand($input_selector, 'val', [implode(',', $ids)])); - $response->addCommand(new InvokeCommand($submit_selector, 'trigger', ['mousedown'])); - $response->addCommand(new CloseModalDialogCommand()); - $form_state->setResponse($response); - } - } - -} diff -u b/core/modules/media_library/templates/media--media-library.html.twig b/core/modules/media_library/templates/media--media-library.html.twig --- b/core/modules/media_library/templates/media--media-library.html.twig +++ b/core/modules/media_library/templates/media--media-library.html.twig @@ -28,10 +28,9 @@ * - attributes: HTML attributes for the containing element. * - title_attributes: Same as attributes, except applied to the main title * tag that appears in the template. - * - author_info: This is a Media Library specific variable that contains the - * display name of the author for this media. * - created_ago: This is a Media Library specific variable that contains a * formatted "time ago" version of the created date. + * - url: Direct URL of the media. * * @see template_preprocess_media() * @@ -40,9 +39,13 @@ #} {% if content %} - {{ content|without('name', 'created', 'uid') }} +
+ {{ content|without('name', 'created', 'uid') }} +
-
{{ name }}
+
{{ created_ago }}
{{ media.bundle()|title }}
reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_form_display.node.media_library_test.default.yml +++ /dev/null @@ -1,71 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.field.node.media_library_test.field_media - - field.field.node.media_library_test.field_multivalue_media - - node.type.media_library_test - module: - - media_library - - path -id: node.media_library_test.default -targetEntityType: node -bundle: media_library_test -mode: default -content: - created: - type: datetime_timestamp - weight: 10 - region: content - settings: { } - third_party_settings: { } - field_media: - weight: 31 - settings: { } - third_party_settings: { } - type: media_library_widget - region: content - field_multivalue_media: - weight: 32 - settings: { } - third_party_settings: { } - type: media_library_widget - region: content - path: - type: path - weight: 30 - region: content - settings: { } - third_party_settings: { } - promote: - type: boolean_checkbox - settings: - display_label: true - weight: 15 - region: content - third_party_settings: { } - sticky: - type: boolean_checkbox - settings: - display_label: true - weight: 16 - region: content - 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 - settings: - match_operator: CONTAINS - size: 60 - placeholder: '' - region: content - third_party_settings: { } -hidden: { } reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/core.entity_view_display.node.media_library_test.default.yml +++ /dev/null @@ -1,34 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.field.node.media_library_test.field_media - - field.field.node.media_library_test.field_multivalue_media - - node.type.media_library_test - module: - - user -id: node.media_library_test.default -targetEntityType: node -bundle: media_library_test -mode: default -content: - field_media: - weight: 101 - label: above - settings: - link: true - third_party_settings: { } - type: entity_reference_label - region: content - field_multivalue_media: - weight: 102 - label: above - settings: - link: true - third_party_settings: { } - type: entity_reference_label - region: content - links: - weight: 100 - region: content -hidden: { } reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_media.yml +++ /dev/null @@ -1,29 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.storage.node.field_media - - media.type.type_one - - media.type.type_two - - node.type.media_library_test -id: node.media_library_test.field_media -field_name: field_media -entity_type: node -bundle: media_library_test -label: 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: type_one -field_type: entity_reference reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/field.field.node.media_library_test.field_multivalue_media.yml +++ /dev/null @@ -1,29 +0,0 @@ -langcode: en -status: true -dependencies: - config: - - field.storage.node.field_multivalue_media - - media.type.type_one - - media.type.type_two - - node.type.media_library_test -id: node.media_library_test.field_multivalue_media -field_name: field_multivalue_media -entity_type: node -bundle: media_library_test -label: 'Multivalue 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: type_one -field_type: entity_reference reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_media.yml +++ /dev/null @@ -1,19 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - media - - node -id: node.field_media -field_name: field_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 reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/field.storage.node.field_multivalue_media.yml +++ /dev/null @@ -1,19 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - media - - node -id: node.field_multivalue_media -field_name: field_multivalue_media -entity_type: node -type: entity_reference -settings: - target_type: media -module: core -locked: false -cardinality: 5 -translatable: true -indexes: { } -persist_with_no_fields: false -custom_storage: false reverted: --- b/core/modules/media_library/tests/modules/media_library_test/config/install/node.type.media_library_test.yml +++ /dev/null @@ -1,17 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - menu_ui -third_party_settings: - menu_ui: - available_menus: - - main - parent: 'main:' -name: 'Media Library Test' -type: media_library_test -description: '' -help: '' -new_revision: true -preview_mode: 1 -display_submitted: true diff -u b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml --- b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml +++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.info.yml @@ -9,3 +9,2 @@ - drupal:menu_ui - - drupal:node - drupal:path diff -u b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php --- b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php @@ -43,14 +43,8 @@ // Create a user who can use the Media Library. $user = $this->drupalCreateUser([ - 'access content', - 'access content overview', 'access administration pages', - 'administer media', - 'create media_library_test content', - 'edit own media_library_test content', - 'delete any media', - 'delete own media_library_test content', + 'access media overview', 'view media', ]); $this->drupalLogin($user); @@ -68,7 +62,7 @@ $this->assertSession()->pageTextContains('media_1'); $this->assertSession()->pageTextContains('media_3'); - // Test the tabs that allow users to filter by type. + // Test that users can filter by type. $this->getSession()->getPage()->selectFieldOption('Media type', 'Type One'); $this->getSession()->getPage()->pressButton('Apply Filters'); $this->assertSession()->assertWaitOnAjaxRequest(); only in patch2: unchanged: --- /dev/null +++ b/core/modules/media_library/config/optional/core.entity_view_display.media.audio.media_library.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.media_library + - field.field.media.audio.field_media_audio_file + - image.style.medium + - media.type.audio + module: + - image +id: media.audio.media_library +targetEntityType: media +bundle: audio +mode: media_library +content: + thumbnail: + type: image + weight: 0 + region: content + label: hidden + settings: + image_style: medium + image_link: '' + third_party_settings: { } +hidden: + created: true + field_media_audio_file: true + name: true + uid: true only in patch2: unchanged: --- /dev/null +++ b/core/modules/media_library/config/optional/core.entity_view_display.media.video.media_library.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.media_library + - field.field.media.video.field_media_video_file + - image.style.medium + - media.type.video + module: + - image +id: media.video.media_library +targetEntityType: media +bundle: video +mode: media_library +content: + thumbnail: + type: image + weight: 0 + region: content + label: hidden + settings: + image_style: medium + image_link: '' + third_party_settings: { } +hidden: + created: true + field_media_video_file: true + name: true + uid: true only in patch2: unchanged: --- /dev/null +++ b/core/modules/media_library/js/media_library.view.es6.js @@ -0,0 +1,73 @@ +/** + * @file media_library.view.js + */ + +(function ($, Drupal) { + + "use strict"; + + /** + * Allows users to click on hidden Views select checkboxes by clicking on the + * media item (which is also a Views row). + */ + Drupal.behaviors.MediaLibraryCheckboxes = { + attach: function (context) { + $('.media-library-item .media-library-item-preview', context).once('media-library-checkbox').on('click', function (event) { + // Links inside the rendered media item should not be click-able. + event.preventDefault(); + // Click the hidden checkbox when the media item is clicked. + const $input = $(this).parents('.media-library-item').find('.media-library-item-checkbox input'); + $input.prop('checked', !$input.prop('checked')); + if ($input.prop('checked')) { + $(this).parents('.media-library-item').addClass('checked'); + } + else { + $(this).parents('.media-library-item').removeClass('checked'); + } + + if ($('.media-library-item.checked').length) { + $('#edit-header').show(); + $('.view-content #edit-actions').show(); + } + else { + $('#edit-header').hide(); + $('.view-content #edit-actions').hide(); + } + + $input.trigger('change'); + }); + } + }; + + /** + * Allows users to toggle metadata on media items. + */ + Drupal.behaviors.MediaLibraryMetadataToggle = { + attach: function (context) { + // Add the toggle when javascript is available + $('.media-library-item-attributes', context).after(''); + $('.media-library-item-toggle').once('media-library-toggle').on('click', function () { + if ($(this).parents('.media-library-item').hasClass('expanded')) { + $(this).parents('.media-library-item').removeClass('expanded'); + } + else { + $(this).parents('.media-library-item').addClass('expanded'); + } + }); + } + }; + + /** + * Adds hover effect to media items. + */ + Drupal.behaviors.MediaLibraryHover = { + attach: function (context) { + $('.media-library-item .media-library-item-preview', context).once('media-library-item').on('mouseover', function () { + $(this).parents('.media-library-item').addClass('is-hover'); + }).on('mouseout', function () { + $(this).parents('.media-library-item').removeClass('is-hover'); + }); + } + }; + +})(jQuery, Drupal); only in patch2: unchanged: --- a/core/modules/views/src/Plugin/views/field/BulkForm.php +++ b/core/modules/views/src/Plugin/views/field/BulkForm.php @@ -2,15 +2,12 @@ namespace Drupal\views\Plugin\views\field; -use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\RevisionableInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\Routing\RedirectDestinationTrait; use Drupal\Core\TypedData\TranslatableInterface; -use Drupal\views\Entity\Render\EntityTranslationRenderTrait; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\Plugin\views\style\Table; use Drupal\views\ResultRow; @@ -22,18 +19,7 @@ * * @ViewsField("bulk_form") */ -class BulkForm extends FieldPluginBase implements CacheableDependencyInterface { - - use RedirectDestinationTrait; - use UncacheableFieldHandlerTrait; - use EntityTranslationRenderTrait; - - /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; +class BulkForm extends SelectFormBase { /** * The action storage. @@ -49,13 +35,6 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface { */ protected $actions = []; - /** - * The language manager. - * - * @var \Drupal\Core\Language\LanguageManagerInterface - */ - protected $languageManager; - /** * Constructs a new BulkForm object. * @@ -71,11 +50,8 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface { * The language manager. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - - $this->entityManager = $entity_manager; + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $language_manager); $this->actionStorage = $entity_manager->getStorage('action'); - $this->languageManager = $language_manager; } /** only in patch2: unchanged: --- /dev/null +++ b/core/modules/views/src/Plugin/views/field/SelectFormBase.php @@ -0,0 +1,305 @@ +entityManager = $entity_manager; + $this->languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager'), + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // @todo Consider making the bulk operation form cacheable. See + // https://www.drupal.org/node/2503009. + return 0; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : []; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getEntityTypeId() { + return $this->getEntityType(); + } + + /** + * {@inheritdoc} + */ + protected function getEntityManager() { + return $this->entityManager; + } + + /** + * {@inheritdoc} + */ + protected function getLanguageManager() { + return $this->languageManager; + } + + /** + * {@inheritdoc} + */ + protected function getView() { + return $this->view; + } + + /** + * {@inheritdoc} + */ + public function preRender(&$values) { + parent::preRender($values); + + // If the view is using a table style, provide a placeholder for a + // "select all" checkbox. + if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) { + // Add the tableselect css classes. + $this->options['element_label_class'] .= 'select-all'; + // Hide the actual label of the field on the table header. + $this->options['label'] = ''; + } + } + + /** + * {@inheritdoc} + */ + public function getValue(ResultRow $row, $field = NULL) { + return ''; + } + + /** + * Form constructor for the bulk 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(&$form, FormStateInterface $form_state) { + // Make sure we do not accidentally cache this form. + // @todo Evaluate this again in https://www.drupal.org/node/2503009. + $form['#cache']['max-age'] = 0; + + // Add the tableselect javascript. + $form['#attached']['library'][] = 'core/drupal.tableselect'; + $use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo()); + + // Only add the bulk form options and buttons if there are results. + if (!empty($this->view->result)) { + // Render checkboxes for all rows. + $form[$this->options['id']]['#tree'] = TRUE; + foreach ($this->view->result as $row_index => $row) { + $entity = $this->getEntityTranslation($this->getEntity($row), $row); + + $form[$this->options['id']][$row_index] = [ + '#type' => 'checkbox', + // We are not able to determine a main "title" for each row, so we can + // only output a generic label. + '#title' => $this->t('Select this item'), + '#title_display' => 'invisible', + '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL, + '#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision), + ]; + } + + // Replace the form submit button label. + $form['actions']['submit']['#value'] = $this->t('Selected items'); + } + else { + // Remove the default actions build array. + unset($form['actions']); + } + } + + /** + * Returns the message to be displayed when there are no selected items. + * + * @return string + * Message displayed when no items are selected. + */ + protected function emptySelectedMessage() { + return $this->t('No items selected.'); + } + + /** + * {@inheritdoc} + */ + public function viewsFormValidate(&$form, FormStateInterface $form_state) { + $selected = array_filter($form_state->getValue($this->options['id'])); + if (empty($selected)) { + $form_state->setErrorByName('', $this->emptySelectedMessage()); + } + } + + /** + * {@inheritdoc} + */ + public function query() { + if ($this->languageManager->isMultilingual()) { + $this->getEntityTranslationRenderer()->query($this->query, $this->relationship); + } + } + + /** + * {@inheritdoc} + */ + public function clickSortable() { + return FALSE; + } + + /** + * Wraps drupal_set_message(). + */ + protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) { + drupal_set_message($message, $type, $repeat); + } + + /** + * Calculates a bulk form key. + * + * This generates a key that is used as the checkbox return value when + * submitting a bulk form. This key allows the entity for the row to be loaded + * totally independently of the executed view row. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to calculate a bulk form key for. + * @param bool $use_revision + * Whether the revision id should be added to the bulk form key. This should + * be set to TRUE only if the view is listing entity revisions. + * + * @return string + * The bulk form key representing the entity's id, language and revision (if + * applicable) as one string. + * + * @see self::loadEntityFromBulkFormKey() + */ + protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) { + $key_parts = [$entity->language()->getId(), $entity->id()]; + + if ($entity instanceof RevisionableInterface && $use_revision) { + $key_parts[] = $entity->getRevisionId(); + } + + // An entity ID could be an arbitrary string (although they are typically + // numeric). JSON then Base64 encoding ensures the bulk_form_key is + // safe to use in HTML, and that the key parts can be retrieved. + $key = json_encode($key_parts); + return base64_encode($key); + } + + /** + * Loads an entity based on a bulk form key. + * + * @param string $bulk_form_key + * The bulk form key representing the entity's id, language and revision (if + * applicable) as one string. + * + * @return \Drupal\Core\Entity\EntityInterface + * The entity loaded in the state (language, optionally revision) specified + * as part of the bulk form key. + */ + protected function loadEntityFromBulkFormKey($bulk_form_key) { + $key = base64_decode($bulk_form_key); + $key_parts = json_decode($key); + $revision_id = NULL; + + // If there are 3 items, vid will be last. + if (count($key_parts) === 3) { + $revision_id = array_pop($key_parts); + } + + // The first two items will always be langcode and ID. + $id = array_pop($key_parts); + $langcode = array_pop($key_parts); + + // Load the entity or a specific revision depending on the given key. + $storage = $this->entityManager->getStorage($this->getEntityType()); + $entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id); + + if ($entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($langcode); + } + + return $entity; + } + +}