diff --git a/modules/file_attachment/markdownify_file_attachment.module b/modules/file_attachment/markdownify_file_attachment.module index b2ea999d44f0f0fb31bdcba088f52afa0c75eec7..b121d7cbe858f59598e041e20c5bd130721a3964 100644 --- a/modules/file_attachment/markdownify_file_attachment.module +++ b/modules/file_attachment/markdownify_file_attachment.module @@ -10,78 +10,37 @@ declare(strict_types=1); use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\Environment; use Drupal\Core\Entity\FieldableEntityInterface; -use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Render\BubbleableMetadata; -use Drupal\file\FileInterface; /** - * Implements hook_markdownify_entity_html_alter(). + * Implements hook_markdownify_entity_build_alter(). + * + * Ensures entity reference fields pointing to files are rendered using the + * "md_file_attachment_file_embed" formatter when the format is Markdown. */ -function markdownify_file_attachment_markdownify_entity_html_alter(string &$html, array $context, ?BubbleableMetadata $metadata): void { +function markdownify_file_attachment_markdownify_entity_build_alter(array &$build, array $context, ?BubbleableMetadata $metadata): void { $entity = $context['entity'] ?? NULL; if (!$entity instanceof FieldableEntityInterface) { return; } - - $file_attachment_lines = []; - $config = \Drupal::config('markdownify_file_attachment.settings'); - $allowed_extensions = $config->get('allowed_extensions'); - $max_file_embed_size = min(Bytes::toNumber($config->get('max_file_embed_size') ?? 0), Environment::getUploadMaxSize()); - - foreach ($entity->getFields() as $field) { - // Skip fields that not file reference fields. - if (!$field instanceof EntityReferenceFieldItemListInterface || $field->isEmpty()) { - continue; - } - if (($field->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type') ?? '') !== 'file') { - continue; - } - - foreach ($field->referencedEntities() as $file) { - assert($file instanceof FileInterface); - - $access_result = $file->access('view', Drupal::currentUser(), TRUE); - $metadata->addCacheableDependency($access_result); - - if (!$access_result->isAllowed()) { - continue; - } - - $filename = $file->getFilename(); - $filesize = $file->getSize(); - $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); - $uri = $file->getFileUri(); - $url = \Drupal::service('file_url_generator')->generateAbsoluteString($uri); - - if (in_array($extension, $allowed_extensions, TRUE) && $filesize <= $max_file_embed_size) { - $real_path = \Drupal::service('file_system')->realpath($uri); - if (is_readable($real_path)) { - $content = @file_get_contents($real_path); - $line = "Attached file {$filename} with {$extension} extension available at {$url} follows:\n"; - $line .= <<<EOD - ``` - {$content} - ``` - EOD; - $file_attachment_lines[] = $line; - } - } - else { - $file_attachment_lines[] = "Attached file {$filename} with {$extension} extension is available at {$url}."; + foreach ($entity->getFieldDefinitions() as $field) { + $field_name = $field->getName(); + if ($field->getType() === 'file') { + $config = \Drupal::config('markdownify_file_attachment.settings'); + $display_options = [ + 'type' => 'md_file_attachment_file_embed', + 'settings' => [ + 'allowed_extensions' => $config->get('allowed_extensions'), + 'max_size' => min(Bytes::toNumber($config->get('max_file_embed_size') ?? 0), Environment::getUploadMaxSize()), + ], + ]; + $field_name = $field->getName(); + $field_build = $entity->get($field_name)->view($display_options); + $build[$field_name] = $field_build; + if ($metadata) { + $cacheability = BubbleableMetadata::createFromRenderArray($field_build); + $metadata->merge($cacheability); } } } - - /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = \Drupal::service('renderer'); - if ($renderer->hasRenderContext()) { - $build = []; - $metadata->applyTo($build); - $renderer->render($build); - } - - if ($file_attachment_lines !== []) { - $html .= nl2br("\n" . implode("\n\n", $file_attachment_lines)); - } - } diff --git a/modules/file_attachment/src/Plugin/Field/FieldFormatter/MdFileAttachmentFieldFormatter.php b/modules/file_attachment/src/Plugin/Field/FieldFormatter/MdFileAttachmentFieldFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..689296a4f26d162ed08ec2a635415572c1406fc3 --- /dev/null +++ b/modules/file_attachment/src/Plugin/Field/FieldFormatter/MdFileAttachmentFieldFormatter.php @@ -0,0 +1,152 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\markdownify_file_attachment\Plugin\Field\FieldFormatter; + +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\File\FileUrlGeneratorInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\RenderContext; +use Drupal\Core\Render\RendererInterface; +use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase; +use Drupal\file\Plugin\Field\FieldType\FileFieldItemList; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Plugin implementation of the 'entity_reference_file_attachment' formatter. + * + * @FieldFormatter( + * id = "md_file_attachment_file_embed", + * label = @Translation("Embed file as Markdown"), + * field_types = { + * "file" + * } + * ) + */ +final class MdFileAttachmentFieldFormatter extends FileFormatterBase implements ContainerFactoryPluginInterface { + + /** + * File URL generator. + * + * @var \Drupal\Core\File\FileUrlGeneratorInterface + */ + private FileUrlGeneratorInterface $fileUrlGenerator; + + /** + * Renderer interface. + * + * @var \Drupal\Core\Render\RendererInterface + */ + private RendererInterface $renderer; + + /** + * Logger interface. + * + * @var \Psr\Log\LoggerInterface + */ + private LoggerInterface $logger; + + /** + * File system interface. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + private FileSystemInterface $fileSystem; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self { + $instance = new self( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + ); + + $instance->fileUrlGenerator = $container->get('file_url_generator'); + $instance->renderer = $container->get('renderer'); + $instance->logger = $container->get('logger.channel.markdownify'); + $instance->fileSystem = $container->get('file_system'); + + return $instance; + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings(): array { + return [ + 'allowed_extensions' => ['yml', 'txt'], + 'max_size' => 1024, + ]; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode): array { + assert($items instanceof FileFieldItemList); + $element = []; + + $allowed_extensions = $this->getSetting('allowed_extensions'); + $max_size = $this->getSetting('max_size'); + + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) { + + if (!$file) { + continue; + } + + $file_size = $file->getSize(); + $file_name = $file->getFilename(); + $extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); + $uri = $file->getFileUri(); + $url = ''; + try { + $context = new RenderContext(); + $url = $this->renderer->executeInRenderContext($context, function () use ($uri) { + return $this->fileUrlGenerator->generateAbsoluteString($uri); + }); + } + catch (\Exception $e) { + $this->logger->error('Failed to render file URL: @message', ['@message' => $e->getMessage()]); + } + + $real_path = $this->fileSystem->realpath($uri); + + if (in_array($extension, $allowed_extensions, TRUE) && $file_size <= $max_size) { + $content = @file_get_contents($real_path); + $element[$delta] = [ + '#type' => 'markup', + '#markup' => $this->t('Attached file %filename with %extension extension available at %url follows:<br>%file', [ + '%filename' => $file_name, + '%extension' => $extension, + '%file' => $content, + '%url' => $url, + ]), + ]; + } + else { + $element[$delta] = [ + '#type' => 'markup', + '#markup' => $this->t('Attached file %filename with %extension extension available at %url', [ + '%filename' => $file_name, + '%extension' => $extension, + '%url' => $url, + ]), + ]; + } + + } + + return $element; + } + +}