diff --git a/docs/developer-documentation.md b/docs/developer-documentation.md index 53967dda8..fcad95e34 100644 --- a/docs/developer-documentation.md +++ b/docs/developer-documentation.md @@ -172,6 +172,25 @@ Provides the following factory methods: - `ImageValueObject::fromImageItem(ImageItem $image_item)`: accepts a \Drupal\image\Plugin\Field\FieldType\ImageItem object. +#### `MediaValueObject` + +Used in the following patterns: + +- [`media_container`](../templates/patterns/media_container/media_container.ui_patterns.yml) + +Provides the following factory methods: + +- `MediaValueObject::fromArray(array $values = [])`: accepts an array with the following properties: + - `video`: Embed media HTML code. + - `ratio`: Ratio of the video. + - `sources`: Media resources for media element. + - `tracks`: Text tracks for video elements. + - `image`: ImageValueObject media element. +- `MediaValueObject::fromMediaObject(Media $media, string $image_style = '', string $view_mode = '')`: accepts these values: + - `media`: \Drupal\media\Entity\Media media element. + - `image_style`: Image style. + - `view_mode`: Video display view mode. + [1]: https://www.drupal.org/project/ui_patterns [2]: https://ui-patterns.readthedocs.io [3]: https://ui-patterns.readthedocs.io/en/8.x-1.x/content/developer-documentation.html#working-with-pattern-suggestions diff --git a/modules/oe_theme_content_event/src/Plugin/ExtraField/Display/DescriptionExtraField.php b/modules/oe_theme_content_event/src/Plugin/ExtraField/Display/DescriptionExtraField.php index b3bf74649..ddbb65e8a 100755 --- a/modules/oe_theme_content_event/src/Plugin/ExtraField/Display/DescriptionExtraField.php +++ b/modules/oe_theme_content_event/src/Plugin/ExtraField/Display/DescriptionExtraField.php @@ -15,6 +15,7 @@ use Drupal\media\MediaInterface; use Drupal\oe_content_event\EventNodeWrapper; use Drupal\oe_theme\ValueObject\ImageValueObject; +use Drupal\oe_theme\ValueObject\MediaValueObject; use Drupal\oe_time_caching\Cache\TimeBasedCacheTagGeneratorInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -200,7 +201,10 @@ protected function addFeaturedMediaThumbnail(array &$build, ContentEntityInterfa } $cache->addCacheableDependency($thumbnail->entity); - $build['#fields']['image'] = ImageValueObject::fromImageItem($thumbnail); + $media = [ + 'image' => ImageValueObject::fromImageItem($thumbnail), + ]; + $build['#fields']['media'] = MediaValueObject::fromArray($media); // Only display a caption if we have an image to be captioned by. /** @var \Drupal\Core\Entity\EntityViewBuilderInterface $view_builder */ diff --git a/modules/oe_theme_helper/config/schema/oe_theme_helper.schema.yml b/modules/oe_theme_helper/config/schema/oe_theme_helper.schema.yml index cee96f167..515c6858c 100644 --- a/modules/oe_theme_helper/config/schema/oe_theme_helper.schema.yml +++ b/modules/oe_theme_helper/config/schema/oe_theme_helper.schema.yml @@ -63,6 +63,9 @@ field.formatter.settings.oe_theme_helper_featured_media_formatter: image_style: type: string label: 'Image style' + view_mode: + type: string + label: 'Media view mode' field.formatter.settings.oe_theme_helper_featured_media_thumbnail_url_formatter: type: field.formatter.settings.oe_theme_helper_featured_media_formatter diff --git a/modules/oe_theme_helper/src/Plugin/Field/FieldFormatter/FeaturedMediaFormatter.php b/modules/oe_theme_helper/src/Plugin/Field/FieldFormatter/FeaturedMediaFormatter.php index 12789a808..ba1fca94e 100644 --- a/modules/oe_theme_helper/src/Plugin/Field/FieldFormatter/FeaturedMediaFormatter.php +++ b/modules/oe_theme_helper/src/Plugin/Field/FieldFormatter/FeaturedMediaFormatter.php @@ -5,23 +5,24 @@ namespace Drupal\oe_theme_helper\Plugin\Field\FieldFormatter; use Drupal\Core\Cache\CacheableMetadata; -use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase; use Drupal\Core\Form\FormStateInterface; use Drupal\media\MediaInterface; -use Drupal\media\Plugin\media\Source\OEmbed; -use Drupal\oe_media_iframe\Plugin\media\Source\Iframe; -use Drupal\media_avportal\Plugin\media\Source\MediaAvPortalVideoSource; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; +use Drupal\oe_theme\ValueObject\MediaValueObject; +use Drupal\Core\Entity\Entity\EntityViewDisplay; /** * Display a featured media field using the ECL media container. * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * * @FieldFormatter( * id = "oe_theme_helper_featured_media_formatter", * label = @Translation("Media container"), @@ -47,6 +48,13 @@ class FeaturedMediaFormatter extends EntityReferenceFormatterBase { */ protected $entityRepository; + /** + * The entity display repository. + * + * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface + */ + protected $entityDisplayRepository; + /** * Constructs a FeaturedMediaFormatter object. * @@ -68,18 +76,21 @@ class FeaturedMediaFormatter extends EntityReferenceFormatterBase { * The entity type manager. * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository * The entity repository. + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository + * The entity display repository. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository) { + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, EntityDisplayRepositoryInterface $entity_display_repository) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); $this->entityTypeManager = $entity_type_manager; $this->entityRepository = $entity_repository; + $this->entityDisplayRepository = $entity_display_repository; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], $container->get('entity_type.manager'), $container->get('entity.repository')); + return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], $container->get('entity_type.manager'), $container->get('entity.repository'), $container->get('entity_display.repository')); } /** @@ -88,6 +99,7 @@ public static function create(ContainerInterface $container, array $configuratio public static function defaultSettings() { return [ 'image_style' => '', + 'view_mode' => 'oe_theme_main_content', ] + parent::defaultSettings(); } @@ -95,13 +107,29 @@ public static function defaultSettings() { * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state) { + $view_mode = []; + + foreach ($this->entityDisplayRepository->getViewModes('media') as $key => $value) { + if ($value['status']) { + $view_mode[$key] = $value['label']; + } + } + $element['image_style'] = [ - '#title' => t('Image style'), + '#title' => $this->t('Image style'), '#type' => 'select', '#default_value' => $this->getSetting('image_style'), - '#empty_option' => t('None (original image)'), + '#empty_option' => $this->t('None (original image)'), '#options' => image_style_options(FALSE), - '#description' => t('Image style to be used if the Media is an image.'), + '#description' => $this->t('Image style to be used if the Media is an image.'), + ]; + $element['view_mode'] = [ + '#title' => $this->t('View mode'), + '#type' => 'select', + '#default_value' => $this->getSetting('view_mode'), + '#required' => TRUE, + '#options' => $view_mode, + '#description' => $this->t('View mode of the media element.'), ]; return $element; @@ -120,10 +148,10 @@ public function settingsSummary() { // their styles in code. $image_style_setting = $this->getSetting('image_style'); if (isset($image_styles[$image_style_setting])) { - $summary[] = t('@image_style image style', ['@image_style' => $image_styles[$image_style_setting]]); + $summary[] = $this->t('@image_style image style', ['@image_style' => $image_styles[$image_style_setting]]); } else { - $summary[] = t('Original image style.'); + $summary[] = $this->t('Original image style.'); } return $summary; @@ -154,88 +182,36 @@ public function viewElements(FieldItemListInterface $items, $langcode) { * The ECL media-container parameters. */ protected function viewElement(FieldItemInterface $item, string $langcode): array { - $build = ['#theme' => 'oe_theme_helper_featured_media']; - $params = ['description' => $item->caption]; + $pattern = []; $media = $item->entity; - $cacheability = CacheableMetadata::createFromRenderArray($build); if (!$media instanceof MediaInterface) { - return []; + return $pattern; } + $image_style = $this->getSetting('image_style'); + $view_mode = $this->getSetting('view_mode'); + $cacheability = CacheableMetadata::createFromRenderArray($pattern); // Retrieve the correct media translation. $media = $this->entityRepository->getTranslationFromContext($media, $langcode); - // Caches are handled by the formatter usually. Since we are not rendering - // the original render arrays, we need to propagate our caches to the - // oe_theme_helper_featured_media template. + // the original render arrays, we need to propagate our caches. $cacheability->addCacheableDependency($media); + $cacheability->addCacheableDependency($this->entityTypeManager->getStorage('media_type')->load($media->bundle())); + $cacheability->addCacheableDependency(EntityViewDisplay::collectRenderDisplay($media, $view_mode)); + + $pattern = [ + '#type' => 'pattern', + '#id' => 'media_container', + '#fields' => [ + 'media' => MediaValueObject::fromMediaObject($media, $image_style, $view_mode), + 'description' => $item->caption, + ], + ]; - // Get the media source. - $source = $media->getSource(); - - if ($source instanceof MediaAvPortalVideoSource || $source instanceof OEmbed || $source instanceof Iframe) { - // Default video aspect ratio is set to 16:9. - $params['ratio'] = '16-9'; - - // Load information about the media and the display. - $media_type = $this->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()); - $oembed_type = $source->getMetadata($media, 'type'); - - // 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 && in_array($oembed_type, ['video', 'html'])) { - $params['embedded_media'] = $media->{$source_field->getName()}->view($display_options); - $build['#params'] = $params; - $cacheability->applyTo($build); - - return $build; - } - - // If its an AvPortal video or an iframe video, render it. - $params['embedded_media'] = $media->{$source_field->getName()}->view($display_options); - - // When dealing with iframe videos, also respect its given aspect ratio. - if ($media->bundle() === 'video_iframe') { - $ratio = $media->get('oe_media_iframe_ratio')->value; - $params['ratio'] = str_replace('_', '-', $ratio); - } - - $build['#params'] = $params; - $cacheability->applyTo($build); - - return $build; - } - - // If its an image media, render it and assign it to the image variable. - /** @var \Drupal\image\Plugin\Field\FieldType\ImageItem $thumbnail */ - $thumbnail = $media->get('thumbnail')->first(); - /** @var \Drupal\Core\Entity\Plugin\DataType\EntityAdapter $file */ - $file = $thumbnail->get('entity')->getTarget(); - $image_style = $this->getSetting('image_style'); - $style = $this->entityTypeManager->getStorage('image_style')->load($image_style); - - if ($style) { - // Use image style url if set. - $image_url = $style->buildUrl($file->get('uri')->getString()); - $cacheability->addCacheableDependency($image_style); - } - else { - // Use original file url. - $image_url = file_create_url($file->get('uri')->getString()); - } - - $params['alt'] = $thumbnail->get('alt')->getString(); - $params['image'] = $image_url; - $build['#params'] = $params; - $cacheability->applyTo($build); + $cacheability->applyTo($pattern); - return $build; + return $pattern; } } diff --git a/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php b/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php index 3d9d839e5..60c63d0db 100644 --- a/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php +++ b/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php @@ -18,6 +18,7 @@ use Drupal\oe_theme_helper\EuropeanUnionLanguages; use Drupal\smart_trim\Truncate\TruncateHTML; use Drupal\Core\Template\TwigExtension as CoreTwigExtension; +use Drupal\oe_theme\ValueObject\MediaValueObject; /** * Collection of extra Twig extensions as filters and functions. @@ -69,6 +70,7 @@ public function getFilters(): array { new \Twig_SimpleFilter('smart_trim', [$this, 'smartTrim'], ['needs_environment' => TRUE]), new \Twig_SimpleFilter('is_external_url', [UrlHelper::class, 'isExternal']), new \Twig_SimpleFilter('filter_empty', [$this, 'filterEmpty']), + new \Twig_SimpleFilter('validate_ratio', [MediaValueObject::class, 'validateRatio']), ]; } diff --git a/modules/oe_theme_helper/tests/src/Kernel/Plugin/Field/FieldFormatter/FeaturedMediaFormatterTest.php b/modules/oe_theme_helper/tests/src/Kernel/Plugin/Field/FieldFormatter/FeaturedMediaFormatterTest.php index 8ee793340..04e65afcc 100644 --- a/modules/oe_theme_helper/tests/src/Kernel/Plugin/Field/FieldFormatter/FeaturedMediaFormatterTest.php +++ b/modules/oe_theme_helper/tests/src/Kernel/Plugin/Field/FieldFormatter/FeaturedMediaFormatterTest.php @@ -25,7 +25,9 @@ class FeaturedMediaFormatterTest extends AbstractKernelTestBase { 'node', 'media', 'file', + 'file_link', 'filter', + 'link', 'image', 'views', 'entity_browser', @@ -34,13 +36,8 @@ class FeaturedMediaFormatterTest extends AbstractKernelTestBase { 'oe_media', 'oe_media_avportal', 'oe_media_iframe', - 'oe_media_oembed_mock', - 'media_avportal_mock', 'oe_content_featured_media_field', 'system', - 'file_link', - 'link', - 'options', ]; /** @@ -64,7 +61,14 @@ protected function setUp(): void { 'oe_media_avportal', 'oe_media_iframe', 'oe_content_featured_media_field', + 'oe_theme_helper', ]); + // Enable and set OpenEuropa Theme as default. + \Drupal::service('theme_installer')->install(['oe_theme']); + \Drupal::configFactory()->getEditable('system.theme')->set('default', 'oe_theme')->save(); + // Rebuild the ui_pattern definitions to collect the ones provided by + // oe_theme itself. + \Drupal::service('plugin.manager.ui_patterns')->clearCachedDefinitions(); // Create a content type. $type = NodeType::create(['name' => 'Test content type', 'type' => 'test_ct']); @@ -106,7 +110,7 @@ protected function setUp(): void { /** * Test the featured media formatter. */ - public function testFormatter(): void { + public function testFeaturedMediaFormatter(): void { $this->container->get('file_system')->copy($this->root . '/core/misc/druplicon.png', 'public://example.jpg'); $image = File::create(['uri' => 'public://example.jpg']); $image->save(); diff --git a/oe_theme.theme b/oe_theme.theme index 8e2458c85..900df383b 100644 --- a/oe_theme.theme +++ b/oe_theme.theme @@ -15,21 +15,16 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; use Drupal\Core\Url; -use Drupal\file\FileInterface; -use Drupal\file_link\Plugin\Field\FieldType\FileLinkItem; use Drupal\media\MediaInterface; use Drupal\media\Plugin\media\Source\Image; -use Drupal\media\Plugin\media\Source\OEmbed; -use Drupal\media_avportal\Plugin\media\Source\MediaAvPortalPhotoSource; use Drupal\media_avportal\Plugin\media\Source\MediaAvPortalSourceInterface; -use Drupal\media_avportal\Plugin\media\Source\MediaAvPortalVideoSource; use Drupal\oe_theme\DocumentMediaValueExtractor; use Drupal\oe_theme\ValueObject\FileValueObject; use Drupal\oe_theme\ValueObject\DateValueObject; use Drupal\oe_theme\ValueObject\GalleryItemValueObject; use Drupal\oe_theme\ValueObject\ImageValueObject; +use Drupal\oe_theme\ValueObject\MediaValueObject; use Drupal\oe_theme_helper\EuropeanUnionLanguages; -use Drupal\oe_media_iframe\Plugin\media\Source\Iframe; use \Drupal\Component\Render\FormattableMarkup; /** @@ -853,8 +848,6 @@ function oe_theme_preprocess_paragraph__oe_content_row__variant_inpage_navigatio * Implements hook_preprocess_paragraph() for paragraph--oe-text-feature-media.html.twig. */ function oe_theme_preprocess_paragraph__oe_text_feature_media(array &$variables): void { - $variables['image'] = []; - /** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */ $paragraph = $variables['paragraph']; @@ -863,10 +856,9 @@ function oe_theme_preprocess_paragraph__oe_text_feature_media(array &$variables) 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; @@ -878,54 +870,11 @@ function oe_theme_preprocess_paragraph__oe_text_feature_media(array &$variables) // 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 = CacheableMetadata::createFromRenderArray($variables); $cacheability->addCacheableDependency($media); - - // Get the media source. - $source = $media->getSource(); - - if ($source instanceof MediaAvPortalVideoSource || $source instanceof OEmbed || $source instanceof Iframe) { - // Default video aspect ratio is set to 16:9. - $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 its an AvPortal video or an iframe video, 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 its 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'); - $cacheability->applyTo($variables); - return; - } + $cacheability->addCacheableDependency(\Drupal::entityTypeManager()->getStorage('media_type')->load($media->bundle())); + $cacheability->addCacheableDependency(EntityViewDisplay::collectRenderDisplay($media, 'oe_theme_medium_no_crop')); + $variables['media'] = MediaValueObject::fromMediaObject($media, 'oe_theme_medium_no_crop', 'oe_theme_main_content'); $cacheability->applyTo($variables); } @@ -1011,13 +960,6 @@ function oe_theme_preprocess_pattern_date_block__preview(array &$variables): voi $variables['date'] = DateValueObject::fromArray($variables['date']); } -/** - * Implements hook_preprocess_HOOK(). - */ -function oe_theme_preprocess_pattern_text_featured_media__preview(array &$variables): void { - $variables['image'] = ImageValueObject::fromArray($variables['image']); -} - /** * Implements hook_theme_suggestions_HOOK_alter(). */ diff --git a/src/ValueObject/ImageValueObject.php b/src/ValueObject/ImageValueObject.php index 6c5b1afd3..fe517e141 100644 --- a/src/ValueObject/ImageValueObject.php +++ b/src/ValueObject/ImageValueObject.php @@ -9,7 +9,7 @@ /** * Handle information about an image, such as source, alt, name and responsive. */ -class ImageValueObject extends ValueObjectBase { +class ImageValueObject extends ValueObjectBase implements ImageValueObjectInterface { /** * Image Source. @@ -73,40 +73,28 @@ public static function fromArray(array $values = []): ValueObjectInterface { } /** - * Getter. - * - * @return string - * Property value. + * {@inheritdoc} */ public function getSource(): string { return $this->src; } /** - * Getter. - * - * @return string - * Property value. + * {@inheritdoc} */ public function getName(): string { return $this->name; } /** - * Getter. - * - * @return string - * Property value. + * {@inheritdoc} */ public function getAlt(): string { return $this->alt; } /** - * Getter. - * - * @return bool - * Property value. + * {@inheritdoc} */ public function isResponsive(): bool { return $this->responsive; @@ -125,16 +113,9 @@ public function getArray(): array { } /** - * Construct object from a Drupal image field. - * - * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item - * Field holding the image. - * - * @throws \Drupal\Core\TypedData\Exception\MissingDataException - * - * @return $this + * {@inheritdoc} */ - public static function fromImageItem(ImageItem $image_item): ValueObjectInterface { + public static function fromImageItem(ImageItem $image_item): ImageValueObjectInterface { $image_file = $image_item->get('entity')->getTarget(); $image_object = new static( @@ -149,20 +130,9 @@ public static function fromImageItem(ImageItem $image_item): ValueObjectInterfac } /** - * Construct object from a Drupal image field and image style. - * - * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item - * The field image item instance. - * @param string $style_name - * The image style name. - * - * @return $this - * A image value object instance. - * - * @throws \InvalidArgumentException - * Thrown when the image style is not found. + * {@inheritdoc} */ - public static function fromStyledImageItem(ImageItem $image_item, string $style_name): ValueObjectInterface { + public static function fromStyledImageItem(ImageItem $image_item, string $style_name): ImageValueObjectInterface { $image_file = $image_item->get('entity')->getTarget(); $style = \Drupal::entityTypeManager()->getStorage('image_style')->load($style_name); diff --git a/src/ValueObject/ImageValueObjectInterface.php b/src/ValueObject/ImageValueObjectInterface.php new file mode 100644 index 000000000..1236f55ca --- /dev/null +++ b/src/ValueObject/ImageValueObjectInterface.php @@ -0,0 +1,74 @@ +video = $video; + $this->ratio = $ratio; + $this->sources = $sources; + $this->tracks = $tracks; + $this->image = $image; + } + + /** + * {@inheritdoc} + */ + public function getVideo(): string { + return $this->video; + } + + /** + * {@inheritdoc} + */ + public function getRatio(): string { + return $this->ratio; + } + + /** + * {@inheritdoc} + */ + public function getSources(): array { + return $this->sources; + } + + /** + * {@inheritdoc} + */ + public function getTracks(): array { + return $this->tracks; + } + + /** + * {@inheritdoc} + */ + public function getImage(): ?ImageValueObjectInterface { + return $this->image; + } + + /** + * {@inheritdoc} + */ + public function getArray(): array { + return [ + 'video' => $this->getVideo(), + 'ratio' => $this->getRatio(), + 'sources' => $this->getSources(), + 'tracks' => $this->getTracks(), + 'image' => $this->getImage(), + ]; + } + + /** + * {@inheritdoc} + */ + public static function fromArray(array $values = []): ValueObjectInterface { + $values += [ + 'video' => '', + 'ratio' => '', + 'sources' => [], + 'tracks' => [], + 'image' => NULL, + ]; + + $values['ratio'] = self::validateRatio($values['ratio']); + + return new static( + $values['video'], + $values['ratio'], + $values['sources'], + $values['tracks'], + $values['image'] + ); + } + + /** + * {@inheritdoc} + */ + public static function fromMediaObject(Media $media, string $image_style = '', string $view_mode = ''): MediaValueObjectInterface { + $source = $media->getSource(); + $values = [ + 'video' => '', + 'ratio' => '', + 'sources' => [], + 'tracks' => [], + 'image' => NULL, + ]; + + if ($source instanceof MediaAvPortalVideoSource || $source instanceof OEmbed || $source instanceof Iframe) { + $media_type = \Drupal::service('entity_type.manager')->getStorage('media_type')->load($media->bundle()); + $source_field = $source->getSourceFieldDefinition($media_type); + $display = EntityViewDisplay::collectRenderDisplay($media, $view_mode); + $display_options = $display->getComponent($source_field->getName()); + $values['video'] = $media->{$source_field->getName()}->view($display_options); + + // When dealing with iframe videos, also respect it's given aspect ratio. + if ($media->bundle() === 'video_iframe') { + $values['ratio'] = $media->get('oe_media_iframe_ratio')->value; + } + + $values['ratio'] = self::validateRatio($values['ratio']); + // Render the result. + $values['video'] = \Drupal::service('renderer')->renderPlain($values['video'])->__toString(); + + return new static( + $values['video'], + $values['ratio'], + $values['sources'], + $values['tracks'], + $values['image'] + ); + } + + if ($image_style === '') { + $values['image'] = ImageValueObject::fromImageItem($media->get('thumbnail')->first()); + } + else { + $values['image'] = ImageValueObject::fromStyledImageItem($media->get('thumbnail')->first(), $image_style); + } + + return new static( + $values['video'], + $values['ratio'], + $values['sources'], + $values['tracks'], + $values['image'] + ); + } + + /** + * {@inheritdoc} + */ + public static function validateRatio(string $ratio): string { + $ratio = str_replace(['_', ':'], '-', $ratio); + + if (!in_array($ratio, MediaValueObjectInterface::ALLOWED_VALUES)) { + $ratio = MediaValueObjectInterface::DEFAULT_RATIO; + } + + return $ratio; + } + +} diff --git a/src/ValueObject/MediaValueObjectInterface.php b/src/ValueObject/MediaValueObjectInterface.php new file mode 100644 index 000000000..d90731118 --- /dev/null +++ b/src/ValueObject/MediaValueObjectInterface.php @@ -0,0 +1,94 @@ +{{ title }} {% endif %}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Non enim iam stirpis bonum quaeret, sed animalis. Ego vero volo in virtute vim esse quam maximam; A mene tu? Quid, quod res alia tota est? Duo Reges: constructio interrete.
" - video_ratio: - type: "text" - label: "Video aspect ratio" - description: "Aspect ratio used to display videos in media container. Allowed values: 16:9, 4:3, 3:2, 1:1. Defaults to 16:9 if in invalid value is passed. This will not have any effects when used with images." - preview: "16:9" + media: + type: "MediaValueObject" + label: "Media" + description: "Media element." + preview: + image: + src: "https://placeimg.com/100/100" + alt: "Alternative text for logo" diff --git a/tests/Kernel/ValueObject/MediaValueObjectTest.php b/tests/Kernel/ValueObject/MediaValueObjectTest.php new file mode 100644 index 000000000..724b56a62 --- /dev/null +++ b/tests/Kernel/ValueObject/MediaValueObjectTest.php @@ -0,0 +1,103 @@ +installSchema('file', 'file_usage'); + $this->installEntitySchema('user'); + $this->installEntitySchema('media'); + $this->installEntitySchema('file'); + + $this->installConfig([ + 'user', + 'file', + 'media', + 'oe_media', + ]); + } + + /** + * Test the media value object has the correct values returned. + */ + public function testMediaValueObject() { + // Create image file. + $image = file_save_data(file_get_contents(drupal_get_path('theme', 'oe_theme') . '/tests/fixtures/example_1.jpeg'), 'public://example_1.jpeg'); + $image->setPermanent(); + $image->save(); + + // Create a test style. + /** @var \Drupal\image\ImageStyleInterface $style */ + $style = ImageStyle::create(['name' => 'main_style']); + $style->save(); + + // Create an image item. + $alt = $this->randomString(); + $title = $this->randomString(); + + $media = \Drupal::entityTypeManager() + ->getStorage('media')->create([ + 'bundle' => 'image', + 'name' => $title, + 'oe_media_image' => [ + 'target_id' => (int) $image->id(), + 'alt' => $alt, + ], + 'status' => 1, + ]); + $media->save(); + + $media_value_object = MediaValueObject::fromMediaObject($media, 'main_style'); + $this->assertInstanceOf(MediaValueObject::class, $media_value_object); + $image_value_object = $media_value_object->getImage(); + $this->assertInstanceOf(ImageValueObject::class, $image_value_object); + $this->assertEquals($alt, $image_value_object->getAlt()); + $this->assertContains('/styles/main_style/public/example_1.jpeg', $image_value_object->getSource()); + + $remote_video = 'https://www.youtube.com/watch?v=OkPW9mK5Vw8'; + $media = $this->container->get('entity_type.manager') + ->getStorage('media')->create([ + 'bundle' => 'remote_video', + 'oe_media_oembed_video' => $remote_video, + 'status' => 1, + ]); + $media->save(); + + $media_value_object = MediaValueObject::fromMediaObject($media); + $this->assertInstanceOf(MediaValueObject::class, $media_value_object); + $this->assertContains(UrlHelper::encodePath($remote_video), $media_value_object->getVideo()); + } + +} diff --git a/tests/Kernel/fixtures/rendering.yml b/tests/Kernel/fixtures/rendering.yml index 8abbddf81..5cbc77a61 100644 --- a/tests/Kernel/fixtures/rendering.yml +++ b/tests/Kernel/fixtures/rendering.yml @@ -2236,131 +2236,6 @@ '.ecl-timeline2__item:nth-child(4) .ecl-timeline2__content': 'Lorem Ipsum 4' '.ecl-timeline2__item:nth-child(5) .ecl-timeline2__content': 'Lorem Ipsum 5' '.ecl-timeline2__item:nth-child(6) .ecl-timeline2__content': 'Lorem Ipsum 6' -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - title: "Heading" - image: - src: "http://via.placeholder.com/150x150" - caption: "Some caption text for the image" - text: "Some more text" - assertions: - count: - 'div.ecl-row': 1 - 'figure.ecl-media-container': 1 - 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 - 'div.ecl-media-container__media--ratio-16-9 iframe': 0 - 'figcaption.ecl-media-container__caption': 1 - equals: - 'h2.ecl-u-type-heading-2': "Heading" - 'figcaption.ecl-media-container__caption': "Some caption text for the image" - 'div.ecl-col-md-6.ecl-editor': "Some more text" -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - image: - src: "http://via.placeholder.com/150x150" - caption: "Some caption text for the image" - text: "Some more text" - assertions: - count: - 'h2.ecl-u-type-heading-2': 0 - 'div.ecl-row': 1 - 'figure.ecl-media-container': 1 - 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 - 'div.ecl-media-container__media--ratio-16-9 iframe': 0 - 'figcaption.ecl-media-container__caption': 1 - equals: - 'figcaption.ecl-media-container__caption': "Some caption text for the image" - 'div.ecl-col-md-6.ecl-editor': "Some more text" -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - title: "Heading" - caption: "Some caption text for the image" - text: "Some more text" - assertions: - count: - 'div.ecl-row': 1 - 'figure.ecl-media-container': 0 - 'div.ecl-col-md-6.ecl-editor': 0 - 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 0 - 'div.ecl-media-container__media--ratio-16-9 iframe': 0 - 'figcaption.ecl-media-container__caption': 0 - equals: - 'h2.ecl-u-type-heading-2': "Heading" - 'div.ecl-col-12.ecl-editor': "Some more text" -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - title: "Heading" - image: - src: "http://via.placeholder.com/150x150" - text: "Some more text" - assertions: - count: - 'div.ecl-row': 1 - 'figure.ecl-media-container': 1 - 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 - 'div.ecl-media-container__media--ratio-16-9 iframe': 0 - 'figcaption.ecl-media-container__caption': 0 - equals: - 'h2.ecl-u-type-heading-2': "Heading" - 'div.ecl-col-md-6.ecl-editor': "Some more text" -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - title: "Heading" - caption: "Some caption" - assertions: - count: - 'div.ecl-row': 0 - 'figure.ecl-media-container': 0 - 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 0 - 'div.ecl-media-container__media--ratio-16-9 iframe': 0 - 'figcaption.ecl-media-container__caption': 0 - 'h2.ecl-u-type-heading-2': 0 - equals: {} -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - title: "Heading" - video: "" - text: "Some more text" - assertions: - count: - 'div.ecl-row': 1 - 'figure.ecl-media-container': 1 - 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 0 - 'div.ecl-media-container__media--ratio-16-9 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 - 'figcaption.ecl-media-container__caption': 0 - equals: - 'h2.ecl-u-type-heading-2': "Heading" - 'div.ecl-col-md-6.ecl-editor': "Some more text" -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - video: "" - video_ratio: "invalid" - assertions: - count: - 'div.ecl-media-container__media--ratio-16-9 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 -- array: - '#type': pattern - '#id': text_featured_media - '#fields': - video: "" - video_ratio: "1:1" - assertions: - count: - 'div.ecl-media-container__media--ratio-1-1 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 - array: '#type': pattern '#id': icons_with_text @@ -2665,3 +2540,262 @@ 'div.ecl-u-mr-xs.ecl-u-mt-s.ecl-tag.ecl-tag--removable': 'No link' equals: 'div.active-search-filters__name span': 'Source' +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + media: + video: "" + ratio: "1:1" + assertions: + count: + 'div.ecl-media-container__media--ratio-1-1 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + media: + video: "" + ratio: "4:3" + assertions: + count: + 'div.ecl-media-container__media--ratio-4-3 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + title: "Heading" + media: + image: + src: "http://via.placeholder.com/150x150" + caption: "Some caption text for the image" + text: "Some more text" + assertions: + count: + 'div.ecl-row': 1 + 'figure.ecl-media-container': 1 + 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 + 'div.ecl-media-container__media--ratio-16-9 iframe': 0 + 'figcaption.ecl-media-container__caption': 1 + equals: + 'h2.ecl-u-type-heading-2': "Heading" + 'figcaption.ecl-media-container__caption': "Some caption text for the image" + 'div.ecl-col-md-6.ecl-editor': "Some more text" +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + media: + image: + src: "http://via.placeholder.com/150x150" + caption: "Some caption text for the image" + text: "Some more text" + assertions: + count: + 'h2.ecl-u-type-heading-2': 0 + 'div.ecl-row': 1 + 'figure.ecl-media-container': 1 + 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 + 'div.ecl-media-container__media--ratio-16-9 iframe': 0 + 'figcaption.ecl-media-container__caption': 1 + equals: + 'figcaption.ecl-media-container__caption': "Some caption text for the image" + 'div.ecl-col-md-6.ecl-editor': "Some more text" +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + title: "Heading" + caption: "Some caption text for the image" + text: "Some more text" + assertions: + count: + 'div.ecl-row': 1 + 'figure.ecl-media-container': 0 + 'div.ecl-col-md-6.ecl-editor': 0 + 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 0 + 'div.ecl-media-container__media--ratio-16-9 iframe': 0 + 'figcaption.ecl-media-container__caption': 0 + equals: + 'h2.ecl-u-type-heading-2': "Heading" + 'div.ecl-col-12.ecl-editor': "Some more text" +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + title: "Heading" + media: + image: + src: "http://via.placeholder.com/150x150" + text: "Some more text" + assertions: + count: + 'div.ecl-row': 1 + 'figure.ecl-media-container': 1 + 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 + 'div.ecl-media-container__media--ratio-16-9 iframe': 0 + 'figcaption.ecl-media-container__caption': 0 + equals: + 'h2.ecl-u-type-heading-2': "Heading" + 'div.ecl-col-md-6.ecl-editor': "Some more text" +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + title: "Heading" + caption: "Some caption" + assertions: + count: + 'div.ecl-row': 0 + 'figure.ecl-media-container': 0 + 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 0 + 'div.ecl-media-container__media--ratio-16-9 iframe': 0 + 'figcaption.ecl-media-container__caption': 0 + 'h2.ecl-u-type-heading-2': 0 +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + title: "Heading" + media: + video: "" + text: "Some more text" + assertions: + count: + 'div.ecl-row': 1 + 'figure.ecl-media-container': 1 + 'img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 0 + 'div.ecl-media-container__media--ratio-16-9 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 + 'figcaption.ecl-media-container__caption': 0 + equals: + 'h2.ecl-u-type-heading-2': "Heading" + 'div.ecl-col-md-6.ecl-editor': "Some more text" +- array: + '#type': pattern + '#id': text_featured_media + '#fields': + media: + video: "" + ratio: "invalid" + assertions: + count: + 'div.ecl-media-container__media--ratio-16-9 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + image: + src: "http://via.placeholder.com/150x150" + alt: "Alt text" + assertions: + count: + 'figure.ecl-media-container img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media[alt="Alt text"]': 1 + equals: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': Caption +- array: + '#type': pattern + '#id': media_container + '#fields': + media: + image: + src: "http://via.placeholder.com/150x150" + alt: "Image with no caption" + assertions: + count: + 'figure.ecl-media-container img.ecl-media-container__media[src="http://via.placeholder.com/150x150"]': 1 + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 0 + 'figure.ecl-media-container .ecl-media-container__media[alt="Image with no caption"]': 1 +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + video: "" + ratio: "16:9" + assertions: + count: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-16-9 iframe': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-16-9 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 + equals: + '.ecl-media-container figcaption.ecl-media-container__caption': Caption +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + video: "" + ratio: "4-3" + assertions: + count: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-4-3 iframe': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-4-3 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 + equals: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': Caption +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + video: "" + ratio: "3_2" + assertions: + count: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-3-2 iframe': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-3-2 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 + equals: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': Caption +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + video: "" + ratio: "1:1" + assertions: + count: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-1-1 iframe': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-1-1 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 + equals: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': Caption +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + video: "" + ratio: "20:15" + assertions: + count: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-16-9 iframe': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-16-9 iframe[src="//ec.europa.eu/avservices/play.cfm?ref=I-181645"]': 1 + equals: + '.ecl-media-container figcaption.ecl-media-container__caption': Caption +- array: + '#type': pattern + '#id': media_container + '#fields': + description: "Caption" + media: + video: "" + assertions: + count: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': 1 + 'figure.ecl-media-container .ecl-media-container__media iframe': 1 + 'figure.ecl-media-container .ecl-media-container__media--ratio-16-9 iframe[src="https://www.youtube.com/embed/UmFFTkjs-O0"]': 1 + equals: + 'figure.ecl-media-container figcaption.ecl-media-container__caption': Caption +