8 private function cms() {
9 return dbx()->get_include_obj(
'dbxKiCmsService',
'dbxKi');
12 private function bundle() {
13 return dbx()->get_include_obj(
'dbxKiBundleService',
'dbxKi');
16 private function briefing() {
17 return dbx()->get_include_obj(
'dbxKiBriefingService',
'dbxKi');
20 private function moduleBriefing() {
21 return dbx()->get_include_obj(
'dbxKiModuleBriefingService',
'dbxKi');
24 private function help() {
25 return dbx()->get_include_obj(
'dbxKiHelp',
'dbxKi');
28 private function provision() {
29 require_once __DIR__ .
'/include/dbxKiCmsHelpProvision.class.php';
33 private function html_start():
string {
34 require_once __DIR__ .
'/include/dbxKiCmsHelpProvision.class.php';
37 $briefing =
'?dbx_modul=dbxKi&dbx_run1=briefing';
38 $bundle =
'?dbx_modul=dbxKi&dbx_run1=bundle';
39 $syncAll =
'?dbx_modul=dbxKi&dbx_run1=translation_sync_all';
40 $api =
'?dbx_modul=dbxKi&dbx_run1=api';
42 return '<div class="container py-4">'
44 .
'<p>Formular-basierte KI-Auftraege fuer das Content-CMS.</p>'
45 .
'<div class="d-flex flex-wrap gap-2 mb-3">'
46 .
'<a class="btn btn-primary" href="' . htmlspecialchars($briefing, ENT_QUOTES,
'UTF-8') .
'">KI-Auftrag starten</a>'
47 .
'<a class="btn btn-outline-primary" href="' . htmlspecialchars($syncAll, ENT_QUOTES,
'UTF-8') .
'">Sprache komplett uebersetzen</a>'
48 .
'<a class="btn btn-outline-primary" href="' . htmlspecialchars($bundle, ENT_QUOTES,
'UTF-8') .
'">Bundle importieren</a>'
49 .
'<a class="btn btn-outline-secondary" href="' . htmlspecialchars($anleitungUrl, ENT_QUOTES,
'UTF-8') .
'">Anleitung</a>'
51 .
'<p class="small text-muted">Ablauf: Formular → ZIP an ChatGPT → fertige ZIP importieren und ausfuehren.</p>'
52 .
'<p><strong>API:</strong> <code>' . htmlspecialchars($api, ENT_QUOTES,
'UTF-8') .
'</code></p>'
56 private function esc($value):
string {
57 return htmlspecialchars((
string)$value, ENT_QUOTES,
'UTF-8');
60 private function language_options(
string $selected):
string {
62 foreach (\
dbx\
dbxContent\dbxContentLngSync::accessibleLngs() as $lng) {
63 $sel = $lng === $selected ?
' selected' :
'';
64 $html .=
'<option value="' . $this->esc($lng) .
'"' . $sel .
'>' . $this->esc(strtoupper($lng)) .
'</option>';
69 private function target_language_checkboxes(
string $sourceLng, array $selected):
string {
71 foreach (\
dbx\
dbxContent\dbxContentLngSync::accessibleLngs() as $lng) {
72 if ($lng === $sourceLng) {
75 $checked = in_array($lng, $selected,
true) ?
' checked' :
'';
76 $html .=
'<label class="form-check form-check-inline">'
77 .
'<input class="form-check-input" type="checkbox" name="target_lngs[]" value="' . $this->esc($lng) .
'"' . $checked .
'>'
78 .
'<span class="form-check-label">' . $this->esc(strtoupper($lng)) .
'</span>'
81 return $html !==
'' ? $html :
'<span class="text-muted">Keine weitere aktive Sprache vorhanden.</span>';
84 private function translation_sync_params_from_request(): array {
85 $sourceLng = strtolower(trim((
string)
dbx()->get_request_var(
'source_lng', \
dbx\
dbxContent\dbxContentLngSync::masterLng(),
'*')));
86 $targets =
dbx()->get_request_var(
'target_lngs', array(),
'*');
87 if (is_string($targets)) {
88 $targets = array($targets);
90 if (!is_array($targets)) {
94 'source_lng' => $sourceLng,
95 'target_lngs' => array_values(array_filter(array_map(
static function($lng) {
96 return strtolower(trim((
string)$lng));
98 'root_folder_id' => max(0, (
int)
dbx()->get_request_var(
'root_folder_id', 0,
'*')),
99 'update_existing' => !array_key_exists(
'sync_mode', $_REQUEST) ||
dbx()->get_request_var(
'update_existing',
'0',
'*') ===
'1' ? 1 : 0,
100 'skip_manual' =>
dbx()->get_request_var(
'skip_manual',
'0',
'*') ===
'1' ? 1 : 0,
101 'copy_media' => !array_key_exists(
'sync_mode', $_REQUEST) ||
dbx()->get_request_var(
'copy_media',
'0',
'*') ===
'1' ? 1 : 0,
102 'replace_media_usage' =>
dbx()->get_request_var(
'replace_media_usage',
'0',
'*') ===
'1' ? 1 : 0,
106 private function render_translation_sync_all():
string {
107 $params = $this->translation_sync_params_from_request();
108 $hasSubmitted = array_key_exists(
'sync_mode', $_REQUEST);
109 if (!$hasSubmitted && !count($params[
'target_lngs'])) {
110 foreach (\
dbx\
dbxContent\dbxContentLngSync::accessibleLngs() as $lng) {
111 if ($lng !== $params[
'source_lng']) {
112 $params[
'target_lngs'][] = $lng;
117 $mode = strtolower(trim((
string)
dbx()->get_request_var(
'sync_mode',
'',
'*')));
119 if ($mode ===
'preview' || $mode ===
'execute') {
121 $plan = $this->cms()->bundleBuildPlan(
'translation.sync_all', $params);
122 if ($mode ===
'execute') {
123 $token = trim((
string)
dbx()->get_request_var(
'token',
'',
'*'));
124 if (!
dbx()->check_action_token(
'dbxKi.translation_sync_all', $token)) {
125 throw new \RuntimeException(
'Ungueltiger oder abgelaufener Token.');
127 $result = $this->cms()->bundleExecutePlan(
'translation.sync_all', $params, $plan);
128 $resultHtml = $this->translation_sync_result_html(
$result,
true);
130 $resultHtml = $this->translation_sync_plan_html($plan);
132 }
catch (\Throwable $e) {
133 $resultHtml =
'<div class="alert alert-danger">' . $this->esc($e->getMessage()) .
'</div>';
137 $token =
dbx()->action_token(
'dbxKi.translation_sync_all');
138 return '<div class="container py-4 dbx-ki-translation-sync">'
139 .
'<div class="d-flex align-items-center justify-content-between mb-3">'
140 .
'<div><h1 class="h3 mb-1">Sprache komplett uebersetzen</h1><p class="text-muted mb-0">CMS-Struktur aus einer Sprache in Zielsprachen uebertragen.</p></div>'
141 .
'<a class="btn btn-outline-secondary btn-sm" href="?dbx_modul=dbxKi">Zurueck</a>'
143 .
'<form method="post" class="card p-3 mb-3" action="?dbx_modul=dbxKi&dbx_run1=translation_sync_all">'
144 .
'<input type="hidden" name="token" value="' . $this->esc($token) .
'">'
145 .
'<div class="row g-3">'
146 .
'<div class="col-md-4"><label class="form-label">Von Sprache</label><select class="form-select" name="source_lng">' . $this->language_options($params[
'source_lng']) .
'</select></div>'
147 .
'<div class="col-md-8"><label class="form-label d-block">Zu Zielsprache</label><div class="border rounded p-2 bg-light">' . $this->target_language_checkboxes($params[
'source_lng'], $params[
'target_lngs']) .
'</div></div>'
148 .
'<div class="col-md-4"><label class="form-label">Root-Ordner-ID</label><input class="form-control" type="number" name="root_folder_id" min="0" value="' . $this->esc((
string)$params[
'root_folder_id']) .
'"><div class="form-text">0 bedeutet komplette Sprache.</div></div>'
149 .
'<div class="col-md-8"><label class="form-label d-block">Optionen</label>'
150 . $this->checkbox_html(
'update_existing',
'Vorhandene Uebersetzungen aktualisieren', (
int)$params[
'update_existing'] === 1)
151 . $this->checkbox_html(
'skip_manual',
'Manuelle Zielinhalte ueberspringen', (
int)$params[
'skip_manual'] === 1)
152 . $this->checkbox_html(
'copy_media',
'Medienzuordnungen uebernehmen', (
int)$params[
'copy_media'] === 1)
153 . $this->checkbox_html(
'replace_media_usage',
'Medienzuordnungen im Ziel ersetzen', (
int)$params[
'replace_media_usage'] === 1)
156 .
'<div class="d-flex flex-wrap gap-2 mt-3">'
157 .
'<button class="btn btn-outline-primary" type="submit" name="sync_mode" value="preview">Vorschau</button>'
158 .
'<button class="btn btn-primary" type="submit" name="sync_mode" value="execute">Jetzt ausfuehren</button>'
165 private function checkbox_html(
string $name,
string $label,
bool $checked):
string {
166 return '<label class="form-check form-check-inline mb-2">'
167 .
'<input class="form-check-input" type="checkbox" name="' . $this->esc($name) .
'" value="1"' . ($checked ?
' checked' :
'') .
'>'
168 .
'<span class="form-check-label">' . $this->esc($label) .
'</span>'
172 private function translation_sync_plan_html(array $plan):
string {
173 $counts = is_array($plan[
'counts'] ??
null) ? $plan[
'counts'] : array();
174 $targets = is_array($plan[
'target_lngs'] ??
null) ? implode(
', ', array_map(
'strtoupper', $plan[
'target_lngs'])) :
'';
175 return '<div class="alert alert-info">'
176 .
'<strong>Vorschau:</strong> '
177 . $this->esc((
string)($counts[
'folders'] ?? 0)) .
' Ordner und '
178 . $this->esc((
string)($counts[
'pages'] ?? 0)) .
' Seiten von '
179 . $this->esc(strtoupper((
string)($plan[
'source_lng'] ??
''))) .
' nach '
180 . $this->esc($targets) .
'. Provider: '
181 . $this->esc((
string)($plan[
'provider'] ??
'')) .
'.'
185 private function translation_sync_result_html(array
$result,
bool $executed):
string {
186 $folders = is_array(
$result[
'folders'] ??
null) ?
$result[
'folders'] : array();
187 $pages = is_array(
$result[
'pages'] ??
null) ?
$result[
'pages'] : array();
188 $errors = is_array(
$result[
'errors'] ??
null) ?
$result[
'errors'] : array();
189 $warnings = is_array(
$result[
'warnings'] ??
null) ?
$result[
'warnings'] : array();
190 $class = count($errors) ?
'alert-warning' :
'alert-success';
191 $html =
'<div class="alert ' . $class .
'"><strong>' . ($executed ?
'Ausgefuehrt.' :
'Ergebnis.') .
'</strong> '
192 .
'Ordner: ' . count($folders[
'created'] ?? array()) .
' neu, ' . count($folders[
'updated'] ?? array()) .
' aktualisiert. '
193 .
'Seiten: ' . count($pages[
'created'] ?? array()) .
' neu, ' . count($pages[
'updated'] ?? array()) .
' aktualisiert. '
194 .
'Medienzuordnungen: ' . (int)(
$result[
'media_copied'] ?? 0) .
'.</div>';
195 if (count($errors)) {
196 $html .=
'<div class="card p-3 mb-3"><h2 class="h6">Fehler</h2><ul class="mb-0">';
197 foreach ($errors as $error) {
198 $html .=
'<li>' . $this->esc($error) .
'</li>';
200 $html .=
'</ul></div>';
202 if (count($warnings)) {
203 $html .=
'<div class="card p-3 mb-3"><h2 class="h6">Hinweise</h2><ul class="mb-0">';
204 foreach ($warnings as $warning) {
205 $msg = is_array($warning) ? (($warning[
'message'] ??
'') .
' ' . ($warning[
'lng'] ??
'')) : (string)$warning;
206 $html .=
'<li>' . $this->esc(trim($msg)) .
'</li>';
208 $html .=
'</ul></div>';
213 private function html_provision_result(array
$result):
string {
214 $ok = empty(
$result[
'errors']);
215 $class = $ok ?
'alert-success' :
'alert-danger';
216 $msg = $ok ?
'CMS-Anleitung provisioniert.' :
'Provision fehlgeschlagen.';
218 $back =
'?dbx_modul=dbxKi';
220 return '<div class="container py-4">'
221 .
'<div class="alert ' . $class .
'">' . htmlspecialchars($msg, ENT_QUOTES,
'UTF-8') .
'</div>'
222 .
'<pre class="bg-light p-3 rounded">' . htmlspecialchars(json_encode(
$result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), ENT_QUOTES,
'UTF-8') .
'</pre>'
223 .
'<div class="d-flex flex-wrap gap-2">'
224 .
'<a class="btn btn-primary" href="' . htmlspecialchars($pageUrl, ENT_QUOTES,
'UTF-8') .
'">Zur Anleitung</a>'
225 .
'<a class="btn btn-outline-secondary" href="' . htmlspecialchars($back, ENT_QUOTES,
'UTF-8') .
'">dbxKi Uebersicht</a>'
229 private function html_error(
string $message,
string $backUrl):
string {
230 return '<div class="container py-4"><div class="alert alert-danger">'
231 . htmlspecialchars($message, ENT_QUOTES,
'UTF-8')
232 .
'</div><p><a class="btn btn-secondary" href="' . htmlspecialchars($backUrl, ENT_QUOTES,
'UTF-8') .
'">Zurueck</a></p></div>';
235 public function run($action =
'') {
236 if ($action ===
'') {
237 $action =
dbx()->get_modul_var(
'dbx_run1',
'start',
'parameter');
245 $this->cms()->handle($action);
249 return $this->briefing()->renderHub();
251 case 'briefing_module':
252 return $this->moduleBriefing()->renderBriefing();
254 case 'briefing_module_export':
255 $this->moduleBriefing()->handleExport();
258 case 'module_bundle':
259 return $this->moduleBriefing()->renderBundleStart();
261 case 'module_bundle_import':
262 return $this->moduleBriefing()->handleBundleImport();
265 $this->moduleBriefing()->handleApi();
268 case 'briefing_page_create':
269 return $this->briefing()->renderPageCreateForm();
271 case 'briefing_page_update':
272 return $this->briefing()->renderPageUpdateForm();
274 case 'briefing_page_update_preview':
275 $this->briefing()->handlePageUpdatePreviewJson();
278 case 'briefing_page_translate':
279 return $this->briefing()->renderPageTranslateForm();
281 case 'translation_sync_all':
282 return $this->render_translation_sync_all();
284 case 'briefing_styles':
285 return $this->briefing()->renderStylesAdmin();
287 case 'briefing_styles_save':
288 return $this->briefing()->handleStylesSave();
290 case 'briefing_styles_reset':
291 return $this->briefing()->handleStylesReset();
293 case 'briefing_export':
295 $this->briefing()->handleExport();
296 }
catch (\Throwable $e) {
297 dbx()->sys_msg(
'error',
'dbxKi',
'briefing_export',
'Export fehlgeschlagen', $e->getMessage());
298 $recipe = strtolower(trim((
string)
dbx()->get_request_var(
'recipe',
'',
'*')));
299 return $this->html_error($e->getMessage(), $this->briefing()->exportBackUrl($recipe));
304 return $this->bundle()->renderStartPage();
306 case 'bundle_import':
307 return $this->bundle()->handleImport();
309 case 'bundle_process':
310 return $this->bundle()->handleProcess();
312 case 'bundle_export':
313 $this->bundle()->handleExport();
316 case 'bundle_describe':
317 $this->bundle()->handleDescribeJson();
321 return $this->help()->renderHelpWindow();
323 case 'provision_anleitung':
324 require_once __DIR__ .
'/include/dbxKiCmsHelpProvision.class.php';
326 if (empty(
$result[
'errors'])) {
334 return $this->html_provision_result(
$result);
338 require_once __DIR__ .
'/include/dbxKiCmsHelpProvision.class.php';
339 return $this->html_start();
if(! $db->connect_db_server($server)) $result