update schema and so on

This commit is contained in:
2026-01-20 02:24:23 +01:00
parent da290a8ff8
commit e01c643683
5 changed files with 305 additions and 8 deletions

View File

@@ -172,7 +172,7 @@ class ApiKernel
private function resolveAction(): void { /* ... Logik bleibt unverändert ... */
$action = $this->val($this->in, 'action', '');
$resource = $this->val($this->in, 'resource', null);
$allowedResources = ['templates', 'sections', 'blocks', 'snippets', 'content', 'sections_config'];
$allowedResources = ['templates', 'sections', 'blocks', 'snippets', 'content', 'sections_config', 'content_versions'];
if ($resource && in_array($resource, $allowedResources, true) && strpos((string)$action, '.') === false) {
$verb = strtolower((string)$action);
if (in_array($verb, ['list', 'get', 'create', 'update', 'delete'], true)) $action = $resource . '.' . $verb;
@@ -188,6 +188,7 @@ class ApiKernel
'snippets' => $tables['snippets'] ?? 'emailtemplate_snippets',
'content_items' => $tables['content_items'] ?? 'emailtemplate_content_items',
'content_sections' => $tables['content_sections'] ?? 'emailtemplate_content_sections',
'content_versions' => $tables['content_versions'] ?? 'emailtemplate_content_versions',
];
}
@@ -342,6 +343,11 @@ class ApiKernel
return $this->tableMap['content_sections'] ?? $this->lookupTableName('content_sections', 'emailtemplate_content_sections');
}
private function contentVersionsTable(): string
{
return $this->tableMap['content_versions'] ?? $this->lookupTableName('content_versions', 'emailtemplate_content_versions');
}
private function resolveContentItemColumns(string $table): array
{
$cols = $this->tableColumns($table);
@@ -360,6 +366,59 @@ class ApiKernel
return $this->tableExists($this->contentItemsTable()) && $this->tableExists($this->contentSectionsTable());
}
private function createContentVersion(array $current, array $itemCols, int $customerId, int $sectionId): void
{
$table = $this->contentVersionsTable();
if (!$this->tableExists($table)) return;
$contentId = (int)($current['id'] ?? 0);
if ($contentId <= 0) return;
$jsonCol = $itemCols['json'] ?? null;
$htmlCol = $itemCols['html'] ?? null;
$editorCol = $itemCols['editor'] ?? null;
$craftCol = $itemCols['craft'] ?? null;
$settingsCol = $itemCols['settings'] ?? null;
$json = $jsonCol ? ($current[$jsonCol] ?? null) : null;
$html = $htmlCol ? ($current[$htmlCol] ?? null) : null;
$editorType = $editorCol ? ($current[$editorCol] ?? null) : null;
$craftJson = $craftCol ? ($current[$craftCol] ?? null) : null;
$settings = $settingsCol ? ($current[$settingsCol] ?? null) : null;
try {
$stmt = $this->pdo->prepare("SELECT MAX(`version_no`) FROM `$table` WHERE `content_id` = :cid");
$stmt->execute([':cid' => $contentId]);
$nextVersion = (int)($stmt->fetchColumn() ?: 0) + 1;
$stmt = $this->pdo->prepare(
"INSERT INTO `$table` (`customer_id`,`content_id`,`section_id`,`version_no`,`editor_type`,`json_content`,`html`,`craft_json`,`settings_json`)
VALUES (:cust,:content,:section,:ver,:editor,:json,:html,:craft,:settings)"
);
$stmt->execute([
':cust' => $customerId,
':content' => $contentId,
':section' => $sectionId,
':ver' => $nextVersion,
':editor' => $editorType,
':json' => $json,
':html' => $html,
':craft' => $craftJson,
':settings' => $settings,
]);
$cleanup = $this->pdo->prepare(
"DELETE FROM `$table` WHERE `id` IN (
SELECT `id` FROM (
SELECT `id` FROM `$table` WHERE `content_id` = :cid ORDER BY `id` DESC LIMIT 10, 1000000
) t
)"
);
$cleanup->execute([':cid' => $contentId]);
} catch (Throwable $e) {
// Versioning darf nicht das Speichern blockieren.
}
}
private function isLegacyContentKind(string $kind): bool
{
return in_array($kind, ['templates', 'sections', 'blocks', 'snippets'], true);
@@ -843,6 +902,20 @@ class ApiKernel
return;
}
if (!empty($section['is_template'])) {
$versionCols = array_filter([$jsonCol, $htmlCol, $craftCol, $settingsCol, $editorCol]);
$shouldSnapshot = false;
foreach ($versionCols as $col) {
if (array_key_exists($col, $data)) {
$shouldSnapshot = true;
break;
}
}
if ($shouldSnapshot) {
$this->createContentVersion($current, $itemCols, $customerId, (int)($section['id'] ?? 0));
}
}
$set = implode(',', array_map(static fn($c) => "`$c` = :$c", array_keys($data)));
$data['id'] = $id;
$data['customer_id'] = $customerId;
@@ -880,6 +953,121 @@ class ApiKernel
$this->respond(['ok' => true, 'kind' => 'content', 'id' => $id, 'deleted' => true]);
}
private function handleContentVersionsList(): void
{
$auth = $this->requireAuth();
$customerId = (int)($auth['customer_id'] ?? 0);
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
$contentId = (int)$this->val($this->in, ['content_id', 'id'], 0);
if ($contentId <= 0) $this->fail('content_id required', null, 422);
$table = $this->contentVersionsTable();
if (!$this->tableExists($table)) {
$this->respond(['ok' => true, 'items' => [], 'data' => []]);
return;
}
$itemsTable = $this->contentItemsTable();
if ($this->tableExists($itemsTable)) {
$stmt = $this->pdo->prepare("SELECT `id` FROM `$itemsTable` WHERE `id` = :id AND `customer_id` = :cid LIMIT 1");
$stmt->execute([':id' => $contentId, ':cid' => $customerId]);
if (!$stmt->fetch()) $this->fail('Not found', ['id' => $contentId], 404);
}
$stmt = $this->pdo->prepare(
"SELECT `id`,`content_id`,`section_id`,`version_no`,`editor_type`,`created_at`
FROM `$table` WHERE `customer_id` = :cid AND `content_id` = :content
ORDER BY `id` DESC LIMIT 10"
);
$stmt->execute([':cid' => $customerId, ':content' => $contentId]);
$rows = $stmt->fetchAll() ?: [];
$items = array_map(static function ($row) {
return [
'id' => (int)($row['id'] ?? 0),
'content_id' => (int)($row['content_id'] ?? 0),
'section_id' => (int)($row['section_id'] ?? 0),
'version_no' => (int)($row['version_no'] ?? 0),
'editor_type' => $row['editor_type'] ?? null,
'created_at' => $row['created_at'] ?? null,
];
}, $rows);
$this->respond(['ok' => true, 'items' => $items, 'data' => $items]);
}
private function handleContentVersionsGet(): void
{
$auth = $this->requireAuth();
$customerId = (int)($auth['customer_id'] ?? 0);
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
$id = (int)$this->pullId($this->in);
if ($id <= 0) $this->fail('id required', null, 422);
$contentId = (int)$this->val($this->in, ['content_id', 'content'], 0);
$table = $this->contentVersionsTable();
if (!$this->tableExists($table)) $this->fail('Versions table not available', null, 500);
$sql = "SELECT * FROM `$table` WHERE `id` = :id AND `customer_id` = :cid";
$params = [':id' => $id, ':cid' => $customerId];
if ($contentId > 0) {
$sql .= " AND `content_id` = :content";
$params[':content'] = $contentId;
}
$sql .= " LIMIT 1";
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$row = $stmt->fetch();
if (!$row) $this->fail('Not found', ['id' => $id], 404);
$this->respond(['ok' => true, 'item' => $row, 'data' => $row]);
}
private function handleContentVersionsRestore(): void
{
$auth = $this->requireAuth();
$customerId = (int)($auth['customer_id'] ?? 0);
if ($customerId <= 0) $this->fail('Customer context missing', null, 500);
$versionId = (int)$this->val($this->in, ['id', 'version_id', 'version'], 0);
if ($versionId <= 0) $this->fail('version id required', null, 422);
$contentId = (int)$this->val($this->in, ['content_id', 'content'], 0);
$versionsTable = $this->contentVersionsTable();
$itemsTable = $this->contentItemsTable();
if (!$this->tableExists($versionsTable) || !$this->tableExists($itemsTable)) {
$this->fail('Content tables not available', null, 500);
}
$sql = "SELECT * FROM `$versionsTable` WHERE `id` = :id AND `customer_id` = :cid";
$params = [':id' => $versionId, ':cid' => $customerId];
if ($contentId > 0) {
$sql .= " AND `content_id` = :content";
$params[':content'] = $contentId;
}
$sql .= " LIMIT 1";
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
$version = $stmt->fetch();
if (!$version) $this->fail('Not found', ['id' => $versionId], 404);
$itemCols = $this->resolveContentItemColumns($itemsTable);
$data = [];
if (!empty($itemCols['json'])) $data[$itemCols['json']] = $version['json_content'] ?? null;
if (!empty($itemCols['html'])) $data[$itemCols['html']] = $version['html'] ?? null;
if (!empty($itemCols['craft'])) $data[$itemCols['craft']] = $version['craft_json'] ?? null;
if (!empty($itemCols['settings'])) $data[$itemCols['settings']] = $version['settings_json'] ?? null;
if (!empty($itemCols['editor'])) $data[$itemCols['editor']] = $version['editor_type'] ?? null;
if ($data) {
$set = implode(',', array_map(static fn($c) => "`$c` = :$c", array_keys($data)));
$data['id'] = (int)($version['content_id'] ?? 0);
$data['customer_id'] = $customerId;
$sql = "UPDATE `$itemsTable` SET $set WHERE `id` = :id AND `customer_id` = :customer_id LIMIT 1";
$stmt = $this->pdo->prepare($sql);
foreach ($data as $k => $v) $stmt->bindValue(":$k", $v);
$stmt->execute();
}
$this->respond(['ok' => true, 'restored' => true, 'content_id' => (int)($version['content_id'] ?? 0)]);
}
private function handleSectionsConfigList(): void
{
$auth = $this->requireAuth();
@@ -2072,19 +2260,24 @@ class ApiKernel
case 'sections_config.reorder':
$this->handleSectionsConfigReorder();
break;
case 'content_versions.restore':
$this->handleContentVersionsRestore();
break;
/* ---------- CRUD HANDLER ---------- */
default:
if (in_array($kind, ['templates', 'sections', 'blocks', 'snippets', 'content', 'sections_config'])) {
if (in_array($kind, ['templates', 'sections', 'blocks', 'snippets', 'content', 'sections_config', 'content_versions'])) {
switch ($operation) {
case 'list':
if ($kind === 'content') $this->handleContentList();
elseif ($kind === 'sections_config') $this->handleSectionsConfigList();
elseif ($kind === 'content_versions') $this->handleContentVersionsList();
else $this->handleList($kind);
break;
case 'get':
if ($kind === 'content') $this->handleContentGet();
elseif ($kind === 'sections_config') $this->handleSectionsConfigGet();
elseif ($kind === 'content_versions') $this->handleContentVersionsGet();
else $this->handleGet($kind);
break;
case 'create':