| Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/DataHandling/ |
| Current File : /var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/DataHandling/ReferenceIndexUpdater.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\Core\DataHandling;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\ReferenceIndex;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* 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
*/
class ReferenceIndexUpdater
{
/**
* [ workspaceId => [ tableName => [ uid ] ] ]
*
* @var array<int, array<string, array<int, int>>>
*/
protected $updateRegistry = [];
/**
* [ workspaceId => [ tableName => [
* 'uid' => uid,
* 'targetWorkspace' => $targetWorkspace
* ] ] ]
*
* @var array<int, array<string, array<int, array<string, string|int>>>>
*/
protected $updateRegistryToItem = [];
/**
* [ workspaceId => [ tableName => [ uid ] ] ]
*
* @var array<int, array<string, array<int, int>>>
*/
protected $dropRegistry = [];
/**
* 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 && !BackendUtility::isTableWorkspaceEnabled($table)) {
// 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 && !BackendUtility::isTableWorkspaceEnabled($table)) {
// 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 && !BackendUtility::isTableWorkspaceEnabled($table)) {
// 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 = GeneralUtility::makeInstance(ConnectionPool::class)->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 = GeneralUtility::makeInstance(ConnectionPool::class)->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
$referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
foreach ($this->updateRegistry as $workspace => $tableArray) {
$referenceIndex->setWorkspaceId($workspace);
foreach ($tableArray as $table => $uidArray) {
foreach ($uidArray as $uid) {
$referenceIndex->updateRefIndexTable($table, $uid);
}
}
}
$this->updateRegistry = [];
}
}