Files

255 lines
8.9 KiB
PHP

<?php
/*
* 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\Reports\Report\Status;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FolderInterface;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Resource\Service\ResourceConsistencyService;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Validation\ResultException;
use TYPO3\CMS\Core\Validation\ResultRenderingTrait;
use TYPO3\CMS\Reports\Status;
use TYPO3\CMS\Reports\Status as ReportStatus;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Performs several checks about the FAL status
*/
readonly class FalStatus implements StatusProviderInterface
{
use ResultRenderingTrait;
public function __construct(
private ResourceConsistencyService $resourceConsistencyService,
private ConnectionPool $connectionPool,
private StorageRepository $storageRepository,
) {}
/**
* Determines the status of the FAL index.
*
* @return Status[] List of statuses
*/
public function getStatus(): array
{
return [
'MissingFiles' => $this->getMissingFilesStatus(),
'ConsistencyCheck' => $this->getConsistencyCheckStatus(),
];
}
public function getDetailedStatus(): array
{
return [
'ConsistencyCheck' => $this->getConsistencyCheckStatus(),
];
}
public function getLabel(): string
{
return 'fal';
}
/**
* Checks if there are files marked as missed.
*
* @return \TYPO3\CMS\Reports\Status An object representing whether there are files marked as missed or not
*/
protected function getMissingFilesStatus()
{
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_none');
$count = 0;
$maxFilesToShow = 100;
$message = '';
$severity = ContextualFeedbackSeverity::OK;
$storages = $this->getBrowsableStorages();
if (!empty($storages)) {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file');
$count = $queryBuilder
->count('*')
->from('sys_file')
->where(
$queryBuilder->expr()->eq(
'missing',
$queryBuilder->createNamedParameter(1, Connection::PARAM_INT)
),
$queryBuilder->expr()->in(
'storage',
$queryBuilder->createNamedParameter(array_keys($storages), Connection::PARAM_INT_ARRAY)
)
)
->executeQuery()
->fetchOne();
}
if ($count) {
$value = sprintf($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_missingFilesCount'), $count);
$severity = ContextualFeedbackSeverity::WARNING;
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file');
$files = $queryBuilder
->select('identifier', 'storage')
->from('sys_file')
->where(
$queryBuilder->expr()->eq(
'missing',
$queryBuilder->createNamedParameter(1, Connection::PARAM_INT)
),
$queryBuilder->expr()->in(
'storage',
$queryBuilder->createNamedParameter(array_keys($storages), Connection::PARAM_INT_ARRAY)
)
)
->setMaxResults($maxFilesToShow)
->executeQuery()
->fetchAllAssociative();
$message = '<p>' . $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_missingFilesMessage') . '</p>';
foreach ($files as $file) {
$message .= $storages[$file['storage']]->getName() . ' ' . $file['identifier'] . '<br />';
}
if ($count > $maxFilesToShow) {
$message .= '...<br />';
}
}
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_missingFiles'), $value, $message, $severity);
}
protected function getConsistencyCheckStatus(): ReportStatus
{
// @todo for performance reasons, consider using this only in CLI context as `ExtendedStatusProviderInterface`
$storages = $this->getBrowsableStorages();
$inconsistenciesMessage = '';
foreach ($storages as $storage) {
$inconsistencies = $this->checkFolderConsistency($storage->getRootLevelFolder());
if ($inconsistencies !== []) {
$inconsistenciesMessage .= sprintf(
'<h5>%s</h5>%s',
htmlspecialchars(sprintf(
'Storage "%s" (id:%d)',
$storage->getName(),
$storage->getUid()
)),
$this->wrapInHtmlUnorderedList($inconsistencies)
);
}
}
if ($inconsistenciesMessage === '') {
return new ReportStatus(
'Consistency check',
'No inconsistencies found in these storages',
// make sure we have a list of strings (0… index) with `array_values` to ensure correct rendering
$this->wrapInHtmlUnorderedList(array_values(array_map(
static fn(ResourceStorage $storage): string => sprintf(
'%s (id: %d)',
$storage->getName(),
$storage->getUid()
),
$storages,
))),
ContextualFeedbackSeverity::OK,
);
}
return new ReportStatus(
'Consistency Status',
'Inconsistent files have been found',
$inconsistenciesMessage,
ContextualFeedbackSeverity::ERROR,
);
}
private function checkFolderConsistency(FolderInterface $folder): array
{
$inconsistencies = [];
foreach ($folder->getFiles() as $file) {
if (!$file instanceof File) {
continue;
}
try {
$this->resourceConsistencyService->validate($file->getStorage(), $file);
} catch (ResultException $exception) {
$inconsistencies[$file->getCombinedIdentifier()] = $this->compileResultMessages(
$exception->messages,
$this->getLanguageService()
);
}
}
foreach ($folder->getSubfolders() as $subFolder) {
$inconsistencies = [...$inconsistencies, ...$this->checkFolderConsistency($subFolder)];
}
return $inconsistencies;
}
/**
* @param list<string>|array<string, list<string>> $items
*/
protected function wrapInHtmlUnorderedList(array $items): string
{
if (array_is_list($items)) {
return sprintf(
'<ul>%s</ul>',
implode('', array_map(
static fn(string $item): string => '<li>' . htmlspecialchars($item) . '</li>',
$items
))
);
}
return sprintf(
'<ul>%s</ul>',
implode('', array_map(
fn(string $key, array $values): string => sprintf(
'<li>%s%s</li>',
htmlspecialchars($key),
$this->wrapInHtmlUnorderedList($values)
),
array_keys($items),
array_values($items)
))
);
}
/**
* Filter available storages that are actually browsable
*
* @return array<int,ResourceStorage>
*/
protected function getBrowsableStorages(): array
{
$storages = [];
foreach ($this->storageRepository->findAll() as $storageObject) {
if ($storageObject->isBrowsable()) {
$storages[$storageObject->getUid()] = $storageObject;
}
}
return $storages;
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}