TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
<?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\Core\DataHandling;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\ReferenceIndex;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Helper class for DataHandler to gather requests for reference index updates
|
||||
* and perform them in one go after other operations have been done.
|
||||
* This is used to suppress multiple reference index update calls for the same
|
||||
* workspace/table/uid combination within one DataHandler main call.
|
||||
*
|
||||
* @internal should only be used by the TYPO3 Core
|
||||
*/
|
||||
#[Autoconfigure(public: true, shared: false)]
|
||||
class ReferenceIndexUpdater
|
||||
{
|
||||
/**
|
||||
* [ workspaceId => [ tableName => [ uid ] ] ]
|
||||
*
|
||||
* @var array<int, array<string, array<int, int>>>
|
||||
*/
|
||||
protected array $updateRegistry = [];
|
||||
|
||||
/**
|
||||
* [ workspaceId => [ tableName => [
|
||||
* 'uid' => uid,
|
||||
* 'targetWorkspace' => $targetWorkspace
|
||||
* ] ] ]
|
||||
*
|
||||
* @var array<int, array<string, array<int, array<string, string|int>>>>
|
||||
*/
|
||||
protected array $updateRegistryToItem = [];
|
||||
|
||||
/**
|
||||
* [ workspaceId => [ tableName => [ uid ] ] ]
|
||||
*
|
||||
* @var array<int, array<string, array<int, int>>>
|
||||
*/
|
||||
protected array $dropRegistry = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly TcaSchemaFactory $tcaSchemaFactory,
|
||||
private readonly ConnectionPool $connectionPool,
|
||||
private readonly ReferenceIndex $referenceIndex,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Register a workspace/table/uid row for update
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param int $uid Record uid
|
||||
* @param int $workspace Workspace the record lives in
|
||||
*/
|
||||
public function registerForUpdate(string $table, int $uid, int $workspace): void
|
||||
{
|
||||
if ($workspace && !$this->tcaSchemaFactory->get($table)->isWorkspaceAware()) {
|
||||
// If a user is in some workspace and changes relations of not workspace aware
|
||||
// records, the reference index update needs to be performed as if the user
|
||||
// is in live workspace. This is detected here and the update is registered for live.
|
||||
$workspace = 0;
|
||||
}
|
||||
if (!isset($this->updateRegistry[$workspace][$table])) {
|
||||
$this->updateRegistry[$workspace][$table] = [];
|
||||
}
|
||||
if (!in_array($uid, $this->updateRegistry[$workspace][$table], true)) {
|
||||
$this->updateRegistry[$workspace][$table][] = $uid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find reference index rows pointing to given table/uid combination and register them for update. Important in
|
||||
* delete and publish scenarios where a child is deleted to make sure any references to this child are dropped, too.
|
||||
* In publish scenarios reference index may exist for a non-live workspace, but should be updated for live workspace.
|
||||
* The optional $targetWorkspace argument is used for this.
|
||||
*
|
||||
* @param string $table Table name, used as ref_table
|
||||
* @param int $uid Record uid, used as ref_uid
|
||||
* @param int $workspace The workspace given record lives in
|
||||
* @param int|null $targetWorkspace The target workspace the record has been swapped to
|
||||
*/
|
||||
public function registerUpdateForReferencesToItem(string $table, int $uid, int $workspace, ?int $targetWorkspace = null): void
|
||||
{
|
||||
if ($workspace && !$this->tcaSchemaFactory->get($table)->isWorkspaceAware()) {
|
||||
// If a user is in some workspace and changes relations of not workspace aware
|
||||
// records, the reference index update needs to be performed as if the user
|
||||
// is in live workspace. This is detected here and the update is registered for live.
|
||||
$workspace = 0;
|
||||
}
|
||||
if ($targetWorkspace === null) {
|
||||
$targetWorkspace = $workspace;
|
||||
}
|
||||
if (!isset($this->updateRegistryToItem[$workspace][$table])) {
|
||||
$this->updateRegistryToItem[$workspace][$table] = [];
|
||||
}
|
||||
$recordAndTargetWorkspace = [
|
||||
'uid' => $uid,
|
||||
'targetWorkspace' => $targetWorkspace,
|
||||
];
|
||||
if (!in_array($recordAndTargetWorkspace, $this->updateRegistryToItem[$workspace][$table], true)) {
|
||||
$this->updateRegistryToItem[$workspace][$table][] = $recordAndTargetWorkspace;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete rows from sys_refindex a table / uid combination is involved in:
|
||||
* Either on left side (tablename + recuid) OR right side (ref_table + ref_uid).
|
||||
* Useful in scenarios like workspace-discard where parents or children are hard deleted: The
|
||||
* expensive updateRefIndex() does not need to be called since we can just drop straight ahead.
|
||||
*
|
||||
* @param string $table Table name, used as tablename and ref_table
|
||||
* @param int $uid Record uid, used as recuid and ref_uid
|
||||
* @param int $workspace Workspace the record lives in
|
||||
*/
|
||||
public function registerForDrop(string $table, int $uid, int $workspace): void
|
||||
{
|
||||
if ($workspace && !$this->tcaSchemaFactory->get($table)->isWorkspaceAware()) {
|
||||
// If a user is in some workspace and changes relations of not workspace aware
|
||||
// records, the reference index update needs to be performed as if the user
|
||||
// is in live workspace. This is detected here and the update is registered for live.
|
||||
$workspace = 0;
|
||||
}
|
||||
if (!isset($this->dropRegistry[$workspace][$table])) {
|
||||
$this->dropRegistry[$workspace][$table] = [];
|
||||
}
|
||||
if (!in_array($uid, $this->dropRegistry[$workspace][$table], true)) {
|
||||
$this->dropRegistry[$workspace][$table][] = $uid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the reference index update operations
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
// Register updates to an item for update
|
||||
foreach ($this->updateRegistryToItem as $workspace => $tableArray) {
|
||||
foreach ($tableArray as $table => $recordArray) {
|
||||
foreach ($recordArray as $item) {
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_refindex');
|
||||
$statement = $queryBuilder
|
||||
->select('tablename', 'recuid')
|
||||
->from('sys_refindex')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter($table)),
|
||||
$queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($item['uid'], Connection::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('workspace', $queryBuilder->createNamedParameter($workspace, Connection::PARAM_INT))
|
||||
)
|
||||
->executeQuery();
|
||||
while ($row = $statement->fetchAssociative()) {
|
||||
$this->registerForUpdate($row['tablename'], (int)$row['recuid'], (int)$item['targetWorkspace']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->updateRegistryToItem = [];
|
||||
|
||||
// Drop rows from reference index if requested. Note this is performed *after* update-to-item, to
|
||||
// find rows pointing to a record and register updates before rows are dropped. Needed if a record
|
||||
// changes the workspace during publish: In this case all records pointing to the record in a workspace
|
||||
// need to be registered for update for live workspace and after that the workspace rows can be dropped.
|
||||
foreach ($this->dropRegistry as $workspace => $tableArray) {
|
||||
foreach ($tableArray as $table => $uidArray) {
|
||||
foreach ($uidArray as $uid) {
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_refindex');
|
||||
$queryBuilder->delete('sys_refindex')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('workspace', $queryBuilder->createNamedParameter($workspace, Connection::PARAM_INT)),
|
||||
$queryBuilder->expr()->or(
|
||||
$queryBuilder->expr()->and(
|
||||
$queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($table)),
|
||||
$queryBuilder->expr()->eq('recuid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT))
|
||||
),
|
||||
$queryBuilder->expr()->and(
|
||||
$queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter($table)),
|
||||
$queryBuilder->expr()->eq('ref_uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT))
|
||||
)
|
||||
)
|
||||
)
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->dropRegistry = [];
|
||||
|
||||
// Perform reference index updates
|
||||
foreach ($this->updateRegistry as $workspace => $tableArray) {
|
||||
foreach ($tableArray as $table => $uidArray) {
|
||||
foreach ($uidArray as $uid) {
|
||||
$this->referenceIndex->updateRefIndexTable($table, $uid, false, $workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->updateRegistry = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user