From 5e3f489ae92619b98ad6547c6eb6db62ed306b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= <f.mably@gmail.com> Date: Sat, 14 Dec 2024 00:52:52 +0100 Subject: [PATCH] Issue #3493944 by mably: Make ICS compatible with the responsive_image module --- .../image_compare_media.info.yml | 2 +- .../ImageCompareSliderMediaFormatter.php | 33 +- .../ImageCompareSliderMediaFormatterTrait.php | 43 ++ .../image_compare_media_responsive.schema.yml | 3 + .../image_compare_media_responsive.info.yml | 8 + ...eCompareSliderResponsiveMediaFormatter.php | 95 ++++ .../image_compare_responsive.schema.yml | 3 + .../image_compare_responsive.info.yml | 8 + .../ImageCompareSliderResponsiveFormatter.php | 154 ++++++ .../ImageCompareSliderFormatter.php | 397 +--------------- .../ImageCompareSliderFormatterTrait.php | 447 ++++++++++++++++++ templates/image-compare-slider.html.twig | 50 +- 12 files changed, 798 insertions(+), 445 deletions(-) create mode 100644 modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php create mode 100644 modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml create mode 100644 modules/image_compare_media_responsive/image_compare_media_responsive.info.yml create mode 100644 modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php create mode 100644 modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml create mode 100644 modules/image_compare_responsive/image_compare_responsive.info.yml create mode 100644 modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php create mode 100644 src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php diff --git a/modules/image_compare_media/image_compare_media.info.yml b/modules/image_compare_media/image_compare_media.info.yml index 5a9163d..b0effaa 100644 --- a/modules/image_compare_media/image_compare_media.info.yml +++ b/modules/image_compare_media/image_compare_media.info.yml @@ -4,5 +4,5 @@ description: Add an Image Compare Accessible Slider formatter to media fields. package: Media core_version_requirement: ^10 || ^11 dependencies: - - image_compare:image_compare - drupal:media + - image_compare:image_compare diff --git a/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php index 025fd53..a7936ce 100644 --- a/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php +++ b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php @@ -3,8 +3,6 @@ namespace Drupal\image_compare_media\Plugin\Field\FieldFormatter; use Drupal\Core\Cache\Cache; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\image\Plugin\Field\FieldType\ImageItem; @@ -24,6 +22,8 @@ use Drupal\media\MediaInterface; */ class ImageCompareSliderMediaFormatter extends ImageCompareSliderFormatter { + use ImageCompareSliderMediaFormatterTrait; + /** * {@inheritdoc} */ @@ -80,33 +80,4 @@ class ImageCompareSliderMediaFormatter extends ImageCompareSliderFormatter { return $this->buildRenderArray($images, $options, $cache_tags); } - /** - * {@inheritdoc} - * - * This has to be overridden because FileFormatterBase expects $item to be - * of type \Drupal\file\Plugin\Field\FieldType\FileItem and calls - * isDisplayed() which is not in FieldItemInterface. - */ - protected function needsEntityLoad(EntityReferenceItem $item) { - return !$item->hasNewEntity(); - } - - /** - * {@inheritdoc} - */ - public static function isApplicable(FieldDefinitionInterface $field_definition) { - // This formatter is only available for entity types that reference - // media items. - return ($field_definition->getFieldStorageDefinition() - ->getSetting('target_type') == 'media'); - } - - /** - * {@inheritdoc} - */ - protected function checkAccess(EntityInterface $entity) { - return $entity->access('view', NULL, TRUE) - ->andIf(parent::checkAccess($entity)); - } - } diff --git a/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php new file mode 100644 index 0000000..c46e46e --- /dev/null +++ b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\image_compare_media\Plugin\Field\FieldFormatter; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; + +/** + * Provides a trait for the Image Compare Slider Media field formatter. + */ +trait ImageCompareSliderMediaFormatterTrait { + + /** + * {@inheritdoc} + * + * This has to be overridden because FileFormatterBase expects $item to be + * of type \Drupal\file\Plugin\Field\FieldType\FileItem and calls + * isDisplayed() which is not in FieldItemInterface. + */ + protected function needsEntityLoad(EntityReferenceItem $item) { + return !$item->hasNewEntity(); + } + + /** + * {@inheritdoc} + */ + public static function isApplicable(FieldDefinitionInterface $field_definition) { + // This formatter is only available for entity types that reference + // media items. + return ($field_definition->getFieldStorageDefinition() + ->getSetting('target_type') == 'media'); + } + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity) { + return $entity->access('view', NULL, TRUE) + ->andIf(parent::checkAccess($entity)); + } + +} diff --git a/modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml b/modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml new file mode 100644 index 0000000..27e4781 --- /dev/null +++ b/modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml @@ -0,0 +1,3 @@ +field.formatter.settings.image_compare_slider_media_responsive: + type: field.formatter.settings.image_compare_slider + label: Image Compare Accessible Slider formatter settings for responsive media diff --git a/modules/image_compare_media_responsive/image_compare_media_responsive.info.yml b/modules/image_compare_media_responsive/image_compare_media_responsive.info.yml new file mode 100644 index 0000000..abe600f --- /dev/null +++ b/modules/image_compare_media_responsive/image_compare_media_responsive.info.yml @@ -0,0 +1,8 @@ +name: Image Compare Accessible Slider for Responsive Media +type: module +description: Add an Image Compare Accessible Slider formatter to responsive media fields. +package: Media +core_version_requirement: ^10 || ^11 +dependencies: + - drupal:media + - image_compare_responsive:image_compare_responsive diff --git a/modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php b/modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php new file mode 100644 index 0000000..b92efba --- /dev/null +++ b/modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php @@ -0,0 +1,95 @@ +<?php + +namespace Drupal\image_compare_media_responsive\Plugin\Field\FieldFormatter; + +use Drupal\Core\Cache\Cache; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; +use Drupal\image\Plugin\Field\FieldType\ImageItem; +use Drupal\image_compare_responsive\Plugin\Field\FieldFormatter\ImageCompareSliderResponsiveFormatter; +use Drupal\image_compare_media\Plugin\Field\FieldFormatter\ImageCompareSliderMediaFormatterTrait; +use Drupal\media\MediaInterface; + +/** + * Implementation of the 'Image Compare Slider' formatter for responsive media. + * + * @FieldFormatter( + * id = "image_compare_slider_responsive_media", + * label = @Translation("Image Compare Accessible Responsive Media Slider"), + * field_types = { + * "entity_reference" + * } + * ) + */ +class ImageCompareSliderResponsiveMediaFormatter extends ImageCompareSliderResponsiveFormatter { + + use ImageCompareSliderMediaFormatterTrait; + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + + $entities = parent::getEntitiesToView($items, $langcode); + + // Early opt-out if the field is empty. + if (empty($entities)) { + return []; + } + + $settings = $this->getSettings(); + + $cache_tags = []; + + /** @var \Drupal\responsive_image\Entity\ResponsiveImageStyle $style */ + $style = $this->getResponsiveImageStyle($settings, $cache_tags); + $fallback_style = $this->loadImageStyle($style->getFallbackImageStyle()); + $breakpoints = array_reverse($this->breakpointManager->getBreakpointsByGroup($style->getBreakpointGroup())); + + $options = $this->buildTemplateOptions($items, $cache_tags); + + $images = []; + + foreach ($entities as $media) { + $source_field_name = $media->getSource() + ->getConfiguration()['source_field']; + $image_item = $media->get($source_field_name)[0]; + // Experimental handling of media entities referencing other entities. + // See: https://www.drupal.org/project/media_entity_reference. + if (($image_item instanceof EntityReferenceItem) + && !($image_item instanceof ImageItem)) { + $target_entity = $image_item->entity; + // We only handle referenced entities of type Media. + if ($target_entity instanceof MediaInterface) { + $target_source_field_name = $target_entity->getSource() + ->getConfiguration()['source_field']; + $image_item = $target_entity->get($target_source_field_name)[0]; + } + } + // We only handle medias of type Image. + if ($image_item instanceof ImageItem) { + $image = $this->buildImageData($image_item, $media, $fallback_style); + if (!is_null($image)) { + $variables['uri'] = $image_item->entity->getFileUri(); + $variables['width'] = $image_item->width; + $variables['height'] = $image_item->height; + foreach ($style->getKeyedImageStyleMappings() as $breakpoint_id => $multipliers) { + if (isset($breakpoints[$breakpoint_id])) { + $image['sources'][] = _responsive_image_build_source_attributes($variables, $breakpoints[$breakpoint_id], $multipliers); + } + } + $images[] = $image; + $cache_tags = Cache::mergeTags( + $cache_tags, $image_item->entity->getCacheTags()); + } + + if (count($images) == 2) { + break; + } + } + } + + return $this->buildRenderArray($images, $options, $cache_tags); + } + +} diff --git a/modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml b/modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml new file mode 100644 index 0000000..17f424d --- /dev/null +++ b/modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml @@ -0,0 +1,3 @@ +field.formatter.settings.image_compare_slider_responsive: + type: field.formatter.settings.image_compare_slider + label: Image Compare Accessible Slider formatter settings for responsive images diff --git a/modules/image_compare_responsive/image_compare_responsive.info.yml b/modules/image_compare_responsive/image_compare_responsive.info.yml new file mode 100644 index 0000000..197415f --- /dev/null +++ b/modules/image_compare_responsive/image_compare_responsive.info.yml @@ -0,0 +1,8 @@ +name: Image Compare Accessible Slider for responsive image +type: module +description: Add an Image Compare Accessible Slider formatter to responsive image fields. +package: Media +core_version_requirement: ^10 || ^11 +dependencies: + - drupal:responsive_image + - image_compare:image_compare diff --git a/modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php b/modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php new file mode 100644 index 0000000..059c32a --- /dev/null +++ b/modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php @@ -0,0 +1,154 @@ +<?php + +namespace Drupal\image_compare_responsive\Plugin\Field\FieldFormatter; + +use Drupal\Core\Cache\Cache; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Url; +use Drupal\Core\Utility\Error; +use Drupal\image_compare\Plugin\Field\FieldFormatter\ImageCompareSliderFormatterTrait; +use Drupal\responsive_image\Plugin\Field\FieldFormatter\ResponsiveImageFormatter; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Implementation of the 'Image Compare Accessible Slider' responsive formatter. + * + * @FieldFormatter( + * id = "image_compare_slider_responsive", + * label = @Translation("Image Compare Accessible Slider Responsive"), + * field_types = { + * "image" + * } + * ) + */ +class ImageCompareSliderResponsiveFormatter extends ResponsiveImageFormatter { + + use ImageCompareSliderFormatterTrait; + + /** + * The file URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + protected $fileUrlGenerator; + + /** + * The breakpoint manager. + * + * @var \Drupal\breakpoint\BreakpointManagerInterface + */ + protected $breakpointManager; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->entityFieldManager = $container->get('entity_field.manager'); + $instance->loggerFactory = $container->get('logger.factory'); + $instance->fileUrlGenerator = $container->get('file_url_generator'); + $instance->breakpointManager = $container->get('breakpoint.manager'); + return $instance; + } + + /** + * {@inheritdoc} + */ + protected function addImageStyleSelect(array &$elements, array $settings) { + $responsive_image_options = []; + $responsive_image_styles = $this->responsiveImageStyleStorage->loadMultiple(); + uasort($responsive_image_styles, '\Drupal\responsive_image\Entity\ResponsiveImageStyle::sort'); + if (!empty($responsive_image_styles)) { + foreach ($responsive_image_styles as $machine_name => $responsive_image_style) { + if ($responsive_image_style->hasImageStyleMappings()) { + $responsive_image_options[$machine_name] = $responsive_image_style->label(); + } + } + } + + $elements['image_style'] = [ + '#title' => $this->t('Image style'), + '#type' => 'select', + '#default_value' => $this->getSetting('image_style'), + '#required' => TRUE, + '#options' => $responsive_image_options, + '#description' => [ + '#markup' => $this->linkGenerator->generate($this->t('Configure Responsive Image Styles'), new Url('entity.responsive_image_style.collection')), + '#access' => $this->currentUser->hasPermission('administer responsive image styles'), + ], + ]; + } + + /** + * Load ResponsiveImageStyle entity to use for display. + * + * @param array $settings + * Formatter settings. + * @param array $cache_tags + * The cache tags to update with style tags. + * + * @return \Drupal\Core\Entity\EntityInterface|null + * The ImageStyle object. + */ + protected function getResponsiveImageStyle(array $settings, array &$cache_tags) { + try { + $responsive_image_style = $this->responsiveImageStyleStorage->load($settings['image_style']); + if ($responsive_image_style) { + $cache_tags = Cache::mergeTags($cache_tags, $responsive_image_style->getCacheTags()); + } + return $responsive_image_style; + } + catch (\Exception $e) { + Error::logException($this->loggerFactory->get('image_compare'), $e); + return NULL; + } + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + + $files = parent::getEntitiesToView($items, $langcode); + + // Early opt-out if the field is empty. + if (empty($files)) { + return []; + } + + $settings = $this->getSettings(); + + $cache_tags = []; + + /** @var \Drupal\responsive_image\Entity\ResponsiveImageStyle $style */ + $style = $this->getResponsiveImageStyle($settings, $cache_tags); + $fallback_style = $this->loadImageStyle($style->getFallbackImageStyle()); + $breakpoints = array_reverse($this->breakpointManager->getBreakpointsByGroup($style->getBreakpointGroup())); + + $options = $this->buildTemplateOptions($items, $cache_tags); + + $images = []; + foreach ($files as $file) { + $item = $file->_referringItem; + $image = $this->buildImageData($item, NULL, $fallback_style); + if (!is_null($image)) { + $variables['uri'] = $item->entity->getFileUri(); + $variables['width'] = $item->width; + $variables['height'] = $item->height; + foreach ($style->getKeyedImageStyleMappings() as $breakpoint_id => $multipliers) { + if (isset($breakpoints[$breakpoint_id])) { + $image['sources'][] = _responsive_image_build_source_attributes($variables, $breakpoints[$breakpoint_id], $multipliers); + } + } + $images[] = $image; + $cache_tags = Cache::mergeTags($cache_tags, $file->getCacheTags()); + } + if (count($images) == 2) { + break; + } + } + + return $this->buildRenderArray($images, $options, $cache_tags); + } + +} diff --git a/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php index 406d809..a4241ec 100644 --- a/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php +++ b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php @@ -2,24 +2,16 @@ namespace Drupal\image_compare\Plugin\Field\FieldFormatter; -use Drupal\Component\Utility\Html; use Drupal\Core\Cache\Cache; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\File\FileUrlGeneratorInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; -use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; -use Drupal\Core\Utility\Error; -use Drupal\image\ImageStyleInterface; -use Drupal\image\Plugin\Field\FieldType\ImageItem; -use Drupal\image_compare\ImageCompareSliderUtils as ICSUtils; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -35,19 +27,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFactoryPluginInterface { - /** - * The entity field manager. - * - * @var \Drupal\Core\Entity\EntityFieldManagerInterface - */ - protected EntityFieldManagerInterface $entityFieldManager; - - /** - * The logger factory. - * - * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface - */ - protected LoggerChannelFactoryInterface $loggerFactory; + use ImageCompareSliderFormatterTrait; /** * Constructs an ImageFormatter object. @@ -106,22 +86,7 @@ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFac /** * {@inheritdoc} */ - public static function defaultSettings() { - return [ - 'image_style' => '', - 'starting_position' => '', - 'additional_options' => '', - 'caption_field' => '', - 'options_field' => '', - ] + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $settings = $this->getSettings(); - + protected function addImageStyleSelect(array &$elements, array $settings) { $image_styles = image_style_options(FALSE); $description_link = Link::fromTextAndUrl( $this->t('Configure Image Styles'), @@ -137,73 +102,6 @@ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFac '#access' => $this->currentUser->hasPermission('administer image styles'), ], ]; - - $elements['starting_position'] = [ - '#type' => 'textfield', - '#title' => $this->t('Starting position'), - '#description' => $this->t('The starting position is expressed as a percentage, ex: 70. Defaults to 50 if empty.'), - '#default_value' => $settings['starting_position'], - '#placeholder' => 'Ex: 30', - ]; - - $elements['additional_options'] = [ - '#type' => 'textarea', - '#rows' => 5, - '#title' => $this->t('Additional options'), - '#description' => $this->t('Allows to define additional options for the Image Compare Slider formatter.'), - '#default_value' => $settings['additional_options'], - '#placeholder' => 'Ex: label_text=Use the slider to control the visibility of the two images', - ]; - - $available_fields_for_options = - $this->getEntityFieldsByType(['string_long']); - $elements['options_field'] = [ - '#title' => $this->t('Options field'), - '#type' => 'select', - '#default_value' => $this->getSetting('options_field'), - '#empty_option' => $this->t('None'), - '#options' => $available_fields_for_options, - '#description' => $this->t('Specify the parent entity field that we should use to get entity specific slider configuration options.'), - ]; - - $available_fields_for_caption = - $this->getRelatedEntityFieldsByType(['string', 'string_long']); - $elements['caption_field'] = [ - '#title' => $this->t('Caption field'), - '#type' => 'select', - '#default_value' => $this->getSetting('caption_field'), - '#empty_option' => $this->t('None'), - '#options' => $available_fields_for_caption, - '#description' => $this->t('Specify the related entity field that we should use as a caption for slider images.'), - ]; - - return $elements; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $settings = $this->getSettings(); - $summary = []; - - if ($settings['image_style']) { - $summary[] = $this->t('Image style: @image_style', ['@image_style' => $settings['image_style']]); - } - - if ($settings['starting_position']) { - $summary[] = $this->t('Starting position: @starting_position', ['@starting_position' => $settings['starting_position']]); - } - - if (!empty($settings['options_field'])) { - $summary[] = $this->t('Options field: @field', ['@field' => $settings['options_field']]); - } - - if (!empty($settings['caption_field'])) { - $summary[] = $this->t('Caption field: @field', ['@field' => $settings['caption_field']]); - } - - return $summary; } /** @@ -242,295 +140,4 @@ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFac return $this->buildRenderArray($images, $options, $cache_tags); } - /** - * Build options array for rendering template. - * - * @param \Drupal\Core\Field\FieldItemListInterface $items - * List of image entities to use form rendering slider. - * @param array $cache_tags - * List of cache tags to update if needed, depending on options source. - * - * @return array - * Options for template. - */ - protected function buildTemplateOptions( - FieldItemListInterface $items, - array &$cache_tags, - ): array { - - $options = []; - - $settings = $this->getSettings(); - - $options['starting_position'] = $settings['starting_position'] ?? '50'; - - $additional_options = ICSUtils::parseKeyValueString($settings['additional_options']); - - if (!empty($settings['options_field'])) { - $parent_entity = $items->getParent()->getEntity(); - if (isset($parent_entity) - && $parent_entity->hasField($settings['options_field'])) { - $field_items = $parent_entity->get($settings['options_field']); - if (!$field_items->isEmpty()) { - foreach ($field_items as $field_item) { - $entity_specific_options = ICSUtils::parseKeyValueString($field_item->value); - $cache_tags = Cache::mergeTags($cache_tags, $parent_entity->getCacheTags()); - $options = array_merge($options, $entity_specific_options); - } - } - } - } - - return array_merge($options, $additional_options); - } - - /** - * Generate image data for the Twig template from an image item. - * - * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item - * The image item. - * @param \Drupal\Core\Entity\ContentEntityInterface|null $caption_entity - * The entity to get the image caption from. - * @param \Drupal\image\ImageStyleInterface|null $style - * The image style to use for display. - * - * @return array|null - * The image data for the Twig template or NULL if file entity missing. - */ - protected function buildImageData( - $image_item, - $caption_entity, - ?ImageStyleInterface $style, - ): array|null { - $image_entity = $image_item->entity; - if ($image_entity !== NULL) { - $url = is_null($style) - ? $this->fileUrlGenerator->generateAbsoluteString($image_entity->getFileUri()) - : $style->buildUrl($image_entity->getFileUri()); - - $alt = $image_item->get('alt')->getValue(); - - $image = []; - $image['url'] = $url; - $image['alt'] = $alt; - - $settings = $this->getSettings(); - if (!empty($settings['caption_field'])) { - $caption_field = $settings['caption_field']; - if (is_null($caption_entity)) { - $this->setCaptionFromImageItem( - $image_item, $caption_field, $image); - } - else { - if ($caption_entity->hasField($caption_field)) { - $this->setCaptionFromEntity( - $caption_entity, $caption_field, $image); - } - // Handle image alt and title cases. - else { - $this->setCaptionFromImageItem( - $image_item, $caption_field, $image); - } - } - } - - return $image; - } - else { - return NULL; - } - } - - /** - * Load ImageStyle entity to use for display. - * - * @param array $settings - * Formatter settings. - * - * @return \Drupal\image\ImageStyleInterface|null - * The ImageStyle object. - */ - protected function getImageStyle(array $settings): ?ImageStyleInterface { - try { - return $this->imageStyleStorage->load($settings['image_style']); - } - catch (\Exception $e) { - Error::logException($this->loggerFactory->get('image_compare'), $e); - return NULL; - } - } - - /** - * Builds the CIS template render array. - * - * @param array $images - * Images array to add to the Image Compare slider. - * @param array $options - * Configuration options for the Image Compare slider. - * @param array $cache_tags - * Array for cache tags. - * - * @return array - * The Image Compare Accessible Slider render array. - */ - protected function buildRenderArray( - array $images, - array $options, - array $cache_tags, - ): array { - $id = Html::getUniqueId('image_compare_slider'); - return [ - '#theme' => 'image_compare_slider', - '#id' => $id, - '#images' => $images, - '#options' => $options, - '#cache' => [ - 'tags' => $cache_tags, - ], - '#attached' => [ - 'library' => [ - 'image_compare/image_compare', - ], - ], - ]; - } - - /** - * Build a list of all text fields of the related entity. - * - * @return array - * The related entity text fields. - */ - protected function getEntityFieldsByType(array $types): array { - - $found_fields = []; - - try { - $entity_type = $this->fieldDefinition->getTargetEntityTypeId(); - $bundle = $this->fieldDefinition->getTargetBundle(); - - $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle); - - foreach ($fields as $field_name => $field_definition) { - // Check if the field type is in list of wanted types. - if (in_array($field_definition->getType(), $types)) { - $found_fields[$field_name] = $field_definition->getLabel(); - } - } - } - catch (\Exception $e) { - Error::logException($this->loggerFactory->get('image_compare'), $e); - } - - return $found_fields; - } - - /** - * Build a list of all text fields of the related entity. - * - * @return array - * The related entity text fields. - */ - protected function getRelatedEntityFieldsByType(array $types): array { - - $found_fields = []; - - $field_type = $this->fieldDefinition->getType(); - $field_settings = $this->fieldDefinition->getSettings(); - - if ($field_type === 'image') { - if ($field_settings['alt_field']) { - $found_fields['alt'] = $this->t('Image Alt'); - } - if ($field_settings['title_field']) { - $found_fields['title'] = $this->t('Image Title'); - } - } - else { - $found_fields['alt'] = $this->t('Image Alt (if available)'); - $found_fields['title'] = $this->t('Image Title (if available)'); - } - - if ($field_type === 'entity_reference') { - try { - $handler_settings = - $this->fieldDefinition->getSetting('handler_settings'); - - $entity_type = $field_settings['target_type']; - $bundles = $handler_settings['target_bundles']; - - foreach ($bundles as $bundle) { - $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle); - foreach ($fields as $field_name => $field_definition) { - // Check if the field type is in list of wanted types. - if (in_array($field_definition->getType(), $types)) { - $found_fields[$field_name] = - $field_definition->getLabel() . ' (' . $field_name . ')'; - } - } - } - } - catch (\Exception $e) { - Error::logException($this->loggerFactory->get('image_compare'), $e); - } - } - - return $found_fields; - } - - /** - * Defines slider image caption from referenced entity. - * - * @param \Drupal\Core\Entity\ContentEntityInterface $entity - * The entity to get the caption from. - * @param string $caption_field - * The caption field name. - * @param array $image - * The image array to set the caption to. - */ - protected function setCaptionFromEntity( - ContentEntityInterface $entity, - string $caption_field, - array &$image, - ): void { - try { - $caption_field = $entity->get($caption_field); - if (isset($caption_field[0])) { - $caption = $caption_field[0]->value; - if (!empty($caption)) { - $image['caption'] = $caption; - } - } - } - catch (\Exception $e) { - Error::logException($this->loggerFactory->get('image_compare'), $e); - } - } - - /** - * Defines slider image caption from image item. - * - * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item - * The entity to get the caption from. - * @param string $caption_field - * The caption field name. - * @param array $image - * The image array to set the caption to. - */ - protected function setCaptionFromImageItem( - ImageItem $image_item, - string $caption_field, - array &$image, - ): void { - try { - $caption = $image_item->get($caption_field)->getValue(); - if (!empty($caption)) { - $image['caption'] = $caption; - } - } - catch (\Exception $e) { - Error::logException($this->loggerFactory->get('image_compare'), $e); - } - } - } diff --git a/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php new file mode 100644 index 0000000..853a459 --- /dev/null +++ b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php @@ -0,0 +1,447 @@ +<?php + +namespace Drupal\image_compare\Plugin\Field\FieldFormatter; + +use Drupal\Component\Utility\Html; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Utility\Error; +use Drupal\image\ImageStyleInterface; +use Drupal\image\Plugin\Field\FieldType\ImageItem; +use Drupal\image_compare\ImageCompareSliderUtils as ICSUtils; + +/** + * Provides a trait for the Image Compare Slider field formatter. + */ +trait ImageCompareSliderFormatterTrait { + + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected EntityFieldManagerInterface $entityFieldManager; + + /** + * The logger factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return [ + 'image_style' => '', + 'starting_position' => '', + 'additional_options' => '', + 'caption_field' => '', + 'options_field' => '', + ] + parent::defaultSettings(); + } + + /** + * Adds an image style selector to the form elements. + * + * This method populates the provided form elements array with a dropdown + * allowing users to select an image style. It integrates the available image + * styles in the system and includes a link to configure image styles if the + * user has the necessary permissions. + * + * @param array &$elements + * The form elements array to modify. This array will have a new + * 'image_style' key with the configured selector. + * @param array $settings + * The current formatter settings, used to prepopulate the default selection + * for the image style. + */ + abstract protected function addImageStyleSelect(array &$elements, array $settings); + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $settings = $this->getSettings(); + + $elements = []; + + $this->addImageStyleSelect($elements, $settings); + + $elements['starting_position'] = [ + '#type' => 'textfield', + '#title' => $this->t('Starting position'), + '#description' => $this->t('The starting position is expressed as a percentage, ex: 70. Defaults to 50 if empty.'), + '#default_value' => $settings['starting_position'], + '#placeholder' => 'Ex: 30', + ]; + + $elements['additional_options'] = [ + '#type' => 'textarea', + '#rows' => 5, + '#title' => $this->t('Additional options'), + '#description' => $this->t('Allows to define additional options for the Image Compare Slider formatter.'), + '#default_value' => $settings['additional_options'], + '#placeholder' => 'Ex: label_text=Use the slider to control the visibility of the two images', + ]; + + $available_fields_for_options = + $this->getEntityFieldsByType(['string_long']); + $elements['options_field'] = [ + '#title' => $this->t('Options field'), + '#type' => 'select', + '#default_value' => $this->getSetting('options_field'), + '#empty_option' => $this->t('None'), + '#options' => $available_fields_for_options, + '#description' => $this->t('Specify the parent entity field that we should use to get entity specific slider configuration options.'), + ]; + + $available_fields_for_caption = + $this->getRelatedEntityFieldsByType(['string', 'string_long']); + $elements['caption_field'] = [ + '#title' => $this->t('Caption field'), + '#type' => 'select', + '#default_value' => $this->getSetting('caption_field'), + '#empty_option' => $this->t('None'), + '#options' => $available_fields_for_caption, + '#description' => $this->t('Specify the related entity field that we should use as a caption for slider images.'), + ]; + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $settings = $this->getSettings(); + $summary = []; + + if ($settings['image_style']) { + $summary[] = $this->t('Image style: @image_style', ['@image_style' => $settings['image_style']]); + } + + if ($settings['starting_position']) { + $summary[] = $this->t('Starting position: @starting_position', ['@starting_position' => $settings['starting_position']]); + } + + if (!empty($settings['options_field'])) { + $summary[] = $this->t('Options field: @field', ['@field' => $settings['options_field']]); + } + + if (!empty($settings['caption_field'])) { + $summary[] = $this->t('Caption field: @field', ['@field' => $settings['caption_field']]); + } + + return $summary; + } + + /** + * Build options array for rendering template. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * List of image entities to use form rendering slider. + * @param array $cache_tags + * List of cache tags to update if needed, depending on options source. + * + * @return array + * Options for template. + */ + protected function buildTemplateOptions( + FieldItemListInterface $items, + array &$cache_tags, + ): array { + + $options = []; + + $settings = $this->getSettings(); + + $options['starting_position'] = $settings['starting_position'] ?? '50'; + + $additional_options = ICSUtils::parseKeyValueString($settings['additional_options']); + + if (!empty($settings['options_field'])) { + $parent_entity = $items->getParent()->getEntity(); + if (isset($parent_entity) + && $parent_entity->hasField($settings['options_field'])) { + $field_items = $parent_entity->get($settings['options_field']); + if (!$field_items->isEmpty()) { + foreach ($field_items as $field_item) { + $entity_specific_options = ICSUtils::parseKeyValueString($field_item->value); + $cache_tags = Cache::mergeTags($cache_tags, $parent_entity->getCacheTags()); + $options = array_merge($options, $entity_specific_options); + } + } + } + } + + return array_merge($options, $additional_options); + } + + /** + * Generate image data for the Twig template from an image item. + * + * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item + * The image item. + * @param \Drupal\Core\Entity\ContentEntityInterface|null $caption_entity + * The entity to get the image caption from. + * @param \Drupal\image\ImageStyleInterface|null $style + * The image style to use for display. + * + * @return array|null + * The image data for the Twig template or NULL if file entity missing. + */ + protected function buildImageData( + $image_item, + $caption_entity, + ?ImageStyleInterface $style, + ): array|null { + $image_entity = $image_item->entity; + if ($image_entity !== NULL) { + $url = is_null($style) + ? $this->fileUrlGenerator->generateAbsoluteString($image_entity->getFileUri()) + : $style->buildUrl($image_entity->getFileUri()); + + $alt = $image_item->get('alt')->getValue(); + + $image = []; + $image['url'] = $url; + $image['alt'] = $alt; + + $settings = $this->getSettings(); + if (!empty($settings['caption_field'])) { + $caption_field = $settings['caption_field']; + if (is_null($caption_entity)) { + $this->setCaptionFromImageItem( + $image_item, $caption_field, $image); + } + else { + if ($caption_entity->hasField($caption_field)) { + $this->setCaptionFromEntity( + $caption_entity, $caption_field, $image); + } + // Handle image alt and title cases. + else { + $this->setCaptionFromImageItem( + $image_item, $caption_field, $image); + } + } + } + + return $image; + } + else { + return NULL; + } + } + + /** + * Load ImageStyle entity to use for display. + * + * @param array $settings + * Formatter settings. + * + * @return \Drupal\image\ImageStyleInterface|null + * The ImageStyle object. + */ + protected function getImageStyle(array $settings): ?ImageStyleInterface { + return $this->loadImageStyle($settings['image_style']); + } + + /** + * Load ImageStyle entity to use for display. + * + * @param string $id + * Style identifier. + * + * @return \Drupal\image\ImageStyleInterface|null + * The ImageStyle object. + */ + protected function loadImageStyle(string $id): ?ImageStyleInterface { + try { + return $this->imageStyleStorage->load($id); + } + catch (\Exception $e) { + Error::logException($this->loggerFactory->get('image_compare'), $e); + return NULL; + } + } + + /** + * Builds the CIS template render array. + * + * @param array $images + * Images array to add to the Image Compare slider. + * @param array $options + * Configuration options for the Image Compare slider. + * @param array $cache_tags + * Array for cache tags. + * + * @return array + * The Image Compare Accessible Slider render array. + */ + protected function buildRenderArray( + array $images, + array $options, + array $cache_tags, + ): array { + $id = Html::getUniqueId('image_compare_slider'); + return [ + '#theme' => 'image_compare_slider', + '#id' => $id, + '#images' => $images, + '#options' => $options, + '#cache' => [ + 'tags' => $cache_tags, + ], + '#attached' => [ + 'library' => [ + 'image_compare/image_compare', + ], + ], + ]; + } + + /** + * Build a list of all text fields of the related entity. + * + * @return array + * The related entity text fields. + */ + protected function getEntityFieldsByType(array $types): array { + + $found_fields = []; + + try { + $entity_type = $this->fieldDefinition->getTargetEntityTypeId(); + $bundle = $this->fieldDefinition->getTargetBundle(); + + $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle); + + foreach ($fields as $field_name => $field_definition) { + // Check if the field type is in list of wanted types. + if (in_array($field_definition->getType(), $types)) { + $found_fields[$field_name] = $field_definition->getLabel(); + } + } + } + catch (\Exception $e) { + Error::logException($this->loggerFactory->get('image_compare'), $e); + } + + return $found_fields; + } + + /** + * Build a list of all text fields of the related entity. + * + * @return array + * The related entity text fields. + */ + protected function getRelatedEntityFieldsByType(array $types): array { + + $found_fields = []; + + $field_type = $this->fieldDefinition->getType(); + $field_settings = $this->fieldDefinition->getSettings(); + + if ($field_type === 'image') { + if ($field_settings['alt_field']) { + $found_fields['alt'] = $this->t('Image Alt'); + } + if ($field_settings['title_field']) { + $found_fields['title'] = $this->t('Image Title'); + } + } + else { + $found_fields['alt'] = $this->t('Image Alt (if available)'); + $found_fields['title'] = $this->t('Image Title (if available)'); + } + + if ($field_type === 'entity_reference') { + try { + $handler_settings = + $this->fieldDefinition->getSetting('handler_settings'); + + $entity_type = $field_settings['target_type']; + $bundles = $handler_settings['target_bundles']; + + foreach ($bundles as $bundle) { + $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle); + foreach ($fields as $field_name => $field_definition) { + // Check if the field type is in list of wanted types. + if (in_array($field_definition->getType(), $types)) { + $found_fields[$field_name] = + $field_definition->getLabel() . ' (' . $field_name . ')'; + } + } + } + } + catch (\Exception $e) { + Error::logException($this->loggerFactory->get('image_compare'), $e); + } + } + + return $found_fields; + } + + /** + * Defines slider image caption from referenced entity. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity to get the caption from. + * @param string $caption_field + * The caption field name. + * @param array $image + * The image array to set the caption to. + */ + protected function setCaptionFromEntity( + ContentEntityInterface $entity, + string $caption_field, + array &$image, + ): void { + try { + $caption_field = $entity->get($caption_field); + if (isset($caption_field[0])) { + $caption = $caption_field[0]->value; + if (!empty($caption)) { + $image['caption'] = $caption; + } + } + } + catch (\Exception $e) { + Error::logException($this->loggerFactory->get('image_compare'), $e); + } + } + + /** + * Defines slider image caption from image item. + * + * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item + * The entity to get the caption from. + * @param string $caption_field + * The caption field name. + * @param array $image + * The image array to set the caption to. + */ + protected function setCaptionFromImageItem( + ImageItem $image_item, + string $caption_field, + array &$image, + ): void { + try { + $caption = $image_item->get($caption_field)->getValue(); + if (!empty($caption)) { + $image['caption'] = $caption; + } + } + catch (\Exception $e) { + Error::logException($this->loggerFactory->get('image_compare'), $e); + } + } + +} diff --git a/templates/image-compare-slider.html.twig b/templates/image-compare-slider.html.twig index 09311a3..dfb15b5 100644 --- a/templates/image-compare-slider.html.twig +++ b/templates/image-compare-slider.html.twig @@ -1,28 +1,42 @@ -<image-compare label-text="{{ options.label_text }}" id="{{ id }}"{% - if options.starting_position|length %} start="{{ options.starting_position }}"{% endif %}{% - if options.step|length %} step="{{ options.step }}"{% endif %}{% - if options.keyboard_step|length %} keyboard_step="{{ options.keyboard_step }}"{% endif %}> +<image-compare label-text="{{ options.label_text }}" + id="{{ id }}"{% if options.starting_position|length %} start="{{ options.starting_position }}"{% endif %}{% if options.step|length %} step="{{ options.step }}"{% endif %}{% if options.keyboard_step|length %} keyboard_step="{{ options.keyboard_step }}"{% endif %}> {% if images|length >= 2 %} {% if options.show_captions == 'false' %} - <img slot="image-1" src="{{ images[0].url }}" alt="{{ images[0].alt }}"> - <img slot="image-2" src="{{ images[1].url }}" alt="{{ images[1].alt }}"> + <picture slot="image-1"> + {% for source in images[0].sources %} + <source{{ source }}/> + {% endfor %} + <img src="{{ images[0].url }}" alt="{{ images[0].alt }}"> + </picture> + <picture slot="image-2"> + {% for source in images[1].sources %} + <source{{ source }}/> + {% endfor %} + <img src="{{ images[1].url }}" alt="{{ images[1].alt }}"> + </picture> {% else %} - {% if images[0].caption|length %} - <figure slot="image-1"> + <figure slot="image-1"> + <picture> + {% for source in images[0].sources %} + <source{{ source }}/> + {% endfor %} <img src="{{ images[0].url }}" alt="{{ images[0].alt }}"> + </picture> + {% if images[0].caption|length %} <figcaption class="image-label">{{ images[0].caption }}</figcaption> - </figure> - {% else %} - <img slot="image-1" src="{{ images[0].url }}" alt="{{ images[0].alt }}"> - {% endif %} - {% if images[1].caption|length %} - <figure slot="image-2"> + {% endif %} + </figure> + <figure slot="image-2"> + <picture> + {% for source in images[1].sources %} + <source{{ source }}/> + {% endfor %} <img src="{{ images[1].url }}" alt="{{ images[1].alt }}"> + </picture> + {% if images[1].caption|length %} <figcaption class="image-label">{{ images[1].caption }}</figcaption> - </figure> - {% else %} - <img slot="image-2" src="{{ images[1].url }}" alt="{{ images[1].alt }}"> - {% endif %} + {% endif %} + </figure> {% endif %} {% endif %} </image-compare> -- GitLab