inline_image_saver-1.0.x-dev/src/InlineImageFinder.php
src/InlineImageFinder.php
<?php
declare(strict_types=1);
namespace Drupal\inline_image_saver;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\file\FileInterface;
use Drupal\file\FileRepositoryInterface;
use Drupal\filehash\Algorithm;
use Drupal\filehash\FileHashInterface;
use Drupal\inline_image_saver\Struct\InlineImageData;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* Provides a inline image finder.
*/
class InlineImageFinder implements InlineImageFinderInterface {
/**
* Cache of stream wrapper base URIs mapped to their base URLs.
*
* @var array<string, string>
*/
protected array $streamRootUris;
/**
* Cache of enabled file hash algorithms.
*
* @var string[]
*/
protected array $enabledHashAlgorithms;
public function __construct(
protected readonly EntityTypeManagerInterface $entityTypeManager,
protected readonly FileRepositoryInterface $fileRepository,
protected readonly UuidInterface $uuid,
protected readonly StreamWrapperManagerInterface $streamWrapperManager,
protected readonly ModuleExtensionList $moduleExtensionList,
#[Autowire('@filehash')]
protected ?FileHashInterface $fileHash = NULL,
) {}
/**
* {@inheritdoc}
*/
public function findByHash(InlineImageData $data): ?FileInterface {
if (!$hash_algorithms = $this->getEnabledHashAlgorithms()) {
return NULL;
}
$file_storage = $this->entityTypeManager->getStorage('file');
$file_query = $file_storage->getQuery()->accessCheck();
foreach ($hash_algorithms as $algo) {
$file_query->condition($algo, hash($algo, $data->data));
}
/** @var \Drupal\file\FileInterface $file */
foreach ($file_storage->loadMultiple($file_query->execute()) as $file) {
if ($file->access('download')) {
return $file;
}
}
return NULL;
}
/**
* Returns the list of enabled hash algorithms from the filehash module.
*
* @return string[]
* An array of enabled hash algorithm names.
*/
protected function getEnabledHashAlgorithms(): array {
if (!isset($this->enabledHashAlgorithms)) {
$this->enabledHashAlgorithms = [];
if ($this->fileHash) {
$filehash_version = $this->moduleExtensionList->getExtensionInfo('filehash')['version'];
if (version_compare($filehash_version, '3.0.0', '>=') && version_compare($filehash_version, '4.0.0', '<')) {
foreach ($this->fileHash->getEnabledAlgorithms() as $algo) {
if ($algo = Algorithm::from($algo)->getHashAlgo()) {
$this->enabledHashAlgorithms[] = $algo;
}
}
}
}
}
return $this->enabledHashAlgorithms;
}
/**
* {@inheritdoc}
*/
public function findBySrc(InlineImageData $data): ?FileInterface {
if (!$data->src || !$stream_uris = $this->getStreamUris()) {
return NULL;
}
$src = $this->stripProtocol($data->src);
foreach ($stream_uris as $base_uri => $base_path) {
if (!str_starts_with($src, $base_path)) {
continue;
}
$file = $this->fileRepository->loadByUri(str_replace($base_path, $base_uri, $src));
if (!$file || !$file->access('download')) {
continue;
}
$data_hash ??= hash(static::HASH_ALGO, $data->data);
if (hash_file(static::HASH_ALGO, $file->getFileUri()) === $data_hash) {
return $file;
}
}
return NULL;
}
/**
* Returns a mapping of stream wrapper URIs to their external base URLs.
*
* @return array<string, string>
* The URI-to-URL mapping.
*/
protected function getStreamUris(): array {
if (!isset($this->streamRootUris)) {
$this->streamRootUris = [];
$fake_path = $this->uuid->generate();
foreach ($this->streamWrapperManager->getWrappers(StreamWrapperInterface::READ_VISIBLE) as $scheme => $definition) {
$uri_base = "$scheme://";
try {
$fake_url = $this->streamWrapperManager->getViaUri("$uri_base/$fake_path")->getExternalUrl();
if (str_ends_with($fake_url, $fake_path) && substr_count($fake_url, $fake_path) === 1) {
$this->streamRootUris[$uri_base] = $this->stripProtocol(str_replace($fake_path, '', $fake_url));
}
}
catch (\Throwable) {
// Ignore.
}
}
}
return $this->streamRootUris;
}
/**
* Removes the scheme and leading slashes from a URL.
*
* @param string $url
* The URL to normalize.
*
* @return string
* The cleaned value without scheme or slashes.
*/
protected function stripProtocol(string $url): string {
return preg_replace('#^(.*:)?//#', '', $url);
}
}
