Files
cms-form/Classes/Service/DatabaseService.php
T

220 lines
9.2 KiB
PHP

<?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\Form\Service;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Form\Domain\Repository\FormDefinitionRepository;
/**
* This class is subjected to change.
* **Do NOT subclass**
*
* Scope: frontend / backend
* @internal
*/
class DatabaseService
{
public function __construct(
private readonly ResourceFactory $resourceFactory,
private readonly ConnectionPool $connectionPool,
) {}
/**
* Returns an array with all sys_refindex database rows which be
* connected to a formDefinition identified by $persistenceIdentifier
*
* $persistenceIdentifier string can contain:
* - number -> interpreted as a sys_file reference UID to a FAL-stored YAML file (user-generated content)
* - EXT:... -> interpreted as a NON-FAL extension-based file
* - any string -> interpreted as FAL-based filename
*
* Note that we explicitly do NOT check for file existence here,
* because we want to be able to reveal sys_refindex entries to files
* that have been deleted meanwhile!
*
* @internal
*/
public function getReferencesByPersistenceIdentifier(string $persistenceIdentifier): array
{
if (empty($persistenceIdentifier)) {
throw new \InvalidArgumentException('$persistenceIdentifier must not be empty.', 1472238493);
}
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_refindex');
$constraints = [$queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('formPersistenceIdentifier'))];
// Indicator whether the string-based lookup in sys_refindex shall be performed (true; non FAL-based) or not (false; FAL-based)
$useStringReference = false;
// Check what $persistenceIdentifier contains.
if (PathUtility::isExtensionPath($persistenceIdentifier)) {
// Uses "EXT:" notation, so it cannot be a FAL identifier.
// We pass the whole "EXT:..." lookup through to the sys_refindex query
// due to its constraint on softref_key=formPersistenceIdentifier,
// we expect no false entries even with "weird" string notations. If sys_refindex
// has it, we yield it.
$useStringReference = true;
} elseif (MathUtility::canBeInterpretedAsInteger($persistenceIdentifier)) {
$constraints[] = $queryBuilder->expr()->or(
$queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($persistenceIdentifier)),
$queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($persistenceIdentifier, Connection::PARAM_INT))
);
} else {
// Anything else would be either a notation like "/fileadmin/something.form.yaml"
// or a numeric identifier for a sys_file.
try {
// We use this "bulk method" because this is the best-bet from resourceFactory
// to resolve both an integer-ish input value or a FAL value. There is no
// substitute for an "only get a file, not a directory" lookup.
$file = $this->resourceFactory->retrieveFileOrFolderObject($persistenceIdentifier);
if ($file === null) {
// The associated identifier could (no longer) be retrieved via FAL.
// However, we do want to see existing entries to such stale entries to
// be able to reveal bad references, either by its ref_string or ref_uid
$useStringReference = true;
} elseif ($file instanceof File) {
// We succeeded in retrieving the FAL file object.
$constraints[] = $queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($file->getUid(), Connection::PARAM_INT));
} else {
// We might have retrieved a "Folder" object. Fall back to passthrough
// with the intent, to retrieve all possible sys_refindex entries.
// If that fails, it's ok to return an empty array.
$useStringReference = true;
}
} catch (ResourceDoesNotExistException) {
// This exception gets triggered when $persistenceIdentifier is not something
// that could be resolved by the bulk-method.
// As above, we want to retrieve all the possible sys_refindex entries,
// so we fall back again to "ref_string".
// This should happen when $persistenceIdentifier is set to a string like '/fileadmin/somefile.form.yaml',
// and a FAL storage could be retrieved, but not the actual file.
$useStringReference = true;
}
}
if ($useStringReference) {
$constraints[] = $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($persistenceIdentifier));
}
return $queryBuilder
->select('*')
->from('sys_refindex')
->where(...$constraints)
->executeQuery()
->fetchAllAssociative();
}
/**
* Returns an array with all form definition persistenceIdentifiers
* as keys and their reference counts as values.
*
* @internal
*/
public function getAllReferencesForPersistenceIdentifier(): array
{
$items = [];
foreach ($this->getAllReferences('ref_string') as $item) {
$items[$item['identifier']] = $item['items'];
}
return $items;
}
/**
* Returns an array with all form definition file uids as keys
* and their reference counts as values.
*
* @internal
*/
public function getAllReferencesForFileUid(): array
{
$items = [];
foreach ($this->getAllReferences('ref_uid') as $item) {
$items[$item['identifier']] = $item['items'];
}
return $items;
}
/**
* Returns an array with all database-stored form definition UIDs as keys
* and their reference counts as values.
*
* These are tracked in sys_refindex via ref_table='form_definition' and ref_uid=<form_definition UID>.
*
* @return array<string, int> persistenceIdentifier (UID as string) => reference count
* @internal
*/
public function getAllReferencesForFormDefinitionUid(): array
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_refindex');
$rows = $queryBuilder
->select('ref_uid AS identifier')
->addSelectLiteral('COUNT(' . $queryBuilder->quoteIdentifier('ref_uid') . ') AS ' . $queryBuilder->quoteIdentifier('items'))
->from('sys_refindex')
->where(
$queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('formPersistenceIdentifier')),
$queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter(FormDefinitionRepository::TABLE_NAME)),
$queryBuilder->expr()->gt('ref_uid', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT))
)
->groupBy('ref_uid')
->executeQuery()
->fetchAllAssociative();
$items = [];
foreach ($rows as $row) {
$items[(string)$row['identifier']] = (int)$row['items'];
}
return $items;
}
protected function getAllReferences(string $column): array
{
if ($column !== 'ref_string' && $column !== 'ref_uid') {
throw new \InvalidArgumentException('$column must be "ref_string" or "ref_uid".', 1535406600);
}
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_refindex');
$constraints = [
$queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('formPersistenceIdentifier')),
];
if ($column === 'ref_string') {
$constraints[] = $queryBuilder->expr()->neq('ref_string', $queryBuilder->createNamedParameter(''));
} else {
$constraints[] = $queryBuilder->expr()->gt('ref_uid', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT));
}
return $queryBuilder
->select($column . ' AS identifier')
->addSelectLiteral('COUNT(' . $queryBuilder->quoteIdentifier($column) . ') AS ' . $queryBuilder->quoteIdentifier('items'))
->from('sys_refindex')
->where(...$constraints)
->groupBy($column)
->executeQuery()
->fetchAllAssociative();
}
}