dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxKi.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxKi;
3
4require_once dirname(__DIR__) . '/dbxContent/include/dbxContent_bootstrap_sync.php';
5
6class dbxKi {
7
8 private function cms() {
9 return dbx()->get_include_obj('dbxKiCmsService', 'dbxKi');
10 }
11
12 private function bundle() {
13 return dbx()->get_include_obj('dbxKiBundleService', 'dbxKi');
14 }
15
16 private function briefing() {
17 return dbx()->get_include_obj('dbxKiBriefingService', 'dbxKi');
18 }
19
20 private function moduleBriefing() {
21 return dbx()->get_include_obj('dbxKiModuleBriefingService', 'dbxKi');
22 }
23
24 private function help() {
25 return dbx()->get_include_obj('dbxKiHelp', 'dbxKi');
26 }
27
28 private function provision() {
29 require_once __DIR__ . '/include/dbxKiCmsHelpProvision.class.php';
31 }
32
33 private function html_start(): string {
34 require_once __DIR__ . '/include/dbxKiCmsHelpProvision.class.php';
36 $anleitungUrl = dbxKiCmsHelpProvision::pageUrl();
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';
41
42 return '<div class="container py-4">'
43 . '<h1>dbxKi</h1>'
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>'
50 . '</div>'
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>'
53 . '</div>';
54 }
55
56 private function esc($value): string {
57 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
58 }
59
60 private function language_options(string $selected): string {
61 $html = '';
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>';
65 }
66 return $html;
67 }
68
69 private function target_language_checkboxes(string $sourceLng, array $selected): string {
70 $html = '';
71 foreach (\dbx\dbxContent\dbxContentLngSync::accessibleLngs() as $lng) {
72 if ($lng === $sourceLng) {
73 continue;
74 }
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>'
79 . '</label>';
80 }
81 return $html !== '' ? $html : '<span class="text-muted">Keine weitere aktive Sprache vorhanden.</span>';
82 }
83
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);
89 }
90 if (!is_array($targets)) {
91 $targets = array();
92 }
93 return array(
94 'source_lng' => $sourceLng,
95 'target_lngs' => array_values(array_filter(array_map(static function($lng) {
96 return strtolower(trim((string)$lng));
97 }, $targets))),
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,
103 );
104 }
105
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;
113 }
114 }
115 }
116
117 $mode = strtolower(trim((string)dbx()->get_request_var('sync_mode', '', '*')));
118 $resultHtml = '';
119 if ($mode === 'preview' || $mode === 'execute') {
120 try {
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.');
126 }
127 $result = $this->cms()->bundleExecutePlan('translation.sync_all', $params, $plan);
128 $resultHtml = $this->translation_sync_result_html($result, true);
129 } else {
130 $resultHtml = $this->translation_sync_plan_html($plan);
131 }
132 } catch (\Throwable $e) {
133 $resultHtml = '<div class="alert alert-danger">' . $this->esc($e->getMessage()) . '</div>';
134 }
135 }
136
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>'
142 . '</div>'
143 . '<form method="post" class="card p-3 mb-3" action="?dbx_modul=dbxKi&amp;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)
154 . '</div>'
155 . '</div>'
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>'
159 . '</div>'
160 . '</form>'
161 . $resultHtml
162 . '</div>';
163 }
164
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>'
169 . '</label>';
170 }
171
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'] ?? '')) . '.'
182 . '</div>';
183 }
184
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>';
199 }
200 $html .= '</ul></div>';
201 }
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>';
207 }
208 $html .= '</ul></div>';
209 }
210 return $html;
211 }
212
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';
219
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>'
226 . '</div></div>';
227 }
228
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>';
233 }
234
235 public function run($action = '') {
236 if ($action === '') {
237 $action = dbx()->get_modul_var('dbx_run1', 'start', 'parameter');
238 }
239
240 switch ($action) {
241 case 'api':
242 case 'describe':
243 case 'preview':
244 case 'execute':
245 $this->cms()->handle($action);
246 return '';
247
248 case 'briefing':
249 return $this->briefing()->renderHub();
250
251 case 'briefing_module':
252 return $this->moduleBriefing()->renderBriefing();
253
254 case 'briefing_module_export':
255 $this->moduleBriefing()->handleExport();
256 return '';
257
258 case 'module_bundle':
259 return $this->moduleBriefing()->renderBundleStart();
260
261 case 'module_bundle_import':
262 return $this->moduleBriefing()->handleBundleImport();
263
264 case 'module_api':
265 $this->moduleBriefing()->handleApi();
266 return '';
267
268 case 'briefing_page_create':
269 return $this->briefing()->renderPageCreateForm();
270
271 case 'briefing_page_update':
272 return $this->briefing()->renderPageUpdateForm();
273
274 case 'briefing_page_update_preview':
275 $this->briefing()->handlePageUpdatePreviewJson();
276 return '';
277
278 case 'briefing_page_translate':
279 return $this->briefing()->renderPageTranslateForm();
280
281 case 'translation_sync_all':
282 return $this->render_translation_sync_all();
283
284 case 'briefing_styles':
285 return $this->briefing()->renderStylesAdmin();
286
287 case 'briefing_styles_save':
288 return $this->briefing()->handleStylesSave();
289
290 case 'briefing_styles_reset':
291 return $this->briefing()->handleStylesReset();
292
293 case 'briefing_export':
294 try {
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));
300 }
301 return '';
302
303 case 'bundle':
304 return $this->bundle()->renderStartPage();
305
306 case 'bundle_import':
307 return $this->bundle()->handleImport();
308
309 case 'bundle_process':
310 return $this->bundle()->handleProcess();
311
312 case 'bundle_export':
313 $this->bundle()->handleExport();
314 return '';
315
316 case 'bundle_describe':
317 $this->bundle()->handleDescribeJson();
318 return '';
319
320 case 'help':
321 return $this->help()->renderHelpWindow();
322
323 case 'provision_anleitung':
324 require_once __DIR__ . '/include/dbxKiCmsHelpProvision.class.php';
325 $result = $this->provision();
326 if (empty($result['errors'])) {
327 $config = dbx()->get_config('dbxKi');
328 if (!is_array($config)) {
329 $config = array();
330 }
332 dbx()->set_config('dbxKi', $config);
333 }
334 return $this->html_provision_result($result);
335
336 case 'start':
337 default:
338 require_once __DIR__ . '/include/dbxKiCmsHelpProvision.class.php';
339 return $this->html_start();
340 }
341 }
342}
343
344?>
run($action='')
$config['version']
Definition config.php:2
DBX schema administration.
if(! $db->connect_db_server($server)) $result