8 private function texts() {
12 $texts = new \dbxForm();
13 $texts->set_form_help_enabled(
false);
14 $texts->_fd =
'dbxAdmin|module-wizard';
15 $texts->load_fd_messages();
16 $this->texts = $texts;
20 private function text($key, $default =
'') {
21 return $this->texts()->get_fd_message((
string)$key, (
string)$default);
24 private function esc(
$value) {
25 return htmlspecialchars((
string)
$value, ENT_QUOTES,
'UTF-8');
28 private function modules_root() {
32 private function backup_root() {
35 @mkdir(
$dir, 0777,
true);
40 private function valid_name($name) {
41 return is_string($name) && preg_match(
'/^[A-Za-z][A-Za-z0-9_]{1,63}$/', $name);
44 private function valid_file_name($name) {
45 return is_string($name) && preg_match(
'/^[A-Za-z0-9_.-]+$/', $name) && strpos($name,
'..') ===
false;
48 private function module_dir($modul) {
49 return dbx()->os_path($this->modules_root() . $modul . DIRECTORY_SEPARATOR);
52 private function module_path($modul, $rel) {
53 $rel = str_replace(array(
'\\',
"\0"), array(
'/',
''), (
string)$rel);
54 $rel = ltrim($rel,
'/');
55 if ($rel ===
'' || strpos($rel,
'../') !==
false || strpos($rel,
'..\\') !==
false) {
58 $base = $this->module_dir($modul);
59 $path =
dbx()->os_path(
$base . $rel);
60 $baseNorm = str_replace(
'\\',
'/', rtrim(
$base,
'/\\') .
'/');
61 $pathNorm = str_replace(
'\\',
'/', $path);
62 if (strpos($pathNorm, $baseNorm) !== 0) {
68 private function module_options($withNew =
false) {
69 $options = $withNew ? array(
'' => $this->text(
'option_choose')) : array();
70 $root = $this->modules_root();
71 foreach (glob(
$root .
'*', GLOB_ONLYDIR) ?: array() as
$dir) {
72 $name = basename(
$dir);
73 if ($this->valid_name($name)) {
81 private function dd_options($withEmpty =
true) {
82 $options = $withEmpty ? array(
'' => $this->text(
'option_choose_manual')) : array();
83 foreach (glob($this->modules_root() .
'*', GLOB_ONLYDIR) ?: array() as
$dir) {
84 $modul = basename(
$dir);
85 if (!$this->valid_name($modul)) {
90 if ($this->valid_name(
$dd)) {
99 private function options(
$items) {
103 private function collect_input(
$form =
null) {
104 $get =
function($name, $default =
'',
$rules =
'parameter') use (
$form) {
105 if (
$form && array_key_exists($name,
$form->_post)) {
106 return $form->_post[$name];
108 return dbx()->get_modul_var($name, $default,
$rules);
111 $target = (string)$get(
'target_mode',
'new');
112 $existingModul = trim((
string)$get(
'existing_modul',
''));
113 $modul = trim((
string)$get(
'xmodul',
''));
114 if (
$target ===
'existing' && $existingModul !==
'') {
115 $modul = $existingModul;
118 $modul = trim((
string)$get(
'modul',
''));
121 $ddMode = (string)$get(
'dd_mode',
'new');
122 $ddRef = trim((
string)$get(
'dd_ref',
''));
123 $title = trim((
string)$get(
'title', $modul));
124 $dd = trim((
string)$get(
'dd_name', $modul ? $modul .
'Data' :
''));
125 if ($ddRef !==
'' && strpos($ddRef,
'|') !==
false) {
126 list($refModul, $refDd) = explode(
'|', $ddRef, 2);
127 if (
$target ===
'existing' && $modul ===
'' && $this->valid_name($refModul)) {
130 if ($this->valid_name($refDd)) {
134 if (
$dd ===
'' && $ddMode !==
'none' && $modul !==
'') {
135 $dd = $modul .
'Data';
137 $table = trim((
string)$get(
'table_name',
$dd));
138 if (
$table ===
'' && $ddMode !==
'none') {
141 $dbFile = trim((
string)$get(
'db_file', $modul ? $modul .
'.db3' :
'modul.db3'));
142 if ($modul !==
'' && (
$dbFile ===
'' ||
$dbFile ===
'modul.db3')) {
145 $defaultRun1 = trim((
string)$get(
'default_run1',
'run'));
146 if ($defaultRun1 ===
'') {
147 $defaultRun1 =
'run';
151 'target_mode' => in_array(
$target, array(
'new',
'existing'),
true) ?
$target :
'new',
152 'existing_modul' => $existingModul,
155 'title' => $title !==
'' ? $title : $modul,
156 'default_run1' => $defaultRun1,
157 'default_run2' => trim((
string)$get(
'default_run2',
'')),
158 'module_template' => (
string)$get(
'module_template',
'form_report'),
159 'dd_mode' => $ddMode,
163 'field_preset' => (
string)$get(
'field_preset',
'basic'),
164 'create_include' => (
int)$get(
'create_include', 1,
'int'),
165 'create_form' => (
int)$get(
'create_form', 1,
'int'),
166 'create_report' => (
int)$get(
'create_report', 1,
'int'),
167 'create_templates' => (
int)$get(
'create_templates', 1,
'int'),
168 'overwrite' => (
int)$get(
'overwrite', 0,
'int'),
169 'backup' => (
int)$get(
'backup', 1,
'int'),
170 'sync_mode' => (
string)$get(
'sync_mode',
'link'),
171 'ki_package' => (
int)$get(
'ki_package', 1,
'int'),
175 private function validate_input(array $in, &
$errors) {
177 if (!$this->valid_name($in[
'xmodul'])) {
178 $errors[] = $this->text(
'validation_module_name');
180 if (!in_array($in[
'module_template'], array(
'blank',
'form',
'report',
'form_report',
'api'),
true)) {
181 $errors[] = $this->text(
'validation_template');
183 if (!in_array($in[
'dd_mode'], array(
'none',
'new',
'existing'),
true)) {
184 $errors[] = $this->text(
'validation_dd_mode');
186 if (((
int)$in[
'create_form'] === 1 || (
int)$in[
'create_report'] === 1) && $in[
'dd_mode'] ===
'none') {
187 $errors[] = $this->text(
'validation_dd_required');
189 if (($in[
'module_template'] ===
'api' || (
int)$in[
'create_form'] === 1 || (
int)$in[
'create_report'] === 1) && (
int)$in[
'create_include'] !== 1) {
190 $errors[] = $this->text(
'validation_service_required');
192 if (((
int)$in[
'create_form'] === 1 || (
int)$in[
'create_report'] === 1) && (
int)$in[
'create_templates'] !== 1) {
193 $errors[] = $this->text(
'validation_templates_required');
195 if ($in[
'dd_mode'] !==
'none' && !$this->valid_name($in[
'dd_name'])) {
196 $errors[] = $this->text(
'validation_dd_name');
198 if ($in[
'dd_mode'] !==
'none' && !$this->valid_name($in[
'table_name'])) {
199 $errors[] = $this->text(
'validation_table_name');
201 if ($in[
'dd_mode'] !==
'none' && (!$this->valid_file_name($in[
'db_file']) || !preg_match(
'/\.db3$/i', $in[
'db_file']))) {
202 $errors[] = $this->text(
'validation_db_file');
204 if ($in[
'dd_mode'] ===
'existing') {
205 if ($in[
'dd_ref'] !==
'' && strpos($in[
'dd_ref'],
'|') !==
false) {
206 list($refModul, $refDd) = explode(
'|', $in[
'dd_ref'], 2);
207 if ($refModul !== $in[
'xmodul'] || $refDd !== $in[
'dd_name']) {
208 $errors[] = $this->text(
'validation_dd_module');
211 $ddFile = $this->module_path($in[
'xmodul'],
'dd/' . $in[
'dd_name'] .
'.dd.php');
212 if ($ddFile ===
'' || !is_file($ddFile)) {
213 $errors[] = $this->text(
'validation_dd_missing');
217 $dir = $this->module_dir($in[
'xmodul']);
218 if ($in[
'target_mode'] ===
'new' && is_dir(
$dir)) {
219 $errors[] = $this->text(
'validation_module_exists');
221 if ($in[
'target_mode'] ===
'existing' && !is_dir(
$dir)) {
222 $errors[] = $this->text(
'validation_module_missing');
228 private function ensure_dirs($modul, array &$log) {
230 $root = $this->module_dir($modul);
232 $log[] = array(
'type' =>
'error',
'text' =>
'Modulverzeichnis konnte nicht erstellt werden.');
235 foreach (array(
'cfg',
'dd',
'db',
'fd',
'include',
'tpl',
'tpl/htm',
'tpl/mod',
'tpl/css',
'tpl/js',
'tpl/img') as
$dir) {
236 $path = $this->module_path($modul,
$dir);
237 if ($path ===
'' || (!is_dir($path) && !@mkdir($path, 0777,
true) && !is_dir($path))) {
238 $log[] = array(
'type' =>
'error',
'text' =>
'Verzeichnis konnte nicht erstellt werden: ' .
$dir);
245 private function write_module_file($modul, $rel,
$content, $overwrite, array &$log) {
246 $path = $this->module_path($modul, $rel);
248 $log[] = array(
'type' =>
'error',
'text' =>
'Pfad blockiert: ' . $rel);
251 $dir = dirname($path);
253 @mkdir(
$dir, 0777,
true);
255 if (is_file($path) && !$overwrite) {
256 $log[] = array(
'type' =>
'skip',
'text' => $rel .
' existiert bereits.');
259 $ok = file_put_contents($path,
$content) !==
false;
260 $log[] = array(
'type' =>
$ok ?
'ok' :
'error',
'text' => (
$ok ?
'geschrieben: ' :
'Fehler: ') . $rel);
264 private function backup_module($modul) {
265 if (!class_exists(
'\\ZipArchive')) {
268 $dir = $this->module_dir($modul);
272 $file = date(
'Ymd-His') .
'__' . $modul .
'.zip';
273 $path = $this->backup_root() .
$file;
274 $zip = new \ZipArchive();
275 if ($zip->open($path, \ZipArchive::OVERWRITE) !==
true) {
278 $it = new \RecursiveIteratorIterator(
279 new \RecursiveDirectoryIterator(
$dir, \FilesystemIterator::SKIP_DOTS),
280 \RecursiveIteratorIterator::SELF_FIRST
282 foreach (
$it as $item) {
283 $full = $item->getPathname();
284 $rel = str_replace(
'\\',
'/', substr($full, strlen(rtrim(
$dir,
'/\\')) + 1));
285 if ($item->isDir()) {
286 $zip->addEmptyDir($rel);
288 $zip->addFile($full, $rel);
295 private function restore_module_backup() {
296 $modul =
dbx()->get_modul_var(
'xmodul',
'',
'parameter');
297 $file = basename((
string)
dbx()->get_modul_var(
'file',
'',
'parameter+.-_'));
298 $confirm = (int)
dbx()->get_modul_var(
'confirm', 0,
'int');
299 if (!$this->valid_name($modul) || !$this->valid_file_name(
$file)) {
300 return '<div class="alert alert-danger">Restore nicht moeglich: ungueltige Parameter.</div>';
302 $zipPath = $this->backup_root() .
$file;
303 if (!is_file($zipPath)) {
304 return '<div class="alert alert-warning">Backup-ZIP nicht gefunden.</div>';
307 $url =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_new&dbx_run3=restore_backup&xmodul=' . rawurlencode($modul) .
'&file=' . rawurlencode(
$file) .
'&confirm=1';
308 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>';
311 $currentBackup = $this->backup_module($modul);
312 $dir = $this->module_dir($modul);
314 @mkdir(
$dir, 0777,
true);
316 $this->empty_dir(
$dir);
317 $zip = new \ZipArchive();
318 if ($zip->open($zipPath) !==
true) {
319 return '<div class="alert alert-danger">Backup konnte nicht geoeffnet werden.</div>';
321 for ($i = 0; $i < $zip->numFiles; $i++) {
322 $name = str_replace(
'\\',
'/', (
string)$zip->getNameIndex($i));
323 if ($name ===
'' || strpos($name,
'../') !==
false || strpos($name,
'..\\') !==
false || $name[0] ===
'/') {
325 return '<div class="alert alert-danger">Backup enthaelt ungueltige Pfade.</div>';
328 $zip->extractTo(
$dir);
330 return '<div class="alert alert-success">Modul wiederhergestellt. Sicherheitsbackup vorher: ' . $this->esc($currentBackup) .
'</div>';
333 private function empty_dir(
$dir) {
334 if (!is_dir(
$dir))
return;
335 $it = new \RecursiveIteratorIterator(
336 new \RecursiveDirectoryIterator(
$dir, \FilesystemIterator::SKIP_DOTS),
337 \RecursiveIteratorIterator::CHILD_FIRST
339 foreach (
$it as $item) {
340 if ($item->isDir()) {
341 @rmdir($item->getPathname());
343 @unlink($item->getPathname());
348 private function field_defs($preset) {
350 array(
'id',
'int',
'11',
'',
'ID',
'int',
'hidden',
'PRI',
''),
351 array(
'create_date',
'datetime',
'-1',
'',
'Erstellt',
'datetime',
'hidden',
'',
''),
352 array(
'create_uid',
'int',
'11',
'0',
'Erstellt von',
'int',
'hidden',
'MUL',
''),
353 array(
'update_date',
'datetime',
'-1',
'',
'Aktualisiert',
'datetime',
'hidden',
'',
''),
354 array(
'update_uid',
'int',
'11',
'0',
'Aktualisiert von',
'int',
'hidden',
'MUL',
''),
355 array(
'owner',
'int',
'11',
'0',
'Owner',
'int',
'hidden',
'MUL',
''),
356 array(
'trash',
'int',
'1',
'0',
'Trash',
'int',
'hidden',
'MUL',
''),
357 array(
'activ',
'int',
'1',
'1',
'Aktiv',
'int',
'checkbox-label',
'MUL',
''),
358 array(
'sorter',
'varchar',
'16',
'',
'Sortierung',
'varchar',
'text-label',
'',
''),
359 array(
'title',
'varchar',
'254',
'',
'Titel',
'varchar|min=1|max=254',
'text-label',
'MUL',
''),
360 array(
'description',
'text',
'-1',
'',
'Beschreibung',
'text',
'textarea-label',
'',
''),
362 if (in_array($preset, array(
'content',
'workflow'),
true)) {
363 $fields[] = array(
'content',
'text',
'-1',
'',
'Inhalt',
'text',
'textarea-label',
'',
'');
365 if (in_array($preset, array(
'status',
'workflow'),
true)) {
366 $fields[] = array(
'status',
'varchar',
'32',
'open',
'Status',
'parameter|max=32',
'select-single-label',
'MUL', array(
'open' =>
'Offen',
'review' =>
'Pruefung',
'done' =>
'Erledigt',
'archived' =>
'Archiv'));
371 private function dd_field_php(array $f) {
372 list($name, $type, $length, $default, $label,
$rules,
$tpl,
$index) = array_pad($f, 8,
'');
374 return "\$field['name']=" . var_export($name,
true) .
";\n"
375 .
"\$field['type']=" . var_export($type,
true) .
";\n"
376 .
"\$field['index']=" . var_export(
$index,
true) .
";\n"
377 .
"\$field['length']=" . var_export($length,
true) .
";\n"
378 .
"\$field['default']=" . var_export($default,
true) .
";\n"
379 .
"\$field['label']=" . var_export($label,
true) .
";\n"
380 .
"\$field['rules']=" . var_export(
$rules,
true) .
";\n"
381 .
"\$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"
382 .
"\$field['options']=" . var_export(
$options,
true) .
";\n"
383 .
"\$field['tpl']=" . var_export(
$tpl,
true) .
";\n"
384 .
"\$field['js']='';\n\$field['prompt']='';\n\$fields[]=\$field;\n\n";
387 private function generate_dd(array $in) {
389 $out .=
"/* =========================================================\n TABLE\n ========================================================= */\n";
390 $out .=
"\$table['server']=" . var_export($in[
'xmodul'] .
'|' . $in[
'db_file'],
true) .
";\n";
391 $out .=
"\$table['table']=" . var_export($in[
'table_name'],
true) .
";\n";
392 $out .=
"\$table['datadic']=" . var_export($in[
'dd_name'],
true) .
";\n";
393 $out .=
"\$table['primary']='id';\n\$table['language']='0';\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 ASC';\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";
394 $out .=
"/* =========================================================\n FIELDS\n ========================================================= */\n";
395 foreach ($this->field_defs($in[
'field_preset']) as
$field) {
398 $out .=
"/* =========================================================\n INDEXES\n ========================================================= */\n";
400 array(
'idx_' . $in[
'table_name'] .
'_title',
'title'),
401 array(
'idx_' . $in[
'table_name'] .
'_activ',
'activ'),
403 if (in_array($in[
'field_preset'], array(
'status',
'workflow'),
true)) {
404 $indexes[] = array(
'idx_' . $in[
'table_name'] .
'_status',
'status');
407 $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";
412 private function generate_form_fd(array $in) {
414 .
"\$messages = array();\n"
415 .
"\$messages['save_success'] = 'Daten wurden gespeichert';\n"
416 .
"\$messages['save_succeass'] = \$messages['save_success'];\n"
417 .
"\$messages['save_error'] = 'Daten konnten nicht gespeichert werden';\n\n";
418 foreach ($this->field_defs($in[
'field_preset']) as $f) {
419 if (in_array($f[0], array(
'id',
'create_date',
'create_uid',
'update_date',
'update_uid',
'owner',
'trash'),
true)) {
422 $out .= $this->dd_field_php($f);
427 private function generate_report_fd() {
431$messages[
'save_success'] =
'Daten wurden gespeichert';
433$messages[
'save_error'] =
'Daten konnten nicht gespeichert werden';
435$field[
'name']=
'dbx_rrows';
437$field[
'tpl']=
'select-single-label';
439$field[
'label']=
'Anz.Seite';
441$field[
'options']=
'10=10&20=20&50=50&100=100&0=alle';
444$field[
'name']=
'dbx_rsort';
446$field[
'tpl']=
'select-single-label';
448$field[
'label']=
'Sortierung';
449$field[
'rules']=
'sqlsearch';
450$field[
'options']=
'id=id&title=Titel&update_date=Update&sorter=Sortierung&activ=Aktiv';
453$field[
'name']=
'dbx_rdesc';
455$field[
'tpl']=
'select-single-label';
458$field[
'rules']=
'parameter';
459$field[
'options']=
'ASC=Aufsteigend&DESC=Absteigend';
462$field[
'name']=
'dbx_rwhere';
464$field[
'tpl']=
'dbx|search';
467$field[
'rules']=
'parameter';
471$field[
'name']=
'dbx_rselect';
473$field[
'tpl']=
'select-single-label';
477$field[
'options']=
'0=Alle&1=Ausgewaehlte';
482 private function generate_form_template() {
484<div
id=
"dbxForm_{i}" class=
"dbx-panel dbxForm_wrapper">
485 <form action=
"{action}" method=
"post" id=
"dbx_form_{i}" class=
"dbxAjax" data-target=
"dbxForm_{i}">
487 <div
class=
"dbx-panel-body">
488 <div
class=
"mb-3">{obj:form_msg}</div>
489 <div
class=
"row g-3">[
dbx:form]</div>
497 private function generate_report_template() {
499[tpl=
dbx|report-shell-head]
501 <div
class=
"table-responsive">
502 <table
class=
"table table-striped table-bordered table-light table-hover align-middle">
504 <tr
class=
"{tr-class}">[rpt:row]</tr>
507 <hr
class=
"dbx_split">
508 <tr
class=
"{tr-class}">[rpt:row]</tr>
509 <hr
class=
"dbx_split">
513[tpl=
dbx|report-shell-foot]
517 private function generate_main_class(array $in) {
518 $modul = $in[
'xmodul'];
520 $default = $in[
'default_run1'] ?:
'run';
521 $startContent = $this->esc(
'Modul ' . $modul .
' ist bereit.');
522 $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";
523 if ((
int)$in[
'create_templates'] !== 1) {
524 $startMethod =
" private function start() {\n return '<div class=\"p-3\"><h2>" . $this->esc($in[
'title']) .
"</h2><p>" . $startContent .
"</p></div>';\n }\n";
527 $cases .=
" case " . var_export($default,
true) .
":\n \$content = \$this->start();\n break;\n\n";
528 if ($in[
'create_form']) {
529 $cases .=
" case 'form':\n \$content = \$this->service()->form();\n break;\n\n";
531 if ($in[
'create_report']) {
532 $cases .=
" case 'report':\n \$content = \$this->service()->report();\n break;\n\n";
533 $cases .=
" case 'detail':\n \$content = \$this->service()->detail();\n break;\n\n";
535 if ($in[
'module_template'] ===
'api' || $in[
'create_include']) {
536 $cases .=
" case 'api':\n \$this->service()->api();\n break;\n\n";
538 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";
541 private function generate_service_class(array $in) {
542 $modul = $in[
'xmodul'];
543 $class = $modul .
'Service';
544 $ddRef = $in[
'dd_mode'] !==
'none' ? $modul .
'|' . $in[
'dd_name'] :
'';
545 $formFd = $modul .
'|' . $in[
'dd_name'] .
'-form';
546 $reportFd = $modul .
'|rpt-' . $in[
'dd_name'] .
'-selection';
548 foreach ($this->field_defs($in[
'field_preset']) as $f) {
549 if (in_array($f[0], array(
'id',
'title',
'description',
'content',
'status',
'activ',
'update_date'),
true)) {
553 $fieldsExport = var_export(
$fields,
true);
556namespace dbx\__MODUL__;
571 private $dd = __DD_REF__;
572 private $formFd = __FORM_FD__;
573 private $reportFd = __REPORT_FD__;
575 private function db() {
576 return dbx()->get_system_obj(
'dbxDB');
579 private function tpl() {
580 return dbx()->get_system_obj(
'dbxTPL');
583 private function base_url($run1 =
'report', array $params = array()) {
584 $url =
'?dbx_modul=__MODUL__&dbx_run1=' . rawurlencode($run1);
585 foreach ($params as $key =>
$value) {
586 $url .=
'&' . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)
$value);
601 public function form($rid =
null) {
602 if ($this->dd ===
'')
return '<div class="alert alert-info">Kein DD konfiguriert.</div>';
604 $rid = $rid ===
null ? (int)
dbx()->get_modul_var(
'rid', 0,
'int') : (int)$rid;
605 $do =
dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
607 if ($do ===
'delete' && $rid > 0) {
608 return $this->delete_record($rid);
611 $data = $rid > 0 ? $this->db()->select1($this->dd, $rid) : array(
'activ' => 1);
612 if (!is_array($data)) $data = array(
'activ' => 1);
614 $form =
dbx()->get_system_obj(
'dbxForm');
615 $form->init(
'__MODUL__-form');
616 $form->set_form_callback_owner($this);
617 $form->set_init_callback(
'form_init_callback');
618 $form->set_submit_callback(
'form_submit_callback');
619 $form->set_run_callback(
'form_run_callback');
622 $form->_fd = $this->formFd;
623 $form->_data = $data;
625 $form->_action = $this->base_url(
'form', $rid > 0 ? array(
'rid' => $rid) : array());
626 $form->add_rep(
'bar_title', $rid > 0 ?
'Datensatz bearbeiten' :
'Neuer Datensatz');
627 $form->add_rep(
'bar_subtitle', $this->dd);
628 $form->add_obj(
'bar_actions',
'obj-value', $this->form_action_buttons($rid));
629 $form->_msg_info =
'Felder ausfuellen und mit Speichern uebernehmen.';
631 $this->configure_form_fields(
$form);
634 $ok =
$form->save_post($this->dd, $rid > 0 ? $rid :
'new', $this->form_save_defaults($rid));
636 $form->_msg_success =
'Datensatz gespeichert.';
638 $form->_msg_error =
'Datensatz konnte nicht gespeichert werden.';
645 private function form_action_buttons($rid) {
646 $html =
'<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-save"></i> Speichern</button>';
648 $delete = $this->base_url(
'form', array(
'rid' => $rid,
'dbx_do' =>
'delete'));
649 $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>';
651 $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>';
655 private function configure_form_fields(
$form) {
672 private function form_save_defaults($rid) {
673 $uid = (int)
dbx()->user();
674 $now = date(
'Y-m-d H:i:s');
675 $defaults = array(
'update_date' =>
$now,
'update_uid' => $uid);
676 if ((
int)$rid <= 0) {
677 $defaults[
'create_date'] =
$now;
678 $defaults[
'create_uid'] = $uid;
679 $defaults[
'owner'] = $uid;
680 $defaults[
'trash'] = 0;
685 public function delete_record($rid) {
687 if ($rid <= 0)
return '<div class="alert alert-warning">Kein Datensatz gewaehlt.</div>';
688 $ok = $this->db()->delete($this->dd, $rid);
690 return '<div class="alert alert-success">Datensatz geloescht.</div>' . $this->report();
692 return '<div class="alert alert-danger">Datensatz konnte nicht geloescht werden.</div>' . $this->report();
695 public function detail($rid =
null) {
696 $rid = $rid ===
null ? (int)
dbx()->get_modul_var(
'rid', 0,
'int') : (int)$rid;
697 if ($rid <= 0)
return '<div class="alert alert-warning">Kein Datensatz gewaehlt.</div>';
698 $record = $this->db()->select1($this->dd, $rid);
699 if (!is_array($record))
return '<div class="alert alert-warning">Datensatz nicht gefunden.</div>';
702 foreach ($record as $key =>
$value) {
703 $rows .=
'<tr><th>' . htmlspecialchars((
string)$key, ENT_QUOTES,
'UTF-8') .
'</th><td>' . htmlspecialchars((
string)
$value, ENT_QUOTES,
'UTF-8') .
'</td></tr>';
705 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>';
717 public function report() {
718 if ($this->dd ===
'')
return '<div class="alert alert-info">Kein DD konfiguriert.</div>';
721 $report->init(
'__MODUL__-report');
722 $report->set_callback_owner($this);
723 $report->set_report_header_callback(
'report_header_callback');
724 $report->set_page_header_callback(
'report_page_header_callback');
725 $report->set_header_callback(
'report_table_header_callback');
726 $report->set_body_callback(
'report_body_callback');
727 $report->set_footer_callback(
'report_table_footer_callback');
728 $report->set_page_footer_callback(
'report_page_footer_callback');
729 $report->set_report_footer_callback(
'report_footer_callback');
730 $report->set_next_record_callback(
'report_next_record_callback');
731 $report->set_callback(
'row_action_data',
'report_row_action_data_callback');
734 $report->_action = $this->base_url(
'report');
738 $report->_multi_page_select = 1;
739 $report->_create_sel_flds =
true;
740 $report->_create_row_select =
true;
741 $report->_create_row_edit =
true;
742 $report->_create_row_show =
true;
743 $report->_create_row_delete =
true;
744 $report->_msg_confirm_delete =
'Diesen Datensatz wirklich loeschen?';
745 $report->add_rep(
'bar_title',
'Datensaetze');
746 $report->add_rep(
'bar_subtitle', $this->dd);
747 $report->add_rep(
'bar_icon',
'bi-table');
748 $report->add_rep(
'bar_class',
'dbx-module-bar');
749 $report->add_rep(
'bar_title_class',
'dbx-module-bar-titleblock');
750 $report->add_rep(
'bar_actions_class',
'dbx-module-bar-actions');
751 $report->add_rep(
'bar_title_pre',
'');
752 $report->add_rep(
'bar_title_heading_attrs',
'');
753 $report->add_rep(
'bar_middle',
'');
754 $report->add_rep(
'bar_extra',
'');
755 $report->add_rep(
'bar_actions',
'<a class="btn btn-primary btn-sm" href="' . htmlspecialchars($this->base_url(
'form'), ENT_QUOTES,
'UTF-8') .
'"><i class="bi bi-plus-lg"></i> Neu</a>');
757 $report->add_action(
'rows_select',
'action_button_select',
'&dbx_do=rows_select');
758 $report->add_action(
'rows_deselect',
'action_button_deselect',
'&dbx_do=clear_selects');
759 $report->add_action(
'rows_delete',
'action_button_delete',
'&dbx_do=multi_delete');
760 $report->create_selection_fields($this->reportFd);
762 $actionContent = $this->handle_report_action(
$report);
763 if ($actionContent !==
'') {
764 return $actionContent;
767 $rwhere =
$report->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=64');
768 $rsort =
$report->get_fld_val(
'dbx_rsort',
'title',
'parameter');
769 $rdesc =
$report->get_fld_val(
'dbx_rdesc',
'ASC',
'parameter');
770 $rrows =
$report->get_fld_val(
'dbx_rrows', 20,
'int');
771 $rpos =
$report->get_fld_val(
'dbx_rpos', 0,
'int');
772 $rselect =
$report->get_fld_val(
'dbx_rselect', 0,
'int');
774 if ($rwhere !==
'') {
775 $rwhere = array(
'search' => array(
'value' => $rwhere,
'like' => array(
'title',
'description'),
'mode' =>
'contains'));
778 $rwhere =
$report->add_rwhere_select(is_string($rwhere) ? $rwhere :
'');
781 $flds = $this->report_fields();
785 $report->_count_all = $this->db()->count($this->dd);
786 $report->_rcount = $this->db()->count($this->dd, $rwhere);
787 $report->_rdata = $this->db()->select($this->dd, $rwhere, $flds, $rsort, $rdesc,
'', $rrows, $rpos);
792 private function handle_report_action(
$report) {
793 $do =
dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
794 $rid = (int)
dbx()->get_modul_var(
'rid', 0,
'int');
796 if ($do ===
'row_edit' && $rid > 0) {
797 return $this->form($rid);
799 if (($do ===
'row_show' || $do ===
'detail') && $rid > 0) {
800 return $this->detail($rid);
802 if ($do ===
'row_delete' && $rid > 0) {
803 $ok = $this->db()->delete($this->dd, $rid);
805 $report->_msg_success =
$ok ?
'Datensatz geloescht.' :
'';
806 $report->_msg_error =
$ok ?
'' :
'Datensatz konnte nicht geloescht werden.';
809 if ($do ===
'multi_delete') {
817 private function report_fields() {
818 return __FIELDS_EXPORT__;
849 public function report_next_record_callback(
$report, $record) {
850 if (is_array($record) && isset($record[
'status'])) {
851 $record[
'status'] = strtoupper((
string)$record[
'status']);
856 public function report_row_action_data_callback(
$report, $payload) {
857 if (is_array($payload) && isset($payload[
'data'], $payload[
'type'])) {
858 if ($payload[
'type'] ===
'show') {
859 $payload[
'data'][
'tooltip'] =
'Details anzeigen';
865 public function form_init_callback(
$form,
$value) {
869 public function form_submit_callback(
$form,
$value) {
877 public function api() {
878 dbx()->json_response(array(
'ok' => 1,
'module' =>
'__MODUL__',
'dd' => $this->dd));
884 array(
'__MODUL__',
'__CLASS__',
'__DD_REF__',
'__FORM_FD__',
'__REPORT_FD__',
'__FIELDS_EXPORT__'),
885 array($modul,
$class, var_export($ddRef,
true), var_export($formFd,
true), var_export($reportFd,
true), $fieldsExport),
890 private function generate_result_template(array $in) {
891 return '<div class="p-3"><h2>' . $this->esc($in[
'title']) .
'</h2><p>{content}</p></div>';
894 private function generate_module_readme(array $in) {
895 $modul = $in[
'xmodul'];
896 $dd = $in[
'dd_name'];
898# __MODUL__ Modul-Wizard Dokumentation
900Diese Datei wurde vom
dbxAdmin Modul-Wizard erzeugt. Sie ist absichtlich knapp und technisch, damit Mensch und KI die Struktur direkt verstehen.
904- Modulwurzel: `
dbx/modules/__MODUL__/`
905- KI darf nur innerhalb dieser Modulwurzel arbeiten.
906- DD-Dateien muessen vollstaendig sein, keine DD-Includes.
907- Templates werden in PHP ueber `
dbx()->get_system_obj(
'dbxTPL')->get_tpl(
'__MODUL__|template', $data)` gerendert.
908- DD->DB Sync nur fuer `__MODUL__|__DD__`.
912- `__MODUL__.class.php`: Einstieg und Routing nach `dbx_run1`.
913- `include/__MODUL__Service.class.php`: Formular, Report, Detail, API und Callbacks.
914- `dd/__DD__.dd.php`: Data Dictionary und DB-Struktur.
915- `fd/__DD__-form.fd.php`: Formularfelder.
916- `fd/rpt-__DD__-selection.fd.php`: Report-Filter und Report-Auswahl.
917- `tpl/htm/start.htm`: Start-Template.
921Route: `?dbx_modul=__MODUL__&dbx_run1=form`
925- `save_post()` fuer Insert/Update.
926- `dbx_do=
delete` fuer Delete.
927- `form_action_buttons()` fuer Save/Delete/Report Buttons.
928- `configure_form_fields()` als zentraler Ort fuer Felder.
934Gängige Feldtemplates:
936- `text-label`: einzeiliger Text.
937- `textarea-label`: mehrzeiliger Text, auch fuer HTML-Felder geeignet wenn Regeln es zulassen.
938- `checkbox-label`: 0/1 Checkbox.
939- `select-single-label`: einfache Auswahl mit `options`.
940- `select-multible-label` oder `multiselect2`: Mehrfachauswahl.
941- `date-label`: Datum.
942- `integer-label`: Zahlen.
943- `password-label`: Passwort.
944- `hidden`: verstecktes Feld.
948Route: `?dbx_modul=__MODUL__&dbx_run1=report`
953- Multi-Delete ueber `delete_multi_selected_records()`.
954- Row-Edit ueber `dbx_do=row_edit`.
955- Row-Detail ueber `dbx_do=row_show` oder `dbx_do=detail`.
956- Row-Delete ueber `dbx_do=row_delete`.
957- Filter `dbx_rselect=1` zeigt nur ausgewaehlte IDs.
968- `report_next_record_callback(
$report, $record)`
969- `report_row_action_data_callback(
$report, $payload)`
973Route: `?dbx_modul=__MODUL__&dbx_run1=api`
975Die API-Methode ist ein Platzhalter und liefert JSON. Erweiterungen bleiben im Modul.
978 return str_replace(array(
'__MODUL__',
'__DD__'), array($modul,
$dd),
$template);
981 private function generate_module(array $in, array &$log) {
982 $modul = $in[
'xmodul'];
983 $ok = $this->ensure_dirs($modul, $log);
984 $overwrite = (int)$in[
'overwrite'] === 1;
986 $written = $this->write_module_file($modul, $rel,
$content, $overwrite || $in[
'target_mode'] ===
'new', $log);
990 if ((
int)$in[
'backup'] === 1 && $in[
'target_mode'] ===
'existing') {
991 $backup = $this->backup_module($modul);
993 $log[] = array(
'type' =>
'ok',
'text' =>
'Backup erstellt: ' .
$backup);
994 $restore =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_new&dbx_run3=restore_backup&xmodul=' . rawurlencode($modul) .
'&file=' . rawurlencode(
$backup);
995 $log[] = array(
'type' =>
'link',
'text' =>
'Restore-Link: ' . $restore);
997 $log[] = array(
'type' =>
'skip',
'text' =>
'Backup nicht erstellt (ZipArchive nicht verfuegbar oder Modul leer).');
1001 $config =
"<?php\n\$config['version']='1.0';\n\$config['activ']='1';\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";
1003 $write($modul .
'.class.php', $this->generate_main_class($in));
1005 if ((
int)$in[
'create_include'] === 1) {
1006 $write(
'include/' . $modul .
'Service.class.php', $this->generate_service_class($in));
1008 $write(
'README-MODUL-WIZARD.md', $this->generate_module_readme($in));
1009 if ($in[
'dd_mode'] ===
'new') {
1010 $write(
'dd/' . $in[
'dd_name'] .
'.dd.php', $this->generate_dd($in));
1012 if ((
int)$in[
'create_form'] === 1 && $in[
'dd_mode'] !==
'none') {
1013 $write(
'fd/' . $in[
'dd_name'] .
'-form.fd.php', $this->generate_form_fd($in));
1015 if ((
int)$in[
'create_report'] === 1 && $in[
'dd_mode'] !==
'none') {
1016 $write(
'fd/rpt-' . $in[
'dd_name'] .
'-selection.fd.php', $this->generate_report_fd());
1018 if ((
int)$in[
'create_templates'] === 1) {
1019 $write(
'tpl/htm/modul-help.htm',
'<p>' . $this->esc($in[
'title']) .
'</p>');
1020 $write(
'tpl/htm/start.htm', $this->generate_result_template($in));
1021 if ((
int)$in[
'create_form'] === 1) {
1022 $write(
'tpl/htm/' . strtolower($modul) .
'-form.htm', $this->generate_form_template());
1024 if ((
int)$in[
'create_report'] === 1) {
1025 $write(
'tpl/htm/' . strtolower($modul) .
'-report.htm', $this->generate_report_template());
1029 if ($in[
'dd_mode'] !==
'none') {
1030 $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';
1031 if ($in[
'sync_mode'] ===
'apply') {
1032 $state = $this->sync_dd_to_db($modul, $in[
'dd_name']);
1033 $syncOk = ((
$state[
'status'] ??
'') ===
'finished');
1034 $log[] = array(
'type' => $syncOk ?
'ok' :
'error',
'text' =>
'DD->DB Sync: ' . (
string)(
$state[
'message'] ?? (
$state[
'status'] ??
'')));
1036 } elseif ($in[
'sync_mode'] ===
'link') {
1037 $log[] = array(
'type' =>
'link',
'text' =>
'DD->DB Sync: ' . $syncUrl);
1041 $startUrl =
'?dbx_modul=' . rawurlencode($modul) .
'&dbx_run1=' . rawurlencode($in[
'default_run1'] ?:
'run');
1042 $log[] = array(
'type' =>
'link',
'text' =>
'Modul starten: ' . $startUrl);
1043 if ((
int)$in[
'create_form'] === 1) {
1044 $log[] = array(
'type' =>
'link',
'text' =>
'Formular oeffnen: ?dbx_modul=' . rawurlencode($modul) .
'&dbx_run1=form');
1046 if ((
int)$in[
'create_report'] === 1) {
1047 $log[] = array(
'type' =>
'link',
'text' =>
'Report oeffnen: ?dbx_modul=' . rawurlencode($modul) .
'&dbx_run1=report');
1049 if ((
int)$in[
'create_include'] === 1) {
1050 $log[] = array(
'type' =>
'link',
'text' =>
'API pruefen: ?dbx_modul=' . rawurlencode($modul) .
'&dbx_run1=api');
1052 if ($in[
'dd_mode'] !==
'none') {
1053 $log[] = array(
'type' =>
'link',
'text' =>
'DD bearbeiten: ?dbx_modul=dbxAdmin&dbx_run1=edit_dd&modul=' . rawurlencode($modul) .
'&dd=' . rawurlencode($in[
'dd_name']));
1054 if ((
int)$in[
'create_form'] === 1) {
1055 $log[] = array(
'type' =>
'link',
'text' =>
'Form-FD bearbeiten: ?dbx_modul=dbxAdmin&dbx_run1=edit_fd&modul=' . rawurlencode($modul) .
'&fd=' . rawurlencode($in[
'dd_name'] .
'-form'));
1057 if ((
int)$in[
'create_report'] === 1) {
1058 $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'));
1062 if ((
int)$in[
'ki_package'] === 1) {
1063 $kiUrl =
'?dbx_modul=dbxKi&dbx_run1=briefing_module&xmodul=' . rawurlencode($modul) .
'&dd_name=' . rawurlencode($in[
'dd_name']);
1064 $apiUrl =
'?dbx_modul=dbxKi&dbx_run1=module_api&action=module.describe&xmodul=' . rawurlencode($modul);
1065 $log[] = array(
'type' =>
'link',
'text' =>
'KI-Modulauftrag in dbxKi: ' . $kiUrl);
1066 $log[] = array(
'type' =>
'link',
'text' =>
'dbxKi Modul-API: ' . $apiUrl);
1071 private function sync_dd_to_db($modul,
$dd) {
1072 $oDD =
dbx()->get_system_obj(
'dbxDD');
1073 $oDD->sync_dd_to_db($modul,
$dd,
'reset');
1074 $state = array(
'status' =>
'running',
'message' =>
'');
1075 for ($i = 0; $i < 20; $i++) {
1077 if (in_array((
string)(
$state[
'status'] ??
''), array(
'finished',
'error',
'cancelled'),
true)) {
1081 return is_array(
$state) ?
$state : array(
'status' =>
'error',
'message' =>
'Sync ohne Status');
1084 private function log_html(array $log) {
1085 if (!$log)
return '';
1086 $html =
'<div class="dbx-admin-dashboard-panel mt-3"><div class="p-3"><h3>Ergebnis</h3><ul class="list-unstyled mb-0">';
1087 foreach ($log as $row) {
1088 $type = (string)($row[
'type'] ??
'info');
1089 $text = (string)($row[
'text'] ??
'');
1090 $class = $type ===
'error' ?
'text-danger' : ($type ===
'skip' ?
'text-muted' :
'text-success');
1091 if ($type ===
'link' && strpos($text,
': ?') !==
false) {
1092 list($label,
$url) = explode(
': ', $text, 2);
1093 $html .=
'<li class="' .
$class .
'"><a href="' . $this->esc(
$url) .
'">' . $this->esc($label) .
'</a></li>';
1095 $html .=
'<li class="' .
$class .
'">' . $this->esc($text) .
'</li>';
1098 return $html .
'</ul></div></div>';
1101 private function ki_api() {
1102 $modul =
dbx()->get_modul_var(
'xmodul',
'',
'parameter');
1103 $dd =
dbx()->get_modul_var(
'dd_name',
'',
'parameter');
1104 if (!$this->valid_name($modul)) {
1105 dbx()->json_response(array(
'ok' => 0,
'error' =>
'ungueltiges Modul'),
true);
1107 dbx()->json_response($this->ki_manifest($modul,
$dd),
true);
1110 private function ki_manifest($modul,
$dd) {
1113 'bundle_version' =>
'module-wizard.v1',
1117 'root' =>
'dbx/modules/' . $modul .
'/',
1118 'hard_rules' => array(
1119 'Nur Dateien unter dbx/modules/' . $modul .
'/ bearbeiten.',
1120 'Keine globalen Configs, keine anderen Module, keine Dateien in dbx/include aendern.',
1121 'Templates ueber dbxapp-Template-APIs lesen/rendern, z.B. dbx()->get_system_obj(\'dbxTPL\')->get_tpl(...).',
1122 'Template-Aenderungen nur als Modul-Template-Aktion liefern; keine freien Schreibzugriffe ausserhalb von dbx/modules/' . $modul .
'/tpl/.',
1123 'Vor destruktiven Aenderungen ein Modul-ZIP erzeugen.',
1124 'Antwort als ZIP mit manifest.json und job.json liefern.',
1126 'allowed_actions' => array(
1127 'module.file.write' =>
'Datei unter dem Modul schreiben',
1128 'module.file.delete' =>
'Datei unter dem Modul entfernen',
1129 'module.dd.write' =>
'DD unter dd/*.dd.php schreiben',
1130 'module.dd.sync' =>
'DD->DB Sync fuer Modul-DD anfordern',
1131 'module.template.get' =>
'Template ueber dbxTPL/get_tpl lesen oder rendern',
1132 'module.template.set' =>
'Template unter tpl/* ueber die dbxapp-Template-Mechanik schreiben',
1133 'module.php.write' =>
'PHP unter Modulroot oder include schreiben',
1134 'module.form.extend' =>
'Formular mit Save/Delete/Feldtemplates erweitern',
1135 'module.report.extend' =>
'Report mit Callbacks, Multi-Select, Multi-Delete, Edit und Detail erweitern',
1140 private function send_ki_zip() {
1141 if (!class_exists(
'\\ZipArchive')) {
1142 dbx()->json_response(array(
'ok' => 0,
'error' =>
'ZipArchive nicht verfuegbar.'),
true);
1144 $modul =
dbx()->get_modul_var(
'xmodul',
'',
'parameter');
1145 $dd =
dbx()->get_modul_var(
'dd_name',
'',
'parameter');
1146 if (!$this->valid_name($modul)) {
1147 dbx()->json_response(array(
'ok' => 0,
'error' =>
'ungueltiges Modul'),
true);
1151 'job_version' =>
'module-wizard.v1',
1154 array(
'id' =>
'backup',
'action' =>
'module.backup',
'params' => array(
'module' => $modul)),
1155 array(
'id' =>
'plan',
'action' =>
'module.plan',
'params' => array(
'module' => $modul,
'dd' =>
$dd)),
1158 $auftrag =
"# KI-Auftrag Modulprogrammierung\n\n"
1159 .
"Arbeite ausschliesslich im Modul `dbx/modules/$modul/`.\n\n"
1160 .
"## Harte Regeln\n\n"
1161 .
"- Keine Aenderungen ausserhalb von `dbx/modules/$modul/`.\n"
1162 .
"- Kein Umbau von Core, dbx/include, globaler config.php oder anderen Modulen.\n"
1163 .
"- DD-Dateien vollstaendig schreiben, keine include-basierten DDs.\n"
1164 .
"- Templates ueber die dbxapp-Template-Schicht lesen/rendern, z.B. `dbx()->get_system_obj('dbxTPL')->get_tpl(...)`.\n"
1165 .
"- Template-Aenderungen als Modul-Template-Set-Aktion liefern; keine Pfade ausserhalb von `dbx/modules/$modul/tpl/`.\n"
1166 .
"- Formular-Muster mit Save/Delete, Feldtemplates und Callback-Kommentaren erhalten oder sauber erweitern.\n"
1167 .
"- Report-Muster mit Multi-Select, Multi-Delete, Edit, Detail und Callbacks erhalten oder sauber erweitern.\n"
1168 .
"- README-MODUL-WIZARD.md aktualisieren, wenn Formular-, Report-, DD- oder API-Verhalten geaendert wird.\n"
1169 .
"- Wenn DB-Sync noetig ist, nur fuer `$modul|$dd` anfordern.\n"
1170 .
"- Liefere `antwort.zip` mit `manifest.json`, `job.json`, geaenderten Dateien und README.\n\n"
1171 .
"## Aufgabe\n\nBeschreibe im Chat die gewuenschte Modulaufgabe und nutze diese ZIP als Arbeitskontext.\n";
1172 $tmp = tempnam(sys_get_temp_dir(),
'dbxmodki');
1173 $zip = new \ZipArchive();
1174 $zip->open(
$tmp, \ZipArchive::OVERWRITE);
1175 $zip->addFromString(
'manifest.json', json_encode(
$manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
1176 $zip->addFromString(
'job.vorlage.json', json_encode($job, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
1177 $zip->addFromString(
'KI-AUFTRAG.md', $auftrag);
1178 $zip->addFromString(
'README.md',
"Modul-KI-Auftrag fuer $modul.\n");
1180 $name =
'dbxki-modul-' . preg_replace(
'/[^A-Za-z0-9_-]+/',
'-', $modul) .
'.zip';
1181 header(
'Content-Type: application/zip');
1182 header(
'Content-Disposition: attachment; filename="' . $name .
'"');
1183 header(
'Content-Length: ' . filesize(
$tmp));
1190 $in = $this->collect_input();
1192 $oForm =
dbx()->get_system_obj(
'dbxForm');
1193 $oForm->init(
'form-wizzard-new');
1194 $oForm->_fd =
'dbxAdmin|module-wizard';
1195 $oForm->load_fd_messages();
1196 $oForm->_action =
'?dbx_modul=dbxAdmin&dbx_run1=modules&dbx_run2=modul_new';
1197 $oForm->_data = array_merge(array(
1198 'target_mode' =>
'new',
1199 'module_template' =>
'form_report',
1201 'field_preset' =>
'basic',
1202 'create_include' => 1,
1204 'create_report' => 1,
1205 'create_templates' => 1,
1208 'sync_mode' =>
'link',
1211 $oForm->add_rep(
'bar_title', $oForm->get_fd_message(
'bar_title'));
1212 $oForm->add_rep(
'bar_subtitle', $oForm->get_fd_message(
'bar_subtitle'));
1216 '<button class="btn btn-primary btn-sm" type="submit"><i class="bi bi-magic"></i> '
1217 . $this->esc($oForm->get_fd_message(
'action_create')) .
'</button>'
1219 $oForm->_msg_info = $oForm->get_fd_message(
'wizard_info');
1221 $oForm->add_fld(
'target_mode',
'select-single-label', label: $oForm->get_fd_message(
'label_target'), rules:
'parameter', options: $this->options(array(
'new' => $oForm->get_fd_message(
'option_target_new'),
'existing' => $oForm->get_fd_message(
'option_target_existing'))));
1222 $oForm->add_fld(
'existing_modul',
'select-single-label', label: $oForm->get_fd_message(
'label_existing_module'), rules:
'parameter', options: $this->options($this->module_options(
true)));
1223 $oForm->add_fld(
'xmodul',
'text-label', label: $oForm->get_fd_message(
'label_new_module'), rules:
'parameter|max=63', tooltip: $oForm->get_fd_message(
'tooltip_module_name'));
1224 $oForm->add_fld(
'title',
'text-label', label: $oForm->get_fd_message(
'label_title'), rules:
'varchar|max=120');
1225 $oForm->add_fld(
'module_template',
'select-single-label', label: $oForm->get_fd_message(
'label_template'), rules:
'parameter', options: $this->options(array(
'form_report' => $oForm->get_fd_message(
'option_template_form_report'),
'form' => $oForm->get_fd_message(
'option_template_form'),
'report' => $oForm->get_fd_message(
'option_template_report'),
'api' => $oForm->get_fd_message(
'option_template_api'),
'blank' => $oForm->get_fd_message(
'option_template_blank'))));
1226 $oForm->add_fld(
'default_run1',
'text-label', label: $oForm->get_fd_message(
'label_run1'), rules:
'parameter|max=32');
1227 $oForm->add_fld(
'default_run2',
'text-label', label: $oForm->get_fd_message(
'label_run2'), rules:
'parameter|max=32');
1228 $oForm->add_fld(
'dd_mode',
'select-single-label', label: $oForm->get_fd_message(
'label_dd'), rules:
'parameter', options: $this->options(array(
'new' => $oForm->get_fd_message(
'option_dd_new'),
'existing' => $oForm->get_fd_message(
'option_dd_existing'),
'none' => $oForm->get_fd_message(
'option_dd_none'))));
1229 $oForm->add_fld(
'dd_ref',
'select-single-label', label: $oForm->get_fd_message(
'label_existing_dd'), rules:
'parameter', options: $this->options($this->dd_options(
true)));
1230 $oForm->add_fld(
'dd_name',
'text-label', label: $oForm->get_fd_message(
'label_dd_name'), rules:
'parameter|max=63');
1231 $oForm->add_fld(
'table_name',
'text-label', label: $oForm->get_fd_message(
'label_table'), rules:
'parameter|max=63');
1232 $oForm->add_fld(
'db_file',
'text-label', label: $oForm->get_fd_message(
'label_db_file'), rules:
'parameter+.-_|max=80');
1233 $oForm->add_fld(
'field_preset',
'select-single-label', label: $oForm->get_fd_message(
'label_fields'), rules:
'parameter', options: $this->options(array(
'basic' => $oForm->get_fd_message(
'option_fields_basic'),
'content' => $oForm->get_fd_message(
'option_fields_content'),
'status' => $oForm->get_fd_message(
'option_fields_status'),
'workflow' => $oForm->get_fd_message(
'option_fields_workflow'))));
1234 $oForm->add_fld(
'create_include',
'checkbox-label', label: $oForm->get_fd_message(
'label_create_include'), rules:
'int');
1235 $oForm->add_fld(
'create_form',
'checkbox-label', label: $oForm->get_fd_message(
'label_create_form'), rules:
'int');
1236 $oForm->add_fld(
'create_report',
'checkbox-label', label: $oForm->get_fd_message(
'label_create_report'), rules:
'int');
1237 $oForm->add_fld(
'create_templates',
'checkbox-label', label: $oForm->get_fd_message(
'label_create_templates'), rules:
'int');
1238 $oForm->add_fld(
'overwrite',
'checkbox-label', label: $oForm->get_fd_message(
'label_overwrite'), rules:
'int');
1239 $oForm->add_fld(
'backup',
'checkbox-label', label: $oForm->get_fd_message(
'label_backup'), rules:
'int');
1240 $oForm->add_fld(
'sync_mode',
'select-single-label', label: $oForm->get_fd_message(
'label_sync'), rules:
'parameter', options: $this->options(array(
'link' => $oForm->get_fd_message(
'option_sync_link'),
'none' => $oForm->get_fd_message(
'option_sync_none'),
'apply' => $oForm->get_fd_message(
'option_sync_apply'))));
1241 $oForm->add_fld(
'ki_package',
'checkbox-label', label: $oForm->get_fd_message(
'label_ki_package'), rules:
'int');
1242 $oForm->add_js_code(<<<
'JS'
1244 var byName =
function (name) {
return document.querySelector(
'[name="' + name +
'"]'); };
1245 var modul = byName(
'xmodul');
1246 var dd = byName(
'dd_name');
1247 var table = byName(
'table_name');
1248 var dbFile = byName(
'db_file');
1249 var
template = byName(
'module_template');
1250 var previousModule =
'';
1252 var syncNames =
function () {
1253 if (!modul || !dd || !table || !dbFile)
return;
1254 var name = String(modul.value ||
'').trim();
1255 var oldDd = previousModule ? previousModule +
'Data' :
'';
1256 var oldDb = previousModule ? previousModule +
'.db3' :
'modul.db3';
1257 if (!dd.value || dd.value === oldDd) dd.value = name ? name +
'Data' :
'';
1258 if (!table.value || table.value === oldDd) table.value = name ? name +
'Data' :
'';
1259 if (!dbFile.value || dbFile.value === oldDb || dbFile.value ===
'modul.db3') dbFile.value = name ? name +
'.db3' :
'modul.db3';
1260 previousModule = name;
1263 var applyTemplate =
function () {
1264 if (!
template)
return;
1266 form_report: [1, 1, 1],
1272 var preset = presets[
template.value];
1273 if (!preset)
return;
1274 [
'create_include',
'create_form',
'create_report'].forEach(
function (name, index) {
1275 var checkbox = byName(name);
1276 if (checkbox) checkbox.checked = preset[index] === 1;
1281 modul.addEventListener(
'input', syncNames);
1284 if (
template)
template.addEventListener(
'change', applyTemplate);
1289 if ($oForm->submit()) {
1290 $in = $this->collect_input($oForm);
1292 if ($this->validate_input($in,
$errors) && !$oForm->errors()) {
1293 if ($this->generate_module($in, $log)) {
1294 $oForm->_msg_success = $oForm->get_fd_message(
'wizard_success');
1296 $oForm->_msg_error = $oForm->get_fd_message(
'wizard_error');
1300 $oForm->add_fld_error(
'xmodul', $err);
1302 $oForm->_msg_error = $oForm->get_fd_message(
'check_input');
1306 return $oForm->run() . $this->log_html($log);
1310 $run3 =
dbx()->get_modul_var(
'dbx_run3',
'',
'parameter');
1311 if ($run3 ===
'restore_backup') {
1312 return $this->restore_module_backup();
1314 if ($run3 ===
'ki_zip') {
1315 $this->send_ki_zip();
1317 if ($run3 ===
'ki_api') {