diff --git a/composer.json b/composer.json
index 13cec5bfc..597033285 100644
--- a/composer.json
+++ b/composer.json
@@ -51,7 +51,7 @@
"openeuropa/oe_corporate_countries": "~2.0",
"openeuropa/oe_media": "~1.12",
"openeuropa/oe_multilingual": "~1.8",
- "openeuropa/oe_paragraphs": "~1.10",
+ "openeuropa/oe_paragraphs": "dev-EWPP-1862",
"openeuropa/oe_search": "1.x-dev",
"openeuropa/oe_webtools": "~1.12",
"openeuropa/oe_contact_forms": "~1.1",
diff --git a/oe_theme.theme b/oe_theme.theme
index 80834ad29..817c2e6e5 100644
--- a/oe_theme.theme
+++ b/oe_theme.theme
@@ -10,11 +10,13 @@ declare(strict_types = 1);
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
+use Drupal\Core\Session\AccountInterface;
use Drupal\media\MediaInterface;
use Drupal\media\Plugin\media\Source\Image;
use Drupal\media\Plugin\media\Source\OEmbed;
@@ -1711,3 +1713,153 @@ function oe_theme_preprocess_oe_theme_helper_site_navigation(&$variables) {
unset($variables['site_name']);
}
}
+
+/**
+ * Process common fields of illustration list paragraphs.
+ *
+ * @param array $variables
+ * Render array.
+ */
+function _oe_theme_preprocess_paragraph_oe_illustration_list(array &$variables) {
+ $paragraph = $variables['paragraph'];
+ if (!$paragraph->get('field_oe_title')->isEmpty()) {
+ $variables['title'] = $paragraph->get('field_oe_title')->value;
+ }
+ $variables['zebra'] = (bool) $paragraph->get('field_oe_illustration_alternate')->value;
+ $variables['column'] = $paragraph->get('field_oe_illustration_columns')->value;
+
+ // First part of the variant. Second part should be added in the preprocess
+ // of the specific paragraph.
+ $variant_value = $paragraph->get('oe_paragraphs_variant')->first()->value;
+ $variables['variant'] = $variant_value === 'oe_illustration_vertical' ? 'vertical' : 'horizontal';
+}
+
+/**
+ * Validate and prepare media entity to be processed in theme preprocesses.
+ *
+ * @param mixed $media
+ * MediaInterface entity will be processed only.
+ * @param \Drupal\Core\Session\AccountInterface $user
+ * Active user.
+ * @param \Drupal\Core\Cache\CacheableDependencyInterface $cacheability
+ * Object to collect cacheable dependencies.
+ * @param string $language_id
+ * Language of the Media to return.
+ *
+ * @return \Drupal\media\MediaInterface|null
+ * Media entity or null if media isn't accessible.
+ */
+function _oe_theme_prepare_media($media, AccountInterface $user, CacheableDependencyInterface $cacheability, string $language_id): ?MediaInterface {
+ if (!$media instanceof MediaInterface) {
+ // The media entity is not available.
+ return NULL;
+ }
+ $cacheability->addCacheableDependency($media);
+
+ // Run access checks on the media entity.
+ $access = $media->access('view', $user, TRUE);
+ $cacheability->addCacheableDependency($access);
+ if (!$access->isAllowed()) {
+ // Media isn't accessible.
+ return NULL;
+ }
+ // Retrieve the correct media translation.
+ return \Drupal::service('entity.repository')->getTranslationFromContext($media, $language_id);
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function oe_theme_preprocess_paragraph__oe_illustration_list_flags(array &$variables): void {
+ _oe_theme_preprocess_paragraph_oe_illustration_list($variables);
+
+ $variables['variant'] .= '_icons';
+ $variables['items'] = [];
+
+ $paragraph = $variables['paragraph'];
+ $ratio = $paragraph->get('field_oe_illustration_ratio')->first()->value;
+
+ /** @var \Drupal\paragraphs\Entity\Paragraph $sub_paragraph */
+ foreach ($paragraph->get('field_oe_paragraphs')->referencedEntities() as $sub_paragraph) {
+ // Get sub-paragraph translation.
+ $sub_paragraph = \Drupal::service('entity.repository')
+ ->getTranslationFromContext($sub_paragraph, $paragraph->language()->getId());
+ $description_list = $sub_paragraph->get('field_oe_illustration_text');
+ $icon = $sub_paragraph->get('field_oe_flag')->value;
+ $icon .= $ratio === 'square' ? '-square' : '';
+ $variables['items'][] = [
+ 'title' => $description_list->term,
+ 'description' => $description_list->description,
+ 'icon' => $icon,
+ ];
+ }
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function oe_theme_preprocess_paragraph__oe_illustration_list_icons(array &$variables): void {
+ _oe_theme_preprocess_paragraph_oe_illustration_list($variables);
+
+ $variables['variant'] .= '_icons';
+ $variables['items'] = [];
+
+ $paragraph = $variables['paragraph'];
+ /** @var \Drupal\paragraphs\Entity\Paragraph $sub_paragraph */
+ foreach ($paragraph->get('field_oe_paragraphs')->referencedEntities() as $sub_paragraph) {
+ // Get sub-paragraph translation.
+ $sub_paragraph = \Drupal::service('entity.repository')
+ ->getTranslationFromContext($sub_paragraph, $paragraph->language()->getId());
+ $description_list = $sub_paragraph->get('field_oe_illustration_text');
+ $variables['items'][] = [
+ 'title' => $description_list->term,
+ 'description' => $description_list->description,
+ 'icon' => $sub_paragraph->get('field_oe_icon')->value,
+ ];
+ }
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function oe_theme_preprocess_paragraph__oe_illustration_list_images(array &$variables): void {
+ _oe_theme_preprocess_paragraph_oe_illustration_list($variables);
+
+ $paragraph = $variables['paragraph'];
+ $ratio = $paragraph->get('field_oe_illustration_ratio')->first()->value;
+ $variables['variant'] = $variables['variant'] . '_images_' . $ratio;
+
+ $variables['items'] = [];
+
+ $entity_repository = \Drupal::service('entity.repository');
+ $cacheability = CacheableMetadata::createFromRenderArray($variables);
+ /** @var \Drupal\paragraphs\Entity\Paragraph $sub_paragraph */
+ foreach ($paragraph->get('field_oe_paragraphs')->referencedEntities() as $sub_paragraph) {
+ // Get sub-paragraph translation.
+ $language_id = $paragraph->language()->getId();
+ $sub_paragraph = $entity_repository->getTranslationFromContext($sub_paragraph, $language_id);
+ // Get the image from attached media.
+ $image = [];
+ $media = _oe_theme_prepare_media($sub_paragraph->get('field_oe_media')->entity, $variables['user'], $cacheability, $language_id);
+ if ($media) {
+ $source = $media->getSource();
+ if ($source instanceof Image && ($file_entity = $media->get('oe_media_image')->entity)) {
+ $uri = $file_entity->getFileUri();
+ $cacheability->addCacheableDependency($file_entity);
+ $values = [
+ 'src' => file_create_url($uri),
+ 'alt' => $source->getMetadata($media, 'thumbnail_alt_value') ?? $media->label(),
+ ];
+ $image = ImageValueObject::fromArray($values);
+ }
+ }
+
+ $description_list = $sub_paragraph->get('field_oe_illustration_text');
+ $variables['items'][] = [
+ 'title' => $description_list->term,
+ 'description' => $description_list->description,
+ 'image' => $image,
+ ];
+ }
+ $cacheability->applyTo($variables);
+}
diff --git a/templates/paragraphs/paragraph--oe-illustration-list-flags.html.twig b/templates/paragraphs/paragraph--oe-illustration-list-flags.html.twig
new file mode 100644
index 000000000..75ed620ac
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-illustration-list-flags.html.twig
@@ -0,0 +1,20 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Illustration list with flags' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if title is not empty %}
+
+ {{ title }}
+
+{% endif %}
+
+{{ pattern('list_with_illustration', {
+ 'variant': variant,
+ 'column': column,
+ 'zebra': zebra,
+ 'items': items,
+}) }}
diff --git a/templates/paragraphs/paragraph--oe-illustration-list-icons.html.twig b/templates/paragraphs/paragraph--oe-illustration-list-icons.html.twig
new file mode 100644
index 000000000..00c739927
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-illustration-list-icons.html.twig
@@ -0,0 +1,20 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Illustration list with icons' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if title is not empty %}
+
+ {{ title }}
+
+{% endif %}
+
+{{ pattern('list_with_illustration', {
+ 'variant': variant,
+ 'column': column,
+ 'zebra': zebra,
+ 'items': items,
+}) }}
diff --git a/templates/paragraphs/paragraph--oe-illustration-list-images.html.twig b/templates/paragraphs/paragraph--oe-illustration-list-images.html.twig
new file mode 100644
index 000000000..7bd049e79
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-illustration-list-images.html.twig
@@ -0,0 +1,20 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Illustration list with images' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if title is not empty %}
+
+ {{ title }}
+
+{% endif %}
+
+{{ pattern('list_with_illustration', {
+ 'variant': variant,
+ 'column': column,
+ 'zebra': zebra,
+ 'items': items,
+}) }}
diff --git a/tests/src/Kernel/Paragraphs/IllustrationListsParagraphsTest.php b/tests/src/Kernel/Paragraphs/IllustrationListsParagraphsTest.php
new file mode 100644
index 000000000..41e0875b0
--- /dev/null
+++ b/tests/src/Kernel/Paragraphs/IllustrationListsParagraphsTest.php
@@ -0,0 +1,438 @@
+installEntitySchema('media');
+
+ module_load_include('install', 'media');
+ media_install();
+ $this->container->get('module_handler')->loadInclude('oe_paragraphs_media_field_storage', 'install');
+ oe_paragraphs_media_field_storage_install(FALSE);
+
+ $this->installConfig([
+ 'media',
+ 'options',
+ 'oe_media',
+ 'oe_paragraphs_illustrations_lists',
+ ]);
+ }
+
+ /**
+ * Tests the rendering of the "Illustration list with flags" paragraph.
+ */
+ public function testIllustrationListFlagsRendering(): void {
+ // Create multiple paragraphs to be referenced in the illustration list.
+ $items = [];
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_illustration_text' => [
+ [
+ 'term' => 'Term 1',
+ 'description' => 'Description 1',
+ ],
+ ],
+ 'field_oe_flag' => 'austria',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_illustration_text' => [
+ [
+ 'term' => 'Term 2',
+ ],
+ ],
+ 'field_oe_flag' => 'belgium',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_illustration_text' => [
+ [
+ 'description' => 'Description 3',
+ ],
+ ],
+ 'field_oe_flag' => 'france',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_illustration_text' => [],
+ 'field_oe_flag' => 'finland',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $list_paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_list_flags',
+ 'oe_paragraphs_variant' => 'default',
+ 'field_oe_title' => 'Illustration with flags test',
+ 'field_oe_paragraphs' => $items,
+ 'field_oe_illustration_columns' => 2,
+ 'field_oe_illustration_ratio' => 'landscape',
+ ]);
+ $html = $this->renderParagraph($list_paragraph);
+
+ // Assert paragraph header.
+ $crawler = new Crawler($html);
+ $heading = $crawler->filter('h2.ecl-u-type-heading-2');
+ $this->assertCount(1, $heading);
+ $this->assertEquals('Illustration with flags test', trim($heading->text()));
+
+ // Assert rendered items.
+ $expected_values = [
+ 'column' => 2,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'icon' => 'austria',
+ ], [
+ 'title' => 'Term 2',
+ 'icon' => 'belgium',
+ ], [
+ 'description' => 'Description 3',
+ 'icon' => 'france',
+ ], [
+ 'icon' => 'finland',
+ ],
+ ],
+ ];
+ $assert = new ListWithIllustrationAssert();
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('horizontal_icons', $html);
+
+ // Assert number of columns and ratio.
+ $list_paragraph->set('field_oe_illustration_columns', 4);
+ $list_paragraph->set('field_oe_illustration_ratio', 'square')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values = [
+ 'column' => 4,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'icon' => 'austria-square',
+ ], [
+ 'title' => 'Term 2',
+ 'icon' => 'belgium-square',
+ ], [
+ 'description' => 'Description 3',
+ 'icon' => 'france-square',
+ ], [
+ 'icon' => 'finland-square',
+ ],
+ ],
+ ];
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant.
+ $list_paragraph->set('oe_paragraphs_variant', 'oe_illustration_vertical')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ unset($expected_values['column']);
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('vertical_icons', $html);
+
+ // Assert vertical variant with zebra.
+ $list_paragraph->set('field_oe_illustration_alternate', TRUE)->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['zebra'] = TRUE;
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('vertical_icons', $html);
+ }
+
+ /**
+ * Tests the rendering of the "Illustration list with icons" paragraph.
+ */
+ public function testIllustrationListIconsRendering(): void {
+ $items = [];
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_illustration_text' => [
+ [
+ 'term' => 'Term 1',
+ 'description' => 'Description 1',
+ ],
+ ],
+ 'field_oe_icon' => 'data',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_illustration_text' => [
+ [
+ 'term' => 'Term 2',
+ ],
+ ],
+ 'field_oe_icon' => 'facebook',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_illustration_text' => [
+ [
+ 'description' => 'Description 3',
+ ],
+ ],
+ 'field_oe_icon' => 'global',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_illustration_text' => [],
+ 'field_oe_icon' => 'package',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $list_paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_list_icons',
+ 'oe_paragraphs_variant' => 'default',
+ 'field_oe_title' => 'Illustration with icons test',
+ 'field_oe_paragraphs' => $items,
+ 'field_oe_illustration_columns' => 2,
+ ]);
+ $html = $this->renderParagraph($list_paragraph);
+
+ // Assert paragraph header.
+ $crawler = new Crawler($html);
+ $heading = $crawler->filter('h2.ecl-u-type-heading-2');
+ $this->assertCount(1, $heading);
+ $this->assertEquals('Illustration with icons test', trim($heading->text()));
+
+ // Assert rendered items.
+ $expected_values = [
+ 'column' => 2,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'icon' => 'data',
+ ], [
+ 'title' => 'Term 2',
+ 'icon' => 'facebook',
+ ], [
+ 'description' => 'Description 3',
+ 'icon' => 'global',
+ ], [
+ 'icon' => 'package',
+ ],
+ ],
+ ];
+ $assert = new ListWithIllustrationAssert();
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('horizontal_icons', $html);
+
+ // Assert number of columns.
+ $list_paragraph->set('field_oe_illustration_columns', 3)->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['column'] = 3;
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant.
+ $list_paragraph->set('oe_paragraphs_variant', 'oe_illustration_vertical')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ unset($expected_values['column']);
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('vertical_icons', $html);
+
+ // Assert vertical variant with zebra.
+ $list_paragraph->set('field_oe_illustration_alternate', TRUE)->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['zebra'] = TRUE;
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('vertical_icons', $html);
+ }
+
+ /**
+ * Tests the rendering of the "Illustration list with icons" paragraph.
+ */
+ public function testIllustrationListImagesRendering(): void {
+ // Create media image.
+ $file = file_save_data(file_get_contents(drupal_get_path('theme', 'oe_theme') . '/tests/fixtures/example_1.jpeg'), 'public://example_1.jpeg');
+ $file->setPermanent();
+ $file->save();
+
+ $media = Media::create([
+ 'bundle' => 'image',
+ 'name' => 'test image',
+ 'oe_media_image' => [
+ 'target_id' => $file->id(),
+ 'alt' => 'Alt',
+ ],
+ ]);
+ $media->save();
+
+ // Create Illustration list with images paragraph.
+ $items = [];
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_illustration_text' => [
+ [
+ 'term' => 'Term 1',
+ 'description' => 'Description 1',
+ ],
+ ],
+ 'field_oe_media' => [$media],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_illustration_text' => [
+ [
+ 'term' => 'Term 2',
+ ],
+ ],
+ 'field_oe_media' => [$media],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_illustration_text' => [
+ [
+ 'description' => 'Description 3',
+ ],
+ ],
+ 'field_oe_media' => [$media],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_illustration_text' => [],
+ 'field_oe_media' => [$media],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $list_paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_list_images',
+ 'oe_paragraphs_variant' => 'default',
+ 'field_oe_title' => 'Illustration with images test',
+ 'field_oe_paragraphs' => $items,
+ 'field_oe_illustration_columns' => 2,
+ 'field_oe_illustration_ratio' => 'landscape',
+ ]);
+ $html = $this->renderParagraph($list_paragraph);
+
+ // Assert paragraph header.
+ $crawler = new Crawler($html);
+ $heading = $crawler->filter('h2.ecl-u-type-heading-2');
+ $this->assertCount(1, $heading);
+ $this->assertEquals('Illustration with images test', trim($heading->text()));
+
+ // Assert rendered items.
+ $expected_values = [
+ 'column' => 2,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ], [
+ 'title' => 'Term 2',
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ], [
+ 'description' => 'Description 3',
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ], [
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ],
+ ],
+ ];
+ $assert = new ListWithIllustrationAssert();
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('horizontal_images_landscape', $html);
+
+ // Assert number of columns and ratio.
+ $list_paragraph->set('field_oe_illustration_columns', 3);
+ $list_paragraph->set('field_oe_illustration_ratio', 'square')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['column'] = 3;
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('horizontal_images_square', $html);
+
+ // Assert vertical variant.
+ $list_paragraph->set('oe_paragraphs_variant', 'oe_illustration_vertical')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ unset($expected_values['column']);
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('vertical_images_square', $html);
+
+ // Assert vertical variant with zebra and ratio.
+ $list_paragraph->set('field_oe_illustration_alternate', TRUE);
+ $list_paragraph->set('field_oe_illustration_ratio', 'landscape')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['zebra'] = TRUE;
+ $assert->assertPattern($expected_values, $html);
+ $assert->assertVariant('vertical_images_landscape', $html);
+ }
+
+}