media_helper-2.0.0/src/Service/TwigExtension.php
src/Service/TwigExtension.php
<?php
namespace Drupal\media_helper\Service;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Render\Element;
use Drupal\media\MediaInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
use function is_array;
use function is_int;
use function is_string;
/**
* Twig extension that provides Media Helper's functions and filters.
*/
class TwigExtension extends AbstractExtension {
/**
* Instantiates the TwigExtension service.
*/
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager,
protected MediaHelper $mediaHelper,
) {
}
/**
* {@inheritdoc}
*/
public function getFilters() {
$filters = [
new TwigFilter('media_first_nonempty', [$this, 'mediaFirstNonempty']),
new TwigFilter('media_image', [$this, 'mediaImage']),
new TwigFilter('media_image_url', [$this, 'mediaImageUrl']),
new TwigFilter('media_video', [$this, 'mediaVideo']),
];
return $filters;
}
/**
* {@inheritdoc}
*/
public function getFunctions() {
$filters = [
new TwigFunction('media_bundle', [$this, 'mediaBundle']),
new TwigFunction('media_source', [$this, 'mediaSource']),
];
return $filters;
}
/**
* Get the type for the given media entity(s).
*
* Examples:
* {{ media_bundle(node.field_media_image) }}
* {{ media_bundle(content.field_media_image) }}
* {{ media_bundle(6) }}
* {{ media_bundle(media_entity_object) }}
*
* @param mixed $input
* The input from Twig.
*
* @return string|null
* The bundle ID of the passed media.
* If there is no identifiable media entity, NULL will be returned.
* If there are multiple entities all of the same bundle, that bundle ID will be returned.
* If there are multiple entities of different bundles, the string "mixed" will be returned.
*/
public function mediaBundle($input): ?string {
$medias = $this->getMedias($input);
if ($medias) {
return $this->mediaHelper->mediaBundle($medias);
}
return NULL;
}
/**
* Get the source ID for the given media entity(s).
*
* Examples:
* {{ media_source(node.field_media_image) }}
* {{ media_source(content.field_media_image) }}
* {{ media_source(6) }}
* {{ media_source(media_entity_object) }}
*
* @param mixed $input
* The input from Twig.
*
* @return string|null
* The ID of the source of the passed media.
* If an empty array is passed, NULL will be returned.
* If there are multiple entities all having the same media source, that source ID will be returned.
* If there are multiple entities of different sources, the string "mixed" will be returned.
*/
public function mediaSource($input): ?string {
$medias = $this->getMedias($input);
if ($medias) {
return $this->mediaHelper->mediaSource($medias);
}
return NULL;
}
/**
* Get the first non-empty value in an array of values.
*
* Example:
* {{ [node.field_media_image, fallback_image]|media_first_nonempty }}
*
* @param mixed $input
* Input from Twig. Expected to be iterable.
*
* @return mixed
* The first nonempty value, or NULL if there is no meaningful input.
*/
public function mediaFirstNonempty($input) {
if (!$input) {
return NULL;
}
if (!is_iterable($input)) {
trigger_error('|media_first_nonempty Twig filter expects an iterable such as an array', E_USER_WARNING);
return NULL;
}
foreach ($input as $possible_value) {
$medias = $this->getMedias($possible_value);
if ($medias) {
return $possible_value;
}
elseif ($medias === NULL) {
return $possible_value;
}
}
return NULL;
}
/**
* Render a media image.
*
* Examples:
* {{ node.field_media_image|media_image('500x500') }}
* {{ content.field_media_image|media_image }}
* {{ 6|media_image }}
* {{ media_entity_object|media_image }}
*
* All arguments are optional. In addition to an image style, class(es) to add to the image tag may also be specified.
* Examples:
* {{ node.field_media_image|media_image('500x500', 'my-class') }}
* {{ node.field_media_image|media_image('style', ['hi', 'there']) }}
*
* @param mixed $input
* The input from Twig.
* @param string $style
* (optional) The machine name of the image style to use. If not specified, the image style used on the media's
* default display mode will be used instead - or if that cannot be discovered, the original image.
* Also supports Responsive Image module transparently. Specify the machine name of a responsive image style to use
* it. (Identical machine names in the two modules will resolve to the basic, non-responsive style.)
* @param string|string[] $classes
* (optional) Class(es) to add to the element, as a string or array of strings.
* @param array $attributes
* (optional) Attributes to add to the element, name => value.
*
* @return array|null
* A render array, or NULL if there is no meaningful input or output.
*/
public function mediaImage($input, string $style = '', $classes = '', array $attributes = []): ?array {
$medias = $this->getMedias($input);
if ($medias) {
return $this->mediaHelper->mediaImage($medias, $style, $classes, $attributes);
}
elseif ($medias === NULL) {
return $input;
}
return NULL;
}
/**
* Render a media image URL.
*
* Examples:
* {{ node.field_media_image|media_image('500x500') }}
* {{ content.field_media_image|media_image }}
* {{ 6|media_image }}
* {{ media_entity_object|media_image }}
*
* All arguments are optional.
*
* @param mixed $input
* The input from Twig.
* @param string $style
* (optional) The machine name of the image style to use.
*
* @return array|null
* A render array, or NULL if there is no meaningful input or output.
* (A render array must be used for cache metadata bubbling.)
*/
public function mediaImageUrl($input, string $style = ''): ?array {
$medias = $this->getMedias($input);
if ($medias) {
return $this->mediaHelper->mediaImageUrl($medias, $style);
}
elseif ($medias === NULL) {
return $input;
}
return NULL;
}
/**
* Render a media uploaded video.
*
* Examples:
* {{ node.field_media_video|media_video('custom-class') }}
* {{ content.field_media_video|media_video }}
* {{ 6|media_video }}
* {{ media_entity_object|media_video }}
*
* All arguments are optional. CSS class(es), Drupal video widget settings, and/or <video> tag attributes may also be
* specified. Examples:
* {{ node.field_media_video|media_video('my-custom-class') }}
* {{ node.field_media_video|media_video('', { width: 200, height: 300 }) }}
* {{ node.field_media_video|media_video(
* '',
* {},
* { 'data-custom-attr': 'my_value' }
* )}}
*
* @param mixed $input
* The input from Twig.
* @param string|array $classes
* (optional) Class(es) to add to the <video> element, as a string or array
* of strings.
* @param array $settings
* (optional) Settings overrides for the Drupal file_video display widget.
* These defaults are used:
* 'autoplay' => TRUE
* 'controls' => FALSE
* 'loop' => TRUE
* 'muted' => TRUE
* 'multiple_file_display_type' => 'sources'
* 'width' => NULL
* 'height' => NULL
* and can be overridden by setting the value in this argument.
* @param array $attributes
* (optional) Attributes to add to the <video> element, name => value.
* If the autoplay setting is on (which it is by default), the attributes 'disablePictureInPicture' and
* 'playsinline' are added if not already set.
*
* @return array|null
* A render array, or NULL if there is no meaningful input or output.
*/
public function mediaVideo($input, $classes = '', array $settings = [], array $attributes = []): ?array {
$medias = $this->getMedias($input);
if ($medias) {
return $this->mediaHelper->mediaVideo($medias, $classes, $settings, $attributes);
}
elseif ($medias === NULL) {
return $input;
}
else {
return NULL;
}
}
/**
* Returns media object(s) given Twig input.
*
* @param mixed $input
* The input from Twig. Multiple input types are accepted:
* - A media reference field (e.g. node.field_id).
* - The render array from a media reference field. ("Rendered entity" format supported. Other formats may not be.)
* - One media entity object.
* - One media entity ID.
*
* @return \Drupal\media\MediaInterface[]|null
* A (possibly empty) array of media objects, or NULL if this looks like an empty render array that should be
* output unmodified.
*/
protected function getMedias($input): ?array {
if (!$input) {
return [];
}
// Handle direct input of media entity.
if ($input instanceof MediaInterface) {
return [$input];
}
// Handle reference field.
if ($input instanceof EntityReferenceFieldItemListInterface) {
return $input->referencedEntities();
}
// Handle media entity ID.
if (
(is_int($input) && $input > 0)
|| (is_string($input) && ctype_digit($input))
) {
return $this->entityTypeManager->getStorage('media')->loadMultiple([$input]);
}
// Handle reference field render array.
if (is_array($input) && isset($input['#field_type']) && $input['#field_type'] === 'entity_reference') {
$medias = [];
foreach (Element::getVisibleChildren($input) as $key) {
if (isset($input[$key]['#media']) && $input[$key]['#media'] instanceof MediaInterface) {
$medias[] = $input[$key]['#media'];
}
}
return $medias;
}
// Sometimes a render array might be completely empty if the field is empty or if no entities pass access control.
// In those cases, return the render array as-is to make sure the cache data bubbles.
if (is_array($input) && !isset($input['#field_type']) && isset($input['#cache'])) {
return NULL;
}
return [];
}
}
