slick_browser-8.x-2.1/src/SlickBrowserWidget.php
src/SlickBrowserWidget.php
<?php
namespace Drupal\slick_browser;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\blazy\Blazy;
use Drupal\entity_browser\Entity\EntityBrowser;
/**
* Provides SlickBrowserWidget service.
*/
class SlickBrowserWidget extends SlickBrowserAlter implements SlickBrowserWidgetInterface {
/**
* {@inheritdoc}
*/
public function fieldWidgetFormAlter(array &$element, FormStateInterface $form_state, $context) {
$settings = [];
$plugin_id = $context['widget']->getPluginId();
// Always assumes no "Display style" of SB widgets is enabled.
if ($plugin_id == 'entity_browser_entity_reference') {
$settings = $this->entityBrowserEntityReferenceFormAlter($element, $context);
}
elseif ($plugin_id == 'entity_browser_file') {
$settings = $this->entityBrowserFileFormAlter($element, $context);
}
elseif ($plugin_id == 'media_library_widget') {
$settings = $this->mediaLibraryWidgetFormAlter($element, $context);
}
// Ony proceed if we are conciously allowed via "Display style" option.
// This settings may contain configurable third party settings.
if (empty($settings)) {
return;
}
// We are here because we are allowed to.
// Build common settings to all supported plugins.
// The non-image field type has 'display_field', 'description'.
$this->widgetSettings($element, $settings, $context);
$blazies = $settings['blazies'];
// EB only respects field_widget_display with cardinality -1. If not, we
// may need to override its display. Only concerns about File entity.
// The _processed flag means SB plugin output is already processed at plugin
// level, no further work with its display output is needed from here on.
$file = $blazies->get('field.target_type') == 'file';
$unlimited = $blazies->get('field.cardinality') == -1;
$processed = $file && !$unlimited ? FALSE : TRUE;
$blazies->set('is.sb.processed', $processed);
// Don't bother if using label.
if (!$blazies->use('label')) {
// Yet only modify for EB, not core Media Library.
if ($blazies->is('eb')) {
$this->entityBrowserDisplay($element, $settings, $context);
}
}
// Specific for EB, it might add own theme to style entity browser elements.
$this->widgetElement($element, $settings, $context);
$this->widgetAttach($element, $settings);
}
/**
* Modifies the available widget settings.
*/
private function widgetSettings(array &$element, array &$settings, $context) {
$formatter = $this->slickBrowser->formatter();
$plugin = $context['widget'];
$items = $context['items'];
$field = $items->getFieldDefinition();
$entity = $items->getEntity();
$widgetsets = $plugin->getSettings();
$fieldsets = $field->getSettings();
$plugin_id = $plugin->getPluginId();
$cardinality = $field->getFieldStorageDefinition()->getCardinality();
$grids = ['column', 'grid', 'flex', 'nativegrid'];
$settings['slickbrowsers'] = $sb = $formatter->settings();
// @todo remove.
$fieldsets['name'] = $field->getName();
$fieldsets['type'] = $field->getType();
$formatter_id = $settings['plugin_id_widget_formatter'] ?? '';
Blazy::entitySettings($settings, $entity);
$blazies = $formatter->verifySafely($settings);
$sb->set('widget.settings', $widgetsets)
->set('widget.settings.plugin_id', $plugin_id)
->set('widget.plugin_id', $plugin_id);
$blazies->set('field', $fieldsets, TRUE)
->set('field.cardinality', $cardinality)
->set('field.plugin_id', $formatter_id)
->set('field.settings', $fieldsets);
foreach (['alt_field', 'title_field', 'target_type'] as $key) {
$settings[$key] = $value = $fieldsets[$key] ?? FALSE;
$blazies->set('field.' . $key, $value);
}
$settings['media_switch'] = 'media';
$settings['ratio'] = 'fluid';
$thumbnail_style = $settings['thumbnail_style'] ?? NULL;
$settings['thumbnail_style'] = $thumbnail_style ?: 'slick_browser_thumbnail';
// @todo remove these post Blazy:2.10.
$settings['bundle'] = $entity->bundle();
$settings['entity_type_id'] = $entity->getEntityTypeId();
$settings['plugin_id_widget'] = $plugin_id;
$data = [
'cardinality' => $cardinality,
'field_name' => $context['items']->getName(),
'field_type' => $field->getType(),
'target_type' => $fieldsets['target_type'] ?? NULL,
'use_label' => $formatter_id == 'slick_browser_label',
'use_slick' => ($settings['style'] ?? NULL) == 'slick',
'use_autosubmit' => $settings['entity_type_id'] == 'media',
];
foreach ($data as $key => $value) {
$settings[$key] = $value;
}
$id_grid = !empty($settings['style'])
&& !empty($settings['grid'])
&& in_array($settings['style'], $grids);
// Defaults.
$blazies = $settings['blazies'];
foreach ($data as $key => $value) {
$k = $key;
if (strpos($key, 'use_') !== FALSE) {
$k = 'use.' . str_replace('use_', '', $key);
}
elseif (strpos($key, 'field_') !== FALSE) {
$k = 'field.' . str_replace('field_', '', $key);
}
$blazies->set($k, $value);
}
$blazies->set('is.eb', FALSE)
->set('is.media_library', FALSE)
->set('is.grid', $id_grid)
->set('is.detached', !empty($settings['style']))
// ->set('is.player', FALSE)
->set('use.tabs', FALSE)
->set('use.modal', FALSE)
->set('is.unlazy', TRUE);
$this->slickBrowser->formatter()->preSettings($settings);
// Entity Browser integration.
$plugins = ['entity_browser_entity_reference', 'entity_browser_file'];
if (in_array($plugin_id, $plugins)) {
$this->entityBrowserSettings($element, $settings, $context);
}
ksort($settings);
return $settings;
}
/**
* Modifies the available entity browser widget settings.
*/
private function entityBrowserSettings(array &$element, array &$settings, $context) {
$widgetsets = $context['widget']->getSettings();
$blazies = $settings['blazies'];
$sb = &$settings['slickbrowsers'];
$settings['_browser'] = TRUE;
$image_style = $settings['image_style'] ?? '';
$settings['image_style'] = $image_style ?: ($widgetsets['preview_image_style'] ?? '');
// Chances are SB browsers within iframes/modals, even if no SB widgets.
// Or using any of SB field_widget_display.
// Only EntityReferenceBrowserWidget has field_widget_display, not FBW.
$formatter_id = $blazies->get('field.plugin_id') ?: $settings['plugin_id_widget_formatter'] ?? '';
$playable = $formatter_id == 'slick_browser_file'
|| $formatter_id == 'slick_browser_media';
$blazies->set('is.eb', TRUE)
->set('is.browser', TRUE)
->set('libs.media', $playable);
// Load relevant assets based on the chosen SB browsers plugins.
if (!empty($widgetsets['entity_browser'])) {
$id = $widgetsets['entity_browser'];
if ($eb = EntityBrowser::load($id)) {
// Entity display plugins: slick_browser_file, slick_browser_media, etc.
$internal['display'] = $eb->getDisplay()->getConfiguration();
$internal['display']['plugin_id'] = $pid_widget_display = $eb->getDisplay()->getPluginId();
$internal['selection_display'] = $eb->getSelectionDisplay()->getConfiguration();
$internal['selection_display']['plugin_id'] = $eb->getSelectionDisplay()->getPluginId();
$internal['selector'] = $eb->getWidgetSelector()->getConfiguration();
$internal['selector']['plugin_id'] = $pid_widget_selector = $eb->getWidgetSelector()->getPluginId();
// Selection displays: modal, iframe, form, etc.
$use_modal = $pid_widget_display == 'modal';
$use_tabs = $pid_widget_selector == 'slick_browser_tabs';
$blazies->set('use.modal', $use_modal)
->set('use.tabs', $use_tabs);
$sb->set('widget', $internal, TRUE);
}
}
// @todo move it upstream.
if ($current = $element['current'] ?? []) {
$children = Element::children($current);
$count = count($children);
if ($items = ($current['items'] ?? [])) {
$count = count($items);
}
$settings['count'] = $count;
$blazies->set('count', $count);
}
}
/**
* Modifies the widget form element.
*/
private function widgetElement(array &$element, array &$settings, $context) {
$blazies = $settings['blazies'];
$sb = $settings['slickbrowsers'];
$attributes = &$element['#attributes'] ?? [];
// Build the SB widgets, nothing to do with SB browsers here on.
// This used to be "Slick Widget", moved into "Slick Browser".
$classes = $attributes['class'] ?? [];
$sb_classes = ['sb', 'sb--wrapper', 'sb--launcher'];
$attributes['class'] = array_merge($sb_classes, $classes);
$attributes['class'][] = $blazies->use('modal') ? 'sb--wrapper-inline' : 'sb--wrapper-modal';
$skin = $settings['skin'] ?? '';
$attributes['class'][] = $settings['style'] == 'slick' && $skin
? 'sb--skin--' . str_replace('_', '-', $skin)
: 'sb--skin--static';
if ($blazies->use('autosubmit')) {
$attributes['class'][] = 'sb--autoselect';
}
// Media Library integration: plugin_id_widget = media_library_widget.
if (isset($element['selection'])) {
$this->mediaLibraryElement($element, $settings, $context);
}
// Entity Browser integration has property current.
elseif (isset($element['current'])) {
$this->entityBrowserElement($element, $settings, $context);
}
$attributes['data-sb-bundle'] = $blazies->get('entity.bundle');
$attributes['data-sb-entity-type-id'] = $blazies->get('entity.type_id');
$attributes['data-sb-field-type'] = $blazies->get('field.type');
$attributes['data-sb-target-type'] = $blazies->get('field.target_type');
$attributes['data-sb-cardinality'] = $blazies->get('field.cardinality') ?: 0;
$attributes['data-sb-entity-browser'] = $blazies->get('field.plugin_id');
$attributes['data-sb-plugin-id-widget'] = $sb->get('widget.plugin_id');
}
/**
* Modifies the entity browser widget identified by element current.
*/
private function entityBrowserElement(array &$element, array &$settings, $context) {
$current = &$element['current'];
// Prevents collapsed details from breaking lazyload.
if (empty($element['#open'])) {
$element['#open'] = TRUE;
$element['#attributes']['class'][] = 'sb--wrapper-hidden';
}
$current['#settings'] = $settings;
$current['#attributes'] = [];
$current['#theme_wrappers'] = [];
$current['#theme'] = 'slick_browser';
// Removes table markups for regular divities.
if ($settings['plugin_id_widget'] == 'entity_browser_file') {
unset(
$current['#type'],
$current['#header'],
$current['#tabledrag']
);
}
}
/**
* Provides asset attachments.
*/
private function widgetAttach(array &$element, array $settings) {
$formatter = $this->slickBrowser->formatter();
// Enforce Blazy to work with hidden element such as with EB selection.
$load = $formatter->attach($settings);
$load['drupalSettings']['blazy']['loadInvisible'] = TRUE;
$load['library'][] = 'slick_browser/widget';
$blazies = $settings['blazies'];
foreach (['autosubmit', 'modal', 'slick', 'tabs'] as $key) {
if ($blazies->use($key)) {
$load['library'][] = 'slick_browser/' . $key;
}
}
if ($blazies->is('grid')) {
$load['library'][] = 'slick_browser/grid';
}
// Disable tabledrag, including FBW table CSS, for Slick/ CSS grid.
if ($settings['plugin_id_widget'] == 'entity_browser_file') {
$attachments = $load;
}
else {
$attachments = $formatter->merge($load, $element['current'] ?? [], '#attached');
}
$element['#attached'] = $formatter->merge($attachments, $element, '#attached');
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
private function entityBrowserEntityReferenceFormAlter(array &$element, $context) {
$widgetsets = $context['widget']->getSettings();
if (empty($widgetsets['field_widget_display']) || strpos($widgetsets['field_widget_display'], 'slick_browser') === FALSE) {
return [];
}
$settings = $widgetsets['field_widget_display_settings'];
$settings['plugin_id_widget_formatter'] = $widgetsets['field_widget_display'];
return empty($settings['style']) ? [] : array_merge(SlickBrowserDefault::entitySettings(), $settings);
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
private function entityBrowserFileFormAlter(array &$element, $context) {
$widgetsets = $context['widget']->getSettings();
if (empty($widgetsets['entity_browser']) || strpos($widgetsets['entity_browser'], 'slick_browser') === FALSE) {
return [];
}
// Allows Slick Browser to remove File Browser empty table.
$settings = SlickBrowserUtil::buildThirdPartySettings($context['widget']);
return empty($settings['style']) ? [] : $settings;
}
/**
* Implements hook_field_widget_WIDGET_TYPE_form_alter().
*/
private function mediaLibraryWidgetFormAlter(array &$element, $context) {
// Allows to provide configurable grids.
$settings = SlickBrowserUtil::buildThirdPartySettings($context['widget']);
return empty($settings['style']) ? [] : $settings;
}
/**
* Prepare entity displays for entity browser.
*
* EB only respects field_widget_display with cardinality -1. If not, we
* may need to override its display such as for single-value file/ media.
*/
private function entityBrowserDisplay(array &$element, array &$settings, $context) {
$browser = $this->slickBrowser;
$blazies = $settings['blazies'];
$target_type = $blazies->get('field.target_type') ?: ($settings['target_type'] ?? NULL);
// The items property is for entity, not file image, except cardinality -1.
// Cannot rely on $context['items'] for AJAX results. The entity_browser
// property is only available for:
// File, indexed by entity ID: cardinality -1 and > 1, but not 1,
// Media, grouped by items property: cardinality -1, but not 1, nor > 1.
$entities = $element['entity_browser']['#default_value'] ?? [];
if (empty($entities) && isset($element['current'])) {
if ($children = Element::children($element['current'])) {
// Maybe empty, but items is always set.
// File is never here.
// Media, grouped by items property: cardinality 1, > 1, not -1.
$items = $element['current']['items'] ?? [];
if ($items && $subchildren = Element::children($items)) {
foreach ($subchildren as $delta) {
$sets = $settings;
$sets['delta'] = $delta;
if ($entity = ($items[$delta]['display']['#entity'] ?? NULL)) {
$this->entityDisplay($element, $sets, $entity, $delta);
}
}
}
else {
// Indexed by entity ID.
// File, indexed by entity ID: cardinality 1.
foreach ($children as $delta => $id) {
$sets = $settings;
$sets['delta'] = $delta;
if ($entity = $browser->formatter()->load($id, $target_type)) {
$this->entityDisplay($element, $sets, $entity, $delta);
}
}
}
}
}
else {
// Hence we have entities, except single file image.
// File, indexed by entity ID: cardinality -1 and > 1, but not 1,
// Media, grouped by items property: cardinality -1, but not 1, nor > 1.
foreach ($entities as $delta => $entity) {
$sets = $settings;
$sets['delta'] = $delta;
$this->entityDisplay($element, $sets, $entity, $delta);
}
}
}
/**
* Prepares entity item display, applicable to EB and Media Library.
*/
private function entityDisplay(array &$element, array &$settings, $entity, $delta = 0) {
$content = [];
// @todo $browser = $this->slickBrowser;
// $blazies = $settings['blazies'];
// $langcode = $blazies->get('language.current') ?: 'en';
// $entity = $browser->formatter()->getTranslatedEntity($entity, $langcode);
/*
// If not already processed, proceed. Processed means plugin is respected
// which is currently not the case given different cardinality for File.
if (!$blazies->is('sb.processed')) {
$data['#entity'] = $entity;
$data['#settings'] = $settings;
$data['fallback'] = $label;
if (isset($element['selection'])) {
$blazies->set('item.id', 'sb');
$data['#wrapper_attributes']['class'][] = 'grid__content';
$data['#media_attributes']['class'][] =
'sb__preview media-library-item__preview js-media-library-item-preview';
$data['overlay']['sb_label']['#markup'] =
'<div class="sb__label">' . $label . '</div>';
}
$display = $browser->blazyEntity()->build($data);
$settings = $data['#settings'];
$display_settings = $display['#settings'] ?? [];
$display['#settings'] = $browser->formatter()
->merge($display_settings, $settings);
}
*/
// EB put them in items property for cardinality -1 + entity, not image.
// Do not modify anything if already processed such as cardinality -1.
if (isset($element['current'])) {
if (isset($element['current']['items'])) {
$content = &$element['current']['items'][$delta];
}
else {
// @todo recheck if should use translated entity here.
$content = &$element['current'][$entity->id()];
}
// If ($display) {
// $content['display'] = $display;
// }.
}
/*
elseif (isset($element['selection'])) {
// $content = &$element['selection'][$delta];
if ($display) {
// $content['rendered_entity'] = $display;
$element['selection'][$delta]['display'] = $display;
$element['selection'][$delta]['rendered_entity'] = [];
}
}
*/
}
/**
* Modifies the available media library widget settings.
*
* Unlike EB with full override, this is all we do with Media Library for now.
* Basically making the Media Library grid configurable, and blazy-enabled.
*/
private function mediaLibraryElement(array &$element, array $settings, $context) {
$browser = $this->slickBrowser;
$blazies = $settings['blazies'];
$blazies->set('is.media_library', TRUE)
->set('is.grid', TRUE);
if (isset($element['selection'])) {
$selection = &$element['selection'];
$attributes = &$selection['#attributes'];
$children = Element::children($selection);
$settings['count'] = $count = count($children);
$blazies->set('count', $count);
$browser->formatter()->gridAttributes($attributes, $settings);
// Respects anyone adding a suffix here.
$zoom = '<div class="sb__zoom"></div>';
if (isset($selection['#suffix'])) {
$selection['#suffix'] .= $zoom;
}
else {
$selection['#suffix'] = $zoom;
}
// Cannot use content_attributes, not all core themes have it at fieldset.
$attributes['class'][] = 'sb sb--widget';
if ($children) {
foreach ($children as $delta) {
$sets = $settings;
$sets['delta'] = $delta;
$blazy = $sets['blazies']->reset($sets);
$blazy->set('delta', $delta);
$item = &$selection[$delta];
$item_attrs = &$item['#attributes'];
$item_attrs['class'][] = 'grid';
$item_attrs['class'][] = 'grid--sb';
$item_attrs['class'][] = 'grid--' . $delta;
$rendered = $item['rendered_entity'] ?? NULL;
$entity = ($rendered['#media'] ?? NULL);
if ($rendered && $entity) {
$blazy->set('is.sb.processed', FALSE);
$output = $this->mediaLibraryDisplay($rendered, $sets, $entity, $delta);
$element['selection'][$delta]['rendered_entity'] = $output;
}
}
}
}
}
/**
* Prepares entity item display, applicable to EB and Media Library.
*/
private function mediaLibraryDisplay(array $element, array $settings, $entity, $delta = 0) {
$browser = $this->slickBrowser;
$blazies = $settings['blazies'];
// @todo $langcode = $blazies->get('language.current') ?: 'en';
// $entity = $browser->formatter()->getTranslatedEntity($entity, $langcode);
$label = $entity->label();
// If not already processed, proceed. Processed means plugin is respected
// which is currently not the case given different cardinality for File.
if (!$blazies->is('sb.processed')) {
$blazies->set('cache.metadata', $element['#cache'] ?? [], TRUE);
$data = [];
/** @var \Drupal\file\Entity\File $entity */
$data['#entity'] = $entity;
$data['#delta'] = $delta;
$data['#settings'] = $settings;
$data['fallback'] = $label;
$blazies->set('item.id', 'sb')
->set('is.cache_deferred', TRUE);
$data['#wrapper_attributes']['class'][] = 'grid__content';
$data['#media_attributes']['class'][] = 'sb__preview media-library-item__preview js-media-library-item-preview';
$data['overlay']['sb_label']['#markup'] = '<div class="sb__label">' . $label . '</div>';
// $settings = $data['#settings'];
// $display_settings = $display['#settings'] ?? [];
// $display['#settings'] = $browser->formatter()
// ->merge($display_settings, $settings);
$output = $browser->blazyEntity()->build($data);
$blazies = $data['#settings']['blazies'];
$output['#weight'] = $element['#weight'] ?? 0;
$output['#cache'] = $blazies->get('cache.metadata', []);
return $output;
}
return $element;
}
}
