toolshed-8.x-1.x-dev/modules/toolshed_media/src/Plugin/Field/FieldFormatter/FileInfoFormatter.php
modules/toolshed_media/src/Plugin/Field/FieldFormatter/FileInfoFormatter.php
<?php
namespace Drupal\toolshed_media\Plugin\Field\FieldFormatter;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
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\Logger\LoggerChannelTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\toolshed_media\Utility\FileHelper;
use Drupal\toolshed_media\Utility\MimeHelperInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A Field formatter for displaying file information for file or media entities.
*
* @FieldFormatter(
* id = "toolshed_file_info",
* label = @Translation("File info link"),
* field_types = {
* "file",
* "entity_reference",
* },
* )
*/
#[FieldFormatter(
id: 'toolshed_file_info',
label: new TranslatableMarkup('File info link'),
field_types: [
"file",
"entity_reference",
],
)]
class FileInfoFormatter extends EntityReferenceFormatterBase {
use LoggerChannelTrait;
// Entity types that can be used with the file info formatter works with.
const ALLOWED_ENTITY_TYPES = ['file', 'media'];
/**
* Mime helper to transform extensions to friendly display types.
*
* @var \Drupal\toolshed_media\Utility\MimeHelperInterface
*/
protected MimeHelperInterface $mimeManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* Creates a new instance of the FileInfoFormatter formatter plugin.
*
* @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.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\toolshed_media\Utility\MimeHelperInterface $mime_helper
* Toolshed media mime manager. Maintains a map of file extensions to
* configured mime display settings.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, MimeHelperInterface $mime_helper) {
parent::__construct(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings']
);
$this->entityTypeManager = $entity_type_manager;
$this->mimeManager = $mime_helper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('toolshed.media.mime_manager')
);
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $fieldDef): bool {
$storageDef = $fieldDef->getFieldStorageDefinition();
switch ($storageDef->getType()) {
case 'entity_reference':
$fieldType = $storageDef->getSetting('target_type');
return in_array($fieldType, static::ALLOWED_ENTITY_TYPES);
case 'file':
return TRUE;
}
return FALSE;
}
/**
* Get the list of available options for linking the entity reference field.
*
* For media it is valid for the resulting link to point to either the file
* or the original media object. For file, the entity is the file, so they
* actually both generate the same link.
*
* @return array
* Return a key/value list of available options for linking the field.
*/
public function getLinkOptions(): array {
try {
$targetType = $this->fieldDefinition->getSetting('target_type');
$typeLabel = $this->entityTypeManager->getDefinition($targetType)->getLabel();
}
catch (PluginNotFoundException $e) {
// Should never happen, but just incase there are invalid types.
$typeLabel = $this->t('Entity');
}
$options = [
'file' => $this->t('Directly to file path'),
'entity' => $this->t('@type_label entity URL', [
'@type_label' => $typeLabel,
]),
];
if ('media' === $this->fieldDefinition->getTargetEntityTypeId()) {
$options['parent'] = $this->t('Parent media URL');
}
return $options;
}
/**
* Get a list of link title display options.
*
* @return array
* Available link title options formatted to be used as options for a
* "select" or "radios" form element.
*/
public function getTitleDisplayOptions(): array {
$options = [
'file' => $this->t('Filename'),
'custom' => $this->t('Custom title text'),
];
$targetType = $this->fieldDefinition->getSetting('target_type');
if ('media' === $this->fieldDefinition->getTargetEntityTypeId() || 'media' === $targetType) {
$options = ['media' => $this->t('Media name')] + $options;
}
return $options;
}
/**
* Convert the number of raw bytes into a human friendly readable format.
*
* This will convert the bytes integer to a string of the file size with
* the appropriate unit suffix.
*
* @param int $bytes
* The size of a file in number of bytes.
* @param int $decimals
* Number of decimal places to keep in the returned string.
*
* @return string
* Return the file size with a unit suffix, and up to the number
* of decimal places requested.
*/
protected function readableFileSize($bytes, $decimals = 2): string {
$i = 0;
$divisor = 1;
$next = 1024;
$suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
while (($bytes / $next) > 1.0 && isset($suffixes[$i])) {
$divisor = $next;
$next *= 1024;
++$i;
}
return number_format($bytes / $divisor, $decimals) . ' ' . $suffixes[$i];
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode): array {
/** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */
$entityType = $this->fieldDefinition->getSetting('target_type');
if (in_array($entityType, static::ALLOWED_ENTITY_TYPES)) {
$elements = [];
$parentEntity = $items->getEntity();
$linkTo = $this->getSetting('link_to');
$displayInfo = $this->getSetting('info_shown');
$textDisplay = $this->getSetting('link_title_display');
$linkText = $this->getSetting('link_text');
// When not configured value, fallback depending on if custom link text
// has a value or not. This is mostly for backwards compatibility for
// before the text display option was available.
if (empty($textDisplay)) {
$textDisplay = empty($linkText) ? 'file' : 'custom';
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
$fileHelper = FileHelper::fromEntity($entity);
$itemUrl = NULL;
// Determine the label for the text display setting.
switch ($textDisplay) {
case 'custom':
$label = $linkText ?: $entity->label();
break;
case 'media':
$label = 'media' === $entity->getEntityTypeId() ? $entity->label() : $parentEntity->label();
break;
default:
// Use the filename but fallback to the entity label.
$label = $fileHelper ? $fileHelper->getFilename() : $entity->label();
}
if ($fileHelper) {
$info = [];
if (!empty($displayInfo['mime'])) {
$filenameParts = explode('.', $fileHelper->getFilename());
$ext = end($filenameParts);
$info[] = $this->mimeManager->getDisplayName($ext) ?: $fileHelper->getMime();
}
if (!empty($displayInfo['size'])) {
$info[] = $this->readableFileSize($fileHelper->getSize());
}
$label .= (empty($info) ? '' : ' (' . implode(', ', $info) . ')');
}
try {
switch ($linkTo) {
case 'file':
$itemUrl = $fileHelper ? Url::fromUri($fileHelper->buildRawUrl()) : $entity->toUrl('canonical');
break;
case 'entity':
$itemUrl = $entity->toUrl('canonical');
break;
case 'parent':
$itemUrl = $parentEntity->toUrl('canonical');
break;
}
}
catch (EntityMalformedException | UndefinedLinkTemplateException $e) {
// Unable to get the URL report the error, but don't crash.
$this->getLogger('toolshed_media')->error('Unable to create URL for @fieldname on @entity_type (ID: @entity_id).', [
'@fieldname' => $this->fieldDefinition->getName(),
'@entity_type' => $parentEntity->getEntityTypeId(),
'@entity_id' => $parentEntity->id(),
]);
}
if (!empty($itemUrl)) {
$elements[$delta] = [
'#type' => 'link',
'#title' => $label,
'#url' => $itemUrl,
];
}
else {
$elements[$delta] = [
'#prefix' => '<span>',
'#suffix' => '</span>',
'#plain_text' => $label,
];
}
}
return $elements;
}
// Unrecognized entity type, so we return an empty render array.
return [];
}
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
return [
'link_to' => 'file',
'link_title_display' => 'file',
'link_text' => NULL,
'info_shown' => [
'mime' => 'mime',
'size' => 'size',
],
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsSummary(): array {
$summary = [];
$infoDisplay = array_filter($this->getSetting('info_shown'));
if (!empty($infoDisplay)) {
$summary[] = $this->t('Displaying file info: %display', [
'%display' => implode(', ', $infoDisplay),
]);
}
$linkTo = $this->getSetting('link_to');
$linkOpts = $this->getLinkOptions();
if (!empty($linkTo) && isset($linkOpts[$linkTo])) {
$summary[] = $this->t('Linked to <strong>@target</strong>', [
'@target' => $linkOpts[$linkTo],
]);
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state): array {
$form = parent::settingsForm($form, $form_state);
$form['link_to'] = [
'#type' => 'select',
'#title' => $this->t('Link to'),
'#options' => ['' => $this->t('No link')] + $this->getLinkOptions(),
'#default_value' => $this->getSetting('link_to'),
];
$form['link_title_display'] = [
'#type' => 'select',
'#title' => $this->t('Link text'),
'#options' => $this->getTitleDisplayOptions(),
'#default_value' => $this->getSetting('link_title_display'),
];
$form['link_text'] = [
'#type' => 'textfield',
'#title' => $this->t('Custom text'),
'#default_value' => $this->getSetting('link_text'),
'#description' => $this->t('Leave blank to use default'),
'#process' => [static::class . '::addFormLinkTextStates'],
];
$form['info_shown'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Info Shown'),
'#description' => $this->t('Use the options to determine what will be displayed'),
'#options' => [
'mime' => $this->t('File mime'),
'size' => $this->t('File size'),
],
'#default_value' => $this->getSetting('info_shown'),
];
return $form;
}
/**
* Adds conditional display FAPI state information.
*
* The "link_text" setting is only relevant when the link_title_display is
* set to "custom" and so only setup the "link_text" to appear in this case.
*
* @param array $element
* The link_text field formatter settings element to modify.
*
* @return array
* The updated link_text setting element.
*/
public static function addFormLinkTextStates(array $element): array {
$parents = $element['#parents'];
array_pop($parents);
$name = array_shift($parents);
$parents[] = 'link_title_display';
$name .= '[' . implode('][', $parents) . ']';
$element['#states'] = [
'visible' => [
'select[name="' . $name . '"]' => ['value' => 'custom'],
],
'required' => [
'select[name="' . $name . '"]' => ['value' => 'custom'],
],
];
return $element;
}
}
