diff --git a/composer.json b/composer.json
index 6239409883..73e7a4f102 100644
--- a/composer.json
+++ b/composer.json
@@ -57,7 +57,7 @@
"openeuropa/oe_corporate_countries": "^2.0.0-alpha6",
"openeuropa/oe_media": "^1.22",
"openeuropa/oe_multilingual": "dev-master",
- "openeuropa/oe_paragraphs": "^1.17",
+ "openeuropa/oe_paragraphs": "dev-EWPP-3295",
"openeuropa/oe_search": "^1.9",
"openeuropa/oe_webtools": "^1.21",
"openeuropa/rdf_skos": "^1.0.0-alpha9",
diff --git a/oe_theme.theme b/oe_theme.theme
index 4f826b63dc..4de0163653 100644
--- a/oe_theme.theme
+++ b/oe_theme.theme
@@ -2204,3 +2204,88 @@ function oe_theme_preprocess_oe_theme_helper_in_page_navigation__node__oe_person
}
}
}
+
+/**
+ * Implements hook_preprocess_paragraph() for paragraph--oe-av-media.html.twig.
+ */
+function oe_theme_preprocess_paragraph__oe_av_media(array &$variables): void {
+ /** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */
+ $paragraph = $variables['paragraph'];
+
+ // Bail out if there is no media present.
+ if ($paragraph->get('field_oe_media')->isEmpty()) {
+ return;
+ }
+
+ $cacheability = CacheableMetadata::createFromRenderArray($variables);
+
+ /** @var \Drupal\media\Entity\Media $media */
+ $media = $paragraph->get('field_oe_media')->entity;
+ if (!$media instanceof MediaInterface) {
+ // The media entity is not available anymore, bail out.
+ return;
+ }
+
+ // Retrieve the correct media translation.
+ $media = \Drupal::service('entity.repository')->getTranslationFromContext($media, $paragraph->language()->getId());
+
+ // Caches are handled by the formatter usually. Since we are not rendering
+ // the original render arrays, we need to propagate our caches to the
+ // paragraph template.
+ $cacheability->addCacheableDependency($media);
+
+ // Run access checks on the media entity.
+ $access = $media->access('view', $variables['user'], TRUE);
+ $cacheability->addCacheableDependency($access);
+ $cacheability->applyTo($variables);
+ if (!$access->isAllowed()) {
+ $cacheability->applyTo($variables);
+ return;
+ }
+
+ // Get the media source.
+ $source = $media->getSource();
+
+ if ($source instanceof MediaAvPortalVideoSource || $source instanceof OEmbed || $source instanceof Iframe) {
+ if ($source instanceof MediaAvPortalVideoSource) {
+ // Default video aspect ratio is set to 16:9 for AV Portal Video.
+ $variables['ratio'] = '16:9';
+ }
+
+ // Load information about the media and the display.
+ $media_type = \Drupal::entityTypeManager()->getStorage('media_type')->load($media->bundle());
+ $cacheability->addCacheableDependency($media_type);
+ $source_field = $source->getSourceFieldDefinition($media_type);
+ $display = EntityViewDisplay::collectRenderDisplay($media, 'oe_theme_main_content');
+ $cacheability->addCacheableDependency($display);
+ $display_options = $display->getComponent($source_field->getName());
+
+ // If it is an OEmbed resource, render it and pass it as embeddable data
+ // only if it is of type video or html.
+ if ($source instanceof OEmbed) {
+ $oembed_type = $source->getMetadata($media, 'type');
+ if (in_array($oembed_type, ['video', 'html'])) {
+ $variables['video'] = $media->{$source_field->getName()}->view($display_options);
+ $cacheability->applyTo($variables);
+ return;
+ }
+ }
+
+ // If it's an AV Portal video or a Video iframe, render it.
+ $variables['video'] = $media->{$source_field->getName()}->view($display_options);
+ $cacheability->applyTo($variables);
+
+ // When dealing with iframe videos, also respect its given aspect ratio.
+ if ($media->bundle() === 'video_iframe') {
+ $ratio = $media->get('oe_media_iframe_ratio')->value;
+ $variables['ratio'] = str_replace('_', '-', $ratio);
+ }
+ return;
+ }
+
+ // If it's an image media, render it and assign it to the image variable.
+ if ($source instanceof MediaAvPortalPhotoSource || $source instanceof Image) {
+ $thumbnail = $media->get('thumbnail')->first();
+ $variables['image'] = ImageValueObject::fromStyledImageItem($thumbnail, 'oe_theme_medium_no_crop');
+ }
+}
diff --git a/templates/paragraphs/paragraph--oe-av-media.html.twig b/templates/paragraphs/paragraph--oe-av-media.html.twig
new file mode 100644
index 0000000000..d20effcb40
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-av-media.html.twig
@@ -0,0 +1,22 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Media' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if image %}
+ {% set media_container = {
+ 'image': image.src,
+ 'alt': image.alt,
+ } %}
+{% elseif video %}
+ {% set media_container = {
+ 'embedded_media': video,
+ 'ratio': ratio,
+ } %}
+{% endif %}
+{% if media_container %}
+ {% include '@ecl/media-container/media-container.html.twig' with media_container only %}
+{% endif %}
diff --git a/tests/src/Kernel/Paragraphs/MediaParagraphsTest.php b/tests/src/Kernel/Paragraphs/MediaParagraphsTest.php
index 3cfb90e6ee..5c4036a5c5 100644
--- a/tests/src/Kernel/Paragraphs/MediaParagraphsTest.php
+++ b/tests/src/Kernel/Paragraphs/MediaParagraphsTest.php
@@ -39,6 +39,7 @@ class MediaParagraphsTest extends ParagraphsTestBase {
'file_link',
'oe_paragraphs_carousel',
'composite_reference',
+ 'oe_paragraphs_av_media',
];
/**
@@ -61,6 +62,7 @@ protected function setUp(): void {
'options',
'oe_media_iframe',
'oe_paragraphs_carousel',
+ 'oe_paragraphs_av_media',
]);
// Call the install hook of the Media module.
module_load_include('install', 'media');
@@ -1089,4 +1091,168 @@ public function testCarousel(): void {
$assert->assertPattern($expected_values, $html);
}
+ /**
+ * Test Media paragraph rendering.
+ */
+ public function testMediaParagraph(): void {
+ // Set image media translatable.
+ $this->container->get('content_translation.manager')->setEnabled('media', 'image', TRUE);
+ // Make the image field translatable.
+ $field_config = $this->container->get('entity_type.manager')->getStorage('field_config')->load('media.image.oe_media_image');
+ $field_config->set('translatable', TRUE)->save();
+ $this->container->get('router.builder')->rebuild();
+
+ // Create English file.
+ $en_file = file_save_data(file_get_contents(drupal_get_path('theme', 'oe_theme') . '/tests/fixtures/example_1.jpeg'), 'public://example_1_en.jpeg');
+ $en_file->setPermanent();
+ $en_file->save();
+
+ // Create Bulgarian file.
+ $bg_file = file_save_data(file_get_contents(drupal_get_path('theme', 'oe_theme') . '/tests/fixtures/example_1.jpeg'), 'public://example_1_bg.jpeg');
+ $bg_file->setPermanent();
+ $bg_file->save();
+
+ // Create a media.
+ $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
+ $media = $media_storage->create([
+ 'bundle' => 'image',
+ 'name' => 'test image en',
+ 'oe_media_image' => [
+ 'target_id' => $en_file->id(),
+ 'alt' => 'Alt en',
+ ],
+ ]);
+ $media->save();
+ // Translate the media to Bulgarian.
+ $media_bg = $media->addTranslation('bg', [
+ 'name' => 'test image bg',
+ 'oe_media_image' => [
+ 'target_id' => $bg_file->id(),
+ 'alt' => 'Alt bg',
+ ],
+ ]);
+ $media_bg->save();
+
+ // Create a Media paragraph.
+ $paragraph = $this->container
+ ->get('entity_type.manager')
+ ->getStorage('paragraph')->create([
+ 'type' => 'oe_av_media',
+ 'field_oe_media' => [
+ 'target_id' => $media->id(),
+ ],
+ ]);
+ $paragraph->save();
+
+ // Add Bulgarian translation.
+ $paragraph->addTranslation('bg', $paragraph->toArray())->save();
+
+ // Test the translated media is rendered with the translated paragraph.
+ $html = $this->renderParagraph($paragraph);
+ $crawler = new Crawler($html);
+
+ $this->assertCount(1, $crawler->filter('figure.ecl-media-container img.ecl-media-container__media'));
+ $image_element = $crawler->filter('figure.ecl-media-container img.ecl-media-container__media');
+ $this->assertStringContainsString('example_1_en.jpeg', $image_element->attr('src'));
+
+ // Assert bulgarian rendering.
+ $html = $this->renderParagraph($paragraph, 'bg');
+ $crawler = new Crawler($html);
+ $this->assertCount(1, $crawler->filter('figure.ecl-media-container img.ecl-media-container__media'));
+ $image_element = $crawler->filter('figure.ecl-media-container img.ecl-media-container__media');
+ $this->assertStringContainsString('example_1_bg.jpeg', $image_element->attr('src'));
+
+ // Unpublish the media and assert it is not rendered anymore.
+ $media->set('status', 0);
+ $media->save();
+
+ // Since static cache is not cleared due to lack of requests in the test we
+ // need to reset manually.
+ $this->container->get('entity_type.manager')->getAccessControlHandler('media')->resetCache();
+
+ $html = $this->renderParagraph($paragraph);
+ $crawler = new Crawler($html);
+ $this->assertCount(0, $crawler->filter('figure.ecl-media-container'));
+
+ // Create a remote video and add it to the paragraph.
+ $media = $media_storage->create([
+ 'bundle' => 'remote_video',
+ 'oe_media_oembed_video' => [
+ 'value' => 'https://www.youtube.com/watch?v=1-g73ty9v04',
+ ],
+ ]);
+ $media->save();
+ $paragraph->set('field_oe_media', ['target_id' => $media->id()]);
+ $paragraph->save();
+
+ $html = $this->renderParagraph($paragraph);
+ $crawler = new Crawler($html);
+ // Assert remote video is rendered properly.
+ $this->assertCount(1, $crawler->filter('figure.ecl-media-container div.ecl-media-container__media'));
+ $media_container = $crawler->filter('div.ecl-media-container__media');
+ $existing_classes = $media_container->attr('class');
+ $existing_classes = explode(' ', $existing_classes);
+ $this->assertNotContains('ecl-media-container__media--ratio-16-9', $existing_classes);
+ $video_iframe = $media_container->filter('iframe');
+ $partial_iframe_url = Url::fromRoute('media.oembed_iframe', [], [
+ 'query' => [
+ 'url' => 'https://www.youtube.com/watch?v=1-g73ty9v04',
+ ],
+ ])->toString();
+ $this->assertStringContainsString($partial_iframe_url, $video_iframe->attr('src'));
+ $this->assertStringContainsString('459', $video_iframe->attr('width'));
+ $this->assertStringContainsString('344', $video_iframe->attr('height'));
+
+ // Create an avportal video and add it to the paragraph.
+ $media = $media_storage->create([
+ 'bundle' => 'av_portal_video',
+ 'oe_media_avportal_video' => 'I-163162',
+ ]);
+ $media->save();
+ $paragraph->set('field_oe_media', ['target_id' => $media->id()]);
+ $paragraph->save();
+
+ // Assert AV Portal video is rendered properly.
+ $html = $this->renderParagraph($paragraph);
+ $crawler = new Crawler($html);
+ $this->assertCount(1, $crawler->filter('figure.ecl-media-container div.ecl-media-container__media'));
+ $media_container = $crawler->filter('div.ecl-media-container__media');
+ $this->assertEquals('', $media_container->html());
+
+ // Create Video iframe with ratio 16:9 and add it to the paragraph.
+ $media = $media_storage->create([
+ 'bundle' => 'video_iframe',
+ 'oe_media_iframe' => '',
+ 'oe_media_iframe_ratio' => '16_9',
+ ]);
+ $media->save();
+ $paragraph->set('field_oe_media', ['target_id' => $media->id()]);
+ $paragraph->save();
+
+ $html = $this->renderParagraph($paragraph);
+ $crawler = new Crawler($html);
+ $this->assertCount(1, $crawler->filter('figure.ecl-media-container div.ecl-media-container__media'));
+ $media_container = $crawler->filter('div.ecl-media-container__media');
+ $this->assertEquals('', $media_container->html());
+
+ // Create iframe video with aspect ratio 1:1 and add it to the paragraph.
+ $media = $media_storage->create([
+ 'bundle' => 'video_iframe',
+ 'oe_media_iframe' => '',
+ 'oe_media_iframe_ratio' => '1_1',
+ ]);
+ $media->save();
+ $paragraph->set('field_oe_media', ['target_id' => $media->id()]);
+ $paragraph->save();
+
+ $html = $this->renderParagraph($paragraph);
+ $crawler = new Crawler($html);
+ $this->assertCount(1, $crawler->filter('figure.ecl-media-container div.ecl-media-container__media'));
+ $media_container = $crawler->filter('div.ecl-media-container__media');
+ $this->assertEquals('', $media_container->html());
+ $existing_classes = $media_container->attr('class');
+ $existing_classes = explode(' ', $existing_classes);
+ $this->assertTrue(in_array('ecl-media-container__media--ratio-1-1', $existing_classes));
+ }
+
}