Commit 3801adce authored by Christian Kuhn's avatar Christian Kuhn
Browse files

[BUGFIX] Better sys_refindex with workspace mm

This fixes issues regarding sys_refindex handling when dealing
with workspace mm relations. Various DataHandler tests run with
disabled refindex check after performing mm related operations.
All those are enabled now, so all known issues in this area
are fixed.

The patch touches various areas in DataHandler, ReferenceIndex
and RelationHandler to achieve this.

The key changes are triggered by a database scenario that is
unique for mm relations. Usually, when creating a workspace record
or overlay, child record overlays (for instance inline children)
are also created in this workspace.

For mm records, this can be different: When the local or foreign
side of a record is created in a workspace, it does not necessarily
mean that a connected opposite side is also created as workspace
overlay.

This would otherwise create the need for a recursive operation that
would basically end up with "everything" being created as workspace
overlay. However, mm table entries are created for these relations,
leading to a situation that a workspace record can point to a
non-workspace record through the mm table.

Reference index entries for mm table connections are always handled
from the 'local' side. For instance, with categories, 'sys_category'
table is the local side, with 'pages', 'tt_content' and others
being the foreign side. If now one of the records on the foreign
side gets a workspace overlay record, sys_refindex rows of all
connected records of the affected local side need to be created.
This can lead to the funny situation that we end up with refindex
rows that point from a non-workspace to a non-workspace record,
but have a non-0 workspace entry. Those additional rows are needed
to have "the full set" and proper sorting when looking at refindex
of such a relation from the local side.

The patch basically handles especially these 'foreign side has
a workspace overlay' scenarios, plus the side effects that
have to be taken care of when discarding and publishing these
records.

Additionally, a couple of side effects are tackled: First, the
ReferenceIndex->updateIndex() - that's the main logic when
running cli referenceindex:update command - is tuned to drop
reference index entries related to meanwhile deleted workspaces.
This is covered with additional tests and is done now since
the code needs to iterate over existing workspaces for local-side
mm records that may have workspace overlays on the foreign side.
So most of the code had to be created now anyways.

RelationHandler->readMM() receives a fix for a long standing bug
(Issue #83582, introduced by #57169), to no longer show workspace
relations in live when looking at local-side records, for instance
when looking at category items in live when one of the connected
items has a workspace overlay.

Next to lots of comments explaining details in place and
referencing the test cases that nail specific scenarios, a
couple of @todo's are added to point out things we may
want to tackle in the future.

All in all, the patch resolves a series of issues that will
especially lead to 'categories' being much more reliable in
workspaces.

Change-Id: I24407f93665852fa761f6fbe6c5ab249473468d2
Related: #57169
Resolves: #83582
Resolves: #96067
Releases: master, 11.5
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72250

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 7958bf70
......@@ -5703,7 +5703,7 @@ class DataHandler implements LoggerAwareInterface
}
}
} elseif ($this->isReferenceField($fieldConfig) && !empty($fieldConfig['MM'])) {
$this->discardMmRelations($fieldConfig, $record);
$this->discardMmRelations($table, $fieldConfig, $record);
}
// @todo not inline and not mm - probably not handled correctly and has no proper test coverage yet
}
......@@ -5713,10 +5713,11 @@ class DataHandler implements LoggerAwareInterface
* When a workspace record row is discarded that has mm relations, existing mm table rows need
* to be deleted. The method performs the delete operation depending on TCA field configuration.
*
* @param string $table Table name of this record
* @param array $fieldConfig TCA configuration of this field
* @param array $record The full record of a left- or ride-side relation
*/
protected function discardMmRelations(array $fieldConfig, array $record): void
protected function discardMmRelations(string $table, array $fieldConfig, array $record): void
{
$recordUid = (int)$record['uid'];
$mmTableName = $fieldConfig['MM'];
......@@ -5745,6 +5746,14 @@ class DataHandler implements LoggerAwareInterface
);
}
$queryBuilder->execute();
// refindex treatment for mm relation handling: If the to discard record is foreign side of an mm relation,
// there may be other refindex rows that become obsolete when that record is discarded. See Modify
// addCategoryRelation sys_category-29->tt_content-298. We thus register an update for references
// to this item (right side - ref_table, ref_uid) in reference index updater to catch these.
if ($relationUidFieldName === 'uid_foreign') {
$this->referenceIndexUpdater->registerUpdateForReferencesToItem($table, $recordUid, (int)$record['t3ver_wsid']);
}
}
/**
......@@ -5877,7 +5886,7 @@ class DataHandler implements LoggerAwareInterface
*
* @internal should only be used from within DataHandler
*/
public function versionPublishManyToManyRelations(string $table, array $liveRecord, array $workspaceRecord): void
public function versionPublishManyToManyRelations(string $table, array $liveRecord, array $workspaceRecord, int $fromWorkspace): void
{
if (!is_array($GLOBALS['TCA'][$table]['columns'])) {
return;
......@@ -5938,7 +5947,8 @@ class DataHandler implements LoggerAwareInterface
// Update mm table relations of workspace record to uid of live record
foreach ($toUpdateRegistry as $config) {
$uidFieldName = $this->mmRelationIsLocalSide($config) ? 'uid_local' : 'uid_foreign';
$mmRelationIsLocalSide = $this->mmRelationIsLocalSide($config);
$uidFieldName = $mmRelationIsLocalSide ? 'uid_local' : 'uid_foreign';
$mmTableName = $config['MM'];
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($mmTableName);
$queryBuilder->update($mmTableName);
......@@ -5954,6 +5964,17 @@ class DataHandler implements LoggerAwareInterface
));
}
$queryBuilder->execute();
if (!$mmRelationIsLocalSide) {
// refindex treatment for mm relation handling: If the to publish record is foreign side of an mm relation, we need
// to instruct refindex updater to update all local side references for the live record the current workspace record
// has on foreign side. See ManyToMany Publish addCategoryRelation, this will create the sys_category-31->tt_content-297 entry.
$this->referenceIndexUpdater->registerUpdateForReferencesToItem($table, (int)$workspaceRecord['uid'], $fromWorkspace, 0);
// Similar, when in mm foreign side and relations are deleted in live during publish, other relations pointing to the
// same local side record may need updates due to different sorting, and the former refindex entry of the live record
// needs updates. See ManyToMany Publish deleteCategoryRelation scenario.
$this->referenceIndexUpdater->registerUpdateForReferencesToItem($table, (int)$liveRecord['uid'], 0);
}
}
}
......@@ -7452,12 +7473,24 @@ class DataHandler implements LoggerAwareInterface
* @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
* @internal should only be used from within DataHandler
*/
public function registerReferenceIndexRowsForDrop(string $table, int $uid, int $workspace): void
{
$this->referenceIndexUpdater->registerForDrop($table, $uid, $workspace);
}
/**
* Helper method to access referenceIndexUpdater->registerUpdateForReferencesToItem()
* from within workspace DataHandlerHook.
*
* @internal Exists only for workspace DataHandlerHook. May vanish any time.
*/
public function registerReferenceIndexUpdateForReferencesToItem(string $table, int $uid, int $workspace, int $targetWorkspace = null): void
{
$this->referenceIndexUpdater->registerUpdateForReferencesToItem($table, $uid, $workspace, $targetWorkspace);
}
/*********************************************
*
* Misc functions
......
......@@ -82,13 +82,13 @@ class ReferenceIndexUpdater
/**
* 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.
* 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 $targetWorkspace The target workspace the record has been swapped to
* @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
{
......
......@@ -24,25 +24,19 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\View\ProgressListenerInterface;
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\DataHandling\Event\IsTableExcludedFromReferenceIndexEvent;
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Reference index processing and relation extraction
*
* NOTICE: When the reference index is updated for an offline version the results may not be correct.
* First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyze whole database!)
* Secondly, lets assume that in a Draft workspace you have changed the data structure of a parent page record - this is (in TemplaVoila) inherited by subpages.
* When in the LIVE workspace the data structure for the records/pages in the offline workspace will not be evaluated to the right one simply because the data
* structure is taken from a rootline traversal and in the Live workspace that will NOT include the changed DataStructure! Thus the evaluation will be based
* on the Data Structure set in the Live workspace!
* Somehow this scenario is rarely going to happen. Yet, it is an inconsistency and I see now practical way to handle it - other than simply ignoring
* maintaining the index for workspace records. Or we can say that the index is precise for all Live elements while glitches might happen in an offline workspace?
* Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
* @internal Extensions shouldn't fiddle with the reference index themselves, it's task of DataHandler to do this.
*/
class ReferenceIndex implements LoggerAwareInterface
{
......@@ -359,11 +353,13 @@ class ReferenceIndex implements LoggerAwareInterface
*/
protected function createEntryDataUsingRecord(string $tableName, array $record, string $fieldName, string $flexPointer, string $referencedTable, int $referencedUid, string $referenceString = '', int $sort = -1, string $softReferenceKey = '', string $softReferenceId = '')
{
$workspaceId = 0;
$currentWorkspace = $this->getWorkspaceId();
if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
$workspaceId = $this->getWorkspaceId();
if (isset($record['t3ver_wsid']) && (int)$record['t3ver_wsid'] !== $workspaceId) {
// The given record is workspace-enabled but doesn't live in the selected workspace => don't add index as it's not actually there
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
if (isset($record['t3ver_wsid']) && (int)$record['t3ver_wsid'] !== $currentWorkspace && empty($fieldConfig['MM'])) {
// The given record is workspace-enabled but doesn't live in the selected workspace. Don't add index, it's not actually there.
// We still add those rows if the record is a local side live record of an MM relation and can be a target of a workspace record.
// See workspaces ManyToMany Modify addCategoryRelation for details on this case.
return false;
}
}
......@@ -375,7 +371,7 @@ class ReferenceIndex implements LoggerAwareInterface
'softref_key' => $softReferenceKey,
'softref_id' => $softReferenceId,
'sorting' => $sort,
'workspace' => $workspaceId,
'workspace' => $currentWorkspace,
'ref_table' => $referencedTable,
'ref_uid' => $referencedUid,
'ref_string' => mb_substr($referenceString, 0, 1024),
......@@ -458,7 +454,7 @@ class ReferenceIndex implements LoggerAwareInterface
$conf['softref'] = 'typolink';
}
// Add DB:
$resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table);
$resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table, $row);
if (!empty($resultsFromDatabase)) {
// Create an entry for the field with all DB relations:
$outRow[$field] = [
......@@ -567,9 +563,10 @@ class ReferenceIndex implements LoggerAwareInterface
* @param array $conf Field configuration array of type "TCA/columns
* @param int $uid Field uid
* @param string $table Table name
* @param array $row
* @return array|bool If field type is OK it will return an array with the database relations. Else FALSE
*/
protected function getRelations_procDB($value, $conf, $uid, $table = '')
protected function getRelations_procDB($value, $conf, $uid, $table = '', array $row = [])
{
// Get IRRE relations
if (empty($conf)) {
......@@ -586,11 +583,36 @@ class ReferenceIndex implements LoggerAwareInterface
if ($this->isDbReferenceField($conf)) {
$allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
if ($conf['MM_opposite_field'] ?? false) {
// Never handle sys_refindex when looking at MM from foreign side
return [];
}
$dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
$dbAnalysis->setWorkspaceId($this->getWorkspaceId());
$dbAnalysis->start($value, $allowedTables, $conf['MM'] ?? '', $uid, $table, $conf);
return $dbAnalysis->itemArray;
$itemArray = $dbAnalysis->itemArray;
if (ExtensionManagementUtility::isLoaded('workspaces')
&& $this->getWorkspaceId() > 0
&& !empty($conf['MM'] ?? '')
&& !empty($conf['allowed'] ?? '')
&& empty($conf['MM_opposite_field'] ?? '')
&& (int)($row['t3ver_wsid'] ?? 0) === 0
) {
// When dealing with local side mm relations in workspace 0, there may be workspace records on the foreign
// side, for instance when those got an additional category. See ManyToMany Modify addCategoryRelations test.
// In those cases, the full set of relations must be written to sys_refindex as workspace rows.
// But, if the relations in this workspace and live are identical, no sys_refindex workspace rows
// have to be added.
$dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
$dbAnalysis->setWorkspaceId(0);
$dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
$itemArrayLive = $dbAnalysis->itemArray;
if ($itemArrayLive === $itemArray) {
$itemArray = false;
}
}
return $itemArray;
}
return false;
}
......@@ -890,18 +912,34 @@ class ReferenceIndex implements LoggerAwareInterface
* @param bool $testOnly If set, only a test
* @param ProgressListenerInterface|null $progressListener If set, the current progress is added to the listener
* @return array Header and body status content
* @todo: Consider moving this together with the helper methods to a dedicated class.
*/
public function updateIndex($testOnly, ?ProgressListenerInterface $progressListener = null)
{
$errors = [];
$tableNames = [];
$recCount = 0;
// Traverse all tables:
$isWorkspacesLoaded = ExtensionManagementUtility::isLoaded('workspaces');
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$refIndexConnectionName = empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']['sys_refindex'])
? ConnectionPool::DEFAULT_CONNECTION_NAME
: $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']['sys_refindex'];
// Drop sys_refindex rows from deleted workspaces
$listOfActiveWorkspaces = $this->getListOfActiveWorkspaces();
$unusedWorkspaceRows = $this->getAmountOfUnusedWorkspaceRowsInReferenceIndex($listOfActiveWorkspaces);
if ($unusedWorkspaceRows > 0) {
$error = 'Index table hosted ' . $unusedWorkspaceRows . ' indexes for non-existing or deleted workspaces, now removed.';
$errors[] = $error;
if ($progressListener) {
$progressListener->log($error, LogLevel::WARNING);
}
if (!$testOnly) {
$this->removeUnusedWorkspaceRowsFromReferenceIndex($listOfActiveWorkspaces);
}
}
// Main loop traverses all records of all TCA tables
foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
if ($this->shouldExcludeTableFromReferenceIndex($tableName)) {
continue;
......@@ -910,6 +948,18 @@ class ReferenceIndex implements LoggerAwareInterface
? ConnectionPool::DEFAULT_CONNECTION_NAME
: $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
// Some additional magic is needed if the table has a field that is the local side of
// a mm relation. See the variable usage below for details.
$tableHasLocalSideMmRelation = false;
foreach (($cfg['columns'] ?? []) as $fieldConfig) {
if (!empty($fieldConfig['config']['MM'] ?? '')
&& !empty($fieldConfig['config']['allowed'] ?? '')
&& empty($fieldConfig['config']['MM_opposite_field'] ?? '')
) {
$tableHasLocalSideMmRelation = true;
}
}
$fields = ['uid'];
if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
$fields[] = 't3ver_wsid';
......@@ -940,18 +990,38 @@ class ReferenceIndex implements LoggerAwareInterface
if ($progressListener) {
$progressListener->advance();
}
/** @var ReferenceIndex $refIndexObj */
$refIndexObj = GeneralUtility::makeInstance(self::class);
if (isset($record['t3ver_wsid'])) {
$refIndexObj->setWorkspaceId($record['t3ver_wsid']);
}
$result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
$recCount++;
if ($result['addedNodes'] || $result['deletedNodes']) {
$error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
$errors[] = $error;
if ($progressListener) {
$progressListener->log($error, LogLevel::WARNING);
if ($isWorkspacesLoaded && $tableHasLocalSideMmRelation && (int)($record['t3ver_wsid'] ?? 0) === 0) {
// If we have record that can be the local side of a workspace relation, workspace records
// may point to it, even though the record has no workspace overlay. See workspace ManyToMany
// Modify addCategoryRelation as example. In those cases, we need to iterate all active workspaces
// and update refindex for all foreign workspace records that point to it.
foreach ($listOfActiveWorkspaces as $workspaceId) {
$refIndexObj = GeneralUtility::makeInstance(self::class);
$refIndexObj->setWorkspaceId($workspaceId);
$result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
$recCount++;
if ($result['addedNodes'] || $result['deletedNodes']) {
$error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
$errors[] = $error;
if ($progressListener) {
$progressListener->log($error, LogLevel::WARNING);
}
}
}
} else {
$refIndexObj = GeneralUtility::makeInstance(self::class);
if (isset($record['t3ver_wsid'])) {
$refIndexObj->setWorkspaceId($record['t3ver_wsid']);
}
$result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
$recCount++;
if ($result['addedNodes'] || $result['deletedNodes']) {
$error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
$errors[] = $error;
if ($progressListener) {
$progressListener->log($error, LogLevel::WARNING);
}
}
}
}
......@@ -960,6 +1030,13 @@ class ReferenceIndex implements LoggerAwareInterface
}
// Subselect based queries only work on the same connection
// @todo: Consider dropping this in v12 and always use sub select: The base set of tables should
// be in exactly one DB and only tables like caches should be "extractable" to a different DB?!
// Even though sys_refindex is a "cache-like" table since it only holds secondary information that
// can always be re-created by analyzing the entire data set, it shouldn't be possible to run it
// on a different database since that prevents quick joins between sys_refindex and target relations.
// We should probably have some report and/or install tool check to make sure all main tables
// are on the same connection in v12.
if ($refIndexConnectionName !== $tableConnectionName) {
$this->logger->error('Not checking table {table_name} for lost indexes, "sys_refindex" table uses a different connection', ['table_name' => $tableName]);
continue;
......@@ -1017,6 +1094,8 @@ class ReferenceIndex implements LoggerAwareInterface
}
// Searching lost indexes for non-existing tables
// @todo: Consider moving this *before* the main re-index logic to have a smaller
// dataset when starting with heavy lifting.
$lostTables = $this->getAmountOfUnusedTablesInReferenceIndex($tableNames);
if ($lostTables > 0) {
$error = 'Index table hosted ' . $lostTables . ' indexes for non-existing tables, now removed';
......@@ -1044,6 +1123,69 @@ class ReferenceIndex implements LoggerAwareInterface
return ['resultText' => trim($recordsCheckedString), 'errors' => $errors];
}
/**
* Helper method of updateIndex().
* Create list of non-deleted "active" workspace uid's. This contains at least 0 "live workspace".
*
* @return int[]
*/
private function getListOfActiveWorkspaces(): array
{
if (!ExtensionManagementUtility::isLoaded('workspaces')) {
// If ext:workspaces is not loaded, "0" is the only valid one.
return [0];
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
// There are no "hidden" workspaces, which wouldn't make much sense anyways.
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder->select('uid')->from('sys_workspace')->orderBy('uid')->execute();
// "0", plus non-deleted workspaces are active
$activeWorkspaces = [0];
while ($row = $result->fetchFirstColumn()) {
$activeWorkspaces[] = (int)$row[0];
}
return $activeWorkspaces;
}
/**
* Helper method of updateIndex() to find number of rows in sys_refindex that
* relate to a non-existing or deleted workspace record, even if workspaces is
* not loaded at all, but has been loaded somewhere in the past and sys_refindex
* rows have been created.
*/
private function getAmountOfUnusedWorkspaceRowsInReferenceIndex(array $activeWorkspaces): int
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
$numberOfInvalidWorkspaceRecords = $queryBuilder->count('hash')
->from('sys_refindex')
->where(
$queryBuilder->expr()->notIn(
'workspace',
$queryBuilder->createNamedParameter($activeWorkspaces, Connection::PARAM_INT_ARRAY)
)
)
->execute()
->fetchOne();
return (int)$numberOfInvalidWorkspaceRecords;
}
/**
* Pair method of getAmountOfUnusedWorkspaceRowsInReferenceIndex() to actually delete
* sys_refindex rows of deleted workspace records, or all if ext:workspace is not loaded.
*/
private function removeUnusedWorkspaceRowsFromReferenceIndex(array $activeWorkspaces): void
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
$queryBuilder->delete('sys_refindex')
->where(
$queryBuilder->expr()->notIn(
'workspace',
$queryBuilder->createNamedParameter($activeWorkspaces, Connection::PARAM_INT_ARRAY)
)
)
->execute();
}
protected function getAmountOfUnusedTablesInReferenceIndex(array $tableNames): int
{
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
......
......@@ -512,6 +512,11 @@ class RelationHandler
/**
* Reads the record tablename/id into the internal arrays itemArray and tableArray from MM records.
*
* @todo: The source record is not checked for correct workspace. Say there is a category 5 in
* workspace 1. setWorkspace(0) is called, after that readMM('sys_category_record_mm', 5 ...).
* readMM will *still* return the list of records connected to this workspace 1 item,
* even though workspace 0 has been set.
*
* @param string $tableName MM Tablename
* @param int|string $uid Local UID
* @param string $mmOppositeTable Opposite table name
......@@ -1377,8 +1382,15 @@ class RelationHandler
}
/**
* @todo: It *should* be possible to drop all three 'purge' methods by using
* a clever join within readMM - that sounds doable now with pid -1 and
* ws-pair records being gone since v11. It would resolve this indirect
* callback logic and would reduce some queries. The (workspace) mm tests
* should be complete enough now to verify if a change like that would do.
*
* @param int|null $workspaceId
* @return bool Whether items have been purged
* @internal
*/
public function purgeItemArray($workspaceId = null)
{
......@@ -1470,7 +1482,7 @@ class RelationHandler
->from($tableName)
->where(
$queryBuilder->expr()->in(
't3ver_oid',
'uid',
$queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
),
$queryBuilder->expr()->neq(
......
......@@ -26,6 +26,7 @@
,298,89,512,0,0,0,0,0,0,0,0,"Regular Element #2",0,2
"sys_refindex",,,,,,,,,,,,,,
,"hash","tablename","recuid","field","flexpointer","softref_key","softref_id","sorting","workspace","ref_table","ref_uid","ref_string",,
# @todo: Broken. Both 28->297 and 29->297 have sorting 0 here and in mm table ... sys_refindex has no sorting_foreign
,"3c637501ab9c158daa933643bff8cc43","sys_category",28,"items",,,,0,0,"tt_content",297,,,
,"e19100d609a435906e16432a41b55c72","sys_category",29,"items",,,,0,0,"tt_content",297,,,
,"aabda97b66f9b693f30d1faf442b37d6","sys_category",29,"items",,,,1,0,"tt_content",298,,,
......
"pages",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","tx_testirreforeignfield_hotels",,,,,,
,1,0,256,0,0,0,0,0,0,"FunctionalTest",0,,,,,,
,88,1,256,0,0,0,0,0,0,"DataHandlerTest",0,,,,,,
,89,88,256,0,0,0,0,0,0,"Relations",1,,,,,,
"tt_content",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","header","image","tx_testirreforeignfield_hotels",,,
,297,89,256,0,0,0,0,0,0,0,0,"Regular Element #1",0,1,,,
"tx_testirreforeignfield_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l18n_diffsource","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","parentid","parenttable","parentidentifier","offers"
,3,89,1,0,0,0,,0,0,0,0,0,"Hotel #1",297,"tt_content","1nff.hotels",0
"sys_refindex",,,,,,,,,,,,,,,,,
,"hash","tablename","recuid","field","flexpointer","softref_key","softref_id","sorting","workspace","ref_table","ref_uid","ref_string",,,,,
,"b9be8f0166b062001c138957270e72e2","tt_content",297,"tx_testirreforeignfield_hotels",,,,0,0,"tx_testirreforeignfield_hotel",3,,,,,,
# workspace extension not loaded - ws 1 entry should be removed
,"iShouldBeRemoved1c138957270e72e2","tt_content",297,"tx_testirreforeignfield_hotels",,,,0,1,"tx_testirreforeignfield_hotel",3,,,,,,
"pages",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","tx_testirreforeignfield_hotels",,,,,,
,1,0,256,0,0,0,0,0,0,"FunctionalTest",0,,,,,,
,88,1,256,0,0,0,0,0,0,"DataHandlerTest",0,,,,,,
,89,88,256,0,0,0,0,0,0,"Relations",1,,,,,,
"tt_content",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","header","image","tx_testirreforeignfield_hotels",,,
,297,89,256,0,0,0,0,0,0,0,0,"Regular Element #1",0,1,,,
"tx_testirreforeignfield_hotel",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l18n_diffsource","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","parentid","parenttable","parentidentifier","offers"
,3,89,1,0,0,0,,0,0,0,0,0,"Hotel #1",297,"tt_content","1nff.hotels",0
"sys_refindex",,,,,,,,,,,,,,,,,
,"hash","tablename","recuid","field","flexpointer","softref_key","softref_id","sorting","workspace","ref_table","ref_uid","ref_string",,,,,
,"b9be8f0166b062001c138957270e72e2","tt_content",297,"tx_testirreforeignfield_hotels",,,,0,0,"tx_testirreforeignfield_hotel",3,,,,,,
"pages",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title",,,,,,,,
,1,0,256,0,0,0,0,0,"FunctionalTest",,,,,,,,
,88,1,256,0,0,0,0,0,"DataHandlerTest",,,,,,,,
,89,88,256,0,0,0,0,0,"Relations",,,,,,,,
"sys_workspace",,,,,,,,,,,,,,,,,
,"uid","pid","deleted","title","adminusers","members","db_mountpoints","file_mountpoints","freeze","live_edit","publish_access","custom_stages","stagechg_notification","edit_notification_defaults","edit_allow_notificaton_settings","publish_notification_defaults","publish_allow_notificaton_settings"
,1,0,0,"Workspace #1",,,,,0,0,0,0,0,0,0,0,0
"sys_category",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l10n_parent","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","parent","items",,,,
,28,0,256,0,0,0,0,0,0,0,"Category A",0,0,,,,
,29,0,512,0,0,0,0,0,0,0,"Category B",0,0,,,,
,30,0,768,0,0,0,0,0,0,0,"Category C",0,0,,,,
,31,0,1024,0,0,0,0,0,0,0,"Category A.A",28,0,,,,
"sys_category_record_mm",,,,,,,,,,,,,,,,,
,"uid_local","uid_foreign","tablenames","sorting","sorting_foreign","fieldname",,,,,,,,,,,
,28,297,"tt_content",0,1,"categories",,,,,,,,,,,
,29,297,"tt_content",0,2,"categories",,,,,,,,,,,
,29,298,"tt_content",0,1,"categories",,,,,,,,,,,
,30,298,"tt_content",0,2,"categories",,,,,,,,,,,
,28,299,"tt_content",0,1,"categories",,,,,,,,,,,
,29,299,"tt_content",0,2,"categories",,,,,,,,,,,
,31,299,"tt_content",0,3,"categories",,,,,,,,,,,
"tt_content",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","header","image","categories",,,,
,297,89,256,0,0,0,0,0,0,0,"Regular Element #1",0,2,,,,
,298,89,512,0,0,0,0,0,0,0,"Regular Element #2",0,2,,,,
,299,89,256,0,0,0,1,0,0,297,"Regular Element #1",0,3,,,,
"sys_refindex",,,,,,,,,,,,,,,,,
,"hash","tablename","recuid","field","flexpointer","softref_key","softref_id","sorting","workspace","ref_table","ref_uid","ref_string",,,,,
# Should be removed - it is a workspace entry with local & foreign not having ws overlays and there is no other relation to a workspace record
,"d624da48d7d6427f385a8197cb90c391","sys_category",30,"items",,,,0,1,"tt_content",298,,,,,,
"pages",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title",,,,,,,,
,1,0,256,0,0,0,0,0,"FunctionalTest",,,,,,,,
,88,1,256,0,0,0,0,0,"DataHandlerTest",,,,,,,,
,89,88,256,0,0,0,0,0,"Relations",,,,,,,,
"sys_workspace",,,,,,,,,,,,,,,,,
,"uid","pid","deleted","title","adminusers","members","db_mountpoints","file_mountpoints","freeze","live_edit","publish_access","custom_stages","stagechg_notification","edit_notification_defaults","edit_allow_notificaton_settings","publish_notification_defaults","publish_allow_notificaton_settings"
,1,0,0,"Workspace #1",,,,,0,0,0,0,0,0,0,0,0
"sys_category",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l10n_parent","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","parent","items",,,,
,28,0,256,0,0,0,0,0,0,0,"Category A",0,0,,,,
,29,0,512,0,0,0,0,0,0,0,"Category B",0,0,,,,
,30,0,768,0,0,0,0,0,0,0,"Category C",0,0,,,,
,31,0,1024,0,0,0,0,0,0,0,"Category A.A",28,0,,,,
"sys_category_record_mm",,,,,,,,,,,,,,,,,
,"uid_local","uid_foreign","tablenames","sorting","sorting_foreign","fieldname",,,,,,,,,,,
,28,297,"tt_content",0,1,"categories",,,,,,,,,,,
,29,297,"tt_content",0,2,"categories",,,,,,,,,,,
,29,298,"tt_content",0,1,"categories",,,,,,,,,,,
,30,298,"tt_content",0,2,"categories",,,,,,,,,,,
,28,299,"tt_content",0,1,"categories",,,,,,,,,,,
,29,299,"tt_content",0,2,"categories",,,,,,,,,,,
,31,299,"tt_content",0,3,"categories",,,,,,,,,,,
"tt_content",,,,,,,,,,,,,,,,,
,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","header","image","categories",,,,
,297,89,256,0,0,0,0,0,0,0,"Regular Element #1",0,2,,,,
,298,89,512,0,0,0,0,0,0,0,"Regular Element #2",0,2,,,,
,299,89,256,0,0,0,1,0,0,297,"Regular Element #1",0,3,,,,
"sys_refindex",,,,,,,,,,,,,,,,,
,"hash","tablename","recuid","field","flexpointer","softref_key","softref_id","sorting","workspace","ref_table","ref_uid","ref_string",,,,,
,"1b70a8e25c22645f7a49a79f57f3cf3f","sys_category",31,"parent",,,,0,0,"sys_category",28,,,,,,
,"3c637501ab9c158daa933643bff8cc43","sys_category",28,"items",,,,0,0,"tt_content",297,,,,,,
,"aabda97b66f9b693f30d1faf442b37d6","sys_category",29,"items",,,,1,0,"tt_content",298,,,,,,
,"b102e2f9b1ed99b14d813363199cb281","sys_category",30,"items",,,,0,0,"tt_content",298,,,,,,
,"e19100d609a435906e16432a41b55c72","sys_category",29,"items",,,,0,0,"tt_content",297,,,,,,
,"7d23196104341ffb4a15728711c8da57","sys_category",28,"items",,,,1,1,"tt_content",299,,,,,,
,"6824a5635d464123a7a91770440e1d23","sys_category",29,"items",,,,1,1,"tt_content",298,,,,,,