toolshed-8.x-1.x-dev/modules/toolshed_media/src/Plugin/Field/FieldFormatter/MediaStyleFormatter.php
modules/toolshed_media/src/Plugin/Field/FieldFormatter/MediaStyleFormatter.php
<?php
namespace Drupal\toolshed_media\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A Field formatter for displaying media with more options.
*
* The formatter used to be applied to media and image fields but it added
* unnecessary overhead that can be removed by separating the formatters. The
* formatter is already in use with this plugin ID and needs to be kept until
* a migration can be provided.
*
* @FieldFormatter(
* id = "toolshed_image_style",
* label = @Translation("Toolshed: Media image styler"),
* field_types = {
* "entity_reference",
* "entity_reference_revision",
* }
* );
*/
#[FieldFormatter(
id: 'toolshed_image_style',
label: new TranslatableMarkup('Toolshed: Media image styler'),
field_types: [
'entity_reference',
'entity_reference_revision',
],
)]
class MediaStyleFormatter extends EntityReferenceFormatterBase implements ContainerFactoryPluginInterface {
use ImageStyleTrait {
settingsForm as stylerSettingsForm;
}
/**
* Service for managing the various entity view modes.
*
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
*/
protected EntityDisplayRepositoryInterface $entityDisplayRepo;
/**
* Media image style formatter object.
*
* @param array $configuration
* Array of plugin configuration options which includes the field formatter
* definition information:
* - $field_definition
* - $settings
* - $label
* - $view_mode
* - $third_party_settings
* These are the usual values for constructing a standard field formatter.
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Service for retrieving and maintain Drupal system configurations.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity manager service, for getting entity definitions and handlers.
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
* Manage entity view mode configurations and displays.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) {
parent::__construct(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings']
);
$this->configFactory = $config_factory;
$this->entityTypeManager = $entity_type_manager;
$this->entityDisplayRepo = $entity_display_repository;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('config.factory'),
$container->get('entity_type.manager'),
$container->get('entity_display.repository')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
return [
'image_style' => '',
'link_to' => NULL,
'decorative' => FALSE,
'image_loading' => 'lazy',
'fallback_display' => 'default',
] + parent::defaultSettings();
}
/**
* Is the media reference have types that are compatible with an images.
*
* @param string[] $bundles
* The allowed bundles for the media type.
*
* @return bool
* Are the bundles a image or video compatible type?
*/
protected static function hasAllowedMediaBundles(array $bundles = []): bool {
static $imageBundles = NULL;
if (empty($bundles)) {
return TRUE;
}
if (!isset($imageBundles)) {
try {
$sources = ['image', 'video'];
$imageBundles = \Drupal::entityTypeManager()
->getStorage('media_type')
->getQuery()
->accessCheck(FALSE)
->condition('source', $sources, 'IN')
->execute();
}
catch (\throwable) {
$imageBundles = [];
}
}
return (bool) array_intersect($imageBundles, $bundles);
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $fieldDef): bool {
$fieldStorageDef = $fieldDef->getFieldStorageDefinition();
$settings = $fieldDef->getSetting('handler_settings');
return 'media' === $fieldStorageDef->getSettings()['target_type']
&& static::hasAllowedMediaBundles($settings['target_bundles'] ?? []);
}
/**
* Get the current options for linking the image to.
*
* @return array<string, string|\Stringable>
* An array of 'link to' options with the labels as the value.
*/
public function getLinkOptions(): array {
return [
'entity' => $this->t('Content entity'),
'media' => $this->t('Media'),
];
}
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
* The field entity reference items.
* @param string $langcode
* The language that should be used to render the field.
*/
public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = [];
if ('media' !== $this->fieldDefinition->getSetting('target_type')) {
return $elements;
}
$display = $this->getDisplayRenderable($items);
$linkToMedia = 'media' === $this->getSetting('link_to');
/** @var \Drupal\media\MediaInterface $media */
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $media) {
// Should only be applied to media entity types, which have a base field
// of "thumbnail" so field should always exist, but it can still be empty
// if the source plugin does not produce a thumbnail image file.
/** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $imageItem */
$imageItem = $media->get('thumbnail')->first();
if ($imageItem && $imageItem->entity) {
$elements[$delta] = $display;
$elements[$delta]['#item'] = $imageItem;
if ($linkToMedia) {
$elements[$delta]['#url'] = $media->toUrl();
}
// Ensure that the item cache includes the media entity that owns it.
CacheableMetadata::createFromRenderArray($display)
->addCacheableDependency($media)
->applyTo($elements[$delta]);
}
else {
// Fallback, to use the media view builder. Used if media entity is not
// targeting an image or if we are unable to load the needed file info.
$elements[$delta] = $this->entityTypeManager
->getViewBuilder('media')
->view($media, $this->getSetting('fallback_display'));
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state): array {
$form = $this->stylerSettingsForm($form, $form_state);
// Provide media entity display mode as a fallback when no media thumbnail.
$fieldStorageDef = $this->fieldDefinition->getFieldStorageDefinition();
$targetType = $fieldStorageDef->getSettings()['target_type'];
$viewModes = ['default' => $this->t('Default')];
foreach ($this->entityDisplayRepo->getViewModes($targetType) as $viewModeId => $viewMode) {
$viewModes[$viewModeId] = Html::escape((string) $viewMode['label']);
}
$form['fallback_display'] = [
'#type' => 'select',
'#title' => $this->t('Fallback display mode'),
'#options' => $viewModes,
'#default_value' => $this->getSetting('fallback_display'),
'#description' => $this->t('If the the media entity is not an image, image styles cannot be applied and will use this view mode to render as a fallback.'),
];
return $form;
}
}
