TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:27 +02:00
commit 44c503b2d1
233 changed files with 31556 additions and 0 deletions
+297
View File
@@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Frontend\Resource;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\LinkHandling\LinkService;
use TYPO3\CMS\Core\Resource\Collection\AbstractFileCollection;
use TYPO3\CMS\Core\Resource\Exception;
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
use TYPO3\CMS\Core\Resource\FileCollectionRepository;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
/**
* Object to collect files from various sources during runtime.
* Sources can be file references, file collections or folders.
*
* Use in FILES Content Object or for a Fluid Data Processor.
*
* Is not persisted, use only in FE.
*
* @internal this is an internal TYPO3 implementation and solely used for EXT:frontend and not part of TYPO3's Core API.
*
* @todo The file collector is used for intermediate collection in scoped places. Therefore, the collector
* can't be shared. Evaluate if the collector can be build scope aware and made sharable again.
*/
#[Autoconfigure(public: true, shared: false)]
class FileCollector implements \Countable, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* The collected File or FileReference objects
*
* @var FileInterface[]
*/
protected array $files = [];
public function __construct(
protected readonly ResourceFactory $resourceFactory,
protected readonly FileCollectionRepository $fileCollectionRepository,
protected readonly FileRepository $fileRepository,
protected readonly TcaSchemaFactory $tcaSchemaFactory,
protected readonly LinkService $linkService,
) {}
/**
* Add files by UID
*/
public function addFiles(array $fileUids = []): void
{
foreach ($fileUids as $fileUid) {
try {
$this->addFileObject($this->resourceFactory->getFileObject($fileUid));
} catch (Exception $e) {
$this->logger->warning(
'The file with uid "' . $fileUid
. '" could not be found and won\'t be included in frontend output',
['exception' => $e]
);
}
}
}
/**
* Add files to the collection from a relation.
*
* @param string $relationTable The table of the relation (e.g. tt_content or pages)
* @param string $relationField The field which holds the files (e.g. media or images)
* @param array $referenceRecord the record which is referencing the files
*/
public function addFilesFromRelation(string $relationTable, string $relationField, array $referenceRecord): void
{
$fileReferences = $this->getFileReferences($relationTable, $relationField, $referenceRecord);
if (!empty($fileReferences)) {
$this->addFileObjects($fileReferences);
}
}
/**
* Add files from UIDs of a reference.
*/
public function addFileReferences(array $fileReferenceUids = []): void
{
foreach ($fileReferenceUids as $fileReferenceUid) {
$fileObject = $this->resourceFactory->getFileReferenceObject((int)$fileReferenceUid);
$this->addFileObject($fileObject);
}
}
/**
* Add files to the collection from multiple file collections.
*/
public function addFilesFromFileCollections(array $fileCollectionUids = []): void
{
foreach ($fileCollectionUids as $fileCollectionUid) {
$this->addFilesFromFileCollection((int)$fileCollectionUid);
}
}
/**
* Add files to the collection from one single file collection.
*/
public function addFilesFromFileCollection(int $fileCollectionUid): void
{
if ($fileCollectionUid <= 0) {
return;
}
try {
$fileCollection = $this->fileCollectionRepository->findByUid($fileCollectionUid);
if ($fileCollection instanceof AbstractFileCollection) {
$fileCollection->loadContents();
$files = $fileCollection->getItems();
$this->addFileObjects($files);
}
} catch (Exception $e) {
$this->logger->warning(
'The file-collection with uid "' . $fileCollectionUid
. '" could not be found or contents could not be loaded and won\'t be included in frontend output.',
['exception' => $e]
);
}
}
/**
* Add files to the collection from multiple folders.
*
* @param array $folderIdentifiers The folder identifiers
* @param bool $recursive Add files recursive from given folders
*/
public function addFilesFromFolders(array $folderIdentifiers, bool $recursive = false): void
{
foreach ($folderIdentifiers as $folderIdentifier) {
$this->addFilesFromFolder($folderIdentifier, $recursive);
}
}
/**
* Add files to the collection from one single folder.
*
* @param string $folderIdentifier The folder identifier
* @param bool $recursive Add files recursive from given folders
*/
public function addFilesFromFolder(string $folderIdentifier, bool $recursive = false): void
{
if ($folderIdentifier) {
try {
if (str_starts_with($folderIdentifier, 't3://folder')) {
// a t3://folder link to a folder in FAL
$data = $this->linkService->resolveByStringRepresentation($folderIdentifier);
$folder = $data['folder'];
} else {
$folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($folderIdentifier);
}
if ($folder instanceof Folder) {
$files = $folder->getFiles(0, 0, Folder::FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS, $recursive);
$this->addFileObjects(array_values($files));
}
} catch (Exception $e) {
$this->logger->warning('The folder with identifier "{folder}" could not be found and won\'t be included in frontend output', [
'folder' => $folderIdentifier,
'exception' => $e,
]);
}
}
}
/**
* Sort the file objects based on a property.
*
* @param string $sortingProperty The sorting property
* @param 'ascending'|'descending'|'random' $sortingOrder The sorting order
*/
public function sort(string $sortingProperty = '', string $sortingOrder = 'ascending'): void
{
$sortingOrder = strtolower($sortingOrder);
if ($sortingProperty !== '' && count($this->files) > 1) {
$sortMultiplier = in_array($sortingOrder, ['descending', 'desc'], true) ? -1 : 1;
@usort(
$this->files,
static function (
FileInterface $a,
FileInterface $b
) use ($sortingProperty, $sortMultiplier) {
if ($a->hasProperty($sortingProperty) && $b->hasProperty($sortingProperty)) {
return strnatcasecmp((string)$a->getProperty($sortingProperty), (string)$b->getProperty($sortingProperty)) * $sortMultiplier;
}
return 0;
}
);
switch ($sortingOrder) {
case 'random':
case 'rand':
shuffle($this->files);
break;
}
}
}
/**
* Add a file object to the collection.
*/
public function addFileObject(FileInterface $file): void
{
$this->files[] = $file;
}
/**
* Add multiple file objects to the collection.
*
* @param FileInterface[] $files The file objects
*/
public function addFileObjects(array $files): void
{
$this->files = array_merge($this->files, $files);
}
/**
* Final getter method to fetch the accumulated data.
*/
public function getFiles(): array
{
return $this->files;
}
public function count(): int
{
return count($this->files);
}
/**
* Gets file references for a given record field, also deal with translated elements,
* where file references could be attached.
*
* @param string $tableName Name of the table
* @param string $fieldName Name of the field
* @param array $element The parent element referencing to files
*/
protected function getFileReferences(string $tableName, string $fieldName, array $element): array
{
$currentId = !empty($element['uid']) ? (int)$element['uid'] : 0;
// Fetch the references of the default element
try {
$references = $this->fileRepository->findByRelation($tableName, $fieldName, $currentId);
} catch (FileDoesNotExistException $e) {
/**
* We just catch the exception here
* Reasoning: There is nothing an editor or even admin could do
*/
return [];
} catch (\InvalidArgumentException $e) {
/**
* The storage does not exist anymore
* Log the exception message for admins as they maybe can restore the storage
*/
$this->logger->error('{exception_message}: table: {table}, field: {field}, currentId: {current_id}', [
'table' => $tableName,
'field' => $fieldName,
'currentId' => $currentId,
'exception' => $e,
]);
return [];
}
$localizedId = $element['_computed']['localizedUid'] ?? $element['_LOCALIZED_UID'] ?? null;
if ($localizedId !== null && $this->tcaSchemaFactory->get($tableName)->isLanguageAware()) {
$localizedReferences = $this->fileRepository->findByRelation($tableName, $fieldName, (int)$localizedId);
$references = $localizedReferences;
}
return $references;
}
}