diff --git a/config/current.ver b/config/current.ver
index 84da421..8bd7d68 100644
--- a/config/current.ver
+++ b/config/current.ver
@@ -1 +1 @@
-1.2.39
\ No newline at end of file
+1.2.40
\ No newline at end of file
diff --git a/partials/landingpage/accountsetup/system.php b/partials/landingpage/accountsetup/system.php
index 9b5b033..5a6a43c 100644
--- a/partials/landingpage/accountsetup/system.php
+++ b/partials/landingpage/accountsetup/system.php
@@ -15,6 +15,9 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
+
diff --git a/public/assets/js/ui-user.js b/public/assets/js/ui-user.js
index 354b942..9d790b1 100644
--- a/public/assets/js/ui-user.js
+++ b/public/assets/js/ui-user.js
@@ -442,6 +442,12 @@ function fillSettingsForm(settings) {
if (settingsForm.sender_token) settingsForm.sender_token.value = settings.sender_token || '';
if (settingsForm.external_api_token) settingsForm.external_api_token.value = settings.external_api_token || '';
if (settingsForm.editor_default) settingsForm.editor_default.value = settings.editor_default || 'grapesjs';
+ if (settingsForm.versions_retention) {
+ const retention = Number.isFinite(Number(settings.versions_retention))
+ ? Number(settings.versions_retention)
+ : 0;
+ settingsForm.versions_retention.value = String(Math.max(0, retention));
+ }
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
}
@@ -490,6 +496,11 @@ async function submitSettingsForm(ev) {
if (settingsForm.sender_token) data.sender_token = settingsForm.sender_token.value.trim();
if (settingsForm.external_api_token) data.external_api_token = settingsForm.external_api_token.value.trim();
if (settingsForm.editor_default) data.editor_default = settingsForm.editor_default.value;
+ if (settingsForm.versions_retention) {
+ const raw = settingsForm.versions_retention.value.trim();
+ const parsed = raw === '' ? 0 : Number(raw);
+ data.versions_retention = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : 0;
+ }
if (adminTablesAllSelect && adminTablesSelectedSelect) {
const bridgeTables = normalizeTableList(state.settings.bridge_tables || []);
data.bridge_tables = bridgeTables;
diff --git a/src/ApiKernel.php b/src/ApiKernel.php
index 6b652a4..a40c51f 100644
--- a/src/ApiKernel.php
+++ b/src/ApiKernel.php
@@ -431,14 +431,7 @@ class ApiKernel
$stmt->execute();
$newId = (int)$this->pdo->lastInsertId();
- $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]);
+ $this->applyContentVersionRetention($customerId, $contentId);
return $newId;
} catch (Throwable $e) {
// Versioning darf nicht das Speichern blockieren.
@@ -446,6 +439,35 @@ class ApiKernel
}
}
+ private function applyContentVersionRetention(int $customerId, int $contentId): void
+ {
+ $limit = $this->getContentVersionRetentionLimit($customerId);
+ if ($limit <= 0) return;
+ $table = $this->contentVersionsTable();
+ if (!$this->tableExists($table)) return;
+ try {
+ $versionCols = $this->resolveContentVersionColumns($table);
+ $isActiveCol = $versionCols['is_active'];
+ $activeFilter = $isActiveCol ? " AND (`$isActiveCol` IS NULL OR `$isActiveCol` = 0)" : '';
+ $keepSql = "SELECT `id` FROM `$table` WHERE `content_id` = :cid ORDER BY `version_no` DESC, `id` DESC LIMIT :lim";
+ $deleteSql = "DELETE FROM `$table` WHERE `content_id` = :cid$activeFilter AND `id` NOT IN (SELECT `id` FROM ($keepSql) AS keep_ids)";
+ $stmt = $this->pdo->prepare($deleteSql);
+ $stmt->bindValue(':cid', $contentId, PDO::PARAM_INT);
+ $stmt->bindValue(':lim', $limit, PDO::PARAM_INT);
+ $stmt->execute();
+ } catch (Throwable $e) {
+ // Retention darf nicht das Speichern blockieren.
+ }
+ }
+
+ private function getContentVersionRetentionLimit(int $customerId): int
+ {
+ if ($customerId <= 0) return 0;
+ $settings = $this->getCustomerSettings($customerId);
+ $limit = (int)($settings['versions_retention'] ?? 0);
+ return max(0, $limit);
+ }
+
private function activateContentVersion(int $customerId, int $contentId, int $versionId): bool
{
$table = $this->contentVersionsTable();
@@ -3711,13 +3733,14 @@ class ApiKernel
$hasSenderToken = array_key_exists('sender_token', $this->in);
$hasExternalToken = array_key_exists('external_api_token', $this->in);
$hasEditorDefault = array_key_exists('editor_default', $this->in);
+ $hasVersionsRetention = array_key_exists('versions_retention', $this->in);
$hasListSort = array_key_exists('list_sort', $this->in);
$hasBridgeTables = array_key_exists('bridge_tables', $this->in);
$rotateBridge = !empty($this->in['rotate_bridge_token']);
$rotateSender = !empty($this->in['rotate_sender_token']);
$rotateExternal = !empty($this->in['rotate_external_token']);
$onlyListSort = $hasListSort && !$hasBridgeUrl && !$hasBridgeToken && !$hasSenderToken && !$hasExternalToken
- && !$hasEditorDefault && !$hasBridgeTables && !$rotateBridge && !$rotateSender && !$rotateExternal;
+ && !$hasEditorDefault && !$hasBridgeTables && !$hasVersionsRetention && !$rotateBridge && !$rotateSender && !$rotateExternal;
if (!$onlyListSort) {
$this->ensureRole($user, ['owner', 'admin']);
@@ -3730,6 +3753,7 @@ class ApiKernel
$senderToken = $hasSenderToken ? trim((string)($this->in['sender_token'] ?? '')) : (string)($settings['sender_token'] ?? '');
$externalToken = $hasExternalToken ? trim((string)($this->in['external_api_token'] ?? '')) : (string)($settings['external_api_token'] ?? '');
$editorDefault = $hasEditorDefault ? strtolower(trim((string)($this->in['editor_default'] ?? ''))) : strtolower((string)($settings['editor_default'] ?? ''));
+ $versionsRetention = $hasVersionsRetention ? (int)($this->in['versions_retention'] ?? 0) : (int)($settings['versions_retention'] ?? 0);
$listSort = $hasListSort ? strtolower(trim((string)($this->in['list_sort'] ?? ''))) : '';
$bridgeTables = $hasBridgeTables ? $this->normalizeBridgeTables($this->in['bridge_tables'] ?? []) : ($settings['bridge_tables'] ?? []);
@@ -3749,6 +3773,9 @@ class ApiKernel
if ($editorDefault !== '' && !in_array($editorDefault, ['grapesjs', 'craftjs'], true)) {
$this->fail('Ungültiger Editor-Typ', null, 422);
}
+ if ($versionsRetention < 0) {
+ $this->fail('Ungültiger Aufbewahrungswert', null, 422);
+ }
$settings = $this->saveCustomerSettings($customerId, [
'bridge_url' => $bridgeUrl,
@@ -3757,6 +3784,7 @@ class ApiKernel
'external_api_token' => $externalToken,
'editor_default' => $editorDefault ?: null,
'bridge_tables' => $bridgeTables,
+ 'versions_retention' => $versionsRetention,
]);
} else {
$settings = $customerId ? $this->ensureSettingsTokens($customerId, $settings) : $settings;
@@ -4318,7 +4346,7 @@ class ApiKernel
{
if ($customerId <= 0) return [];
$this->ensureCustomerSettingsTableExists();
- $allowed = ['bridge_url', 'bridge_token', 'sender_token', 'external_api_token', 'editor_default', 'bridge_tables', 'bridge_setup'];
+ $allowed = ['bridge_url', 'bridge_token', 'sender_token', 'external_api_token', 'editor_default', 'bridge_tables', 'bridge_setup', 'versions_retention'];
$fields = array_intersect_key($data, array_flip($allowed));
if (!$fields) return $this->getCustomerSettings($customerId);
if (array_key_exists('bridge_tables', $fields)) {
@@ -4384,6 +4412,11 @@ class ApiKernel
if (empty($row['editor_default'])) {
$row['editor_default'] = 'grapesjs';
}
+ if (!isset($row['versions_retention']) || $row['versions_retention'] === '') {
+ $row['versions_retention'] = 0;
+ } else {
+ $row['versions_retention'] = max(0, (int)$row['versions_retention']);
+ }
return $row;
}