Skip to content
Snippets Groups Projects
Commit cf1e3ee7 authored by Francesco Placella's avatar Francesco Placella
Browse files

Issue #2962525 by samuel.mortenson, jrockowitz, seanB, drpal, chr.fritsch,...

Issue #2962525 by samuel.mortenson, jrockowitz, seanB, drpal, chr.fritsch, ckrina, phenaproxima, webchick, lauriii, beautifulmind, andrewmacpherson, xjm, Gábor Hojtsy: Create a field widget for the Media library module

(cherry picked from commit de52834d)
parent 8d8925e7
No related branches found
No related tags found
No related merge requests found
Showing
with 1452 additions and 107 deletions
......@@ -243,31 +243,9 @@ function media_field_widget_multivalue_form_alter(array &$elements, FormStateInt
// Retrieve the media bundle list and add information for the user based on
// which bundles are available to be created or referenced.
$settings = $context['items']->getFieldDefinition()->getSetting('handler_settings');
$allowed_bundles = isset($settings['target_bundles']) ? $settings['target_bundles'] : [];
$access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media');
$all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
$bundle_labels = [];
$create_bundles = [];
foreach ($allowed_bundles as $bundle) {
$bundle_labels[] = $all_bundles[$bundle]['label'];
if ($access_handler->createAccess($bundle)) {
$create_bundles[] = $bundle;
if (count($create_bundles) > 1) {
// If the user has access to create more than 1 bundle then the
// individual media type form can not be used.
break;
}
}
}
// Add a section about how to create media if the user has access to do so.
if (!empty($create_bundles)) {
if (count($create_bundles) === 1) {
$add_url = Url::fromRoute('entity.media.add_form', ['media_type' => $create_bundles[0]])->toString();
}
elseif (count($create_bundles) > 1) {
$add_url = Url::fromRoute('entity.media.add_page')->toString();
}
$allowed_bundles = !empty($settings['target_bundles']) ? $settings['target_bundles'] : [];
$add_url = _media_get_add_url($allowed_bundles);
if ($add_url) {
$elements['#media_help']['#media_add_help'] = t('Create your media on the <a href=":add_page" target="_blank">media add page</a> (opens a new window), then add it by name to the field below.', [':add_page' => $add_url]);
}
......@@ -308,6 +286,10 @@ function media_field_widget_multivalue_form_alter(array &$elements, FormStateInt
if ($overview_url->access()) {
$elements['#media_help']['#media_list_link'] = t('See the <a href=":list_url" target="_blank">media list</a> (opens a new window) to help locate media.', [':list_url' => $overview_url->toString()]);
}
$all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
$bundle_labels = array_map(function ($bundle) use ($all_bundles) {
return $all_bundles[$bundle]['label'];
}, $allowed_bundles);
$elements['#media_help']['#allowed_types_help'] = t('Allowed media types: %types', ['%types' => implode(", ", $bundle_labels)]);
}
}
......@@ -336,3 +318,32 @@ function media_preprocess_media_reference_help(&$variables) {
}
}
}
/**
* Returns the appropriate URL to add media for the current user.
*
* @todo Remove in https://www.drupal.org/project/drupal/issues/2938116
*
* @param string[] $allowed_bundles
* An array of bundles that should be checked for create access.
*
* @return bool|\Drupal\Core\Url
* The URL to add media, or FALSE if the user cannot create any media.
*
* @internal
* This function is internal and may be removed in a minor release.
*/
function _media_get_add_url($allowed_bundles) {
$access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media');
$create_bundles = array_filter($allowed_bundles, [$access_handler, 'createAccess']);
// Add a section about how to create media if the user has access to do so.
if (count($create_bundles) === 1) {
return Url::fromRoute('entity.media.add_form', ['media_type' => reset($create_bundles)])->toString();
}
elseif (count($create_bundles) > 1) {
return Url::fromRoute('entity.media.add_page')->toString();
}
return FALSE;
}
......@@ -8,6 +8,7 @@ dependencies:
- media_library
module:
- media
- media_library
- user
id: media_library
label: 'Media library'
......@@ -71,7 +72,7 @@ display:
type: default
options:
grouping: { }
row_class: 'media-library-item js-click-to-select'
row_class: 'media-library-item js-media-library-item js-click-to-select'
default_row_class: true
row:
type: fields
......@@ -118,7 +119,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: js-click-to-select__checkbox
element_class: js-click-to-select-checkbox
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -376,51 +377,9 @@ display:
content: 'No media available.'
plugin_id: text_custom
relationships: { }
arguments:
bundle:
id: bundle
table: media_field_data
field: bundle
relationship: none
group_type: group
admin_label: ''
default_action: ignore
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: false
validate:
type: none
fail: 'not found'
validate_options: { }
glossary: false
limit: 0
case: none
path_case: none
transform_dash: false
break_phrase: false
entity_type: media
entity_field: bundle
plugin_id: string
display_extenders: { }
use_ajax: true
css_class: media-library-view
css_class: 'media-library-view js-media-library-view'
cache_metadata:
max-age: 0
contexts:
......@@ -456,3 +415,131 @@ display:
- 'url.query_args:sort_by'
- user.permissions
tags: { }
# @todo Lock down access in https://www.drupal.org/node/2983179
widget:
display_plugin: page
id: widget
display_title: Widget
position: 2
display_options:
display_extenders: { }
path: admin/content/media-widget
fields:
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
media_library_select_form:
id: media_library_select_form
table: media
field: media_library_select_form
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: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: js-click-to-select-checkbox
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
entity_type: media
plugin_id: media_library_select_form
defaults:
fields: false
access: false
display_description: ''
access:
type: perm
options:
perm: 'view media'
cache_metadata:
max-age: -1
contexts:
- 'languages:language_interface'
- url
- url.query_args
- 'url.query_args:sort_by'
- user.permissions
tags: { }
......@@ -2,22 +2,19 @@
* @file media_library.module.css
*/
.media-library-page-form {
display: flex;
flex-wrap: wrap;
}
.media-library-page-form > .form-actions {
.media-library-views-form > .form-actions {
flex-basis: 100%;
}
.media-library-page-form__header > div,
.media-library-views-form,
.media-library-selection,
.media-library-views-form__bulk_form,
.media-library-view .form--inline {
display: flex;
flex-wrap: wrap;
}
.media-library-page-form__header {
.media-library-views-form__header {
flex-basis: 100%;
}
......@@ -25,7 +22,7 @@
position: relative;
}
.media-library-item .js-click-to-select__trigger {
.media-library-item .js-click-to-select-trigger {
overflow: hidden;
cursor: pointer;
}
......@@ -34,7 +31,7 @@
align-self: flex-end;
}
.media-library-item .js-click-to-select__checkbox {
.media-library-item .js-click-to-select-checkbox {
position: absolute;
display: block;
z-index: 1;
......@@ -51,6 +48,25 @@
.media-library-select-all {
flex-basis: 100%;
width: 100%;
}
.media-library-view.view-display-id-widget .media-library-select-all {
display: none;
}
.media-library-item--disabled {
pointer-events: none;
}
.media-library-selection .media-library-item__preview {
cursor: move;
}
/* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
.media-library-widget .media-library-item__name a,
.media-library-view.view-display-id-widget .media-library-item__name a {
pointer-events: none;
}
@media screen and (max-width: 600px) {
......
......@@ -5,7 +5,7 @@
* @see https://www.drupal.org/project/drupal/issues/2980769
*/
.media-library-page-form__header .form-item {
.media-library-views-form__header .form-item {
margin-right: 8px;
}
......@@ -16,21 +16,29 @@
.media-library-item {
justify-content: center;
vertical-align: top;
padding: 2px;
border: 1px solid #ebebeb;
border: 1px solid #dbdbdb;
margin: 16px 16px 2px 2px;
width: 180px;
background: #fff;
transition: border-color 0.2s, color 0.2s, background 0.2s;
}
.media-library-view {
min-height: 300px;
}
.media-library-view .form-actions {
margin: 0.75em 0;
}
.media-library-view .media-library-view--form-actions {
clear: left;
margin: 0.75em 0;
align-self: flex-end;
}
.media-library-item .field--name-thumbnail {
background-color: #ebebeb;
margin: 2px;
overflow: hidden;
text-align: center;
}
......@@ -54,17 +62,17 @@
border-color: #0076c0;
}
.media-library-item .js-click-to-select__checkbox input {
.media-library-item .js-click-to-select-checkbox input {
width: 30px;
height: 30px;
}
.media-library-item .js-click-to-select__checkbox .form-item {
.media-library-item .js-click-to-select-checkbox .form-item {
margin: 0;
}
.media-library-item__preview {
padding-bottom: 44px;
padding-bottom: 34px;
}
.media-library-item__status {
......@@ -90,9 +98,9 @@
position: absolute;
bottom: 0;
display: block;
padding: 10px;
max-width: calc(100% - 20px);
max-height: calc(100% - 60px);
padding: 5px;
max-width: calc(100% - 10px);
max-height: calc(100% - 50px);
overflow: hidden;
background: white;
}
......@@ -135,6 +143,68 @@
margin-right: 10px;
}
.media-library-item--disabled {
opacity: 0.5;
}
.media-library-selection {
margin-bottom: 1.5rem;
}
.media-library-widget {
position: relative;
}
.media-library-widget__toggle-weight {
position: absolute;
right: 5px;
top: 5px;
}
.media-library-item .form-item {
margin: 0.75em;
}
.media-library-item__remove,
.media-library-item__remove:hover,
.media-library-item__remove:focus,
.media-library-item__remove.button,
.media-library-item__remove.button:disabled,
.media-library-item__remove.button:disabled:active,
.media-library-item__remove.button:hover,
.media-library-item__remove.button:focus {
position: absolute;
z-index: 1;
top: 0;
right: 0;
width: 24px;
height: 24px;
margin: 5px;
padding: 0;
background: url('../../../misc/icons/787878/ex.svg') #fff center no-repeat;
background-size: 16px 16px;
border: 2px solid #ccc;
border-radius: 20px;
color: transparent;
text-shadow: none;
transition: 0.2s border-color;
}
.media-library-item__remove:hover,
.media-library-item__remove:focus,
.media-library-item__remove.button:hover,
.media-library-item__remove.button:focus,
.media-library-item__remove.button:disabled:active {
border-color: #40b6ff;
}
/* @todo Remove or re-work in https://www.drupal.org/node/2985168 */
.media-library-widget .media-library-item__name a,
.media-library-view.view-display-id-widget .media-library-item__name a {
color: black;
text-decoration: none;
}
@media screen and (max-width: 600px) {
.media-library-item {
width: 150px;
......
......@@ -8,7 +8,7 @@
*/
Drupal.behaviors.ClickToSelect = {
attach(context) {
$('.js-click-to-select__trigger', context)
$('.js-click-to-select-trigger', context)
.once('media-library-click-to-select')
.on('click', (event) => {
// Links inside the trigger should not be click-able.
......@@ -16,10 +16,10 @@
// 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');
.find('.js-click-to-select-checkbox input');
$input.prop('checked', !$input.prop('checked')).trigger('change');
});
$('.js-click-to-select__checkbox input', context)
$('.js-click-to-select-checkbox input', context)
.once('media-library-click-to-select')
.on('change', ({ currentTarget }) => {
$(currentTarget)
......
......@@ -8,13 +8,13 @@
(function ($, Drupal) {
Drupal.behaviors.ClickToSelect = {
attach: function attach(context) {
$('.js-click-to-select__trigger', context).once('media-library-click-to-select').on('click', function (event) {
$('.js-click-to-select-trigger', context).once('media-library-click-to-select').on('click', function (event) {
event.preventDefault();
var $input = $(event.currentTarget).closest('.js-click-to-select').find('.js-click-to-select__checkbox input');
var $input = $(event.currentTarget).closest('.js-click-to-select').find('.js-click-to-select-checkbox input');
$input.prop('checked', !$input.prop('checked')).trigger('change');
});
$('.js-click-to-select__checkbox input', context).once('media-library-click-to-select').on('change', function (_ref) {
$('.js-click-to-select-checkbox input', context).once('media-library-click-to-select').on('change', function (_ref) {
var currentTarget = _ref.currentTarget;
$(currentTarget).closest('.js-click-to-select').toggleClass('checked', $(currentTarget).prop('checked'));
......
......@@ -7,7 +7,7 @@
*/
Drupal.behaviors.MediaLibraryHover = {
attach(context) {
$('.media-library-item .js-click-to-select__trigger,.media-library-item .js-click-to-select__checkbox', context).once('media-library-item-hover')
$('.media-library-item .js-click-to-select-trigger,.media-library-item .js-click-to-select-checkbox', context).once('media-library-item-hover')
.on('mouseover mouseout', ({ currentTarget, type }) => {
$(currentTarget).closest('.media-library-item').toggleClass('is-hover', type === 'mouseover');
});
......@@ -19,7 +19,7 @@
*/
Drupal.behaviors.MediaLibraryFocus = {
attach(context) {
$('.media-library-item .js-click-to-select__checkbox input', context).once('media-library-item-focus')
$('.media-library-item .js-click-to-select-checkbox input', context).once('media-library-item-focus')
.on('focus blur', ({ currentTarget, type }) => {
$(currentTarget).closest('.media-library-item').toggleClass('is-focus', type === 'focus');
});
......
......@@ -8,7 +8,7 @@
(function ($, Drupal) {
Drupal.behaviors.MediaLibraryHover = {
attach: function attach(context) {
$('.media-library-item .js-click-to-select__trigger,.media-library-item .js-click-to-select__checkbox', context).once('media-library-item-hover').on('mouseover mouseout', function (_ref) {
$('.media-library-item .js-click-to-select-trigger,.media-library-item .js-click-to-select-checkbox', context).once('media-library-item-hover').on('mouseover mouseout', function (_ref) {
var currentTarget = _ref.currentTarget,
type = _ref.type;
......@@ -19,7 +19,7 @@
Drupal.behaviors.MediaLibraryFocus = {
attach: function attach(context) {
$('.media-library-item .js-click-to-select__checkbox input', context).once('media-library-item-focus').on('focus blur', function (_ref2) {
$('.media-library-item .js-click-to-select-checkbox input', context).once('media-library-item-focus').on('focus blur', function (_ref2) {
var currentTarget = _ref2.currentTarget,
type = _ref2.type;
......
/**
* @file media_library.widget.js
*/
(($, Drupal) => {
/**
* Allows users to re-order their selection with drag+drop.
*/
Drupal.behaviors.MediaLibraryWidgetSortable = {
attach(context) {
// Allow media items to be re-sorted with drag+drop in the widget.
$('.js-media-library-selection', context).once('media-library-sortable').sortable({
tolerance: 'pointer',
helper: 'clone',
handle: '.js-media-library-item-preview',
stop: ({ target }) => {
// Update all the hidden "weight" fields.
$(target).children().each((index, child) => {
$(child).find('.js-media-library-item-weight').val(index);
});
},
});
},
};
/**
* Allows selection order to be set without drag+drop for accessibility.
*/
Drupal.behaviors.MediaLibraryWidgetToggleWeight = {
attach(context) {
const strings = {
show: Drupal.t('Show media item weights'),
hide: Drupal.t('Hide media item weights'),
};
$('.js-media-library-widget-toggle-weight', context).once('media-library-toggle')
.on('click', (e) => {
e.preventDefault();
$(e.currentTarget)
.toggleClass('active')
.text($(e.currentTarget).hasClass('active') ? strings.hide : strings.show)
.parent()
.find('.js-media-library-item-weight')
.parent()
.toggle();
})
.text(strings.show);
$('.js-media-library-item-weight', context).once('media-library-toggle').parent().hide();
},
};
/**
* Warn users when clicking outgoing links from the library or widget.
*/
Drupal.behaviors.MediaLibraryWidgetWarn = {
attach(context) {
$('.js-media-library-item a[href]', context).once('media-library-warn-link')
.on('click', (e) => {
const message = Drupal.t('Unsaved changes to the form will be lost. Are you sure you want to leave?');
const confirmation = window.confirm(message);
if (!confirmation) {
e.preventDefault();
}
});
},
};
/**
* Prevent users from selecting more items than allowed in the view.
*/
Drupal.behaviors.MediaLibraryWidgetRemaining = {
attach(context, settings) {
const $view = $('.js-media-library-view', context).once('media-library-remaining');
$view.find('.js-media-library-item input[type="checkbox"]')
.on('change', () => {
if (settings.media_library && settings.media_library.selection_remaining) {
const $checkboxes = $view.find('.js-media-library-item input[type="checkbox"]');
if ($checkboxes.filter(':checked').length === settings.media_library.selection_remaining) {
$checkboxes
.not(':checked')
.prop('disabled', true)
.closest('.js-media-library-item')
.addClass('media-library-item--disabled');
}
else {
$checkboxes
.prop('disabled', false)
.closest('.js-media-library-item')
.removeClass('media-library-item--disabled');
}
}
});
},
};
})(jQuery, Drupal);
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal) {
Drupal.behaviors.MediaLibraryWidgetSortable = {
attach: function attach(context) {
$('.js-media-library-selection', context).once('media-library-sortable').sortable({
tolerance: 'pointer',
helper: 'clone',
handle: '.js-media-library-item-preview',
stop: function stop(_ref) {
var target = _ref.target;
$(target).children().each(function (index, child) {
$(child).find('.js-media-library-item-weight').val(index);
});
}
});
}
};
Drupal.behaviors.MediaLibraryWidgetToggleWeight = {
attach: function attach(context) {
var strings = {
show: Drupal.t('Show media item weights'),
hide: Drupal.t('Hide media item weights')
};
$('.js-media-library-widget-toggle-weight', context).once('media-library-toggle').on('click', function (e) {
e.preventDefault();
$(e.currentTarget).toggleClass('active').text($(e.currentTarget).hasClass('active') ? strings.hide : strings.show).parent().find('.js-media-library-item-weight').parent().toggle();
}).text(strings.show);
$('.js-media-library-item-weight', context).once('media-library-toggle').parent().hide();
}
};
Drupal.behaviors.MediaLibraryWidgetWarn = {
attach: function attach(context) {
$('.js-media-library-item a[href]', context).once('media-library-warn-link').on('click', function (e) {
var message = Drupal.t('Unsaved changes to the form will be lost. Are you sure you want to leave?');
var confirmation = window.confirm(message);
if (!confirmation) {
e.preventDefault();
}
});
}
};
Drupal.behaviors.MediaLibraryWidgetRemaining = {
attach: function attach(context, settings) {
var $view = $('.js-media-library-view', context).once('media-library-remaining');
$view.find('.js-media-library-item input[type="checkbox"]').on('change', function () {
if (settings.media_library && settings.media_library.selection_remaining) {
var $checkboxes = $view.find('.js-media-library-item input[type="checkbox"]');
if ($checkboxes.filter(':checked').length === settings.media_library.selection_remaining) {
$checkboxes.not(':checked').prop('disabled', true).closest('.js-media-library-item').addClass('media-library-item--disabled');
} else {
$checkboxes.prop('disabled', false).closest('.js-media-library-item').removeClass('media-library-item--disabled');
}
}
});
}
};
})(jQuery, Drupal);
\ No newline at end of file
......@@ -21,5 +21,14 @@ view:
dependencies:
- media_library/style
- media_library/click_to_select
widget:
version: VERSION
js:
js/media_library.widget.js: {}
dependencies:
- core/drupal.ajax
- core/jquery.ui.sortable
- media_library/view
- core/drupal.announce
- core/jquery.once
......@@ -5,12 +5,17 @@
* Contains hook implementations for the media_library module.
*/
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\views\Form\ViewsForm;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\Plugin\views\query\Sql;
use Drupal\views\ViewExecutable;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Implements hook_help().
......@@ -43,6 +48,27 @@ function media_library_theme() {
function media_library_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
if ($view->id() === 'media_library') {
$output['#attached']['library'][] = 'media_library/view';
if ($view->current_display === 'widget') {
$query = array_intersect_key(\Drupal::request()->query->all(), array_flip([
'media_library_widget_id',
'media_library_allowed_types',
'media_library_remaining',
]));
// 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']);
$ajax_path = $parsed_url['path'] . '?' . UrlHelper::buildQuery($query);
if (isset($query['media_library_remaining'])) {
$output['#attached']['drupalSettings']['media_library']['selection_remaining'] = (int) $query['media_library_remaining'];
}
}
}
}
}
......@@ -59,7 +85,7 @@ function media_library_preprocess_media(&$variables) {
'language' => $media->language(),
]);
$variables['preview_attributes'] = new Attribute();
$variables['preview_attributes']->addClass('media-library-item__preview', 'js-click-to-select__trigger');
$variables['preview_attributes']->addClass('media-library-item__preview', 'js-media-library-item-preview', 'js-click-to-select-trigger');
$variables['metadata_attributes'] = new Attribute();
$variables['metadata_attributes']->addClass('media-library-item__attributes');
$variables['status'] = $media->isPublished();
......@@ -74,12 +100,10 @@ function media_library_preprocess_media(&$variables) {
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @todo Remove in https://www.drupal.org/project/drupal/issues/2969660
* @todo Remove in https://www.drupal.org/node/2983454
*/
function media_library_form_views_form_media_library_page_alter(array &$form, FormStateInterface $form_state) {
if (isset($form['media_bulk_form']) && isset($form['output'])) {
$form['#attributes']['class'][] = 'media-library-page-form';
$form['header']['#attributes']['class'][] = 'media-library-page-form__header';
/** @var \Drupal\views\ViewExecutable $view */
$view = $form['output'][0]['#view'];
foreach (Element::getVisibleChildren($form['media_bulk_form']) as $key) {
......@@ -93,6 +117,102 @@ function media_library_form_views_form_media_library_page_alter(array &$form, Fo
}
}
/**
* Implements hook_form_alter().
*/
function media_library_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
$form_object = $form_state->getFormObject();
if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
$form['#attributes']['class'][] = 'media-library-views-form';
if (isset($form['header'])) {
$form['header']['#attributes']['class'][] = 'media-library-views-form__header';
$form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
}
}
// Add after build to fix media library views exposed filter's submit button.
if ($form_id === 'views_exposed_form' && $form['#id'] === 'views-exposed-form-media-library-widget') {
$form['#after_build'][] = '_media_library_views_form_media_library_after_build';
}
}
/**
* After build callback for views form media library.
*/
function _media_library_views_form_media_library_after_build(array $form, FormStateInterface $form_state) {
// Remove .form-actions from media library views exposed filter actions
// and replace with .media-library-view--form-actions.
//
// This prevents the views exposed filter's 'Apply filter' submit button from
// being moved into the dialog's buttons.
// @see \Drupal\Core\Render\Element\Actions::processActions
// @see Drupal.behaviors.dialog.prepareDialogButtons
if (($key = array_search('form-actions', $form['actions']['#attributes']['class'])) !== FALSE) {
unset($form['actions']['#attributes']['class'][$key]);
}
$form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
return $form;
}
/**
* Implements hook_views_query_alter().
*
* Alters the widget view's query to only show media that can be selected,
* based on what types are allowed in the field settings.
*
* @todo Remove in https://www.drupal.org/node/2983454
*/
function media_library_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
if ($query instanceof Sql && $view->id() === 'media_library' && $view->current_display === 'widget') {
$types = _media_library_get_allowed_types();
if ($types) {
$entity_type = \Drupal::entityTypeManager()->getDefinition('media');
$group = $query->setWhereGroup();
$query->addWhere($group, $entity_type->getDataTable() . '.' . $entity_type->getKey('bundle'), $types, 'in');
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Limits the types available in the exposed filter to avoid users trying to
* filter by a type that is un-selectable.
*
* @see media_library_views_query_alter()
*
* @todo Remove in https://www.drupal.org/node/2983454
*/
function media_library_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state) {
if (isset($form['#id']) && $form['#id'] === 'views-exposed-form-media-library-widget') {
$types = _media_library_get_allowed_types();
if ($types && isset($form['type']['#options'])) {
$keys = array_flip($types);
// Ensure that the default value (by default "All") persists.
if (isset($form['type']['#default_value'])) {
$keys[$form['type']['#default_value']] = TRUE;
}
$form['type']['#options'] = array_intersect_key($form['type']['#options'], $keys);
}
}
}
/**
* Implements hook_field_ui_preconfigured_options_alter().
*/
function media_library_field_ui_preconfigured_options_alter(array &$options, $field_type) {
// 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().
*
......@@ -107,3 +227,17 @@ function media_library_local_tasks_alter(&$local_tasks) {
}
}
}
/**
* Determines what types are allowed based on the current request.
*
* @return array
* An array of allowed types.
*/
function _media_library_get_allowed_types() {
$types = \Drupal::request()->query->get('media_library_allowed_types');
if ($types && is_array($types)) {
return array_filter($types, 'is_string');
}
return [];
}
<?php
/**
* @file
* Contains Views integration for the media_library module.
*/
/**
* Implements hook_views_data().
*/
function media_library_views_data() {
$data = [];
$data['media']['media_library_select_form'] = [
'title' => t('Select media'),
'help' => t('Provides a field for selecting media entities in our media library view'),
'real field' => 'mid',
'field' => [
'id' => 'media_library_select_form',
],
];
return $data;
}
<?php
namespace Drupal\media_library\Plugin\Field\FieldWidget;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\media\Entity\Media;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Plugin implementation of the 'media_library_widget' widget.
*
* @FieldWidget(
* id = "media_library_widget",
* label = @Translation("Media library"),
* description = @Translation("Allows you to select items from the media library."),
* field_types = {
* "entity_reference"
* },
* multiple_values = TRUE,
* )
*
* @internal
*/
class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
/**
* Entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a MediaLibraryWidget widget.
*
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the widget is associated.
* @param array $settings
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->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) {
// Load the items for form rebuilds from the field state.
$field_state = static::getWidgetState($form['#parents'], $this->fieldDefinition->getName(), $form_state);
if (isset($field_state['items'])) {
usort($field_state['items'], [SortArray::class, 'sortByWeightElement']);
$items->setValue($field_state['items']);
}
return parent::form($items, $form, $form_state, $get_delta);
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */
$referenced_entities = $items->referencedEntities();
$view_builder = $this->entityTypeManager->getViewBuilder('media');
$field_name = $this->fieldDefinition->getName();
$parents = $form['#parents'];
$id_suffix = '-' . implode('-', $parents);
$wrapper_id = $field_name . '-media-library-wrapper' . $id_suffix;
$limit_validation_errors = [array_merge($parents, [$field_name])];
$settings = $this->getFieldSetting('handler_settings');
$element += [
'#type' => 'fieldset',
'#cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
'#target_bundles' => isset($settings['target_bundles']) ? $settings['target_bundles'] : FALSE,
'#attributes' => [
'id' => $wrapper_id,
'class' => ['media-library-widget'],
],
'#attached' => [
'library' => ['media_library/widget'],
],
];
// @todo Remove in https://www.drupal.org/project/drupal/issues/2938116
$allowed_bundles = !empty($element['#target_bundles']) ? $element['#target_bundles'] : [];
$add_url = _media_get_add_url($allowed_bundles);
if ($add_url) {
$element['create_help'] = [
'#type' => 'container',
];
$element['create_help']['label'] = [
'#type' => 'html_tag',
'#tag' => 'h4',
'#attributes' => [
'class' => ['label'],
],
'#value' => $this->t('Create new media'),
];
$element['create_help']['description'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'class' => ['description'],
],
'#value' => $this->t('Create your media on the <a href=":add_page" target="_blank">media add page</a> (opens a new window), then select it in the library.', [':add_page' => $add_url]),
];
}
$element['selection'] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'js-media-library-selection',
'media-library-selection',
],
],
];
if (empty($referenced_entities)) {
$element['empty_selection'] = [
'#markup' => $this->t('<p>No media items are selected.</p>'),
];
}
else {
$element['weight_toggle'] = [
'#type' => 'html_tag',
'#tag' => 'button',
'#value' => $this->t('Show media item weights'),
'#attributes' => [
'class' => [
'link',
'media-library-widget__toggle-weight',
'js-media-library-widget-toggle-weight',
],
'title' => $this->t('Re-order media by numerical weight instead of dragging'),
],
];
}
foreach ($referenced_entities as $delta => $media_item) {
$element['selection'][$delta] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'media-library-item',
'js-media-library-item',
],
],
'preview' => [
'#type' => 'container',
// @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
'rendered_entity' => $view_builder->view($media_item, 'media_library'),
'remove_button' => [
'#type' => 'submit',
'#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
'#value' => $this->t('Remove'),
'#attributes' => [
'class' => ['media-library-item__remove'],
],
'#ajax' => [
'callback' => [static::class, 'updateWidget'],
'wrapper' => $wrapper_id,
],
'#submit' => [[static::class, 'removeItem']],
// Prevent errors in other widgets from preventing removal.
'#limit_validation_errors' => $limit_validation_errors,
],
],
'target_id' => [
'#type' => 'hidden',
'#value' => $media_item->id(),
],
// This hidden value can be toggled visible for accessibility.
'weight' => [
'#type' => 'number',
'#title' => $this->t('Weight'),
'#default_value' => $delta,
'#attributes' => [
'class' => [
'js-media-library-item-weight',
'media-library-item__weight',
],
],
],
];
}
$cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$remaining = $element['#cardinality'] - count($referenced_entities);
// Inform the user of how many items are remaining.
if (!$cardinality_unlimited) {
if ($remaining) {
$cardinality_message = $this->formatPlural($remaining, 'One media item remaining.', '@count media items remaining.');
}
else {
$cardinality_message = $this->t('The maximum number of media items have been selected.');
}
$element['#description'] .= '<br />' . $cardinality_message;
}
// Add a button that will load the Media library in a modal using AJAX.
$element['media_library_open_button'] = [
'#type' => 'link',
'#title' => $this->t('Browse media'),
'#name' => $field_name . '-media-library-open-button' . $id_suffix,
// @todo Make the view configurable in https://www.drupal.org/project/drupal/issues/2971209
'#url' => Url::fromRoute('view.media_library.widget', [], [
'query' => [
'media_library_widget_id' => $field_name . $id_suffix,
'media_library_allowed_types' => $element['#target_bundles'],
'media_library_remaining' => $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining,
],
]),
'#attributes' => [
'class' => ['button', 'use-ajax', 'media-library-open-button'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'dialogClass' => 'media-library-widget-modal',
'height' => '75%',
'width' => '75%',
'title' => $this->t('Media library'),
]),
],
// Prevent errors in other widgets from preventing addition.
'#limit_validation_errors' => $limit_validation_errors,
'#access' => $cardinality_unlimited || $remaining > 0,
];
// This hidden field and button are used to add new items to the widget.
$element['media_library_selection'] = [
'#type' => 'hidden',
'#attributes' => [
// This is used to pass the selection from the modal to the widget.
'data-media-library-widget-value' => $field_name . $id_suffix,
],
];
// When a selection is made this hidden button is pressed to add new media
// items based on the "media_library_selection" value.
$element['media_library_update_widget'] = [
'#type' => 'submit',
'#value' => $this->t('Update widget'),
'#name' => $field_name . '-media-library-update' . $id_suffix,
'#ajax' => [
'callback' => [static::class, 'updateWidget'],
'wrapper' => $wrapper_id,
],
'#attributes' => [
'data-media-library-widget-update' => $field_name . $id_suffix,
'class' => ['js-hide'],
],
'#validate' => [[static::class, 'validateItems']],
'#submit' => [[static::class, 'updateItems']],
// Prevent errors in other widgets from preventing updates.
'#limit_validation_errors' => $limit_validation_errors,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) {
return isset($element['target_id']) ? $element['target_id'] : FALSE;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
if (isset($values['selection'])) {
usort($values['selection'], [SortArray::class, 'sortByWeightElement']);
return $values['selection'];
}
return [];
}
/**
* AJAX callback to update the widget when the selection changes.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return array
* An array representing the updated widget.
*/
public static function updateWidget(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
// This callback is either invoked from the remove button or the update
// button, which have different nesting levels.
$length = end($triggering_element['#parents']) === 'remove_button' ? -4 : -1;
if (count($triggering_element['#array_parents']) < abs($length)) {
throw new \LogicException('The element that triggered the widget update was at an unexpected depth. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
}
$parents = array_slice($triggering_element['#array_parents'], 0, $length);
$element = NestedArray::getValue($form, $parents);
// Always clear the textfield selection to prevent duplicate additions.
$element['media_library_selection']['#value'] = '';
return $element;
}
/**
* Submit callback for remove buttons.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function removeItem(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
// Get the parents required to find the top-level widget element.
if (count($triggering_element['#array_parents']) < 4) {
throw new \LogicException('Expected the remove button to be more than four levels deep in the form. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
}
$parents = array_slice($triggering_element['#array_parents'], 0, -4);
// Get the delta of the item being removed.
$delta = array_slice($triggering_element['#array_parents'], -3, 1)[0];
$element = NestedArray::getValue($form, $parents);
// Get the field state.
$path = $element['#parents'];
$values = NestedArray::getValue($form_state->getValues(), $path);
$field_state = static::getFieldState($element, $form_state);
// Remove the item from the field state and update it.
if (isset($values['selection'][$delta])) {
array_splice($values['selection'], $delta, 1);
$field_state['items'] = $values['selection'];
static::setFieldState($element, $form_state, $field_state);
}
$form_state->setRebuild();
}
/**
* Validates that newly selected items can be added to the widget.
*
* Making an invalid selection from the view should not be possible, but we
* still validate in case other selection methods (ex: upload) are valid.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function validateItems(array $form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_state = static::getFieldState($element, $form_state);
$media = static::getNewMediaItems($element, $form_state);
if (empty($media)) {
return;
}
// Check if more items were selected than we allow.
$cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$selection = count($field_state['items']) + count($media);
if (!$cardinality_unlimited && ($selection > $element['#cardinality'])) {
$form_state->setError($element, \Drupal::translation()->formatPlural($element['#cardinality'], 'Only one item can be selected.', 'Only @count items can be selected.'));
}
// Validate that each selected media is of an allowed bundle.
$all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
$bundle_labels = array_map(function ($bundle) use ($all_bundles) {
return $all_bundles[$bundle]['label'];
}, $element['#target_bundles']);
foreach ($media as $media_item) {
if ($element['#target_bundles'] && !in_array($media_item->bundle(), $element['#target_bundles'], TRUE)) {
$form_state->setError($element, t('The media item "@label" is not of an accepted type. Allowed types: @types', [
'@label' => $media_item->label(),
'@types' => implode(', ', $bundle_labels),
]));
}
}
}
/**
* Updates the field state and flags the form for rebuild.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function updateItems(array $form, FormStateInterface $form_state) {
$button = $form_state->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
$field_state = static::getFieldState($element, $form_state);
$media = static::getNewMediaItems($element, $form_state);
if (!empty($media)) {
$weight = count($field_state['items']);
foreach ($media as $media_item) {
// Any ID can be passed to the widget, so we have to check access.
if ($media_item->access('view')) {
$field_state['items'][] = [
'target_id' => $media_item->id(),
'weight' => $weight++,
];
}
}
static::setFieldState($element, $form_state, $field_state);
}
$form_state->setRebuild();
}
/**
* Gets newly selected media items.
*
* @param array $element
* The wrapping element for this widget.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\media\MediaInterface[]
* An array of selected media items.
*/
protected static function getNewMediaItems(array $element, FormStateInterface $form_state) {
// Get the new media IDs passed to our hidden button.
$values = $form_state->getValues();
$path = $element['#parents'];
$value = NestedArray::getValue($values, $path);
if (!empty($value['media_library_selection'])) {
$ids = explode(',', $value['media_library_selection']);
$ids = array_filter($ids, 'is_numeric');
if (!empty($ids)) {
/** @var \Drupal\media\MediaInterface[] $media */
return Media::loadMultiple($ids);
}
}
return [];
}
/**
* Gets the field state for the widget.
*
* @param array $element
* The wrapping element for this widget.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array[]
* An array of arrays with the following key/value pairs:
* - items: (array) An array of selections.
* - target_id: (int) A media entity ID.
* - weight: (int) A weight for the selection.
*/
protected static function getFieldState(array $element, FormStateInterface $form_state) {
$widget_state = static::getWidgetState($element['#field_parents'], $element['#field_name'], $form_state);
$widget_state['items'] = isset($widget_state['items']) ? $widget_state['items'] : [];
return $widget_state;
}
/**
* Sets the field state for the widget.
*
* @param array $element
* The wrapping element for this widget.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array[] $field_state
* An array of arrays with the following key/value pairs:
* - items: (array) An array of selections.
* - target_id: (int) A media entity ID.
* - weight: (int) A weight for the selection.
*/
protected static function setFieldState(array $element, FormStateInterface $form_state, array $field_state) {
static::setWidgetState($element['#field_parents'], $element['#field_name'], $form_state, $field_state);
}
}
<?php
namespace Drupal\media_library\Plugin\views\field;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Render\ViewsRenderPipelineMarkup;
use Drupal\views\ResultRow;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* Defines a field that outputs a checkbox and form for selecting media.
*
* @ViewsField("media_library_select_form")
*
* @internal
*/
class MediaLibrarySelectForm extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function getValue(ResultRow $row, $field = NULL) {
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
return ViewsRenderPipelineMarkup::create($this->getValue($values));
}
/**
* Form constructor for the media library select form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function viewsForm(array &$form, FormStateInterface $form_state) {
// Only add the bulk form options and buttons if there are results.
if (empty($this->view->result)) {
return;
}
// Render checkboxes for all rows.
$form[$this->options['id']]['#tree'] = TRUE;
foreach ($this->view->result as $row_index => $row) {
$entity = $this->getEntity($row);
$form[$this->options['id']][$row_index] = [
'#type' => 'checkbox',
'#title' => $this->t('Select @label', [
'@label' => $entity->label(),
]),
'#title_display' => 'invisible',
'#return_value' => $entity->id(),
];
}
// @todo Remove in https://www.drupal.org/project/drupal/issues/2504115
// Currently the default URL for all AJAX form elements is the current URL,
// not the form action. This causes bugs when this form is rendered from an
// AJAX path like /views/ajax, which cannot process AJAX form submits.
$url = parse_url($form['#action'], PHP_URL_PATH);
$query = \Drupal::request()->query->all();
$query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
$form['actions']['submit']['#ajax'] = [
'url' => Url::fromUserInput($url),
'options' => [
'query' => $query,
],
'callback' => [static::class, 'updateWidget'],
];
$form['actions']['submit']['#value'] = $this->t('Select media');
$form['actions']['submit']['#field_id'] = $this->options['id'];
}
/**
* Submit handler for the media library select form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* A command to send the selection to the current field widget.
*/
public static function updateWidget(array &$form, FormStateInterface $form_state) {
$widget_id = \Drupal::request()->query->get('media_library_widget_id');
if (!$widget_id || !is_string($widget_id)) {
throw new BadRequestHttpException('The "media_library_widget_id" query parameter is required and must be a string.');
}
$field_id = $form_state->getTriggeringElement()['#field_id'];
$selected = array_values(array_filter($form_state->getValue($field_id, [])));
// Pass the selection to the field widget based on the current widget ID.
return (new AjaxResponse())
->addCommand(new InvokeCommand("[data-media-library-widget-value=\"$widget_id\"]", 'val', [implode(',', $selected)]))
->addCommand(new InvokeCommand("[data-media-library-widget-update=\"$widget_id\"]", 'trigger', ['mousedown']))
->addCommand(new CloseDialogCommand());
}
/**
* {@inheritdoc}
*/
public function viewsFormValidate(array &$form, FormStateInterface $form_state) {
$selected = array_filter($form_state->getValue($this->options['id']));
if (empty($selected)) {
$form_state->setErrorByName('', $this->t('No items selected.'));
}
}
/**
* {@inheritdoc}
*/
public function clickSortable() {
return FALSE;
}
}
langcode: en
status: true
dependencies:
config:
- field.field.node.basic_page.field_twin_media
- field.field.node.basic_page.field_unlimited_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
settings: { }
third_party_settings: { }
region: content
field_unlimited_media:
type: media_library_widget
weight: 121
settings: { }
third_party_settings: { }
region: content
promote:
type: boolean_checkbox
settings:
display_label: true
weight: 15
region: content
third_party_settings: { }
status:
type: boolean_checkbox
settings:
display_label: true
weight: 120
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: { }
langcode: en
status: true
dependencies:
config:
- field.field.node.basic_page.field_twin_media
- field.field.node.basic_page.field_unlimited_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
weight: 102
label: above
settings:
view_mode: default
link: false
third_party_settings: { }
region: content
field_unlimited_media:
type: entity_reference_entity_view
weight: 101
label: above
settings:
view_mode: default
link: false
third_party_settings: { }
region: content
links:
weight: 100
settings: { }
third_party_settings: { }
region: content
hidden: { }
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
sort:
field: _none
auto_create: false
auto_create_bundle: file
field_type: entity_reference
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
sort:
field: _none
auto_create: false
auto_create_bundle: audio
field_type: entity_reference
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% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment