diff --git a/config/current.ver b/config/current.ver index d9e75ec..280fc9d 100644 --- a/config/current.ver +++ b/config/current.ver @@ -1 +1 @@ -1.2.28 \ No newline at end of file +1.2.29 \ No newline at end of file diff --git a/public/assets/js/ui-editor.js b/public/assets/js/ui-editor.js index d76e79e..c58053c 100644 --- a/public/assets/js/ui-editor.js +++ b/public/assets/js/ui-editor.js @@ -107,10 +107,16 @@ export function initEditor() { }); }; - return async ({ title, text, confirmLabel = 'Bestätigen', cancelLabel = 'Abbrechen' }) => { + return async ({ title, text, html = false, confirmLabel = 'Bestätigen', cancelLabel = 'Abbrechen' }) => { ensure(); if (titleEl) titleEl.textContent = title || 'Bestätigung'; - if (textEl) textEl.textContent = text || ''; + if (textEl) { + if (html) { + textEl.innerHTML = text || ''; + } else { + textEl.textContent = text || ''; + } + } if (btnOk) btnOk.textContent = confirmLabel; if (btnCancel) btnCancel.textContent = cancelLabel; if (!dialog.open) dialog.showModal(); @@ -120,6 +126,24 @@ export function initEditor() { }; })(); + function formatReferencesHtml(refs = []) { + if (!refs.length) return ''; + const lines = refs.map(ref => { + const name = String(ref.name || 'Template') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + const id = Number(ref.id || 0); + const versions = Array.isArray(ref.versions) && ref.versions.length + ? ` – Versionen: ${ref.versions.join(', ')}` + : ''; + return `• ${name} #${id}${versions}`; + }); + return lines.join('
'); + } +   // ---------- Hilfen ---------- function activeMode() { const activeSection = window.__activeSection || current?.section || null; @@ -1174,9 +1198,22 @@ export function initEditor() { if (!refs.length) return true; const preview = refs.slice(0, 6).map(r => `${r.name || 'Template'} #${r.id}`).join(', '); const more = refs.length > 6 ? ` und ${refs.length - 6} weitere` : ''; + const escPreview = preview + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + const escMore = more + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); return await showConfirmDialog({ title: 'Template wird verwendet', - text: `Dieses Template wird in ${refs.length} anderen Template(s) verwendet (${preview}${more}). ${actionLabel} trotzdem?`, + html: true, + text: `Dieses Template wird in ${refs.length} anderen Template(s) verwendet (${escPreview}${escMore}).
${formatReferencesHtml(refs)}
${actionLabel} trotzdem?`, confirmLabel: actionLabel, }); } @@ -1211,9 +1248,11 @@ export function initEditor() { if (!okRefs) return; let res = await apiAction('content_versions.deactivate', { method: 'POST', data: { content_id: current.id } }); if (res && res.ok === false && Array.isArray(res.references) && res.references.length) { + const refs = res.references || []; const ok = await showConfirmDialog({ title: 'Template wird verwendet', - text: 'Dieses Template wird in anderen Templates verwendet. Trotzdem deaktivieren?', + html: true, + text: `Dieses Template wird in anderen Templates verwendet.
${formatReferencesHtml(refs)}
Deaktivieren trotzdem?`, confirmLabel: 'Deaktivieren', }); if (!ok) return; diff --git a/public/assets/js/ui-list.js b/public/assets/js/ui-list.js index e4045d7..3047c29 100644 --- a/public/assets/js/ui-list.js +++ b/public/assets/js/ui-list.js @@ -53,10 +53,16 @@ const showConfirmDialog = (() => { }); }; - return async ({ title, text, confirmLabel = 'Bestätigen', cancelLabel = 'Abbrechen' }) => { + return async ({ title, text, html = false, confirmLabel = 'Bestätigen', cancelLabel = 'Abbrechen' }) => { ensure(); if (titleEl) titleEl.textContent = title || 'Bestätigung'; - if (textEl) textEl.textContent = text || ''; + if (textEl) { + if (html) { + textEl.innerHTML = text || ''; + } else { + textEl.textContent = text || ''; + } + } if (btnOk) btnOk.textContent = confirmLabel; if (btnCancel) btnCancel.textContent = cancelLabel; if (!dialog.open) dialog.showModal(); @@ -66,6 +72,19 @@ const showConfirmDialog = (() => { }; })(); +function formatReferencesHtml(refs = []) { + if (!refs.length) return ''; + const lines = refs.map(ref => { + const name = esc(ref.name || 'Template'); + const id = Number(ref.id || 0); + const versions = Array.isArray(ref.versions) && ref.versions.length + ? ` – Versionen: ${ref.versions.join(', ')}` + : ''; + return `• ${name} #${id}${versions}`; + }); + return lines.join('
'); +} + function formatVersionDate(value) { if (!value) return ''; try { @@ -173,7 +192,8 @@ async function openTemplateManager(item, section) { const more = refs.length > 6 ? ` und ${refs.length - 6} weitere` : ''; return await showConfirmDialog({ title: 'Template wird verwendet', - text: `Dieses Template wird in ${refs.length} anderen Template(s) verwendet (${preview}${more}). ${actionLabel} trotzdem?`, + html: true, + text: `Dieses Template wird in ${refs.length} anderen Template(s) verwendet (${esc(preview)}${esc(more)}).
${formatReferencesHtml(refs)}
${esc(actionLabel)} trotzdem?`, confirmLabel: actionLabel, }); }; @@ -327,9 +347,11 @@ async function openTemplateManager(item, section) { } let res = await apiAction('content_versions.deactivate', { method: 'POST', data: { content_id: item.id } }); if (res && res.ok === false && Array.isArray(res.references) && res.references.length) { + const refs = res.references || []; const ok = await showConfirmDialog({ title: 'Template wird verwendet', - text: 'Dieses Template wird in anderen Templates verwendet. Trotzdem deaktivieren?', + html: true, + text: `Dieses Template wird in anderen Templates verwendet.
${formatReferencesHtml(refs)}
Deaktivieren trotzdem?`, confirmLabel: 'Deaktivieren', }); if (!ok) return; @@ -346,9 +368,11 @@ async function openTemplateManager(item, section) { if (!confirm('Version wirklich löschen?')) return; let res = await apiAction('content_versions.delete', { method: 'POST', data: { id: vid, content_id: item.id } }); if (res && res.ok === false && Array.isArray(res.references) && res.references.length) { + const refs = res.references || []; const ok = await showConfirmDialog({ title: 'Template wird verwendet', - text: 'Dieses Template wird in anderen Templates verwendet. Trotzdem löschen?', + html: true, + text: `Dieses Template wird in anderen Templates verwendet.
${formatReferencesHtml(refs)}
Löschen trotzdem?`, confirmLabel: 'Löschen', }); if (!ok) return; diff --git a/src/ApiKernel.php b/src/ApiKernel.php index c594077..5bd6d78 100644 --- a/src/ApiKernel.php +++ b/src/ApiKernel.php @@ -2337,7 +2337,7 @@ class ApiKernel private function findTemplateReferences(int $customerId, int $templateId, array &$debug = []): array { $out = []; - $seen = []; + $byId = []; $debug = [ 'time' => date(DATE_ATOM), 'customer_id' => $customerId, @@ -2347,6 +2347,31 @@ class ApiKernel 'matched_rows' => [], 'template_items_matches' => [], ]; + $addRef = function (int $id, string $name) use (&$out, &$byId) { + if ($id <= 0) return; + if (!isset($byId[$id])) { + $entry = [ + 'id' => $id, + 'name' => $name, + 'versions' => [], + ]; + $byId[$id] = count($out); + $out[] = $entry; + } elseif ($name !== '') { + $out[$byId[$id]]['name'] = $name; + } + }; + $addVersion = function (int $id, ?int $ver) use (&$out, &$byId) { + if ($id <= 0 || $ver === null || $ver <= 0) return; + if (!isset($byId[$id])) { + return; + } + $idx = $byId[$id]; + if (!in_array($ver, $out[$idx]['versions'], true)) { + $out[$idx]['versions'][] = $ver; + sort($out[$idx]['versions']); + } + }; $matches = function (?string $html, array $libKinds = []) use ($templateId): bool { if (!$html) return false; $id = preg_quote((string)$templateId, '/'); @@ -2463,12 +2488,8 @@ class ApiKernel } if ($found) { $id = (int)($row['id'] ?? 0); - if ($id <= 0 || isset($seen[$id])) continue; - $seen[$id] = true; - $out[] = [ - 'id' => $id, - 'name' => (string)($row['name'] ?? ''), - ]; + if ($id <= 0) continue; + $addRef($id, (string)($row['name'] ?? '')); if (count($debug['matched_rows']) < 50) { $debug['matched_rows'][] = [ 'id' => $id, @@ -2478,6 +2499,62 @@ class ApiKernel } } } + + if ($this->tableExists($versionsTable)) { + $vCols = $this->tableColumns($versionsTable); + $vHtml = $versionHtmlCol; + $vJson = $versionJsonCol; + $vCraft = $versionCraftCol; + $vSettings = $versionSettingsCol; + $vNo = $this->firstExisting($vCols, ['version_no', 'version', 'ver', 'version_nr']); + $vContentId = $this->firstExisting($vCols, ['content_id', 'content']); + $vCustomerId = $this->firstExisting($vCols, ['customer_id', 'customer']); + $vSectionId = $this->firstExisting($vCols, ['section_id', 'section']); + if ($vContentId && $vNo) { + $select = "v.`$vContentId` AS content_id, v.`$vNo` AS version_no, i.`name` AS name"; + if ($vHtml) $select .= ", v.`$vHtml` AS version_html"; + if ($vJson) $select .= ", v.`$vJson` AS version_json"; + if ($vCraft) $select .= ", v.`$vCraft` AS version_craft"; + if ($vSettings) $select .= ", v.`$vSettings` AS version_settings"; + $join = "LEFT JOIN `$itemsTable` i ON i.`id` = v.`$vContentId`"; + $where = []; + $params = []; + if ($vCustomerId) { + $where[] = "v.`$vCustomerId` = :cid"; + $params[':cid'] = $customerId; + } + if ($vSectionId) { + $where[] = "v.`$vSectionId` = :sid"; + $params[':sid'] = (int)$section['id']; + } + $where[] = "v.`$vContentId` <> :id"; + $params[':id'] = $templateId; + $sql = "SELECT $select FROM `$versionsTable` v $join WHERE " . implode(' AND ', $where); + $stmt = $this->pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll() ?: []; + foreach ($rows as $row) { + $blobs = [ + (string)($row['version_html'] ?? ''), + (string)($row['version_json'] ?? ''), + (string)($row['version_craft'] ?? ''), + (string)($row['version_settings'] ?? ''), + ]; + $found = false; + foreach ($blobs as $blob) { + if ($matches($blob, $libKinds)) { + $found = true; + break; + } + } + if ($found) { + $cid = (int)($row['content_id'] ?? 0); + $addRef($cid, (string)($row['name'] ?? '')); + $addVersion($cid, (int)($row['version_no'] ?? 0)); + } + } + } + } } if (!$this->useUnifiedContent()) { $table = $this->tableMap['templates'] ?? null; @@ -2520,12 +2597,8 @@ class ApiKernel } if ($found) { $id = (int)($row['id'] ?? 0); - if ($id <= 0 || isset($seen[$id])) continue; - $seen[$id] = true; - $out[] = [ - 'id' => $id, - 'name' => (string)($row['name'] ?? ''), - ]; + if ($id <= 0) continue; + $addRef($id, (string)($row['name'] ?? '')); if (count($debug['matched_rows']) < 50) { $debug['matched_rows'][] = [ 'id' => $id, @@ -2559,12 +2632,8 @@ class ApiKernel $rows = $stmt->fetchAll() ?: []; foreach ($rows as $row) { $id = (int)($row['id'] ?? 0); - if ($id <= 0 || isset($seen[$id])) continue; - $seen[$id] = true; - $out[] = [ - 'id' => $id, - 'name' => (string)($row['name'] ?? ''), - ]; + if ($id <= 0) continue; + $addRef($id, (string)($row['name'] ?? '')); } } } else { @@ -2588,12 +2657,8 @@ class ApiKernel $rows = $stmt->fetchAll() ?: []; foreach ($rows as $row) { $id = (int)($row['id'] ?? 0); - if ($id <= 0 || isset($seen[$id])) continue; - $seen[$id] = true; - $out[] = [ - 'id' => $id, - 'name' => (string)($row['name'] ?? ''), - ]; + if ($id <= 0) continue; + $addRef($id, (string)($row['name'] ?? '')); } } }