[ tableName => [ uid ] ] ] * * @var array>> */ protected array $updateRegistry = []; /** * [ workspaceId => [ tableName => [ * 'uid' => uid, * 'targetWorkspace' => $targetWorkspace * ] ] ] * * @var array>>> */ protected array $updateRegistryToItem = []; /** * [ workspaceId => [ tableName => [ uid ] ] ] * * @var array>> */ 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 = []; } }