dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxWizard.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxAdmin;
3
4class dbxWizard {
5
6 private function esc($value) {
7 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
8 }
9
10 private function modules_root() {
11 return dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/');
12 }
13
14 private function backup_root() {
15 $dir = dbx_os_path_file(dbx_get_file_dir() . 'module-backup/');
16 if (!is_dir($dir)) {
17 @mkdir($dir, 0777, true);
18 }
19 return $dir;
20 }
21
22 private function valid_name($name) {
23 return is_string($name) && preg_match('/^[A-Za-z][A-Za-z0-9_]{1,63}$/', $name);
24 }
25
26 private function valid_file_name($name) {
27 return is_string($name) && preg_match('/^[A-Za-z0-9_.-]+$/', $name) && strpos($name, '..') === false;
28 }
29
30 private function module_dir($modul) {
31 return dbx_os_path_file($this->modules_root() . $modul . DIRECTORY_SEPARATOR);
32 }
33
34 private function module_path($modul, $rel) {
35 $rel = str_replace(array('\\', "\0"), array('/', ''), (string)$rel);
36 $rel = ltrim($rel, '/');
37 if ($rel === '' || strpos($rel, '../') !== false || strpos($rel, '..\\') !== false) {
38 return '';
39 }
40 $base = $this->module_dir($modul);
41 $path = dbx_os_path_file($base . $rel);
42 $baseNorm = str_replace('\\', '/', rtrim($base, '/\\') . '/');
43 $pathNorm = str_replace('\\', '/', $path);
44 if (strpos($pathNorm, $baseNorm) !== 0) {
45 return '';
46 }
47 return $path;
48 }
49
50 private function module_options($withNew = false) {
51 $options = $withNew ? array('' => 'Bitte waehlen') : array();
52 $root = $this->modules_root();
53 foreach (glob($root . '*', GLOB_ONLYDIR) ?: array() as $dir) {
54 $name = basename($dir);
55 if ($this->valid_name($name)) {
56 $options[$name] = $name;
57 }
58 }
59 ksort($options);
60 return $options;
61 }
62
63 private function dd_options($withEmpty = true) {
64 $options = $withEmpty ? array('' => 'Bitte waehlen / manuell') : array();
65 foreach (glob($this->modules_root() . '*', GLOB_ONLYDIR) ?: array() as $dir) {
66 $modul = basename($dir);
67 if (!$this->valid_name($modul)) {
68 continue;
69 }
70 foreach (glob(dbx_os_path_file($dir . '/dd/') . '*.dd.php') ?: array() as $file) {
71 $dd = basename($file, '.dd.php');
72 if ($this->valid_name($dd)) {
73 $options[$modul . '|' . $dd] = $modul . ' | ' . $dd;
74 }
75 }
76 }
77 ksort($options);
78 return $options;
79 }
80
81 private function options($items) {
82 return is_array($items) ? $items : array();
83 }
84
85 private function collect_input($form = null) {
86 $get = function($name, $default = '', $rules = 'parameter') use ($form) {
87 if ($form && isset($form->_post[$name])) {
88 return $form->_post[$name];
89 }
90 return dbx()->get_modul_var($name, $default, $rules);
91 };
92
93 $target = (string)$get('target_mode', 'new');
94 $existingModul = trim((string)$get('existing_modul', ''));
95 $modul = trim((string)$get('xmodul', ''));
96 if ($target === 'existing' && $existingModul !== '') {
97 $modul = $existingModul;
98 }
99 if ($modul === '') {
100 $modul = trim((string)$get('modul', ''));
101 }
102
103 $ddRef = trim((string)$get('dd_ref', ''));
104 $title = trim((string)$get('title', $modul));
105 $dd = trim((string)$get('dd_name', $modul ? $modul . 'Data' : ''));
106 if ($ddRef !== '' && strpos($ddRef, '|') !== false) {
107 list($refModul, $refDd) = explode('|', $ddRef, 2);
108 if ($target === 'existing' && $modul === '' && $this->valid_name($refModul)) {
109 $modul = $refModul;
110 }
111 if ($this->valid_name($refDd)) {
112 $dd = $refDd;
113 }
114 }
115 $table = trim((string)$get('table_name', $dd));
116 $dbFile = trim((string)$get('db_file', $modul ? $modul . '.db3' : 'modul.db3'));
117
118 return array(
119 'target_mode' => in_array($target, array('new', 'existing'), true) ? $target : 'new',
120 'existing_modul' => $existingModul,
121 'dd_ref' => $ddRef,
122 'xmodul' => $modul,
123 'title' => $title !== '' ? $title : $modul,
124 'default_run1' => trim((string)$get('default_run1', 'run')),
125 'default_run2' => trim((string)$get('default_run2', '')),
126 'module_template' => (string)$get('module_template', 'form_report'),
127 'dd_mode' => (string)$get('dd_mode', 'new'),
128 'dd_name' => $dd,
129 'table_name' => $table,
130 'db_file' => $dbFile,
131 'field_preset' => (string)$get('field_preset', 'basic'),
132 'create_include' => (int)$get('create_include', 1, 'int'),
133 'create_form' => (int)$get('create_form', 1, 'int'),
134 'create_report' => (int)$get('create_report', 1, 'int'),
135 'create_templates' => (int)$get('create_templates', 1, 'int'),
136 'overwrite' => (int)$get('overwrite', 0, 'int'),
137 'backup' => (int)$get('backup', 1, 'int'),
138 'sync_mode' => (string)$get('sync_mode', 'link'),
139 'ki_package' => (int)$get('ki_package', 1, 'int'),
140 );
141 }
142
143 private function validate_input(array $in, &$errors) {
144 $errors = array();
145 if (!$this->valid_name($in['xmodul'])) {
146 $errors[] = 'Modulname ungueltig. Erlaubt: Buchstaben, Zahlen, Unterstrich; Beginn mit Buchstabe.';
147 }
148 if (!in_array($in['module_template'], array('blank', 'form', 'report', 'form_report', 'api'), true)) {
149 $errors[] = 'Modulvorlage ungueltig.';
150 }
151 if (!in_array($in['dd_mode'], array('none', 'new', 'existing'), true)) {
152 $errors[] = 'DD-Auswahl ungueltig.';
153 }
154 if ($in['dd_mode'] !== 'none' && !$this->valid_name($in['dd_name'])) {
155 $errors[] = 'DD-Name ungueltig.';
156 }
157 if ($in['dd_mode'] !== 'none' && !$this->valid_name($in['table_name'])) {
158 $errors[] = 'Tabellenname ungueltig.';
159 }
160 if ($in['dd_mode'] !== 'none' && (!$this->valid_file_name($in['db_file']) || !preg_match('/\.db3$/i', $in['db_file']))) {
161 $errors[] = 'SQLite-Datei muss eine einfache *.db3-Datei sein.';
162 }
163 if ($in['dd_mode'] === 'existing') {
164 if ($in['dd_ref'] !== '' && strpos($in['dd_ref'], '|') !== false) {
165 list($refModul, $refDd) = explode('|', $in['dd_ref'], 2);
166 if ($refModul !== $in['xmodul'] || $refDd !== $in['dd_name']) {
167 $errors[] = 'Vorhandenes DD muss zum ausgewaehlten Modul gehoeren.';
168 }
169 }
170 $ddFile = $this->module_path($in['xmodul'], 'dd/' . $in['dd_name'] . '.dd.php');
171 if ($ddFile === '' || !is_file($ddFile)) {
172 $errors[] = 'Das ausgewaehlte DD existiert im Modul nicht.';
173 }
174 }
175
176 $dir = $this->module_dir($in['xmodul']);
177 if ($in['target_mode'] === 'new' && is_dir($dir)) {
178 $errors[] = 'Das Modul existiert bereits. Bitte Ziel "Bestehendes Modul" waehlen.';
179 }
180 if ($in['target_mode'] === 'existing' && !is_dir($dir)) {
181 $errors[] = 'Das ausgewaehlte Modul existiert nicht.';
182 }
183
184 return !count($errors);
185 }
186
187 private function ensure_dirs($modul) {
188 foreach (array('', 'cfg', 'dd', 'db', 'fd', 'include', 'tpl', 'tpl/htm', 'tpl/mod', 'tpl/css', 'tpl/js', 'tpl/img') as $dir) {
189 $path = $this->module_path($modul, $dir);
190 if ($path && !is_dir($path)) {
191 @mkdir($path, 0777, true);
192 }
193 }
194 }
195
196 private function write_module_file($modul, $rel, $content, $overwrite, array &$log) {
197 $path = $this->module_path($modul, $rel);
198 if ($path === '') {
199 $log[] = array('type' => 'error', 'text' => 'Pfad blockiert: ' . $rel);
200 return false;
201 }
202 $dir = dirname($path);
203 if (!is_dir($dir)) {
204 @mkdir($dir, 0777, true);
205 }
206 if (is_file($path) && !$overwrite) {
207 $log[] = array('type' => 'skip', 'text' => $rel . ' existiert bereits.');
208 return true;
209 }
210 $ok = file_put_contents($path, $content) !== false;
211 $log[] = array('type' => $ok ? 'ok' : 'error', 'text' => ($ok ? 'geschrieben: ' : 'Fehler: ') . $rel);
212 return $ok;
213 }
214
215 private function backup_module($modul) {
216 if (!class_exists('\\ZipArchive')) {
217 return '';
218 }
219 $dir = $this->module_dir($modul);
220 if (!is_dir($dir)) {
221 return '';
222 }
223 $file = date('Ymd-His') . '__' . $modul . '.zip';
224 $path = $this->backup_root() . $file;
225 $zip = new \ZipArchive();
226 if ($zip->open($path, \ZipArchive::OVERWRITE) !== true) {
227 return '';
228 }
229 $it = new \RecursiveIteratorIterator(
230 new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
231 \RecursiveIteratorIterator::SELF_FIRST
232 );
233 foreach ($it as $item) {
234 $full = $item->getPathname();
235 $rel = str_replace('\\', '/', substr($full, strlen(rtrim($dir, '/\\')) + 1));
236 if ($item->isDir()) {
237 $zip->addEmptyDir($rel);
238 } else {
239 $zip->addFile($full, $rel);
240 }
241 }
242 $zip->close();
243 return $file;
244 }
245
246 private function restore_module_backup() {
247 $modul = dbx()->get_modul_var('xmodul', '', 'parameter');
248 $file = basename((string)dbx()->get_modul_var('file', '', 'parameter+.-_'));
249 $confirm = (int)dbx()->get_modul_var('confirm', 0, 'int');
250 if (!$this->valid_name($modul) || !$this->valid_file_name($file)) {
251 return '<div class="alert alert-danger">Restore nicht moeglich: ungueltige Parameter.</div>';
252 }
253 $zipPath = $this->backup_root() . $file;
254 if (!is_file($zipPath)) {
255 return '<div class="alert alert-warning">Backup-ZIP nicht gefunden.</div>';
256 }
257 if (!$confirm) {
258 $url = '?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_new&dbx_run3=restore_backup&xmodul=' . rawurlencode($modul) . '&file=' . rawurlencode($file) . '&confirm=1';
259 return '<div class="p-3"><h3>Modul wiederherstellen</h3><p>Restore ersetzt den Inhalt von <code>dbx/modules/' . $this->esc($modul) . '</code> durch das Backup <code>' . $this->esc($file) . '</code>.</p><a class="btn btn-danger" href="' . $this->esc($url) . '">Restore starten</a></div>';
260 }
261
262 $currentBackup = $this->backup_module($modul);
263 $dir = $this->module_dir($modul);
264 if (!is_dir($dir)) {
265 @mkdir($dir, 0777, true);
266 }
267 $this->empty_dir($dir);
268 $zip = new \ZipArchive();
269 if ($zip->open($zipPath) !== true) {
270 return '<div class="alert alert-danger">Backup konnte nicht geoeffnet werden.</div>';
271 }
272 for ($i = 0; $i < $zip->numFiles; $i++) {
273 $name = str_replace('\\', '/', (string)$zip->getNameIndex($i));
274 if ($name === '' || strpos($name, '../') !== false || strpos($name, '..\\') !== false || $name[0] === '/') {
275 $zip->close();
276 return '<div class="alert alert-danger">Backup enthaelt ungueltige Pfade.</div>';
277 }
278 }
279 $zip->extractTo($dir);
280 $zip->close();
281 return '<div class="alert alert-success">Modul wiederhergestellt. Sicherheitsbackup vorher: ' . $this->esc($currentBackup) . '</div>';
282 }
283
284 private function empty_dir($dir) {
285 if (!is_dir($dir)) return;
286 $it = new \RecursiveIteratorIterator(
287 new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
288 \RecursiveIteratorIterator::CHILD_FIRST
289 );
290 foreach ($it as $item) {
291 if ($item->isDir()) {
292 @rmdir($item->getPathname());
293 } else {
294 @unlink($item->getPathname());
295 }
296 }
297 }
298
299 private function field_defs($preset) {
300 $fields = array(
301 array('id', 'int', '11', '', 'id', 'int', 'integer-label', 'PRI', ''),
302 array('create_date', 'datetime', '-1', '', 'create_date', 'datetime', 'textarea-label', '', ''),
303 array('create_uid', 'int', '11', '0', 'create_uid', 'int', 'integer-label', '', ''),
304 array('update_date', 'datetime', '-1', '', 'update_date', 'datetime', 'textarea-label', '', ''),
305 array('update_uid', 'int', '11', '0', 'update_uid', 'int', 'integer-label', '', ''),
306 array('activ', 'int', '1', '1', 'Aktiv', 'int', 'checkbox-label', '', ''),
307 array('sorter', 'varchar', '16', '', 'Sortierung', 'varchar', 'text-label', '', ''),
308 array('title', 'varchar', '254', '', 'Titel', 'varchar|min=1|max=254', 'text-label', 'MUL', ''),
309 array('description', 'text', '-1', '', 'Beschreibung', 'text', 'textarea-label', '', ''),
310 );
311 if (in_array($preset, array('content', 'workflow'), true)) {
312 $fields[] = array('content', 'text', '-1', '', 'Inhalt', 'text', 'textarea-label', '', '');
313 }
314 if (in_array($preset, array('status', 'workflow'), true)) {
315 $fields[] = array('status', 'varchar', '32', 'open', 'Status', 'parameter|max=32', 'select-single-label', 'MUL', array('open' => 'Offen', 'review' => 'Pruefung', 'done' => 'Erledigt', 'archived' => 'Archiv'));
316 }
317 return $fields;
318 }
319
320 private function dd_field_php(array $f) {
321 list($name, $type, $length, $default, $label, $rules, $tpl, $index) = array_pad($f, 8, '');
322 $options = $f[8] ?? '';
323 return "\$field['name']=" . var_export($name, true) . ";\n"
324 . "\$field['type']=" . var_export($type, true) . ";\n"
325 . "\$field['index']=" . var_export($index, true) . ";\n"
326 . "\$field['length']=" . var_export($length, true) . ";\n"
327 . "\$field['default']=" . var_export($default, true) . ";\n"
328 . "\$field['label']=" . var_export($label, true) . ";\n"
329 . "\$field['rules']=" . var_export($rules, true) . ";\n"
330 . "\$field['tooltip']='';\n\$field['errormsg']='';\n\$field['placeholder']='';\n\$field['convert']='';\n\$field['protect']='0';\n\$field['group']='';\n\$field['mask']='';\n\$field['data']='';\n"
331 . "\$field['options']=" . var_export($options, true) . ";\n"
332 . "\$field['tpl']=" . var_export($tpl, true) . ";\n"
333 . "\$field['js']='';\n\$field['prompt']='';\n\$fields[]=\$field;\n\n";
334 }
335
336 private function generate_dd(array $in) {
337 $out = "<?php\n\n";
338 $out .= "/* =========================================================\n TABLE\n ========================================================= */\n";
339 $out .= "\$table['server']=" . var_export($in['xmodul'] . '|' . $in['db_file'], true) . ";\n";
340 $out .= "\$table['table']=" . var_export($in['table_name'], true) . ";\n";
341 $out .= "\$table['datadic']=" . var_export($in['dd_name'], true) . ";\n";
342 $out .= "\$table['primary']='';\n\$table['language']='';\n\$table['version']='1.0';\n\$table['autosync']='0';\n\$table['cache']='0';\n\$table['trash']='1';\n\$table['trace']='0';\n\$table['update_sql']='';\n\$table['default_sort']='title';\n\$table['form-dd-table']='';\n\$table['read']='admin';\n\$table['create']='admin';\n\$table['update']='admin';\n\$table['delete']='admin';\n\$table['read_owner']='owner,admin';\n\$table['create_owner']='owner,admin';\n\$table['update_owner']='owner,admin';\n\$table['delete_owner']='owner,admin';\n\n";
343 $out .= "/* =========================================================\n FIELDS\n ========================================================= */\n";
344 foreach ($this->field_defs($in['field_preset']) as $field) {
345 $out .= $this->dd_field_php($field);
346 }
347 $out .= "/* =========================================================\n INDEXES\n ========================================================= */\n";
348 $indexes = array(
349 array('idx_' . $in['table_name'] . '_title', 'title'),
350 array('idx_' . $in['table_name'] . '_activ', 'activ'),
351 );
352 if (in_array($in['field_preset'], array('status', 'workflow'), true)) {
353 $indexes[] = array('idx_' . $in['table_name'] . '_status', 'status');
354 }
355 foreach ($indexes as $idx) {
356 $out .= "\$index['name']=" . var_export($idx[0], true) . ";\n\$index['type']='INDEX';\n\$index['fields']=" . var_export($idx[1], true) . ";\n\$index['unique']='0';\n\$index['comment']='module wizard';\n\$indexes[]=\$index;\n\n";
357 }
358 return $out;
359 }
360
361 private function generate_form_fd(array $in) {
362 $out = "<?php\n";
363 foreach ($this->field_defs($in['field_preset']) as $f) {
364 if (in_array($f[0], array('id', 'create_date', 'create_uid', 'update_date', 'update_uid'), true)) {
365 continue;
366 }
367 $out .= $this->dd_field_php($f);
368 }
369 return $out;
370 }
371
372 private function generate_report_fd() {
373 return <<<'PHP'
374<?php
375$field['name']='dbx_rrows';
376$field['type']='int';
377$field['tpl']='select-single-label';
378$field['default']='20';
379$field['label']='Anz.Seite';
380$field['rules']='int';
381$field['options']='10=10&20=20&50=50&100=100&0=alle';
383
384$field['name']='dbx_rsort';
385$field['type']='varchar';
386$field['tpl']='select-single-label';
387$field['default']='title';
388$field['label']='Sortierung';
389$field['rules']='sqlsearch';
390$field['options']='id=id&title=Titel&update_date=Update&sorter=Sortierung&activ=Aktiv';
392
393$field['name']='dbx_rdesc';
394$field['type']='varchar';
395$field['tpl']='select-single-label';
396$field['default']='ASC';
397$field['label']='Auf/Ab';
398$field['rules']='parameter';
399$field['options']='ASC=Aufsteigend&DESC=Absteigend';
401
402$field['name']='dbx_rwhere';
403$field['type']='varchar';
404$field['tpl']='dbx|search';
405$field['default']='';
406$field['label']='Suchen';
407$field['rules']='parameter';
408$field['options']='';
410
411$field['name']='dbx_rselect';
412$field['type']='int';
413$field['tpl']='select-single-label';
414$field['default']='0';
415$field['label']='Auswahl';
416$field['rules']='int';
417$field['options']='0=Alle&1=Ausgewaehlte';
419PHP;
420 }
421
422 private function generate_main_class(array $in) {
423 $modul = $in['xmodul'];
424 $service = $modul . 'Service';
425 $default = $in['default_run1'] ?: 'run';
426 $startContent = $this->esc('Modul ' . $modul . ' ist bereit.');
427 $startMethod = " private function start() {\n \$tpl = dbx()->get_system_obj('dbxTPL');\n return \$tpl->get_tpl('$modul|start', array('content' => " . var_export($startContent, true) . "));\n }\n";
428 if ((int)$in['create_templates'] !== 1) {
429 $startMethod = " private function start() {\n return '<div class=\"p-3\"><h2>" . $this->esc($in['title']) . "</h2><p>" . $startContent . "</p></div>';\n }\n";
430 }
431 $cases = '';
432 $cases .= " case " . var_export($default, true) . ":\n \$content = \$this->start();\n break;\n\n";
433 if ($in['create_form']) {
434 $cases .= " case 'form':\n \$content = \$this->service()->form();\n break;\n\n";
435 }
436 if ($in['create_report']) {
437 $cases .= " case 'report':\n \$content = \$this->service()->report();\n break;\n\n";
438 $cases .= " case 'detail':\n \$content = \$this->service()->detail();\n break;\n\n";
439 }
440 if ($in['module_template'] === 'api' || $in['create_include']) {
441 $cases .= " case 'api':\n \$this->service()->api();\n break;\n\n";
442 }
443 return "<?php\nnamespace dbx\\$modul;\n\nclass $modul {\n\n private function service() {\n return dbx()->get_include_obj('$service', '$modul');\n }\n\n$startMethod\n public function run() {\n \$run = dbx()->get_modul_var('dbx_run1', " . var_export($default, true) . ", 'parameter');\n \$content = '';\n switch (\$run) {\n$cases default:\n \$content = '<div class=\"alert alert-warning\">Unbekannter Aufruf: ' . htmlspecialchars((string)\$run, ENT_QUOTES, 'UTF-8') . '</div>';\n }\n return \$content;\n }\n}\n";
444 }
445
446 private function generate_service_class(array $in) {
447 $modul = $in['xmodul'];
448 $class = $modul . 'Service';
449 $ddRef = $in['dd_mode'] !== 'none' ? $modul . '|' . $in['dd_name'] : '';
450 $formFd = $modul . '|' . $in['dd_name'] . '-form';
451 $reportFd = $modul . '|rpt-' . $in['dd_name'] . '-selection';
452 $fields = array();
453 foreach ($this->field_defs($in['field_preset']) as $f) {
454 if (in_array($f[0], array('id', 'title', 'description', 'content', 'status', 'activ', 'update_date'), true)) {
455 $fields[$f[0]] = $f[4];
456 }
457 }
458 $fieldsExport = var_export($fields, true);
459 $template = <<<'PHP'
460<?php
461namespace dbx\__MODUL__;
462
474class __CLASS__ {
475
476 private $dd = __DD_REF__;
477 private $formFd = __FORM_FD__;
478 private $reportFd = __REPORT_FD__;
479
480 private function db() {
481 return dbx()->get_system_obj('dbxDB');
482 }
483
484 private function tpl() {
485 return dbx()->get_system_obj('dbxTPL');
486 }
487
488 private function base_url($run1 = 'report', array $params = array()) {
489 $url = '?dbx_modul=__MODUL__&dbx_run1=' . rawurlencode($run1);
490 foreach ($params as $key => $value) {
491 $url .= '&' . rawurlencode((string)$key) . '=' . rawurlencode((string)$value);
492 }
493 return $url;
494 }
495
506 public function form($rid = null) {
507 if ($this->dd === '') return '<div class="alert alert-info">Kein DD konfiguriert.</div>';
508
509 $rid = $rid === null ? (int)dbx()->get_modul_var('rid', 0, 'int') : (int)$rid;
510 $do = dbx()->get_modul_var('dbx_do', '', 'parameter');
511
512 if ($do === 'delete' && $rid > 0) {
513 return $this->delete_record($rid);
514 }
515
516 $data = $rid > 0 ? $this->db()->select1($this->dd, $rid) : array('activ' => 1);
517 if (!is_array($data)) $data = array('activ' => 1);
518
519 $form = dbx()->get_system_obj('dbxForm');
520 $form->init('__MODUL__-form');
521 $form->set_form_callback_owner($this);
522 $form->set_init_callback('form_init_callback');
523 $form->set_submit_callback('form_submit_callback');
524 $form->set_run_callback('form_run_callback');
525
526 $form->_dd = $this->dd;
527 $form->_fd = $this->formFd;
528 $form->_data = $data;
529 $form->_rid = $rid;
530 $form->_action = $this->base_url('form', $rid > 0 ? array('rid' => $rid) : array());
531 $form->add_rep('bar_title', $rid > 0 ? 'Datensatz bearbeiten' : 'Neuer Datensatz');
532 $form->add_rep('bar_subtitle', $this->dd);
533 $form->add_obj('bar_actions', 'obj-value', $this->form_action_buttons($rid));
534
535 $this->configure_form_fields($form);
536
537 if ($form->submit() && !$form->errors()) {
538 $ok = $form->save_post($this->dd, $rid > 0 ? $rid : 'new', $this->form_save_defaults($rid));
539 if ($ok) {
540 $form->_msg_success = 'Datensatz gespeichert.';
541 } else {
542 $form->_msg_error = 'Datensatz konnte nicht gespeichert werden.';
543 }
544 }
545
546 return $form->run();
547 }
548
549 private function form_action_buttons($rid) {
550 $html = '<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> Speichern</button>';
551 if ($rid > 0) {
552 $delete = $this->base_url('form', array('rid' => $rid, 'dbx_do' => 'delete'));
553 $html .= ' <a class="btn btn-outline-danger btn-sm dbxConfirm" href="' . htmlspecialchars($delete, ENT_QUOTES, 'UTF-8') . '" data-confirm-title="Datensatz loeschen" data-confirm="Diesen Datensatz wirklich loeschen?" data-confirm-buttons="yesno"><i class="bi bi-trash"></i> Loeschen</a>';
554 }
555 $html .= ' <a class="btn btn-outline-secondary btn-sm" href="' . htmlspecialchars($this->base_url('report'), ENT_QUOTES, 'UTF-8') . '"><i class="bi bi-table"></i> Report</a>';
556 return $html;
557 }
558
559 private function configure_form_fields($form) {
560 $form->add_flds();
561
562 /*
563 * Gängige Feldtemplates fuer FD/DD:
564 * - text-label: einzeiliger Text
565 * - textarea-label: mehrzeiliger Text / HTML je nach Regeln
566 * - checkbox-label: 0/1
567 * - select-single-label: einfache Auswahl mit options array(...)
568 * - select-multible-label / multiselect2: Mehrfachauswahl, name[]
569 * - date-label, integer-label, password-label, hidden
570 *
571 * Direktes Feld-Beispiel ohne FD:
572 * $form->add_fld('status', 'select-single-label', label: 'Status', rules: 'parameter', options: array('open' => 'Offen', 'done' => 'Erledigt'));
573 */
574 }
575
576 private function form_save_defaults($rid) {
577 $uid = (int)dbx()->user();
578 $now = date('Y-m-d H:i:s');
579 $defaults = array('update_date' => $now, 'update_uid' => $uid);
580 if ((int)$rid <= 0) {
581 $defaults['create_date'] = $now;
582 $defaults['create_uid'] = $uid;
583 }
584 return $defaults;
585 }
586
587 public function delete_record($rid) {
588 $rid = (int)$rid;
589 if ($rid <= 0) return '<div class="alert alert-warning">Kein Datensatz gewaehlt.</div>';
590 $ok = $this->db()->delete($this->dd, $rid);
591 if ($ok) {
592 return '<div class="alert alert-success">Datensatz geloescht.</div>' . $this->report();
593 }
594 return '<div class="alert alert-danger">Datensatz konnte nicht geloescht werden.</div>' . $this->report();
595 }
596
597 public function detail($rid = null) {
598 $rid = $rid === null ? (int)dbx()->get_modul_var('rid', 0, 'int') : (int)$rid;
599 if ($rid <= 0) return '<div class="alert alert-warning">Kein Datensatz gewaehlt.</div>';
600 $record = $this->db()->select1($this->dd, $rid);
601 if (!is_array($record)) return '<div class="alert alert-warning">Datensatz nicht gefunden.</div>';
602
603 $rows = '';
604 foreach ($record as $key => $value) {
605 $rows .= '<tr><th>' . htmlspecialchars((string)$key, ENT_QUOTES, 'UTF-8') . '</th><td>' . htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8') . '</td></tr>';
606 }
607 return '<div class="p-3"><h3>Detail #' . $rid . '</h3><table class="table table-sm table-bordered">' . $rows . '</table><p><a class="btn btn-primary btn-sm" href="' . htmlspecialchars($this->base_url('form', array('rid' => $rid)), ENT_QUOTES, 'UTF-8') . '">Bearbeiten</a> <a class="btn btn-outline-secondary btn-sm" href="' . htmlspecialchars($this->base_url('report'), ENT_QUOTES, 'UTF-8') . '">Zurueck</a></p></div>';
608 }
609
619 public function report() {
620 if ($this->dd === '') return '<div class="alert alert-info">Kein DD konfiguriert.</div>';
621
622 $report = dbx()->get_system_obj('dbxReport');
623 $report->init('__MODUL__-report');
624 $report->set_callback_owner($this);
625 $report->set_report_header_callback('report_header_callback');
626 $report->set_page_header_callback('report_page_header_callback');
627 $report->set_header_callback('report_table_header_callback');
628 $report->set_body_callback('report_body_callback');
629 $report->set_footer_callback('report_table_footer_callback');
630 $report->set_page_footer_callback('report_page_footer_callback');
631 $report->set_report_footer_callback('report_footer_callback');
632 $report->set_next_record_callback('report_next_record_callback');
633 $report->set_callback('row_action_data', 'report_row_action_data_callback');
634
635 $report->_dd = $this->dd;
636 $report->_action = $this->base_url('report');
637 $report->_pages = true;
638 $report->_mode = 'table';
639 $report->_but_pagination = 9;
640 $report->_multi_page_select = 1;
641 $report->_create_sel_flds = true;
642 $report->_create_row_select = true;
643 $report->_create_row_edit = true;
644 $report->_create_row_show = true;
645 $report->_create_row_delete = true;
646 $report->_msg_confirm_delete = 'Diesen Datensatz wirklich loeschen?';
647
648 $report->add_action('rows_select', 'action_button_select', '&dbx_do=rows_select');
649 $report->add_action('rows_deselect', 'action_button_deselect', '&dbx_do=clear_selects');
650 $report->add_action('rows_delete', 'action_button_delete', '&dbx_do=multi_delete');
651 $report->create_selection_fields($this->reportFd);
652
653 $actionContent = $this->handle_report_action($report);
654 if ($actionContent !== '') {
655 return $actionContent;
656 }
657
658 $rwhere = $report->get_fld_val('dbx_rwhere', '', 'sqlsearch|max=64');
659 $rsort = $report->get_fld_val('dbx_rsort', 'title', 'parameter');
660 $rdesc = $report->get_fld_val('dbx_rdesc', 'ASC', 'parameter');
661 $rrows = $report->get_fld_val('dbx_rrows', 20, 'int');
662 $rpos = $report->get_fld_val('dbx_rpos', 0, 'int');
663 $rselect = $report->get_fld_val('dbx_rselect', 0, 'int');
664
665 if ($rwhere !== '') {
666 $rwhere = array('search' => array('value' => $rwhere, 'like' => array('title', 'description'), 'mode' => 'contains'));
667 }
668 if ($rselect) {
669 $rwhere = $report->add_rwhere_select(is_string($rwhere) ? $rwhere : '');
670 }
671
672 $flds = $this->report_fields();
673 $report->_rflds = $flds;
674 $report->_rrows = $rrows;
675 $report->_rpos = $rpos;
676 $report->_count_all = $this->db()->count($this->dd);
677 $report->_rcount = $this->db()->count($this->dd, $rwhere);
678 $report->_rdata = $this->db()->select($this->dd, $rwhere, $flds, $rsort, $rdesc, '', $rrows, $rpos);
679
680 return $report->run();
681 }
682
683 private function handle_report_action($report) {
684 $do = dbx()->get_modul_var('dbx_do', '', 'parameter');
685 $rid = (int)dbx()->get_modul_var('rid', 0, 'int');
686
687 if ($do === 'row_edit' && $rid > 0) {
688 return $this->form($rid);
689 }
690 if (($do === 'row_show' || $do === 'detail') && $rid > 0) {
691 return $this->detail($rid);
692 }
693 if ($do === 'row_delete' && $rid > 0) {
694 $ok = $this->db()->delete($this->dd, $rid);
695 $report->del_selected($rid);
696 $report->_msg_success = $ok ? 'Datensatz geloescht.' : '';
697 $report->_msg_error = $ok ? '' : 'Datensatz konnte nicht geloescht werden.';
698 return '';
699 }
700 if ($do === 'multi_delete') {
701 $result = $report->delete_multi_selected_records($this->dd);
702 $report->apply_multi_delete_result($result);
703 return '';
704 }
705 return '';
706 }
707
708 private function report_fields() {
709 return __FIELDS_EXPORT__;
710 }
711
712 public function report_header_callback($report, $content) {
713 return $content;
714 }
715
716 public function report_page_header_callback($report, $content) {
717 return $content;
718 }
719
720 public function report_table_header_callback($report, $content) {
721 return $content;
722 }
723
724 public function report_body_callback($report, $content) {
725 return $content;
726 }
727
728 public function report_table_footer_callback($report, $content) {
729 return $content;
730 }
731
732 public function report_page_footer_callback($report, $content) {
733 return $content;
734 }
735
736 public function report_footer_callback($report, $content) {
737 return $content;
738 }
739
740 public function report_next_record_callback($report, $record) {
741 if (is_array($record) && isset($record['status'])) {
742 $record['status'] = strtoupper((string)$record['status']);
743 }
744 return $record;
745 }
746
747 public function report_row_action_data_callback($report, $payload) {
748 if (is_array($payload) && isset($payload['data'], $payload['type'])) {
749 if ($payload['type'] === 'show') {
750 $payload['data']['tooltip'] = 'Details anzeigen';
751 }
752 }
753 return $payload;
754 }
755
756 public function form_init_callback($form, $value) {
757 return $value;
758 }
759
760 public function form_submit_callback($form, $value) {
761 return $value;
762 }
763
764 public function form_run_callback($form, $content) {
765 return $content;
766 }
767
768 public function api() {
769 dbx()->json_response(array('ok' => 1, 'module' => '__MODUL__', 'dd' => $this->dd));
770 }
771}
772PHP;
773
774 return str_replace(
775 array('__MODUL__', '__CLASS__', '__DD_REF__', '__FORM_FD__', '__REPORT_FD__', '__FIELDS_EXPORT__'),
776 array($modul, $class, var_export($ddRef, true), var_export($formFd, true), var_export($reportFd, true), $fieldsExport),
777 $template
778 );
779 }
780
781 private function generate_result_template(array $in) {
782 return '<div class="p-3"><h2>' . $this->esc($in['title']) . '</h2><p>{content}</p></div>';
783 }
784
785 private function generate_module_readme(array $in) {
786 $modul = $in['xmodul'];
787 $dd = $in['dd_name'];
788 $template = <<<'MD'
789# __MODUL__ Modul-Wizard Dokumentation
790
791Diese Datei wurde vom dbxAdmin Modul-Wizard erzeugt. Sie ist absichtlich knapp und technisch, damit Mensch und KI die Struktur direkt verstehen.
792
793## Grenzen
794
795- Modulwurzel: `dbx/modules/__MODUL__/`
796- KI darf nur innerhalb dieser Modulwurzel arbeiten.
797- DD-Dateien muessen vollstaendig sein, keine DD-Includes.
798- Templates werden in PHP ueber `dbx()->get_system_obj('dbxTPL')->get_tpl('__MODUL__|template', $data)` gerendert.
799- DD->DB Sync nur fuer `__MODUL__|__DD__`.
800
801## Dateien
802
803- `__MODUL__.class.php`: Einstieg und Routing nach `dbx_run1`.
804- `include/__MODUL__Service.class.php`: Formular, Report, Detail, API und Callbacks.
805- `dd/__DD__.dd.php`: Data Dictionary und DB-Struktur.
806- `fd/__DD__-form.fd.php`: Formularfelder.
807- `fd/rpt-__DD__-selection.fd.php`: Report-Filter und Report-Auswahl.
808- `tpl/htm/start.htm`: Start-Template.
809
810## Formular
811
812Route: `?dbx_modul=__MODUL__&dbx_run1=form`
813
814Enthaltene Muster:
815
816- `save_post()` fuer Insert/Update.
817- `dbx_do=delete` fuer Delete.
818- `form_action_buttons()` fuer Save/Delete/Report Buttons.
819- `configure_form_fields()` als zentraler Ort fuer Felder.
820- Form-Callbacks:
821 - `form_init_callback($form, $value)`
822 - `form_submit_callback($form, $value)`
823 - `form_run_callback($form, $content)`
824
825Gängige Feldtemplates:
826
827- `text-label`: einzeiliger Text.
828- `textarea-label`: mehrzeiliger Text, auch fuer HTML-Felder geeignet wenn Regeln es zulassen.
829- `checkbox-label`: 0/1 Checkbox.
830- `select-single-label`: einfache Auswahl mit `options`.
831- `select-multible-label` oder `multiselect2`: Mehrfachauswahl.
832- `date-label`: Datum.
833- `integer-label`: Zahlen.
834- `password-label`: Passwort.
835- `hidden`: verstecktes Feld.
836
837## Report
838
839Route: `?dbx_modul=__MODUL__&dbx_run1=report`
840
841Enthaltene Muster:
842
843- Multi-Select ueber dbxReport Remember.
844- Multi-Delete ueber `delete_multi_selected_records()`.
845- Row-Edit ueber `dbx_do=row_edit`.
846- Row-Detail ueber `dbx_do=row_show` oder `dbx_do=detail`.
847- Row-Delete ueber `dbx_do=row_delete`.
848- Filter `dbx_rselect=1` zeigt nur ausgewaehlte IDs.
849
850Report-Callbacks:
851
852- `report_header_callback($report, $content)`
853- `report_page_header_callback($report, $content)`
854- `report_table_header_callback($report, $content)`
855- `report_body_callback($report, $content)`
856- `report_table_footer_callback($report, $content)`
857- `report_page_footer_callback($report, $content)`
858- `report_footer_callback($report, $content)`
859- `report_next_record_callback($report, $record)`
860- `report_row_action_data_callback($report, $payload)`
861
862## API
863
864Route: `?dbx_modul=__MODUL__&dbx_run1=api`
865
866Die API-Methode ist ein Platzhalter und liefert JSON. Erweiterungen bleiben im Modul.
867MD;
868
869 return str_replace(array('__MODUL__', '__DD__'), array($modul, $dd), $template);
870 }
871
872 private function generate_module(array $in, array &$log) {
873 $modul = $in['xmodul'];
874 if ($in['target_mode'] === 'new' && !is_dir($this->module_dir($modul))) {
875 @mkdir($this->module_dir($modul), 0777, true);
876 }
877 $this->ensure_dirs($modul);
878 $overwrite = (int)$in['overwrite'] === 1;
879
880 if ((int)$in['backup'] === 1 && $in['target_mode'] === 'existing') {
881 $backup = $this->backup_module($modul);
882 if ($backup) {
883 $log[] = array('type' => 'ok', 'text' => 'Backup erstellt: ' . $backup);
884 $restore = '?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_new&dbx_run3=restore_backup&xmodul=' . rawurlencode($modul) . '&file=' . rawurlencode($backup);
885 $log[] = array('type' => 'link', 'text' => 'Restore-Link: ' . $restore);
886 } else {
887 $log[] = array('type' => 'skip', 'text' => 'Backup nicht erstellt (ZipArchive nicht verfuegbar oder Modul leer).');
888 }
889 }
890
891 $config = "<?php\n\$config['dbxConfig_modul']='secure';\n\$config['groups']='admin,';\n\$config['title']=" . var_export($in['title'], true) . ";\n\$config['default_run1']=" . var_export($in['default_run1'], true) . ";\n\$config['default_run2']=" . var_export($in['default_run2'], true) . ";\n";
892 $this->write_module_file($modul, 'cfg/config.php', $config, $overwrite || $in['target_mode'] === 'new', $log);
893 $this->write_module_file($modul, $modul . '.class.php', $this->generate_main_class($in), $overwrite || $in['target_mode'] === 'new', $log);
894
895 if ((int)$in['create_include'] === 1) {
896 $this->write_module_file($modul, 'include/' . $modul . 'Service.class.php', $this->generate_service_class($in), $overwrite || $in['target_mode'] === 'new', $log);
897 }
898 $this->write_module_file($modul, 'README-MODUL-WIZARD.md', $this->generate_module_readme($in), $overwrite || $in['target_mode'] === 'new', $log);
899 if ($in['dd_mode'] === 'new') {
900 $this->write_module_file($modul, 'dd/' . $in['dd_name'] . '.dd.php', $this->generate_dd($in), $overwrite || $in['target_mode'] === 'new', $log);
901 }
902 if ((int)$in['create_form'] === 1 && $in['dd_mode'] !== 'none') {
903 $this->write_module_file($modul, 'fd/' . $in['dd_name'] . '-form.fd.php', $this->generate_form_fd($in), $overwrite || $in['target_mode'] === 'new', $log);
904 }
905 if ((int)$in['create_report'] === 1 && $in['dd_mode'] !== 'none') {
906 $this->write_module_file($modul, 'fd/rpt-' . $in['dd_name'] . '-selection.fd.php', $this->generate_report_fd(), $overwrite || $in['target_mode'] === 'new', $log);
907 }
908 if ((int)$in['create_templates'] === 1) {
909 $this->write_module_file($modul, 'tpl/htm/modul-help.htm', '<p>' . $this->esc($in['title']) . '</p>', $overwrite || $in['target_mode'] === 'new', $log);
910 $this->write_module_file($modul, 'tpl/htm/start.htm', $this->generate_result_template($in), $overwrite || $in['target_mode'] === 'new', $log);
911 }
912
913 if ($in['dd_mode'] !== 'none') {
914 $syncUrl = '?dbx_modul=dbxAdmin&dbx_run1=dd&dbx_run2=sync_dd_to_db&modul=' . rawurlencode($modul) . '&dd=' . rawurlencode($in['dd_name']) . '&mode=apply&reset=1';
915 if ($in['sync_mode'] === 'apply') {
916 $state = $this->sync_dd_to_db($modul, $in['dd_name']);
917 $log[] = array('type' => (($state['status'] ?? '') === 'finished') ? 'ok' : 'skip', 'text' => 'DD->DB Sync: ' . (string)($state['message'] ?? ($state['status'] ?? '')));
918 } elseif ($in['sync_mode'] === 'link') {
919 $log[] = array('type' => 'link', 'text' => 'DD->DB Sync: ' . $syncUrl);
920 }
921 }
922
923 $startUrl = '?dbx_modul=' . rawurlencode($modul) . '&dbx_run1=' . rawurlencode($in['default_run1'] ?: 'run');
924 $log[] = array('type' => 'link', 'text' => 'Modul starten: ' . $startUrl);
925 if ((int)$in['create_form'] === 1) {
926 $log[] = array('type' => 'link', 'text' => 'Formular oeffnen: ?dbx_modul=' . rawurlencode($modul) . '&dbx_run1=form');
927 }
928 if ((int)$in['create_report'] === 1) {
929 $log[] = array('type' => 'link', 'text' => 'Report oeffnen: ?dbx_modul=' . rawurlencode($modul) . '&dbx_run1=report');
930 }
931 if ((int)$in['create_include'] === 1) {
932 $log[] = array('type' => 'link', 'text' => 'API pruefen: ?dbx_modul=' . rawurlencode($modul) . '&dbx_run1=api');
933 }
934 if ($in['dd_mode'] !== 'none') {
935 $log[] = array('type' => 'link', 'text' => 'DD bearbeiten: ?dbx_modul=dbxAdmin&dbx_run1=edit_dd&modul=' . rawurlencode($modul) . '&dd=' . rawurlencode($in['dd_name']));
936 if ((int)$in['create_form'] === 1) {
937 $log[] = array('type' => 'link', 'text' => 'Form-FD bearbeiten: ?dbx_modul=dbxAdmin&dbx_run1=edit_fd&modul=' . rawurlencode($modul) . '&fd=' . rawurlencode($in['dd_name'] . '-form'));
938 }
939 if ((int)$in['create_report'] === 1) {
940 $log[] = array('type' => 'link', 'text' => 'Report-FD bearbeiten: ?dbx_modul=dbxAdmin&dbx_run1=edit_fd&modul=' . rawurlencode($modul) . '&fd=' . rawurlencode('rpt-' . $in['dd_name'] . '-selection'));
941 }
942 }
943
944 if ((int)$in['ki_package'] === 1) {
945 $kiUrl = '?dbx_modul=dbxKi&dbx_run1=briefing_module&xmodul=' . rawurlencode($modul) . '&dd_name=' . rawurlencode($in['dd_name']);
946 $apiUrl = '?dbx_modul=dbxKi&dbx_run1=module_api&action=module.describe&xmodul=' . rawurlencode($modul);
947 $log[] = array('type' => 'link', 'text' => 'KI-Modulauftrag in dbxKi: ' . $kiUrl);
948 $log[] = array('type' => 'link', 'text' => 'dbxKi Modul-API: ' . $apiUrl);
949 }
950 }
951
952 private function sync_dd_to_db($modul, $dd) {
953 $oDD = dbx()->get_system_obj('dbxDD');
954 $oDD->sync_dd_to_db($modul, $dd, 'reset');
955 $state = array('status' => 'running', 'message' => '');
956 for ($i = 0; $i < 20; $i++) {
957 $state = $oDD->sync_dd_to_db($modul, $dd, 'apply');
958 if (in_array((string)($state['status'] ?? ''), array('finished', 'error', 'cancelled'), true)) {
959 break;
960 }
961 }
962 return is_array($state) ? $state : array('status' => 'error', 'message' => 'Sync ohne Status');
963 }
964
965 private function log_html(array $log) {
966 if (!$log) return '';
967 $html = '<div class="dbx-admin-dashboard-panel mt-3"><div class="p-3"><h3>Ergebnis</h3><ul class="list-unstyled mb-0">';
968 foreach ($log as $row) {
969 $type = (string)($row['type'] ?? 'info');
970 $text = (string)($row['text'] ?? '');
971 $class = $type === 'error' ? 'text-danger' : ($type === 'skip' ? 'text-muted' : 'text-success');
972 if ($type === 'link' && strpos($text, ': ?') !== false) {
973 list($label, $url) = explode(': ', $text, 2);
974 $html .= '<li class="' . $class . '"><a href="' . $this->esc($url) . '">' . $this->esc($label) . '</a></li>';
975 } else {
976 $html .= '<li class="' . $class . '">' . $this->esc($text) . '</li>';
977 }
978 }
979 return $html . '</ul></div></div>';
980 }
981
982 private function ki_api() {
983 $modul = dbx()->get_modul_var('xmodul', '', 'parameter');
984 $dd = dbx()->get_modul_var('dd_name', '', 'parameter');
985 if (!$this->valid_name($modul)) {
986 dbx()->json_response(array('ok' => 0, 'error' => 'ungueltiges Modul'), true);
987 }
988 dbx()->json_response($this->ki_manifest($modul, $dd), true);
989 }
990
991 private function ki_manifest($modul, $dd) {
992 return array(
993 'ok' => 1,
994 'bundle_version' => 'module-wizard.v1',
995 'area' => 'module',
996 'module' => $modul,
997 'dd' => $dd,
998 'root' => 'dbx/modules/' . $modul . '/',
999 'hard_rules' => array(
1000 'Nur Dateien unter dbx/modules/' . $modul . '/ bearbeiten.',
1001 'Keine globalen Configs, keine anderen Module, keine Dateien in dbx/include aendern.',
1002 'Templates ueber dbxapp-Template-APIs lesen/rendern, z.B. dbx()->get_system_obj(\'dbxTPL\')->get_tpl(...).',
1003 'Template-Aenderungen nur als Modul-Template-Aktion liefern; keine freien Schreibzugriffe ausserhalb von dbx/modules/' . $modul . '/tpl/.',
1004 'Vor destruktiven Aenderungen ein Modul-ZIP erzeugen.',
1005 'Antwort als ZIP mit manifest.json und job.json liefern.',
1006 ),
1007 'allowed_actions' => array(
1008 'module.file.write' => 'Datei unter dem Modul schreiben',
1009 'module.file.delete' => 'Datei unter dem Modul entfernen',
1010 'module.dd.write' => 'DD unter dd/*.dd.php schreiben',
1011 'module.dd.sync' => 'DD->DB Sync fuer Modul-DD anfordern',
1012 'module.template.get' => 'Template ueber dbxTPL/get_tpl lesen oder rendern',
1013 'module.template.set' => 'Template unter tpl/* ueber die dbxapp-Template-Mechanik schreiben',
1014 'module.php.write' => 'PHP unter Modulroot oder include schreiben',
1015 'module.form.extend' => 'Formular mit Save/Delete/Feldtemplates erweitern',
1016 'module.report.extend' => 'Report mit Callbacks, Multi-Select, Multi-Delete, Edit und Detail erweitern',
1017 ),
1018 );
1019 }
1020
1021 private function send_ki_zip() {
1022 if (!class_exists('\\ZipArchive')) {
1023 dbx()->json_response(array('ok' => 0, 'error' => 'ZipArchive nicht verfuegbar.'), true);
1024 }
1025 $modul = dbx()->get_modul_var('xmodul', '', 'parameter');
1026 $dd = dbx()->get_modul_var('dd_name', '', 'parameter');
1027 if (!$this->valid_name($modul)) {
1028 dbx()->json_response(array('ok' => 0, 'error' => 'ungueltiges Modul'), true);
1029 }
1030 $manifest = $this->ki_manifest($modul, $dd);
1031 $job = array(
1032 'job_version' => 'module-wizard.v1',
1033 'module' => $modul,
1034 'steps' => array(
1035 array('id' => 'backup', 'action' => 'module.backup', 'params' => array('module' => $modul)),
1036 array('id' => 'plan', 'action' => 'module.plan', 'params' => array('module' => $modul, 'dd' => $dd)),
1037 ),
1038 );
1039 $auftrag = "# KI-Auftrag Modulprogrammierung\n\n"
1040 . "Arbeite ausschliesslich im Modul `dbx/modules/$modul/`.\n\n"
1041 . "## Harte Regeln\n\n"
1042 . "- Keine Aenderungen ausserhalb von `dbx/modules/$modul/`.\n"
1043 . "- Kein Umbau von Core, dbx/include, globaler config.php oder anderen Modulen.\n"
1044 . "- DD-Dateien vollstaendig schreiben, keine include-basierten DDs.\n"
1045 . "- Templates ueber die dbxapp-Template-Schicht lesen/rendern, z.B. `dbx()->get_system_obj('dbxTPL')->get_tpl(...)`.\n"
1046 . "- Template-Aenderungen als Modul-Template-Set-Aktion liefern; keine Pfade ausserhalb von `dbx/modules/$modul/tpl/`.\n"
1047 . "- Formular-Muster mit Save/Delete, Feldtemplates und Callback-Kommentaren erhalten oder sauber erweitern.\n"
1048 . "- Report-Muster mit Multi-Select, Multi-Delete, Edit, Detail und Callbacks erhalten oder sauber erweitern.\n"
1049 . "- README-MODUL-WIZARD.md aktualisieren, wenn Formular-, Report-, DD- oder API-Verhalten geaendert wird.\n"
1050 . "- Wenn DB-Sync noetig ist, nur fuer `$modul|$dd` anfordern.\n"
1051 . "- Liefere `antwort.zip` mit `manifest.json`, `job.json`, geaenderten Dateien und README.\n\n"
1052 . "## Aufgabe\n\nBeschreibe im Chat die gewuenschte Modulaufgabe und nutze diese ZIP als Arbeitskontext.\n";
1053 $tmp = tempnam(sys_get_temp_dir(), 'dbxmodki');
1054 $zip = new \ZipArchive();
1055 $zip->open($tmp, \ZipArchive::OVERWRITE);
1056 $zip->addFromString('manifest.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
1057 $zip->addFromString('job.vorlage.json', json_encode($job, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
1058 $zip->addFromString('KI-AUFTRAG.md', $auftrag);
1059 $zip->addFromString('README.md', "Modul-KI-Auftrag fuer $modul.\n");
1060 $zip->close();
1061 $name = 'dbxki-modul-' . preg_replace('/[^A-Za-z0-9_-]+/', '-', $modul) . '.zip';
1062 header('Content-Type: application/zip');
1063 header('Content-Disposition: attachment; filename="' . $name . '"');
1064 header('Content-Length: ' . filesize($tmp));
1065 readfile($tmp);
1066 @unlink($tmp);
1067 exit;
1068 }
1069
1070 public function new_modul() {
1071 $in = $this->collect_input();
1072 $log = array();
1073 $oForm = dbx()->get_system_obj('dbxForm');
1074 $oForm->init('form-wizzard-new');
1075 $oForm->_action = '?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_new';
1076 $oForm->_data = array_merge(array(
1077 'target_mode' => 'new',
1078 'module_template' => 'form_report',
1079 'dd_mode' => 'new',
1080 'field_preset' => 'basic',
1081 'create_include' => 1,
1082 'create_form' => 1,
1083 'create_report' => 1,
1084 'create_templates' => 1,
1085 'overwrite' => 0,
1086 'backup' => 1,
1087 'sync_mode' => 'link',
1088 'ki_package' => 1,
1089 ), $in);
1090 $oForm->add_rep('bar_title', 'Modul-Wizard');
1091 $oForm->add_rep('bar_subtitle', 'Struktur, DD, Formular, Report und KI-Auftrag');
1092 $oForm->add_obj('bar_actions', 'obj-value', '<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-magic"></i> Erstellen</button>');
1093 $oForm->_msg_info = 'Wizard erzeugt nur Dateien innerhalb des gewaehlten Moduls. Bestehende Dateien werden ohne Ueberschreiben nicht ersetzt.';
1094
1095 $oForm->add_fld('target_mode', 'select-single-label', label: 'Ziel', rules: 'parameter', options: $this->options(array('new' => 'Neues Modul', 'existing' => 'Bestehendes Modul erweitern')));
1096 $oForm->add_fld('existing_modul', 'select-single-label', label: 'Bestehendes Modul', rules: 'parameter', options: $this->options($this->module_options(true)));
1097 $oForm->add_fld('xmodul', 'text-label', label: 'Neuer Modulname', rules: 'parameter|max=63', tooltip: 'Technischer Modulname, z.B. myShop');
1098 $oForm->add_fld('title', 'text-label', label: 'Titel', rules: 'varchar|max=120');
1099 $oForm->add_fld('module_template', 'select-single-label', label: 'Vorlage', rules: 'parameter', options: $this->options(array('form_report' => 'Formular + Report', 'form' => 'Nur Formular', 'report' => 'Nur Report', 'api' => 'API + Service', 'blank' => 'Leeres Grundmodul')));
1100 $oForm->add_fld('default_run1', 'text-label', label: 'Start run1', rules: 'parameter|max=32');
1101 $oForm->add_fld('default_run2', 'text-label', label: 'Start run2', rules: 'parameter|max=32');
1102 $oForm->add_fld('dd_mode', 'select-single-label', label: 'DD', rules: 'parameter', options: $this->options(array('new' => 'Neues DD erstellen', 'existing' => 'Vorhandenes DD nutzen', 'none' => 'Kein DD')));
1103 $oForm->add_fld('dd_ref', 'select-single-label', label: 'Vorhandenes DD', rules: 'parameter', options: $this->options($this->dd_options(true)));
1104 $oForm->add_fld('dd_name', 'text-label', label: 'DD Name', rules: 'parameter|max=63');
1105 $oForm->add_fld('table_name', 'text-label', label: 'Tabelle', rules: 'parameter|max=63');
1106 $oForm->add_fld('db_file', 'text-label', label: 'SQLite *.db3', rules: 'parameter+.-_|max=80');
1107 $oForm->add_fld('field_preset', 'select-single-label', label: 'Felder', rules: 'parameter', options: $this->options(array('basic' => 'Basis: Titel/Beschreibung', 'content' => 'Content: Textfeld', 'status' => 'Status', 'workflow' => 'Workflow einfach')));
1108 $oForm->add_fld('create_include', 'checkbox-label', label: 'Service-Include', rules: 'int');
1109 $oForm->add_fld('create_form', 'checkbox-label', label: 'Formular erzeugen', rules: 'int');
1110 $oForm->add_fld('create_report', 'checkbox-label', label: 'Report erzeugen', rules: 'int');
1111 $oForm->add_fld('create_templates', 'checkbox-label', label: 'Templates erzeugen', rules: 'int');
1112 $oForm->add_fld('overwrite', 'checkbox-label', label: 'Bestehende Dateien ueberschreiben', rules: 'int');
1113 $oForm->add_fld('backup', 'checkbox-label', label: 'Vorher Modul-ZIP sichern', rules: 'int');
1114 $oForm->add_fld('sync_mode', 'select-single-label', label: 'DD->DB Sync', rules: 'parameter', options: $this->options(array('link' => 'Sync-Link anzeigen', 'none' => 'Nicht synchronisieren', 'apply' => 'Direkt anwenden')));
1115 $oForm->add_fld('ki_package', 'checkbox-label', label: 'KI-Auftrags-ZIP anbieten', rules: 'int');
1116
1117 if ($oForm->submit()) {
1118 $in = $this->collect_input($oForm);
1119 $errors = array();
1120 if ($this->validate_input($in, $errors) && !$oForm->errors()) {
1121 $this->generate_module($in, $log);
1122 $oForm->_msg_success = 'Wizard abgeschlossen.';
1123 } else {
1124 foreach ($errors as $err) {
1125 $oForm->add_fld_error('xmodul', $err);
1126 }
1127 $oForm->_msg_error = 'Bitte Eingaben pruefen.';
1128 }
1129 }
1130
1131 return $oForm->run() . $this->log_html($log);
1132 }
1133
1134 public function run($run = '') {
1135 $run3 = dbx()->get_modul_var('dbx_run3', '', 'parameter');
1136 if ($run3 === 'restore_backup') {
1137 return $this->restore_module_backup();
1138 }
1139 if ($run3 === 'ki_zip') {
1140 $this->send_ki_zip();
1141 }
1142 if ($run3 === 'ki_api') {
1143 $this->ki_api();
1144 }
1145 return $this->new_modul();
1146 }
1147}
$table['server']
Definition .dd.php:6
$indexes[]
Definition .dd.php:167
$index['name']
Definition .dd.php:162
$fields[]
Definition config.dd.php:16
$field
Definition config.dd.php:4
$config['version']
Definition config.php:2
dbx_os_path_file($path_file)
Definition index.php:164
exit
Definition index.php:532
dbx_get_file_dir()
Definition index.php:265
dbx_get_base_dir($cut_Data=0)
Definition index.php:157
DBX schema administration.
if(! $db->connect_db_server($server)) $result