2 Commits

2 changed files with 119 additions and 12 deletions

View File

@@ -149,11 +149,25 @@ final class DummyCopier
$newArticleId = $this->insertRow('tl_article', $articleRow); $newArticleId = $this->insertRow('tl_article', $articleRow);
$contentIds = $this->connection->fetchFirstColumn('SELECT id FROM tl_content WHERE ptable = ? AND pid = ? ORDER BY sorting', ['tl_article', (int) $articleId]); $this->copyArticleContentTree((int) $articleId, $newArticleId, $moduleMap, $result);
}
}
/**
* Copies top-level article content and all nested child elements recursively.
*
* @param array<int,int> $moduleMap
*/
private function copyArticleContentTree(int $sourceArticleId, int $targetArticleId, array $moduleMap, DummyCopyResult $result): void
{
$visited = [];
$contentIds = $this->connection->fetchFirstColumn(
'SELECT id FROM tl_content WHERE ptable = ? AND pid = ? ORDER BY sorting',
['tl_article', $sourceArticleId]
);
foreach ($contentIds as $contentId) { foreach ($contentIds as $contentId) {
$this->copySingleContent((int) $contentId, $newArticleId, $moduleMap, $result); $this->copyContentRecursive((int) $contentId, 'tl_article', $targetArticleId, $moduleMap, $result, $visited);
}
} }
} }
@@ -162,18 +176,34 @@ final class DummyCopier
*/ */
private function copySingleContent(int $sourceContentId, int $targetArticleId, array $moduleMap, DummyCopyResult $result): void private function copySingleContent(int $sourceContentId, int $targetArticleId, array $moduleMap, DummyCopyResult $result): void
{ {
$visited = [];
$this->copyContentRecursive($sourceContentId, 'tl_article', $targetArticleId, $moduleMap, $result, $visited);
}
/**
* Recursively copies one content element and its children.
*
* @param array<int,int> $moduleMap
*/
private function copyContentRecursive(int $sourceContentId, string $targetPtable, int $targetPid, array $moduleMap, DummyCopyResult $result, array &$visited): int
{
if (isset($visited[$sourceContentId])) {
return 0;
}
$visited[$sourceContentId] = true;
$contentRow = $this->fetchRow('tl_content', $sourceContentId); $contentRow = $this->fetchRow('tl_content', $sourceContentId);
if ($contentRow === null) { if ($contentRow === null) {
$result->addNote(sprintf('Content %d nicht gefunden, wurde uebersprungen.', $sourceContentId)); $result->addNote(sprintf('Content %d nicht gefunden, wurde uebersprungen.', $sourceContentId));
return; return 0;
} }
unset($contentRow['id']); unset($contentRow['id']);
$contentRow['pid'] = $targetArticleId; $contentRow['pid'] = $targetPid;
$contentRow['ptable'] = 'tl_article'; $contentRow['ptable'] = $targetPtable;
$contentRow['tstamp'] = time(); $contentRow['tstamp'] = time();
$contentRow['sorting'] = $this->nextSorting('tl_content', 'pid', $targetArticleId, 'ptable', 'tl_article'); $contentRow['sorting'] = $this->nextSorting('tl_content', 'pid', $targetPid, 'ptable', $targetPtable);
if (($contentRow['type'] ?? '') === 'module') { if (($contentRow['type'] ?? '') === 'module') {
$oldModule = (int) ($contentRow['module'] ?? 0); $oldModule = (int) ($contentRow['module'] ?? 0);
@@ -184,12 +214,24 @@ final class DummyCopier
} }
$newContentId = $this->insertRow('tl_content', $contentRow); $newContentId = $this->insertRow('tl_content', $contentRow);
$result->contentMap[$sourceContentId] = $newContentId;
$result->copiedContent++; $result->copiedContent++;
$result->copiedContentIds[] = $newContentId; $result->copiedContentIds[] = $newContentId;
if (isset($contentRow['jumpTo'], $result->pageMap[(int) $contentRow['jumpTo']])) { if (isset($contentRow['jumpTo'], $result->pageMap[(int) $contentRow['jumpTo']])) {
$this->connection->update('tl_content', ['jumpTo' => $result->pageMap[(int) $contentRow['jumpTo']]], ['id' => $newContentId]); $this->connection->update('tl_content', ['jumpTo' => $result->pageMap[(int) $contentRow['jumpTo']]], ['id' => $newContentId]);
} }
$childIds = $this->connection->fetchFirstColumn(
'SELECT id FROM tl_content WHERE ptable = ? AND pid = ? ORDER BY sorting',
['tl_content', $sourceContentId]
);
foreach ($childIds as $childId) {
$this->copyContentRecursive((int) $childId, 'tl_content', $newContentId, $moduleMap, $result, $visited);
}
return $newContentId;
} }
private function rewriteReferences(DummyCopyResult $result): void private function rewriteReferences(DummyCopyResult $result): void
@@ -226,6 +268,23 @@ final class DummyCopier
$params $params
); );
} }
foreach ($result->contentMap as $oldContentId => $newContentId) {
$sourceAliasTarget = (int) $this->connection->fetchOne(
'SELECT cteAlias FROM tl_content WHERE id = ? AND type = ?',
[$oldContentId, 'alias']
);
if ($sourceAliasTarget < 1 || !isset($result->contentMap[$sourceAliasTarget])) {
continue;
}
$this->connection->update(
'tl_content',
['cteAlias' => $result->contentMap[$sourceAliasTarget]],
['id' => $newContentId, 'type' => 'alias']
);
}
} }
private function copyDirectories(DummyCopyOptions $options, DummyCopyResult $result): void private function copyDirectories(DummyCopyOptions $options, DummyCopyResult $result): void
@@ -328,7 +387,7 @@ final class DummyCopier
private function estimateContentCount(DummyCopyOptions $options): int private function estimateContentCount(DummyCopyOptions $options): int
{ {
$count = \count($options->sourceContentIds); $count = $this->countContentTree(array_map('intval', $options->sourceContentIds));
if (!$options->includeContent || $options->sourcePageIds === []) { if (!$options->includeContent || $options->sourcePageIds === []) {
return $count; return $count;
@@ -342,10 +401,55 @@ final class DummyCopier
} }
$articlePlaceholders = implode(',', array_fill(0, \count($articleIds), '?')); $articlePlaceholders = implode(',', array_fill(0, \count($articleIds), '?'));
$rootContentIds = $this->connection->fetchFirstColumn(
return $count + (int) $this->connection->fetchOne( sprintf('SELECT id FROM tl_content WHERE ptable = ? AND pid IN (%s)', $articlePlaceholders),
sprintf('SELECT COUNT(*) FROM tl_content WHERE ptable = ? AND pid IN (%s)', $articlePlaceholders),
array_merge(['tl_article'], array_map('intval', $articleIds)) array_merge(['tl_article'], array_map('intval', $articleIds))
); );
if ($rootContentIds === []) {
return $count;
}
return $count + $this->countContentTree(array_map('intval', $rootContentIds));
}
/**
* Counts content roots and all nested descendants.
*
* @param array<int,int> $rootIds
*/
private function countContentTree(array $rootIds): int
{
if ($rootIds === []) {
return 0;
}
$seen = [];
$queue = array_values(array_filter($rootIds, static fn (int $id): bool => $id > 0));
while ($queue !== []) {
$currentBatch = [];
foreach ($queue as $id) {
if (!isset($seen[$id])) {
$seen[$id] = true;
$currentBatch[] = $id;
}
}
if ($currentBatch === []) {
break;
}
$placeholders = implode(',', array_fill(0, \count($currentBatch), '?'));
$children = $this->connection->fetchFirstColumn(
sprintf('SELECT id FROM tl_content WHERE ptable = ? AND pid IN (%s)', $placeholders),
array_merge(['tl_content'], $currentBatch)
);
$queue = array_map('intval', $children);
}
return \count($seen);
} }
} }

View File

@@ -17,6 +17,9 @@ final class DummyCopyResult
/** @var array<int,int> */ /** @var array<int,int> */
public array $moduleMap = []; public array $moduleMap = [];
/** @var array<int,int> */
public array $contentMap = [];
/** @var array<string> */ /** @var array<string> */
public array $notes = []; public array $notes = [];