This commit is contained in:
2026-01-20 01:44:49 +01:00
parent 3d559924a9
commit 8e6248cea1
7 changed files with 158 additions and 76 deletions

View File

@@ -3,6 +3,7 @@ $appBaseUrl = $GLOBALS['app_base_url'] ?? '';
$defaultNavLinks = [ $defaultNavLinks = [
['id' => 'dashboard', 'label' => 'Dashboard', 'href' => $appBaseUrl . '/admin/dashboard.php'], ['id' => 'dashboard', 'label' => 'Dashboard', 'href' => $appBaseUrl . '/admin/dashboard.php'],
['id' => 'system', 'label' => 'Systemeinstellungen', 'href' => $appBaseUrl . '/admin/system.php'],
['id' => 'settings', 'label' => 'API & Tabellen', 'href' => $appBaseUrl . '/admin/settings.php'], ['id' => 'settings', 'label' => 'API & Tabellen', 'href' => $appBaseUrl . '/admin/settings.php'],
['id' => 'users', 'label' => 'Userverwaltung', 'href' => $appBaseUrl . '/admin/users.php'], ['id' => 'users', 'label' => 'Userverwaltung', 'href' => $appBaseUrl . '/admin/users.php'],
['id' => 'profile', 'label' => 'Mein Konto', 'href' => $appBaseUrl . '/admin/profile.php'], ['id' => 'profile', 'label' => 'Mein Konto', 'href' => $appBaseUrl . '/admin/profile.php'],

View File

@@ -68,11 +68,6 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<button type="button" class="btn" data-rotate="external">Neu erstellen</button> <button type="button" class="btn" data-rotate="external">Neu erstellen</button>
</div> </div>
</div> </div>
<label class="block text-sm text-slate-600">Standard-Editor</label>
<select name="editor_default" class="input">
<option value="grapesjs">GrapesJS</option>
<option value="craftjs">Craft.js</option>
</select>
<div class="flex justify-between gap-2 flex-wrap pt-2"> <div class="flex justify-between gap-2 flex-wrap pt-2">
<div class="flex gap-2" data-role="admin"> <div class="flex gap-2" data-role="admin">
<button type="button" class="btn" data-download="bridge">Bridge-Datei</button> <button type="button" class="btn" data-download="bridge">Bridge-Datei</button>
@@ -83,20 +78,6 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
</form> </form>
</section> </section>
<section class="section-card" data-role="admin" id="sectionsManager">
<div class="flex items-center justify-between flex-wrap gap-3 mb-3">
<div>
<h4>Sections verwalten</h4>
<p class="text-sm text-slate-600">Die Sortierung steuert, welche Inhalte in anderen Sections eingebunden werden dürfen.</p>
</div>
</div>
<form id="sectionsCreateForm" class="flex flex-wrap gap-2 mb-4">
<input type="text" id="sectionNameInput" class="input flex-1 min-w-[220px]" placeholder="Neue Section (Name)" required>
<button type="submit" class="btn">Section hinzufügen</button>
</form>
<ul id="sectionsList" class="space-y-2"></ul>
</section>
<section class="section-card" data-role="admin"> <section class="section-card" data-role="admin">
<div class="flex items-center justify-between flex-wrap gap-3 mb-3"> <div class="flex items-center justify-between flex-wrap gap-3 mb-3">
<div> <div>
@@ -267,21 +248,6 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
</div> </div>
</dialog> </dialog>
<dialog id="sectionsDeleteDialog" class="rounded-xl max-w-md w-[90vw] p-5">
<form id="sectionsDeleteForm" method="dialog" class="space-y-4">
<div class="flex items-center justify-between gap-3 border-b border-slate-200 pb-3">
<h3 class="text-lg font-semibold">Section löschen</h3>
<button type="button" id="sectionsDeleteCancel" class="btn">Abbrechen</button>
</div>
<p id="sectionsDeleteText" class="text-sm text-slate-600"></p>
<label class="block text-sm text-slate-600">Inhalte verschieben nach</label>
<select id="sectionsDeleteTarget" class="input"></select>
<div class="flex justify-end gap-2">
<button type="submit" class="btn btn-danger">Löschen</button>
</div>
</form>
</dialog>
<dialog id="configExampleDialog" class="rounded-xl max-w-2xl w-[90vw]"> <dialog id="configExampleDialog" class="rounded-xl max-w-2xl w-[90vw]">
<form method="dialog" class="space-y-3"> <form method="dialog" class="space-y-3">
<h3 class="text-lg font-semibold">Beispiel: Mapping einer Config-Datei</h3> <h3 class="text-lg font-semibold">Beispiel: Mapping einer Config-Datei</h3>

View File

@@ -0,0 +1,53 @@
<?php
$pageTitle = 'Email Template System Systemeinstellungen';
$pageId = 'admin';
$navActive = 'system';
require __DIR__ . '/accountsetup_config.php';
require dirname(__DIR__) . '/../structure/layout_start.php';
?>
<main class="max-w-5xl mx-auto p-4 md:p-6 flex-1 w-full space-y-6">
<section class="section-card" data-role="admin">
<h4>Standard-Editor</h4>
<p class="text-sm text-slate-600 mb-4">Standardauswahl für neue Inhalte. Kann pro Element spaeter angepasst werden.</p>
<form id="settingsForm" class="space-y-3">
<label class="block text-sm text-slate-600">Standard-Editor</label>
<select name="editor_default" class="input">
<option value="grapesjs">GrapesJS</option>
<option value="craftjs">Craft.js</option>
</select>
<div class="flex justify-end">
<button type="submit" class="btn">Einstellungen speichern</button>
</div>
</form>
</section>
<section class="section-card" data-role="admin" id="sectionsManager">
<div class="flex items-center justify-between flex-wrap gap-3 mb-3">
<div>
<h4>Sections verwalten</h4>
<p class="text-sm text-slate-600">Die Sortierung steuert, welche Inhalte in anderen Sections eingebunden werden duerfen.</p>
</div>
</div>
<form id="sectionsCreateForm" class="flex flex-wrap gap-2 mb-4">
<input type="text" id="sectionNameInput" class="input flex-1 min-w-[220px]" placeholder="Neue Section (Name)" required>
<button type="submit" class="btn">Section hinzufuegen</button>
</form>
<ul id="sectionsList" class="space-y-2"></ul>
</section>
</main>
<dialog id="sectionsDeleteDialog" class="rounded-xl max-w-md w-[90vw] p-5">
<form id="sectionsDeleteForm" method="dialog" class="space-y-4">
<div class="flex items-center justify-between gap-3 border-b border-slate-200 pb-3">
<h3 class="text-lg font-semibold">Section loeschen</h3>
<button type="button" id="sectionsDeleteCancel" class="btn">Abbrechen</button>
</div>
<p id="sectionsDeleteText" class="text-sm text-slate-600"></p>
<label class="block text-sm text-slate-600">Inhalte verschieben nach</label>
<select id="sectionsDeleteTarget" class="input"></select>
<div class="flex justify-end gap-2">
<button type="submit" class="btn btn-danger">Loeschen</button>
</div>
</form>
</dialog>
<?php require dirname(__DIR__) . '/../structure/layout_end.php'; ?>

2
public/admin/system.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../../partials/landingpage/accountsetup/system.php';

View File

@@ -72,7 +72,7 @@ export function initCreate(){
toast('Erstellt',true); toast('Erstellt',true);
window.loadList && window.loadList(section); window.loadList && window.loadList(section);
} else { } else {
toast('Erstellen fehlgeschlagen',false,{duration:3000}); toast(r?.error || 'Erstellen fehlgeschlagen',false,{duration:3000});
console.error('Create failed',r); console.error('Create failed',r);
} }
}; };

View File

@@ -431,17 +431,15 @@ function fillProfileForm(user) {
function fillSettingsForm(settings) { function fillSettingsForm(settings) {
state.settings = settings; state.settings = settings;
if (!settingsForm) return;
settingsForm.bridge_url.value = settings.bridge_url || '';
settingsForm.bridge_token.value = settings.bridge_token || '';
settingsForm.sender_token.value = settings.sender_token || '';
settingsForm.external_api_token.value = settings.external_api_token || '';
if (settingsForm.editor_default) {
settingsForm.editor_default.value = settings.editor_default || 'grapesjs';
}
window.__editorDefault = settings.editor_default || 'grapesjs'; window.__editorDefault = settings.editor_default || 'grapesjs';
window.__listSortDefault = settings.list_sort || 'created_asc'; window.__listSortDefault = settings.list_sort || 'created_asc';
state.rotate = { bridge: false, sender: false, external: false }; state.rotate = { bridge: false, sender: false, external: false };
if (!settingsForm) return;
if (settingsForm.bridge_url) settingsForm.bridge_url.value = settings.bridge_url || '';
if (settingsForm.bridge_token) settingsForm.bridge_token.value = settings.bridge_token || '';
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';
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []); refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
} }
@@ -480,18 +478,20 @@ async function submitPasswordForm(ev) {
async function submitSettingsForm(ev) { async function submitSettingsForm(ev) {
ev.preventDefault(); ev.preventDefault();
const bridgeTables = normalizeTableList(state.settings.bridge_tables || []);
const data = { const data = {
bridge_url: settingsForm.bridge_url.value.trim(),
bridge_token: settingsForm.bridge_token.value.trim(),
sender_token: settingsForm.sender_token.value.trim(),
external_api_token: settingsForm.external_api_token.value.trim(),
editor_default: settingsForm.editor_default ? settingsForm.editor_default.value : undefined,
bridge_tables: bridgeTables,
rotate_bridge_token: state.rotate.bridge ? 1 : 0, rotate_bridge_token: state.rotate.bridge ? 1 : 0,
rotate_sender_token: state.rotate.sender ? 1 : 0, rotate_sender_token: state.rotate.sender ? 1 : 0,
rotate_external_token: state.rotate.external ? 1 : 0, rotate_external_token: state.rotate.external ? 1 : 0,
}; };
if (settingsForm.bridge_url) data.bridge_url = settingsForm.bridge_url.value.trim();
if (settingsForm.bridge_token) data.bridge_token = settingsForm.bridge_token.value.trim();
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 (adminTablesAllSelect && adminTablesSelectedSelect) {
const bridgeTables = normalizeTableList(state.settings.bridge_tables || []);
data.bridge_tables = bridgeTables;
}
try { try {
const res = await apiAction('account.settings.update', { method: 'POST', data }); const res = await apiAction('account.settings.update', { method: 'POST', data });
if (!res?.ok) throw new Error(res?.error || 'Einstellungen konnten nicht gespeichert werden'); if (!res?.ok) throw new Error(res?.error || 'Einstellungen konnten nicht gespeichert werden');

View File

@@ -342,6 +342,19 @@ class ApiKernel
return $this->tableMap['content_sections'] ?? $this->lookupTableName('content_sections', 'emailtemplate_content_sections'); return $this->tableMap['content_sections'] ?? $this->lookupTableName('content_sections', 'emailtemplate_content_sections');
} }
private function resolveContentItemColumns(string $table): array
{
$cols = $this->tableColumns($table);
return [
'category' => $this->firstExisting($cols, ['category', 'cat']),
'html' => $this->firstExisting($cols, ['html', 'html_content', 'body', 'markup', 'content']),
'json' => $this->firstExisting($cols, ['json_content']),
'editor' => $this->firstExisting($cols, ['editor_type', 'editor']),
'craft' => $this->firstExisting($cols, ['craft_json', 'craft_content', 'craft_data']),
'settings' => $this->firstExisting($cols, ['settings_json', 'settings']),
];
}
private function useUnifiedContent(): bool private function useUnifiedContent(): bool
{ {
return $this->tableExists($this->contentItemsTable()) && $this->tableExists($this->contentSectionsTable()); return $this->tableExists($this->contentItemsTable()) && $this->tableExists($this->contentSectionsTable());
@@ -514,6 +527,10 @@ class ApiKernel
if (!$this->tableExists($itemsTable) || !$this->tableExists($sectionsTable)) { if (!$this->tableExists($itemsTable) || !$this->tableExists($sectionsTable)) {
$this->fail('Content tables not available', null, 500); $this->fail('Content tables not available', null, 500);
} }
$itemCols = $this->resolveContentItemColumns($itemsTable);
$catCol = $itemCols['category'];
$htmlCol = $itemCols['html'];
$jsonCol = $itemCols['json'];
$section = $fixedSection ?: $this->resolveSectionFromInput($customerId); $section = $fixedSection ?: $this->resolveSectionFromInput($customerId);
$q = trim((string)$this->val($this->in, 'q', '')); $q = trim((string)$this->val($this->in, 'q', ''));
@@ -527,7 +544,11 @@ class ApiKernel
$params[':sid'] = (int)$section['id']; $params[':sid'] = (int)$section['id'];
} }
if ($q !== '') { if ($q !== '') {
$where .= " AND (i.`name` LIKE :q OR i.`category` LIKE :q) "; $where .= " AND (i.`name` LIKE :q";
if ($catCol) {
$where .= " OR i.`$catCol` LIKE :q";
}
$where .= ") ";
$params[':q'] = '%' . $q . '%'; $params[':q'] = '%' . $q . '%';
} }
@@ -550,7 +571,7 @@ class ApiKernel
'id' => $r['id'] ?? null, 'id' => $r['id'] ?? null,
'name' => $r['name'] ?? null, 'name' => $r['name'] ?? null,
'api_name' => $r['api_name'] ?? null, 'api_name' => $r['api_name'] ?? null,
'category' => $r['category'] ?? null, 'category' => $catCol ? ($r[$catCol] ?? null) : null,
'section_id' => $r['section_id'] ?? null, 'section_id' => $r['section_id'] ?? null,
'section_name' => $r['section_name'] ?? null, 'section_name' => $r['section_name'] ?? null,
'section_slug' => $r['section_slug'] ?? null, 'section_slug' => $r['section_slug'] ?? null,
@@ -559,8 +580,8 @@ class ApiKernel
'updated_at' => $r['updated_at'] ?? null, 'updated_at' => $r['updated_at'] ?? null,
'created_at' => $r['created_at'] ?? null, 'created_at' => $r['created_at'] ?? null,
]; ];
if (array_key_exists('html', $r)) $item['html'] = (string)($r['html'] ?? ''); if ($htmlCol && array_key_exists($htmlCol, $r)) $item['html'] = (string)($r[$htmlCol] ?? '');
if (array_key_exists('json_content', $r)) $item['content'] = $r['json_content']; if ($jsonCol && array_key_exists($jsonCol, $r)) $item['content'] = $r[$jsonCol];
$out[] = $item; $out[] = $item;
} }
@@ -588,6 +609,11 @@ class ApiKernel
if (!$this->tableExists($itemsTable) || !$this->tableExists($sectionsTable)) { if (!$this->tableExists($itemsTable) || !$this->tableExists($sectionsTable)) {
$this->fail('Content tables not available', null, 500); $this->fail('Content tables not available', null, 500);
} }
$itemCols = $this->resolveContentItemColumns($itemsTable);
$htmlCol = $itemCols['html'];
$jsonCol = $itemCols['json'];
$craftCol = $itemCols['craft'];
$editorCol = $itemCols['editor'];
$section = $fixedSection ?: $this->resolveSectionFromInput($customerId); $section = $fixedSection ?: $this->resolveSectionFromInput($customerId);
$params = [':cid' => $customerId, ':id' => $id]; $params = [':cid' => $customerId, ':id' => $id];
@@ -608,8 +634,8 @@ class ApiKernel
$row = $stmt->fetch(); $row = $stmt->fetch();
if (!$row) $this->fail('Not found', ['id' => $id], 404); if (!$row) $this->fail('Not found', ['id' => $id], 404);
$html = (string)($row['html'] ?? ''); $html = $htmlCol ? (string)($row[$htmlCol] ?? '') : '';
$json = $row['json_content'] ?? null; $json = $jsonCol ? ($row[$jsonCol] ?? null) : null;
$gjsComponents = []; $gjsComponents = [];
if ($json !== null) { if ($json !== null) {
$decoded = json_decode((string)$json, true); $decoded = json_decode((string)$json, true);
@@ -635,8 +661,8 @@ class ApiKernel
'html' => $html, 'html' => $html,
'content' => $json, 'content' => $json,
'gjs_components' => $gjsComponents, 'gjs_components' => $gjsComponents,
'editor_type' => $row['editor_type'] ?? null, 'editor_type' => $editorCol ? ($row[$editorCol] ?? null) : null,
'craft_json' => $row['craft_json'] ?? null, 'craft_json' => $craftCol ? ($row[$craftCol] ?? null) : null,
]); ]);
} }
@@ -650,6 +676,13 @@ class ApiKernel
if (!$this->tableExists($itemsTable)) { if (!$this->tableExists($itemsTable)) {
$this->fail('Content table not available', null, 500); $this->fail('Content table not available', null, 500);
} }
$itemCols = $this->resolveContentItemColumns($itemsTable);
$catCol = $itemCols['category'];
$htmlCol = $itemCols['html'];
$jsonCol = $itemCols['json'];
$editorCol = $itemCols['editor'];
$craftCol = $itemCols['craft'];
$settingsCol = $itemCols['settings'];
$name = trim((string)$this->val($this->in, ['name', 'title'], '')); $name = trim((string)$this->val($this->in, ['name', 'title'], ''));
if ($name === '') $this->fail('name required', null, 422); if ($name === '') $this->fail('name required', null, 422);
@@ -683,22 +716,27 @@ class ApiKernel
'name' => $name, 'name' => $name,
'api_name' => $apiName, 'api_name' => $apiName,
]; ];
if ($category !== null) $data['category'] = (string)$category; if ($category !== null && $catCol) $data[$catCol] = (string)$category;
if ($editorType !== '') $data['editor_type'] = $editorType; if ($editorType !== '' && $editorCol) $data[$editorCol] = $editorType;
if ($craftJson !== null) $data['craft_json'] = is_string($craftJson) ? $craftJson : $this->encodeJson($craftJson); if ($craftJson !== null && $craftCol) $data[$craftCol] = is_string($craftJson) ? $craftJson : $this->encodeJson($craftJson);
if ($settings !== null) $data['settings_json'] = is_string($settings) ? $settings : $this->encodeJson($settings); if ($settings !== null && $settingsCol) $data[$settingsCol] = is_string($settings) ? $settings : $this->encodeJson($settings);
if ($json !== null) { if ($json !== null) {
if (!$jsonCol) $this->fail('json_content column missing', null, 500);
$components = is_string($json) ? json_decode($json, true) : $json; $components = is_string($json) ? json_decode($json, true) : $json;
if (is_array($components)) { if (is_array($components)) {
$components = $this->cleanReferenceComponents($components); $components = $this->cleanReferenceComponents($components);
$data['json_content'] = $this->encodeJson($components); $data[$jsonCol] = $this->encodeJson($components);
} else { } else {
$data['json_content'] = is_string($json) ? $json : ''; $data[$jsonCol] = is_string($json) ? $json : '';
}
if ($html !== null) {
if (!$htmlCol) $this->fail('html column missing', null, 500);
$data[$htmlCol] = (string)$html;
} }
if ($html !== null) $data['html'] = (string)$html;
} elseif ($html !== null) { } elseif ($html !== null) {
$data['html'] = (string)$html; if (!$htmlCol) $this->fail('html column missing', null, 500);
$data[$htmlCol] = (string)$html;
} }
$columns = array_keys($data); $columns = array_keys($data);
@@ -721,6 +759,13 @@ class ApiKernel
if (!$this->tableExists($itemsTable)) { if (!$this->tableExists($itemsTable)) {
$this->fail('Content table not available', null, 500); $this->fail('Content table not available', null, 500);
} }
$itemCols = $this->resolveContentItemColumns($itemsTable);
$catCol = $itemCols['category'];
$htmlCol = $itemCols['html'];
$jsonCol = $itemCols['json'];
$editorCol = $itemCols['editor'];
$craftCol = $itemCols['craft'];
$settingsCol = $itemCols['settings'];
$id = $this->pullId($this->in); $id = $this->pullId($this->in);
if ($id === null || $id === '') $this->fail('id required', null, 422); if ($id === null || $id === '') $this->fail('id required', null, 422);
@@ -741,7 +786,7 @@ class ApiKernel
if ($name !== null) $data['name'] = (string)$name; if ($name !== null) $data['name'] = (string)$name;
$category = $this->val($this->in, ['category', 'cat'], null); $category = $this->val($this->in, ['category', 'cat'], null);
if ($category !== null) $data['category'] = (string)$category; if ($category !== null && $catCol) $data[$catCol] = (string)$category;
$apiRaw = $this->val($this->in, ['api_name', 'apiName', 'api'], null); $apiRaw = $this->val($this->in, ['api_name', 'apiName', 'api'], null);
$apiName = $apiRaw !== null ? $this->normalizeApiName((string)$apiRaw) : null; $apiName = $apiRaw !== null ? $this->normalizeApiName((string)$apiRaw) : null;
@@ -769,24 +814,29 @@ class ApiKernel
$html = $this->val($this->in, ['html', 'body', 'markup', 'content'], null); $html = $this->val($this->in, ['html', 'body', 'markup', 'content'], null);
$json = $this->val($this->in, ['content_json', 'json', 'structure_json'], null); $json = $this->val($this->in, ['content_json', 'json', 'structure_json'], null);
if ($json !== null) { if ($json !== null) {
if (!$jsonCol) $this->fail('json_content column missing', null, 500);
$components = is_string($json) ? json_decode($json, true) : $json; $components = is_string($json) ? json_decode($json, true) : $json;
if (is_array($components)) { if (is_array($components)) {
$components = $this->cleanReferenceComponents($components); $components = $this->cleanReferenceComponents($components);
$data['json_content'] = $this->encodeJson($components); $data[$jsonCol] = $this->encodeJson($components);
} else { } else {
$data['json_content'] = is_string($json) ? $json : ''; $data[$jsonCol] = is_string($json) ? $json : '';
}
if ($html !== null) {
if (!$htmlCol) $this->fail('html column missing', null, 500);
$data[$htmlCol] = (string)$html;
} }
if ($html !== null) $data['html'] = (string)$html;
} elseif ($html !== null) { } elseif ($html !== null) {
$data['html'] = (string)$html; if (!$htmlCol) $this->fail('html column missing', null, 500);
$data[$htmlCol] = (string)$html;
} }
$editorType = $this->val($this->in, ['editor_type', 'editor'], null); $editorType = $this->val($this->in, ['editor_type', 'editor'], null);
if ($editorType !== null) $data['editor_type'] = strtolower(trim((string)$editorType)); if ($editorType !== null && $editorCol) $data[$editorCol] = strtolower(trim((string)$editorType));
$craftJson = $this->val($this->in, ['craft_json', 'craft_content', 'craft_data'], null); $craftJson = $this->val($this->in, ['craft_json', 'craft_content', 'craft_data'], null);
if ($craftJson !== null) $data['craft_json'] = is_string($craftJson) ? $craftJson : $this->encodeJson($craftJson); if ($craftJson !== null && $craftCol) $data[$craftCol] = is_string($craftJson) ? $craftJson : $this->encodeJson($craftJson);
$settings = $this->val($this->in, ['settings_json', 'settings'], null); $settings = $this->val($this->in, ['settings_json', 'settings'], null);
if ($settings !== null) $data['settings_json'] = is_string($settings) ? $settings : $this->encodeJson($settings); if ($settings !== null && $settingsCol) $data[$settingsCol] = is_string($settings) ? $settings : $this->encodeJson($settings);
if (!$data) { if (!$data) {
$this->respond(['ok' => true, 'kind' => 'content', 'id' => $id, 'updated' => true]); $this->respond(['ok' => true, 'kind' => 'content', 'id' => $id, 'updated' => true]);
@@ -2313,7 +2363,17 @@ class ApiKernel
return null; return null;
} }
$itemsTable = $this->contentItemsTable(); $itemsTable = $this->contentItemsTable();
$sql = "SELECT `html`,`json_content` FROM `$itemsTable` WHERE `customer_id` = :cid AND `section_id` = :sid AND `id` = :id LIMIT 1"; $itemCols = $this->resolveContentItemColumns($itemsTable);
$htmlCol = $itemCols['html'];
$jsonCol = $itemCols['json'];
if (!$htmlCol && !$jsonCol) {
$cache[$cacheKey] = null;
return null;
}
$selectCols = [];
if ($htmlCol) $selectCols[] = "`$htmlCol`";
if ($jsonCol) $selectCols[] = "`$jsonCol`";
$sql = "SELECT " . implode(',', $selectCols) . " FROM `$itemsTable` WHERE `customer_id` = :cid AND `section_id` = :sid AND `id` = :id LIMIT 1";
$stmt = $this->pdo->prepare($sql); $stmt = $this->pdo->prepare($sql);
$stmt->execute([':cid' => $customerId, ':sid' => (int)$section['id'], ':id' => $id]); $stmt->execute([':cid' => $customerId, ':sid' => (int)$section['id'], ':id' => $id]);
$row = $stmt->fetch(); $row = $stmt->fetch();
@@ -2321,7 +2381,7 @@ class ApiKernel
$cache[$cacheKey] = null; $cache[$cacheKey] = null;
return null; return null;
} }
$html = (string)($row['html'] ?? ''); $html = $htmlCol ? (string)($row[$htmlCol] ?? '') : '';
} else { } else {
if (!$kindKey) return null; if (!$kindKey) return null;
$table = $this->tableMap[$kindKey] ?? null; $table = $this->tableMap[$kindKey] ?? null;