Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42cef07833 | |||
| c9b29620ab | |||
| 7aa8b77d2f |
@@ -3,6 +3,24 @@
|
|||||||
"description": "Contao backend module to clone dummy pages, content elements, modules and rewire references.",
|
"description": "Contao backend module to clone dummy pages, content elements, modules and rewire references.",
|
||||||
"type": "contao-bundle",
|
"type": "contao-bundle",
|
||||||
"license": "proprietary",
|
"license": "proprietary",
|
||||||
|
"keywords": [
|
||||||
|
"contao",
|
||||||
|
"contao-bundle",
|
||||||
|
"backend",
|
||||||
|
"dummy",
|
||||||
|
"migration",
|
||||||
|
"cloner"
|
||||||
|
],
|
||||||
|
"homepage": "https://git.file-archive.de/webfarben/DummyCopier",
|
||||||
|
"support": {
|
||||||
|
"source": "https://git.file-archive.de/webfarben/DummyCopier",
|
||||||
|
"issues": "https://git.file-archive.de/webfarben/DummyCopier/issues"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Webfarben"
|
||||||
|
}
|
||||||
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1",
|
"php": "^8.1",
|
||||||
"contao/core-bundle": "^4.13 || ^5.0",
|
"contao/core-bundle": "^4.13 || ^5.0",
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user