dbXapp 2.0
RAD, CMS, Module und Runtime-IDE fuer dbXapp
Loading...
Searching...
No Matches
dbxSchema.class.php
Go to the documentation of this file.
1<?php
2namespace dbx\dbxAdmin;
3
4dbx()->get_system_obj('dbxReport', 'use');
5
23class dbxSchema extends \dbxObj {
24
31 private function esc($value) {
32 return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
33 }
34
42 private function norm_key($server, $table) {
43 return strtolower((string) $server) . '|' . strtolower((string) $table);
44 }
45
53 private function encode_db_rid($server, $table) {
54 $json = json_encode(array((string)$server, (string)$table), JSON_UNESCAPED_SLASHES);
55 return 'db_' . rtrim(strtr(base64_encode($json), '+/', '-_'), '=');
56 }
57
64 private function decode_db_rid($rid) {
65 $rid = (string)$rid;
66
67 if (str_starts_with($rid, 'db_')) {
68 $raw = substr($rid, 3);
69 $pad = strlen($raw) % 4;
70 if ($pad) {
71 $raw .= str_repeat('=', 4 - $pad);
72 }
73
74 $json = base64_decode(strtr($raw, '-_', '+/'), true);
75 $data = $json !== false ? json_decode($json, true) : null;
76 if (is_array($data) && count($data) >= 2) {
77 return array((string)$data[0], (string)$data[1]);
78 }
79 }
80
81 $parts = explode('|', $rid, 2);
82 return count($parts) == 2 ? $parts : array('', '');
83 }
84
92 private function dd_ref($modul, $dd) {
93 return ($modul && $modul != 'dbx') ? $modul . '|' . $dd : $dd;
94 }
95
102 private function path_rel($path) {
103 $path = str_replace('\\', '/', (string)$path);
104 $base = str_replace('\\', '/', dbx_get_base_dir());
105
106 if ($base !== '' && str_starts_with($path, $base)) {
107 $path = substr($path, strlen($base));
108 }
109
110 $path = ltrim($path, '/');
111 if (str_starts_with($path, 'dbx/modules/')) {
112 $path = substr($path, strlen('dbx/modules/'));
113 }
114
115 return $path;
116 }
117
126 private function build_url($run1, $run2, $params = array()) {
127 $url = '?dbx_modul=dbxAdmin&dbx_run1=' . rawurlencode($run1) . '&dbx_run2=' . rawurlencode($run2);
128
129 foreach ($params as $key => $value) {
130 $url .= '&' . rawurlencode((string) $key) . '=' . rawurlencode((string) $value);
131 }
132
133 return $url;
134 }
135
147 private function openwin($url, $icon, $title, $width = 1200, $height = 780, $class = 'btn-inline') {
148 $titleEsc = $this->esc($title);
149 $urlEsc = $this->esc($url);
150
151 return '<a class="' . $class . ' dbx-win" href="' . $urlEsc . '" title="' . $titleEsc . '" '
152 . 'data-url="' . $urlEsc . '" data-title="' . $titleEsc
153 . '" role="button"><i class="' . $this->esc($icon) . '"></i></a>';
154 }
155
163 private function badge($label, $class = 'secondary') {
164 return '<span class="badge bg-' . $this->esc($class) . '">' . $this->esc($label) . '</span>';
165 }
166
174 private function header_icon($icon, $title) {
175 return '<i class="' . $this->esc($icon) . '" title="' . $this->esc($title) . '" aria-label="' . $this->esc($title) . '"></i>';
176 }
177
184 private function sanitize_dd_name($name) {
185 $name = preg_replace('/[^A-Za-z0-9_]+/', '_', (string) $name);
186 $name = trim($name, '_');
187
188 if ($name === '') {
189 $name = 'new_dd';
190 }
191
192 if (preg_match('/^[0-9]/', $name)) {
193 $name = 'dd_' . $name;
194 }
195
196 return $name;
197 }
198
205 private function sanitize_db_view_part($name) {
206 $name = preg_replace('/[^A-Za-z0-9_.-]+/', '_', (string) $name);
207 $name = trim($name, '._-');
208
209 if ($name === '') {
210 $name = 'db';
211 }
212
213 return $name;
214 }
215
222 private function backup_name_part($name) {
223 $name = preg_replace('/[^A-Za-z0-9_.-]+/', '_', (string)$name);
224 $name = trim($name, '._-');
225 return $name !== '' ? $name : 'db';
226 }
227
235 private function quote_db_ident($server, $name) {
236 $dbType = dbx()->get_system_obj('dbxDB')->get_db_type($server);
237 $name = str_replace(array('`', '"', ']'), '', (string)$name);
238
239 if ($dbType === 'mysql') {
240 return '`' . str_replace('`', '``', $name) . '`';
241 }
242
243 if ($dbType === 'sqlsrv') {
244 return '[' . str_replace(']', ']]', $name) . ']';
245 }
246
247 return '"' . str_replace('"', '""', $name) . '"';
248 }
249
257 private function sql_db_value($server, $value) {
258 if ($value === null) {
259 return 'NULL';
260 }
261
262 if (is_bool($value)) {
263 return $value ? '1' : '0';
264 }
265
266 if (is_array($value) || is_object($value)) {
267 $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
268 }
269
270 $oDB = dbx()->get_system_obj('dbxDB');
271 return "'" . $oDB->escape((string)$value, $server) . "'";
272 }
273
279 private function backup_dir() {
280 $dir = dbx_os_path_file(dbx_get_file_dir() . 'db/backup/');
281 if (!is_dir($dir)) {
282 @mkdir($dir, 0775, true);
283 }
284 return rtrim(str_replace('\\', '/', $dir), '/') . '/';
285 }
286
293 private function backup_file_path($file) {
294 $file = basename((string)$file);
295 if ($file === '' || !preg_match('/\.json$/i', $file)) {
296 return '';
297 }
298
299 $path = $this->backup_dir() . $file;
300 $realDir = realpath($this->backup_dir());
301 $realFile = realpath($path);
302
303 if (!$realDir || !$realFile) {
304 return '';
305 }
306
307 $realDir = rtrim(str_replace('\\', '/', $realDir), '/') . '/';
308 $realFile = str_replace('\\', '/', $realFile);
309
310 return str_starts_with($realFile, $realDir) ? $realFile : '';
311 }
312
319 private function backup_relative_file($file) {
320 return 'files/db/backup/' . basename((string)$file);
321 }
322
330 private function db_view_dd_name($server, $table) {
331 return $this->sanitize_db_view_part($server) . '.' . $this->sanitize_db_view_part($table);
332 }
333
340 private function split_dd_rid($rid) {
341 $parts = explode('|', (string)$rid, 2);
342 if (count($parts) != 2) {
343 return array('', '');
344 }
345
346 $modul = trim($parts[0]);
347 $dd = trim($parts[1]);
348
349 if (!preg_match('/^[A-Za-z0-9_.-]+$/', $modul) || !preg_match('/^[A-Za-z0-9_.-]+$/', $dd)) {
350 return array('', '');
351 }
352
353 return array($modul, $dd);
354 }
355
363 private function dd_file_path($modul, $dd) {
364 return dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/' . $modul . '/dd/' . $dd . '.dd.php');
365 }
366
374 private function dd_editor_file($modul, $dd) {
375 return dbx()->editor_file_path($this->dd_file_path($modul, $dd));
376 }
377
385 private function delete_dd_file($modul, $dd) {
386 $file = $this->dd_file_path($modul, $dd);
387 $base = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/' . $modul . '/dd/');
388 $realBase = realpath($base);
389 $realFile = realpath($file);
390
391 if (!$realBase || !$realFile || !str_starts_with(str_replace('\\', '/', $realFile), rtrim(str_replace('\\', '/', $realBase), '/') . '/')) {
392 return 0;
393 }
394
395 if (!is_file($realFile)) {
396 return 0;
397 }
398
399 $ok = @unlink($realFile) ? 1 : 0;
400 if ($ok) {
401 $oDD = dbx()->get_system_obj('dbxDD');
402 $oDD->clear_dd_cache($modul . '|' . $dd);
403 }
404
405 return $ok;
406 }
407
416 private function merge_auto_dd_fields($oldFields, $dbFields, $server = '') {
417 $oldByName = array();
418 foreach ((array)$oldFields as $field) {
419 $name = strtolower((string)($field['name'] ?? ''));
420 if ($name !== '') {
421 $oldByName[$name] = $field;
422 }
423 }
424
425 $oDD = dbx()->get_system_obj('dbxDD');
426 $dbType = $server ? $oDD->get_db_type($server) : '';
427
428 $fields = array();
429 foreach ((array)$dbFields as $field) {
430 $name = strtolower((string)($field['name'] ?? ''));
431 if ($name !== '' && isset($oldByName[$name])) {
432 $field = $oDD->merge_dd_field_with_db_field($oldByName[$name], $field, $dbType);
433 }
434
435 $fields[] = $field;
436 }
437
438 return $fields;
439 }
440
448 private function ensure_db_view_dd($server, $table) {
449 $oDD = dbx()->get_system_obj('dbxDD');
450 if (!$server || !$table || !$oDD->get_table_exist($server, $table)) {
451 return '';
452 }
453
454 $ddName = $this->db_view_dd_name($server, $table);
455 $ddRef = 'dbx|' . $ddName;
456 $old = file_exists($this->dd_file_path('dbx', $ddName)) ? $oDD->get_dd_model($ddRef) : array();
457
458 $tableMeta = is_array($old['table'] ?? null) ? $old['table'] : array();
459 $tableMeta['server'] = $server;
460 $tableMeta['table'] = $table;
461 $tableMeta['datadic'] = $ddName;
462
463 $fields = $this->merge_auto_dd_fields($old['fields'] ?? array(), $oDD->get_db_fields($server, $table), $server);
464 $indexes = $oDD->get_db_indexes($server, $table);
465
466 if (!$fields) {
467 return '';
468 }
469
470 return $oDD->save_dd('dbx', $ddName, $tableMeta, $fields, $indexes) ? $ddRef : '';
471 }
472
479 private function data_report_fields($ddRef) {
480 $oDD = dbx()->get_system_obj('dbxDD');
481 $fields = $oDD->get_dd_fields($ddRef);
482 $out = array();
483
484 foreach ((array)$fields as $field) {
485 $name = (string)($field['name'] ?? '');
486 if ($name === '') {
487 continue;
488 }
489
490 $label = trim((string)($field['label'] ?? ''));
491 $out[$name] = $label !== '' ? $label : $name;
492 }
493
494 return $out;
495 }
496
504 private function data_grid_cols($ddRef, $primary) {
505 $oDB = dbx()->get_system_obj('dbxDB');
506 $fields = $oDB->get_dd_fields($ddRef);
507 $cols = array();
508
509 foreach ((array)$fields as $field) {
510 $name = (string)($field['name'] ?? '');
511 if ($name === '' || str_starts_with($name, '_')) {
512 continue;
513 }
514
515 $label = trim((string)($field['label'] ?? ''));
516 $label = str_replace(array(':', '[', ']'), '-', $label ?: $name);
517 $fieldType = strtolower(trim((string)($field['type'] ?? '')));
518 $gridType = $oDB->map_dd_type_to_grid_type($fieldType ?: 'text');
519 $protect = isset($field['protect']) ? (string)$field['protect'] : '0';
520
521 if ($protect === '2') {
522 continue;
523 }
524
525 $flag = ($name === $primary || $protect === '1') ? 'p' : '';
526 $options = array();
527
528 if ($name === 'content' || in_array($fieldType, array('mediumtext', 'longtext'), true)) {
529 $options[] = 'editor=textarea';
530 $options[] = 'formatter=truncate';
531 $options[] = 'bigEditor=1';
532 $options[] = 'maxChars=180';
533 $options[] = 'width=420';
534 $options[] = 'minWidth=260';
535 }
536
537 $col = $name . '[' . $label . ']:' . $gridType;
538 if ($flag !== '') {
539 $col .= ':' . $flag;
540 }
541 if ($options) {
542 $col .= ':' . implode(';', $options);
543 }
544
545 $cols[] = $col;
546 }
547
548 return implode(',', $cols);
549 }
550
556 private function data_context_from_request() {
557 $mode = dbx()->get_modul_var('dbx_run1', 'dd', 'parameter');
558 $rid = dbx()->get_modul_var('rid', '', 'parameter');
559 $ddRef = '';
560
561 if ($mode == 'db') {
562 [$server, $table] = $this->decode_db_rid($rid);
563 if ($server && $table) {
564 $ddRef = $this->ensure_db_view_dd($server, $table);
565 }
566 } else {
567 [$modul, $dd] = $this->split_dd_rid($rid);
568 if (!$modul || !$dd) {
569 [$modul, $dd] = $this->dd_params_from_request();
570 }
571 if ($modul && $dd) {
572 $ddRef = $this->dd_ref($modul, $dd);
573 }
574 }
575
576 $oDB = dbx()->get_system_obj('dbxDB');
577 $flds = $ddRef ? $this->data_report_fields($ddRef) : array();
578 $primary = $ddRef ? $oDB->get_dd_primary($ddRef) : 'id';
579
580 if (!$primary || !isset($flds[$primary])) {
581 $primary = isset($flds['id']) ? 'id' : '';
582 }
583
584 return array($ddRef, $flds, $primary);
585 }
586
595 private function data_row_by_id($ddRef, $primary, $id) {
596 if (!$ddRef || !$primary || $id === '' || $id === null) {
597 return array();
598 }
599
600 $oDB = dbx()->get_system_obj('dbxDB');
601 $rows = $oDB->select($ddRef, array($primary => $id), '', '', 'ASC', '', 1, 0, 0);
602 if (is_array($rows) && isset($rows[0]) && is_array($rows[0])) {
603 return $rows[0];
604 }
605
606 return array();
607 }
608
620 private function run_data_report($ddRef, $title, $readUrl, $saveUrl, $insertUrl, $deleteUrl) {
621 $oDB = dbx()->get_system_obj('dbxDB');
622 $oReport = dbx()->get_system_obj('dbxReport');
623 $flds = $this->data_report_fields($ddRef);
624 $primary = $oDB->get_dd_primary($ddRef);
625
626 if (!$flds || !$primary || !isset($flds[$primary])) {
627 return '<div class="alert alert-warning">Keine editierbare Felddefinition mit Primaerschluessel vorhanden.</div>';
628 }
629
630 $oReport->init('report-schema-data-' . substr(md5($ddRef), 0, 10), 'schema-data-report');
631 $oReport->_rflds = $flds;
632 $oReport->_mode = 'tabulurator';
633 $oReport->_rrows = 'auto';
634 $oReport->_grid_id = 'schema_data_' . substr(md5($ddRef), 0, 10);
635 $oReport->_grid_cols = $this->data_grid_cols($ddRef, $primary);
636 $oReport->_grid_layout = 'fitData';
637 $oReport->_grid_read_url = $readUrl;
638 $oReport->_grid_save_url = $saveUrl;
639 $oReport->_grid_insert_url = $insertUrl;
640 $oReport->_grid_delete_url = $deleteUrl;
641 $oReport->add_obj('title', 'obj-value', $this->esc($title));
642 $oReport->add_obj('subtitle', 'obj-value', $this->esc('Direkt editierbares Grid'));
643 $oReport->add_rep('bar_title', $title);
644 $oReport->add_rep('bar_subtitle', 'Direkt editierbares Grid');
645 $oReport->add_obj('primary', 'obj-value', $this->esc($primary));
646
647 return $oReport->run();
648 }
649
655 private function run_data_read() {
656 [$ddRef, $flds, $primary] = $this->data_context_from_request();
657 if (!$ddRef || !$flds || !$primary) {
658 $this->json_response(array('ok' => 0, 'count' => 0, 'rows' => array(), 'msg' => 'Keine Felddefinition vorhanden.'));
659 }
660
661 $oDB = dbx()->get_system_obj('dbxDB');
662 $rows = $oDB->select($ddRef, '', array_keys($flds), '', 'ASC', '', 0, 0, 0);
663 if (!is_array($rows)) {
664 $rows = array();
665 }
666
667 $out = array();
668 foreach (array_values($rows) as $pos => $row) {
669 if (is_array($row)) {
670 $row['_dbx_rownum'] = $pos + 1;
671 $out[] = $row;
672 }
673 }
674
675 $count = $oDB->count($ddRef, '');
676 $this->json_response(array(
677 'ok' => 1,
678 'count' => $count >= 0 ? $count : count($out),
679 'rows' => $out,
680 'server_time' => date('Y-m-d H:i:s'),
681 ));
682 }
683
689 private function run_data_save() {
690 [$ddRef, $flds, $primary] = $this->data_context_from_request();
691 $payload = $this->request_json();
692 $rows = is_array($payload['rows'] ?? null) ? $payload['rows'] : array();
693
694 if (!$ddRef || !$flds || !$primary) {
695 $this->json_response(array('ok' => 0, 'msg' => 'Keine editierbare Felddefinition vorhanden.'));
696 }
697
698 $oDB = dbx()->get_system_obj('dbxDB');
699 $okCount = 0;
700
701 foreach ($rows as $row) {
702 if (!is_array($row)) {
703 continue;
704 }
705
706 $id = $row[$primary] ?? ($row['id'] ?? null);
707 if ($id === null || $id === '') {
708 continue;
709 }
710
711 $values = array();
712 foreach ($flds as $field => $label) {
713 if ($field === $primary || !array_key_exists($field, $row)) {
714 continue;
715 }
716 $values[$field] = $row[$field];
717 }
718
719 if (!$values) {
720 continue;
721 }
722
723 $ok = $oDB->update($ddRef, $values, array($primary => $id), 1, 1, 1, 1);
724 if ($ok === 1 && $oDB->_update_count > 0) {
725 $okCount++;
726 }
727 }
728
729 $this->json_response(array('ok' => 1, 'success' => true, 'count' => $okCount));
730 }
731
737 private function run_data_insert() {
738 [$ddRef, $flds, $primary] = $this->data_context_from_request();
739 if (!$ddRef || !$flds || !$primary) {
740 $this->json_response(array('ok' => 0, 'msg' => 'Keine editierbare Felddefinition vorhanden.'));
741 }
742
743 $oDB = dbx()->get_system_obj('dbxDB');
744 $id = 0;
745 if ($oDB->insert($ddRef, array(), 1, 1, 1, 1) === 1) {
746 $id = $oDB->get_insert_id();
747 }
748 if ($id <= 0) {
749 $this->json_response(array('ok' => 0, 'msg' => 'Datensatz konnte nicht angelegt werden.'));
750 }
751
752 $row = $this->data_row_by_id($ddRef, $primary, $id);
753 $this->json_response(array(
754 'ok' => 1,
755 'success' => true,
756 'row' => $row,
757 ));
758 }
759
765 private function run_data_delete() {
766 [$ddRef, $flds, $primary] = $this->data_context_from_request();
767 $payload = $this->request_json();
768 $id = $payload['id'] ?? null;
769
770 if (!$ddRef || !$flds || !$primary || $id === null || $id === '') {
771 $this->json_response(array('ok' => 0, 'msg' => 'Datensatz-ID fehlt.'));
772 }
773
774 $oDB = dbx()->get_system_obj('dbxDB');
775 $ok = $oDB->delete($ddRef, array($primary => $id), 1, 1);
776 $this->json_response(array('ok' => $ok > 0 ? 1 : 0, 'success' => $ok > 0));
777 }
778
785 private function json_response($data) {
786 header('Content-Type: application/json; charset=utf-8');
787 echo json_encode($data, JSON_UNESCAPED_UNICODE);
788 exit;
789 }
790
796 private function request_json() {
797 $raw = file_get_contents('php://input');
798 $data = $raw ? json_decode($raw, true) : array();
799 return is_array($data) ? $data : array();
800 }
801
807 private function dd_params_from_request() {
808 $modul = dbx()->get_modul_var('modul', '', 'parameter');
809 $dd = dbx()->get_modul_var('dd', '', 'parameter');
810
811 if ((!$modul || !$dd) && strpos((string)$dd, '|') !== false) {
812 [$modul, $dd] = $this->split_dd_rid($dd);
813 }
814
815 if (!$modul && $dd) {
816 $modul = 'dbx';
817 }
818
819 if (!preg_match('/^[A-Za-z0-9_.-]+$/', (string)$modul) || !preg_match('/^[A-Za-z0-9_.-]+$/', (string)$dd)) {
820 return array('', '');
821 }
822
823 return array($modul, $dd);
824 }
825
831 private function dd_field_grid_keys() {
832 $oDD = dbx()->get_system_obj('dbxDD');
833 return $oDD->dd_field_schema_keys();
834 }
835
843 private function dd_field_grid_row($field, $id) {
844 $row = array(
845 'id' => (string)$id,
846 '_pos' => (int)$id + 1,
847 );
848
849 foreach ($this->dd_field_grid_keys() as $key) {
850 $value = $field[$key] ?? '';
851 $row[$key] = is_array($value) ? implode(',', $value) : (string)$value;
852 }
853
854 return $row;
855 }
856
863 private function dd_field_grid_rows($fields) {
864 $rows = array();
865 foreach (array_values((array)$fields) as $id => $field) {
866 $rows[] = $this->dd_field_grid_row((array)$field, $id);
867 }
868
869 return $rows;
870 }
871
877 private function dd_field_grid_cols() {
878 $types = implode('~', array(
879 'varchar', 'char', 'text', 'mediumtext', 'longtext',
880 'int', 'integer', 'bigint', 'smallint', 'mediumint', 'tinyint',
881 'decimal', 'float', 'double',
882 'date', 'datetime', 'timestamp', 'time',
883 'bool', 'boolean', 'json', 'blob',
884 ));
885
886 return implode(',', array(
887 '_pos[#]:number:p:width=62;hozAlign=center;headerHozAlign=center',
888 'name[Name]:text::width=150',
889 'type[Type]:text::editor=list;values=' . $types . ';width=120',
890 'index[Index]:text::editor=list;values==ohne~PRI=Primaer~MU=Mehrfach~INDEX=Index~UNIQUE=Unique;width=115',
891 'length[Laenge]:text::width=88',
892 'default[Default]:text::width=120',
893 'label[Label]:text::width=160',
894 'rules[Regeln]:text::width=180',
895 'tooltip[Tooltip]:text::editor=textarea;width=220',
896 'errormsg[Fehlermeldung]:text::editor=textarea;width=220',
897 'placeholder[Platzhalter]:text::width=160',
898 'convert[Convert]:text::width=120',
899 'protect[Schutz]:text::editor=list;values=0=editierbar~1=geschuetzt~2=versteckt;width=120',
900 'group[Gruppe]:text::width=130',
901 'mask[Maske]:text::width=130',
902 'data[Data]:text::editor=textarea;width=220',
903 'options[Optionen]:text::editor=textarea;width=220',
904 'tpl[TPL]:text::width=160',
905 'js[JS]:text::editor=textarea;width=180',
906 'prompt[Prompt]:text::editor=textarea;width=220',
907 ));
908 }
909
917 private function dd_field_model($modul, $dd) {
918 if (!$modul || !$dd) {
919 return array();
920 }
921
922 $oDD = dbx()->get_system_obj('dbxDD');
923 return $oDD->get_dd_model($this->dd_ref($modul, $dd));
924 }
925
935 private function save_dd_field_model($modul, $dd, $model, $fields) {
936 $oDD = dbx()->get_system_obj('dbxDD');
937 return $oDD->save_dd($modul, $dd, $model['table'] ?? array(), array_values($fields), $model['indexes'] ?? array());
938 }
939
947 private function normalize_dd_field_grid_post($row, $old = array()) {
948 $field = is_array($old) ? $old : array();
949
950 foreach ($this->dd_field_grid_keys() as $key) {
951 if (array_key_exists($key, $row)) {
952 $field[$key] = is_array($row[$key]) ? implode(',', $row[$key]) : trim((string)$row[$key]);
953 }
954 }
955
956 $field['name'] = trim((string)($field['name'] ?? ''));
957 $field['type'] = strtolower(trim((string)($field['type'] ?? 'varchar')));
958
959 if ($field['type'] === '') {
960 $field['type'] = 'varchar';
961 }
962
963 if (!isset($field['protect']) || !in_array((string)$field['protect'], array('0', '1', '2'), true)) {
964 $field['protect'] = '0';
965 }
966
967 return $field;
968 }
969
977 private function validate_dd_fields($fields, &$message = '') {
978 $names = array();
979
980 foreach ((array)$fields as $field) {
981 $name = trim((string)($field['name'] ?? ''));
982 if ($name === '' || !preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $name)) {
983 $message = 'Ungueltiger Feldname: ' . $name;
984 return false;
985 }
986
987 $key = strtolower($name);
988 if (isset($names[$key])) {
989 $message = 'Feldname doppelt: ' . $name;
990 return false;
991 }
992
993 $names[$key] = true;
994 }
995
996 return true;
997 }
998
1004 private function run_dd_fields_grid() {
1005 [$modul, $dd] = $this->dd_params_from_request();
1006 $model = $this->dd_field_model($modul, $dd);
1007
1008 if (!$model) {
1009 return '<div class="alert alert-danger">DD nicht gefunden.</div>';
1010 }
1011
1012 $baseParams = array(
1013 'modul' => $modul,
1014 'dd' => $dd,
1015 );
1016
1017 $oReport = dbx()->get_system_obj('dbxReport');
1018 $oReport->init('report-dd-fields-grid', 'schema-fields-grid');
1019 $oReport->_mode = 'tabulurator';
1020 $oReport->_rrows = 620;
1021 $oReport->_grid_id = 'dd_fields_' . substr(md5($modul . '|' . $dd), 0, 10);
1022 $oReport->_grid_cols = $this->dd_field_grid_cols();
1023 $oReport->_grid_layout = 'fitDataStretch';
1024 $oReport->_grid_read_url = $this->build_url('dd', 'fields_read', $baseParams);
1025 $oReport->_grid_save_url = $this->build_url('dd', 'fields_save', $baseParams);
1026 $oReport->_grid_insert_url = $this->build_url('dd', 'fields_insert', $baseParams);
1027 $oReport->_grid_delete_url = $this->build_url('dd', 'fields_delete', $baseParams);
1028 $oReport->add_rep('bar_title', 'DD-Felder: ' . $modul . '|' . $dd);
1029 $oReport->add_rep('bar_subtitle', $this->path_rel($this->dd_file_path($modul, $dd)));
1030
1031 return $oReport->run();
1032 }
1033
1039 private function run_dd_fields_read() {
1040 [$modul, $dd] = $this->dd_params_from_request();
1041 $model = $this->dd_field_model($modul, $dd);
1042
1043 if (!$model) {
1044 $this->json_response(array('ok' => 0, 'rows' => array(), 'msg' => 'DD nicht gefunden'));
1045 }
1046
1047 $rows = $this->dd_field_grid_rows($model['fields'] ?? array());
1048 $this->json_response(array(
1049 'ok' => 1,
1050 'count' => count($rows),
1051 'rows' => $rows,
1052 'server_time' => date('Y-m-d H:i:s'),
1053 ));
1054 }
1055
1061 private function run_dd_fields_save() {
1062 [$modul, $dd] = $this->dd_params_from_request();
1063 $model = $this->dd_field_model($modul, $dd);
1064 $payload = $this->request_json();
1065 $rows = is_array($payload['rows'] ?? null) ? $payload['rows'] : array();
1066
1067 if (!$model) {
1068 $this->json_response(array('ok' => 0, 'msg' => 'DD nicht gefunden'));
1069 }
1070
1071 $fields = array_values((array)($model['fields'] ?? array()));
1072 foreach ($rows as $row) {
1073 $id = isset($row['id']) ? (int)$row['id'] : -1;
1074 if ($id < 0 || !isset($fields[$id])) {
1075 continue;
1076 }
1077
1078 $fields[$id] = $this->normalize_dd_field_grid_post($row, $fields[$id]);
1079 }
1080
1081 $message = '';
1082 if (!$this->validate_dd_fields($fields, $message)) {
1083 $this->json_response(array('ok' => 0, 'msg' => $message));
1084 }
1085
1086 $ok = $this->save_dd_field_model($modul, $dd, $model, $fields);
1087 $this->json_response(array('ok' => $ok ? 1 : 0, 'success' => $ok ? true : false, 'rows' => $this->dd_field_grid_rows($fields)));
1088 }
1089
1095 private function run_dd_fields_insert() {
1096 [$modul, $dd] = $this->dd_params_from_request();
1097 $model = $this->dd_field_model($modul, $dd);
1098
1099 if (!$model) {
1100 $this->json_response(array('ok' => 0, 'msg' => 'DD nicht gefunden'));
1101 }
1102
1103 $fields = array_values((array)($model['fields'] ?? array()));
1104 $used = array();
1105 foreach ($fields as $field) {
1106 $used[strtolower((string)($field['name'] ?? ''))] = true;
1107 }
1108
1109 $name = 'new_field';
1110 $i = 1;
1111 while (isset($used[strtolower($name)])) {
1112 $i++;
1113 $name = 'new_field_' . $i;
1114 }
1115
1116 $oDD = dbx()->get_system_obj('dbxDD');
1117 $field = $oDD->normalize_dd_field(array(
1118 'name' => $name,
1119 'type' => 'varchar',
1120 'length' => '255',
1121 'index' => '',
1122 'label' => str_replace('_', ' ', ucfirst($name)),
1123 'rules' => '',
1124 'tpl' => 'text-label',
1125 ));
1126
1127 $fields[] = $field;
1128 $ok = $this->save_dd_field_model($modul, $dd, $model, $fields);
1129 $this->json_response(array(
1130 'ok' => $ok ? 1 : 0,
1131 'success' => $ok ? true : false,
1132 'row' => $this->dd_field_grid_row($field, count($fields) - 1),
1133 ));
1134 }
1135
1141 private function run_dd_fields_delete() {
1142 [$modul, $dd] = $this->dd_params_from_request();
1143 $model = $this->dd_field_model($modul, $dd);
1144 $payload = $this->request_json();
1145
1146 if (!$model) {
1147 $this->json_response(array('ok' => 0, 'msg' => 'DD nicht gefunden'));
1148 }
1149
1150 if (!array_key_exists('id', $payload)) {
1151 $this->json_response(array('ok' => 0, 'msg' => 'Feld-ID fehlt'));
1152 }
1153
1154 $id = (int)$payload['id'];
1155 $fields = array_values((array)($model['fields'] ?? array()));
1156 if ($id < 0 || !isset($fields[$id])) {
1157 $this->json_response(array('ok' => 0, 'msg' => 'Feld-ID ungueltig'));
1158 }
1159
1160 array_splice($fields, $id, 1);
1161 $ok = $this->save_dd_field_model($modul, $dd, $model, $fields);
1162 $this->json_response(array('ok' => $ok ? 1 : 0, 'success' => $ok ? true : false));
1163 }
1164
1170 private function get_module_options() {
1171 $options = array();
1172 $base = str_replace('\\', '/', dbx_get_base_dir()) . 'dbx/modules/*';
1173
1174 foreach (glob($base, GLOB_ONLYDIR) as $dir) {
1175 $modul = basename($dir);
1176 if (is_dir($dir . '/dd')) {
1177 $options[$modul] = $modul;
1178 }
1179 }
1180
1181 if (!isset($options['dbx'])) {
1182 $options['dbx'] = 'dbx';
1183 }
1184
1185 ksort($options);
1186 return $options;
1187 }
1188
1194 private function get_server_options() {
1195 $options = array();
1196 $config = dbx()->get_config('dbx', 'db');
1197 $moduleFiles = $this->get_module_db_files();
1198 $moduleFileIndex = array();
1199
1200 foreach ($moduleFiles as $server => $db) {
1201 $options[$server] = $db['label'];
1202
1203 $real = realpath((string)($db['file'] ?? ''));
1204 if ($real) {
1205 $moduleFileIndex[strtolower(str_replace('\\', '/', $real))] = 1;
1206 }
1207 }
1208
1209 if (is_array($config)) {
1210 foreach ($config as $server => $data) {
1211 if (isset($options[$server])) {
1212 continue;
1213 }
1214
1215 $type = strtolower((string)($data['type'] ?? ''));
1216 $host = (string)($data['host'] ?? '');
1217 $name = (string)($data['dbname'] ?? ($data['name'] ?? ''));
1218 $isSqlite = ($type == 'sqlite' || $type == 'sqlite3' || preg_match('/\.(db3|sqlite|sqlite3)$/i', $name));
1219
1220 if ($isSqlite && ($host !== '' || $name !== '')) {
1221 $file = dbx_os_path_file($host . $name);
1222 $real = realpath($file);
1223
1224 if ($real) {
1225 $real = strtolower(str_replace('\\', '/', $real));
1226 if (isset($moduleFileIndex[$real])) {
1227 continue;
1228 }
1229 }
1230 }
1231
1232 $options[$server] = $server;
1233 }
1234 }
1235
1236 return $options;
1237 }
1238
1244
1245
1246 private function get_module_db_files() {
1247 $records = array();
1248 $base = str_replace('\\', '/', dbx_get_base_dir()) . 'dbx/modules/*/db/*';
1249
1250 foreach (glob($base) as $file) {
1251 if (!is_file($file) || !preg_match('/\.(db3|sqlite|sqlite3)$/i', $file)) {
1252 continue;
1253 }
1254
1255 $norm = str_replace('\\', '/', $file);
1256 if (!preg_match('#/dbx/modules/([^/]+)/db/([^/]+)$#', $norm, $match)) {
1257 continue;
1258 }
1259
1260 $modul = $match[1];
1261 $name = $match[2];
1262 $server = ($modul == 'dbx') ? $name : $modul . '|' . $name;
1263
1264 $records[$server] = array(
1265 'server' => $server,
1266 'modul' => $modul,
1267 'name' => $name,
1268 'file' => $norm,
1269 'path' => $this->path_rel($norm),
1270 'label' => $this->path_rel($norm),
1271 );
1272 }
1273
1274 ksort($records);
1275 return $records;
1276 }
1277
1284 private function resolve_module_db_file($server) {
1285 $server = (string)$server;
1286 if (!preg_match('/\.(db3|sqlite|sqlite3)$/i', $server)) {
1287 return '';
1288 }
1289
1290 $modul = 'dbx';
1291 $name = $server;
1292
1293 if (strpos($server, '|') !== false) {
1294 $parts = explode('|', $server, 2);
1295 $modul = trim($parts[0]) ?: 'dbx';
1296 $name = trim($parts[1]);
1297 }
1298
1299 $file = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/' . $modul . '/db/' . $name);
1300 if (file_exists($file)) {
1301 return str_replace('\\', '/', $file);
1302 }
1303
1304 if ($modul != 'dbx') {
1305 $file = dbx_os_path_file(dbx_get_base_dir() . 'dbx/modules/dbx/db/' . $name);
1306 if (file_exists($file)) {
1307 return str_replace('\\', '/', $file);
1308 }
1309 }
1310
1311 return '';
1312 }
1313
1320 private function get_database_label($server) {
1321 $moduleFile = $this->resolve_module_db_file($server);
1322 if ($moduleFile) {
1323 return $this->path_rel($moduleFile);
1324 }
1325
1326 $config = dbx()->get_config('dbx', 'db');
1327
1328 if (is_array($config) && isset($config[$server])) {
1329 $db = $config[$server];
1330 $name = $db['dbname'] ?? ($db['name'] ?? '');
1331 $host = $db['host'] ?? '';
1332 $type = strtolower((string)($db['type'] ?? ''));
1333
1334 if ($name && $host) {
1335 $full = dbx_os_path_file($host . $name);
1336 if ($type == 'sqlite' || preg_match('/[\/\\\\]/', (string)$host)) {
1337 return $this->path_rel($full);
1338 }
1339
1340 return rtrim((string)$host, '/') . '/' . $name;
1341 }
1342
1343 if ($name) {
1344 return $name;
1345 }
1346
1347 if ($host) {
1348 return $host;
1349 }
1350 }
1351
1352 return '';
1353 }
1354
1361 private function get_database_path_label($server) {
1362 $moduleFile = $this->resolve_module_db_file($server);
1363 if ($moduleFile) {
1364 return $this->path_rel($moduleFile);
1365 }
1366
1367 $config = dbx()->get_config('dbx', 'db');
1368 if (!is_array($config) || !isset($config[$server])) {
1369 return '';
1370 }
1371
1372 $db = $config[$server];
1373 $type = strtolower((string)($db['type'] ?? ''));
1374 $host = (string)($db['host'] ?? '');
1375 $name = (string)($db['dbname'] ?? ($db['name'] ?? ''));
1376
1377 if ($type == 'sqlite' && ($host !== '' || $name !== '')) {
1378 return $this->path_rel(dbx_os_path_file($host . $name));
1379 }
1380
1381 return '';
1382 }
1383
1390
1391 private function get_server_module($server) {
1392 $server = (string)$server;
1393 static $moduleFiles = null;
1394 if ($moduleFiles === null) {
1395 $moduleFiles = $this->get_module_db_files();
1396 }
1397
1398 if (isset($moduleFiles[$server])) {
1399 return $moduleFiles[$server]['modul'] ?? '';
1400 }
1401
1402 $config = dbx()->get_config('dbx', 'db');
1403 if (is_array($config) && isset($config[$server])) {
1404 $db = $config[$server];
1405 $type = strtolower((string)($db['type'] ?? ''));
1406 $host = (string)($db['host'] ?? '');
1407 $name = (string)($db['dbname'] ?? ($db['name'] ?? ''));
1408 $isSqlite = ($type == 'sqlite' || $type == 'sqlite3' || preg_match('/\.(db3|sqlite|sqlite3)$/i', $name));
1409
1410 if ($isSqlite && ($host !== '' || $name !== '')) {
1411 $configFile = realpath(dbx_os_path_file($host . $name));
1412
1413 if ($configFile) {
1414 $configFile = strtolower(str_replace('\\', '/', $configFile));
1415
1416 foreach ($moduleFiles as $moduleDb) {
1417 $moduleFile = realpath((string)($moduleDb['file'] ?? ''));
1418
1419 if ($moduleFile && strtolower(str_replace('\\', '/', $moduleFile)) === $configFile) {
1420 return $moduleDb['modul'] ?? '';
1421 }
1422 }
1423 }
1424 }
1425
1426 return 'sys';
1427 }
1428
1429 return '';
1430 }
1431
1438
1439 private function field_type_label($field) {
1440 $type = trim((string)($field['type'] ?? ''));
1441 $len = trim((string)($field['length'] ?? ''));
1442 $idx = trim((string)($field['index'] ?? ''));
1443
1444 $label = $type;
1445 if ($len !== '') {
1446 $label .= '(' . $len . ')';
1447 }
1448 if ($idx !== '') {
1449 $label .= ' / ' . $idx;
1450 }
1451
1452 return $label;
1453 }
1454
1462 private function field_tooltip_count($fields, $title = 'Felder') {
1463 $fields = array_values((array)$fields);
1464 $count = count($fields);
1465
1466 $plain = $title . ': ' . $count;
1467 $html = '<div class="dbx-tooltip-fields">';
1468 $html .= '<div class="dbx-tooltip-title">' . $this->esc($title) . '</div>';
1469
1470 if ($count <= 0) {
1471 $plain .= "\nKeine Felder";
1472 $html .= '<div class="dbx-tooltip-empty">Keine Felder</div>';
1473 } else {
1474 $html .= '<table class="dbx-tooltip-table">';
1475 foreach ($fields as $field) {
1476 $name = trim((string)($field['name'] ?? ''));
1477 if ($name === '') {
1478 continue;
1479 }
1480
1481 $type = $this->field_type_label($field);
1482 $plain .= "\n" . $name . ': ' . $type;
1483
1484 $html .= '<tr>';
1485 $html .= '<td><code>' . $this->esc($name) . '</code></td>';
1486 $html .= '<td>' . $this->esc($type) . '</td>';
1487 $html .= '</tr>';
1488 }
1489 $html .= '</table>';
1490 }
1491
1492 $html .= '</div>';
1493
1494 return '<span class="dbx-schema-field-count badge bg-light text-dark border" '
1495 . 'title="' . $this->esc($plain) . '" '
1496 . 'data-dbx-tooltip="' . $this->esc(str_replace('"', "'", $html)) . '">'
1497 . $this->esc($count)
1498 . '</span>';
1499 }
1500
1507
1508 private function mapping_status_label($status) {
1509 $map = array(
1510 'exact' => array('Direkt', 'success'),
1511 'mapped' => array('Mapping', 'primary'),
1512 'type_conflict' => array('Typ pruefen', 'warning text-dark'),
1513 'new' => array('Neu', 'secondary'),
1514 );
1515
1516 $item = $map[$status] ?? array($status, 'secondary');
1517 return '<span class="badge bg-' . $this->esc($item[1]) . '">' . $this->esc($item[0]) . '</span>';
1518 }
1519
1526 private function mapping_kind_label($kind) {
1527 $map = array(
1528 'dd_to_db' => 'DD -> DB',
1529 'db_to_dd' => 'DB -> DD',
1530 'transfer' => 'Transfer',
1531 );
1532
1533 return $map[$kind] ?? 'Schema-Mapping';
1534 }
1535
1542 private function posted_mapping_from_model($model) {
1543 $posted = $_POST['schema_map'] ?? array();
1544 if (!is_array($posted)) {
1545 return array();
1546 }
1547
1548 $mapping = array();
1549 foreach ($posted as $target => $source) {
1550 $target = trim((string)$target);
1551 $source = trim((string)$source);
1552
1553 if ($target === '' || $source === '') {
1554 continue;
1555 }
1556
1557 $mapping[$source] = $target;
1558 }
1559
1560 $oDD = dbx()->get_system_obj('dbxDD');
1561 return $oDD->normalize_schema_mapping(
1562 $mapping,
1563 $model['source_fields'] ?? array(),
1564 $model['target_fields'] ?? array()
1565 );
1566 }
1567
1575 private function source_options_html($sources, $selected) {
1576 $html = '<option value=""></option>';
1577
1578 foreach ($sources as $source) {
1579 $name = (string)($source['name'] ?? '');
1580 if ($name === '') {
1581 continue;
1582 }
1583
1584 $sel = (strcasecmp($name, (string)$selected) === 0) ? ' selected' : '';
1585 $meta = $this->field_type_label($source);
1586 $html .= '<option value="' . $this->esc($name) . '"' . $sel . '>'
1587 . $this->esc($name . ($meta ? ' - ' . $meta : ''))
1588 . '</option>';
1589 }
1590
1591 return $html;
1592 }
1593
1601 private function render_source_items($sources, $mapping) {
1602 $mapped = array();
1603 foreach ($mapping as $source => $target) {
1604 $mapped[strtolower((string)$source)] = $target;
1605 }
1606
1607 $html = '';
1608 foreach ($sources as $source) {
1609 $name = (string)($source['name'] ?? '');
1610 if ($name === '') {
1611 continue;
1612 }
1613
1614 $target = $mapped[strtolower($name)] ?? '';
1615 $classes = 'dbx-mapping-source';
1616 if ($target) {
1617 $classes .= ' is-used';
1618 }
1619
1620 $html .= '<button type="button" class="' . $classes . '" draggable="true" '
1621 . 'data-mapping-source="' . $this->esc($name) . '" '
1622 . 'title="' . $this->esc($name) . '">'
1623 . '<span class="dbx-mapping-field-name">' . $this->esc($name) . '</span>'
1624 . '<span class="dbx-mapping-field-meta">' . $this->esc($this->field_type_label($source)) . '</span>'
1625 . '</button>';
1626 }
1627
1628 if ($html === '') {
1629 $html = '<div class="dbx-mapping-empty">Keine Quellfelder</div>';
1630 }
1631
1632 return $html;
1633 }
1634
1641 private function render_mapping_rows($model) {
1642 $sources = $model['source_fields'] ?? array();
1643 $html = '';
1644
1645 foreach (($model['target_rows'] ?? array()) as $row) {
1646 $target = $row['target'] ?? array();
1647 $targetName = (string)($row['target_name'] ?? ($target['name'] ?? ''));
1648 if ($targetName === '') {
1649 continue;
1650 }
1651
1652 $sourceName = (string)($row['source_name'] ?? '');
1653 $status = (string)($row['status'] ?? 'new');
1654
1655 $html .= '<div class="dbx-mapping-target" data-mapping-target="' . $this->esc($targetName) . '" '
1656 . 'data-mapping-status="' . $this->esc($status) . '">';
1657 $html .= '<div class="dbx-mapping-target-main">';
1658 $html .= '<div class="dbx-mapping-target-title">';
1659 $html .= '<span class="dbx-mapping-field-name">' . $this->esc($targetName) . '</span>';
1660 $html .= $this->mapping_status_label($status);
1661 $html .= '</div>';
1662 $html .= '<span class="dbx-mapping-field-meta">' . $this->esc($this->field_type_label($target)) . '</span>';
1663 $html .= '</div>';
1664
1665 $html .= '<div class="dbx-mapping-drop" data-mapping-drop="' . $this->esc($targetName) . '">';
1666 $html .= '<select class="form-select form-select-sm" name="schema_map[' . $this->esc($targetName) . ']" '
1667 . 'data-mapping-select data-target="' . $this->esc($targetName) . '" '
1668 . 'data-auto-source="' . $this->esc($sourceName) . '">';
1669 $html .= $this->source_options_html($sources, $sourceName);
1670 $html .= '</select>';
1671 $html .= '<button type="button" class="btn btn-sm btn-outline-secondary" data-mapping-clear-row title="Zuordnung loeschen">'
1672 . '<i class="bi bi-x-lg"></i></button>';
1673 $html .= '</div>';
1674 $html .= '</div>';
1675 }
1676
1677 if ($html === '') {
1678 $html = '<div class="dbx-mapping-empty">Keine Zielfelder</div>';
1679 }
1680
1681 return $html;
1682 }
1683
1690 private function render_mapping_board($model) {
1691 $kind = (string)($model['kind'] ?? 'dd_to_db');
1692 $mapping = $model['mapping'] ?? array();
1693 $sourceCount = count($model['source_fields'] ?? array());
1694 $targetCount = count($model['target_fields'] ?? array());
1695 $mappedCount = count($mapping);
1696 $file = (string)($model['file'] ?? '');
1697 $fileLabel = $file !== '' ? $this->path_rel($file) : '';
1698 $updated = (string)($model['updated_at'] ?? '');
1699
1700 $html = '<div class="dbx-schema-mapping" data-dbx="lib=mapping" data-mapping-root="1">';
1701
1702 $html .= '<div class="dbx-mapping-head">';
1703 $html .= '<div>';
1704 $html .= '<h3>' . $this->esc($this->mapping_kind_label($kind)) . '</h3>';
1705 $html .= '<div class="dbx-mapping-subtitle">'
1706 . '<span>' . $this->esc($model['source_label'] ?? '') . '</span>'
1707 . '<i class="bi bi-arrow-right"></i>'
1708 . '<span>' . $this->esc($model['target_label'] ?? '') . '</span>'
1709 . '</div>';
1710 $html .= '</div>';
1711
1712 $html .= '<div class="dbx-mapping-tools">';
1713 $html .= '<span class="badge bg-secondary" data-mapping-count="mapped">' . $this->esc($mappedCount . ' / ' . $targetCount) . '</span>';
1714 $html .= '<button type="button" class="btn btn-outline-primary" data-mapping-action="auto" title="Auto-Mapping"><i class="bi bi-magic"></i></button>';
1715 $html .= '<button type="button" class="btn btn-outline-secondary" data-mapping-action="clear" title="Leeren"><i class="bi bi-eraser"></i></button>';
1716 $html .= '</div>';
1717 $html .= '</div>';
1718
1719 $html .= '<div class="dbx-mapping-meta">';
1720 $html .= '<span>Quelle: ' . $this->esc((string)$sourceCount) . '</span>';
1721 $html .= '<span>Ziel: ' . $this->esc((string)$targetCount) . '</span>';
1722 if ($updated !== '') {
1723 $html .= '<span>Gespeichert: ' . $this->esc($updated) . '</span>';
1724 }
1725 if ($fileLabel !== '') {
1726 $html .= '<span class="dbx-mapping-file">' . $this->esc($fileLabel) . '</span>';
1727 }
1728 $html .= '</div>';
1729
1730 $html .= '<div class="dbx-mapping-workbench">';
1731 $html .= '<section class="dbx-mapping-panel dbx-mapping-panel-source">';
1732 $html .= '<header><span>Quelle</span><span class="badge bg-light text-dark">' . $this->esc((string)$sourceCount) . '</span></header>';
1733 $html .= '<div class="dbx-mapping-source-list">' . $this->render_source_items($model['source_fields'] ?? array(), $mapping) . '</div>';
1734 $html .= '</section>';
1735
1736 $html .= '<div class="dbx-mapping-canvas" aria-hidden="true"><svg data-mapping-lines></svg></div>';
1737
1738 $html .= '<section class="dbx-mapping-panel dbx-mapping-panel-target">';
1739 $html .= '<header><span>Ziel</span><span class="badge bg-light text-dark">' . $this->esc((string)$targetCount) . '</span></header>';
1740 $html .= '<div class="dbx-mapping-target-list">' . $this->render_mapping_rows($model) . '</div>';
1741 $html .= '</section>';
1742 $html .= '</div>';
1743
1744 $html .= '</div>';
1745
1746 return $html;
1747 }
1748
1755 private function sync_label_from_plan($plan) {
1756 if (!is_array($plan) || empty($plan['ok'])) {
1757 return $this->badge('DD Fehler', 'danger');
1758 }
1759
1760 if (empty($plan['table_exists'])) {
1761 return $this->badge('DB fehlt', 'warning');
1762 }
1763
1764 $adds = count($plan['add_fields'] ?? array());
1765 $idx = count($plan['add_indexes'] ?? array());
1766 $miss = count($plan['missing_in_dd'] ?? array());
1767 $conf = count($plan['type_conflicts'] ?? array());
1768
1769 if (!empty($plan['rebuild_needed'])) {
1770 return $this->badge('Rebuild', 'danger');
1771 }
1772
1773 if ($adds || $idx) {
1774 return $this->badge('Sync offen', 'warning');
1775 }
1776
1777 return $this->badge('Synchron', 'success');
1778 }
1779
1786 private function sync_detail_from_plan($plan) {
1787 if (!is_array($plan) || empty($plan['ok']) || empty($plan['table_exists'])) {
1788 return '';
1789 }
1790
1791 $adds = count($plan['add_fields'] ?? array());
1792 $idx = count($plan['add_indexes'] ?? array());
1793 $miss = count($plan['missing_in_dd'] ?? array());
1794 $conf = count($plan['type_conflicts'] ?? array());
1795
1796 if (!empty($plan['rebuild_needed'])) {
1797 return $this->esc($conf . ' Typ / ' . $miss . ' Extra');
1798 }
1799
1800 if ($adds || $idx) {
1801 return $this->esc($adds . ' Feld / ' . $idx . ' Index');
1802 }
1803
1804 return '';
1805 }
1806
1814 private function get_sync_plan($modul, $dd) {
1815 $oDD = dbx()->get_system_obj('dbxDD');
1816 return $oDD->sync_dd_to_db($modul, $dd, 'plan');
1817 }
1818
1824 private function get_dd_records() {
1825 $records = array();
1826 $oDD = dbx()->get_system_obj('dbxDD');
1827 $base = str_replace('\\', '/', dbx_get_base_dir()) . 'dbx/modules/*/dd/*.dd.php';
1828
1829 foreach (glob($base) as $file) {
1830 $norm = str_replace('\\', '/', $file);
1831
1832 if (!preg_match('#/dbx/modules/([^/]+)/dd/([^/]+)\.dd\.php$#', $norm, $match)) {
1833 continue;
1834 }
1835
1836 $modul = $match[1];
1837 $dd = $match[2];
1838 if ($dd === 'new') {
1839 continue;
1840 }
1841
1842 $ddRef = $this->dd_ref($modul, $dd);
1843 $model = $oDD->get_dd_model($ddRef);
1844 if (!$model) {
1845 continue;
1846 }
1847
1848 $server = $model['table']['server'] ?? '';
1849 $table = $model['table']['table'] ?? '';
1850 if (str_starts_with(strtolower((string)$table), 'sqlite_')) {
1851 continue;
1852 }
1853 $exists = ($server && $table) ? $oDD->get_table_exist($server, $table) : 0;
1854 $count = $exists ? $oDD->count($ddRef) : '-';
1855 $plan = $this->get_sync_plan($modul, $dd);
1856 $actions = $this->dd_actions($modul, $dd, $server, $table, $plan);
1857
1858 $ddFields = array_values((array)($model['fields'] ?? array()));
1859 $dbFields = ($server && $table && $exists) ? $oDD->get_db_fields($server, $table) : array();
1860
1861 $records[] = array(
1862 'rid' => $modul . '|' . $dd,
1863 'modul' => $modul,
1864 'dd' => $dd,
1865 'path' => $this->path_rel($norm),
1866 'server' => $server,
1867 'database' => $this->get_database_label($server),
1868 'table' => $table,
1869 'dd_fields' => $this->field_tooltip_count($ddFields, 'DD-Felder'),
1870 'db_fields' => $this->field_tooltip_count($dbFields, 'DB-Felder'),
1871 'count' => $count,
1872 'sync' => $this->sync_label_from_plan($plan),
1873 'sync_info' => $this->sync_detail_from_plan($plan),
1874 'act_sync' => $actions['sync'] ?? '',
1875 'act_map' => $actions['map'] ?? '',
1876 'act_move' => $actions['transfer'] ?? '',
1877 'act_flds' => $actions['fields'] ?? '',
1878 );
1879 }
1880
1881 usort($records, function($a, $b) {
1882 return strcmp($a['modul'] . '|' . $a['dd'], $b['modul'] . '|' . $b['dd']);
1883 });
1884
1885 return $records;
1886 }
1887
1894 private function db_server_aliases($server) {
1895 $aliases = array();
1896 $server = trim((string)$server);
1897
1898 if ($server === '') {
1899 return array();
1900 }
1901
1902 $add = function($value) use (&$aliases) {
1903 $value = trim(str_replace('\\', '/', (string)$value));
1904 if ($value !== '') {
1905 $aliases[$value] = true;
1906 }
1907 };
1908
1909 $add($server);
1910
1911 $norm = str_replace('\\', '/', $server);
1912 if ($norm !== $server) {
1913 $add($norm);
1914 }
1915
1916 if (strpos($norm, '/') !== false) {
1917 $add(basename($norm));
1918 }
1919
1920 if (strpos($server, '|') !== false) {
1921 $parts = explode('|', $server, 2);
1922 $modul = trim((string)($parts[0] ?? ''));
1923 $name = trim((string)($parts[1] ?? ''));
1924
1925 if ($name !== '') {
1926 $add($name);
1927 }
1928
1929 if ($modul !== '' && $name !== '') {
1930 $add($modul . '|' . $name);
1931 $add($modul . '/db/' . $name);
1932 }
1933 }
1934
1935 $moduleFile = $this->resolve_module_db_file($server);
1936 if ($moduleFile) {
1937 $file = str_replace('\\', '/', $moduleFile);
1938 $add($file);
1939 $add($this->path_rel($file));
1940 $add(basename($file));
1941
1942 if (preg_match('#/dbx/modules/([^/]+)/db/([^/]+)$#', $file, $match)) {
1943 $add($match[2]);
1944 $add($match[1] . '|' . $match[2]);
1945 $add($match[1] . '/db/' . $match[2]);
1946 }
1947 }
1948
1949 return array_keys($aliases);
1950 }
1951
1958
1959
1960 private function get_dd_index_by_db($ddRecords) {
1961 $index = array();
1962
1963 foreach ($ddRecords as $record) {
1964 $server = $record['server'] ?? '';
1965 $table = $record['table'] ?? '';
1966
1967 if (!$server || !$table) {
1968 continue;
1969 }
1970
1971 $rid = (string)($record['rid'] ?? '');
1972 if ($rid === '') {
1973 $rid = (string)($record['modul'] ?? '') . '|' . (string)($record['dd'] ?? '');
1974 }
1975
1976 foreach ($this->db_server_aliases($server) as $alias) {
1977 $key = $this->norm_key($alias, $table);
1978 if (!isset($index[$key])) {
1979 $index[$key] = array();
1980 }
1981
1982 $index[$key][$rid] = $record;
1983 }
1984 }
1985
1986 foreach ($index as $key => $records) {
1987 $index[$key] = array_values($records);
1988 }
1989
1990 return $index;
1991 }
1992
2001 private function get_dd_records_for_db($ddIndex, $server, $table) {
2002 $records = array();
2003
2004 if (!$server || !$table) {
2005 return array();
2006 }
2007
2008 foreach ($this->db_server_aliases($server) as $alias) {
2009 $key = $this->norm_key($alias, $table);
2010 if (!isset($ddIndex[$key])) {
2011 continue;
2012 }
2013
2014 foreach ($ddIndex[$key] as $record) {
2015 $rid = (string)($record['rid'] ?? '');
2016 if ($rid === '') {
2017 $rid = (string)($record['modul'] ?? '') . '|' . (string)($record['dd'] ?? '');
2018 }
2019
2020 $records[$rid] = $record;
2021 }
2022 }
2023
2024 return array_values($records);
2025 }
2026
2037
2038
2039 private function dd_actions($modul, $dd, $server, $table, $plan) {
2040 $actions = array(
2041 'sync' => '',
2042 'map' => '',
2043 'transfer' => '',
2044 'fields' => '',
2045 );
2046 $mode = (!empty($plan['rebuild_needed'])) ? 'force' : 'apply';
2047
2048 $syncUrl = $this->build_url('dd', 'sync_dd_to_db', array(
2049 'modul' => $modul,
2050 'dd' => $dd,
2051 'mode' => $mode,
2052 'reset' => 1,
2053 ));
2054 $actions['sync'] = $this->openwin($syncUrl, 'bi bi-arrow-repeat', 'DD nach DB synchronisieren', 1000, 700);
2055
2056 $mapUrl = $this->build_url('dd', 'mapping', array(
2057 'kind' => 'dd_to_db',
2058 'modul' => $modul,
2059 'dd' => $dd,
2060 ));
2061 $actions['map'] = $this->openwin($mapUrl, 'bi bi-diagram-3', 'Schema-Mapping bearbeiten', 1280, 880);
2062
2063 if ($server && $table) {
2064 $transferUrl = $this->build_url('dd', 'transfer', array(
2065 'source_server' => $server,
2066 'source_table' => $table,
2067 ));
2068 $actions['transfer'] = $this->openwin($transferUrl, 'bi bi-box-arrow-right', 'DB-Tabelle transferieren', 980, 720);
2069 }
2070
2071 $fieldsUrl = $this->build_url('dd', 'fields', array(
2072 'modul' => $modul,
2073 'dd' => $dd,
2074 ));
2075 $actions['fields'] = $this->openwin($fieldsUrl, 'bi bi-list-columns', 'DD-Felder bearbeiten', 1400, 860);
2076
2077 return $actions;
2078 }
2079
2088 private function db_actions($server, $table, $dds) {
2089 $actions = array(
2090 'sync' => '',
2091 'sync_dd_to_db' => '',
2092 'map' => '',
2093 'transfer' => '',
2094 );
2095
2096 if (is_array($dds) && count($dds)) {
2097 $first = $dds[0];
2098 $plan = $this->get_sync_plan($first['modul'], $first['dd']);
2099 $ddToDbMode = (!empty($plan['rebuild_needed'])) ? 'force' : 'apply';
2100
2101 $syncUrl = $this->build_url('db', 'sync_db_to_dd', array(
2102 'server' => $server,
2103 'table' => $table,
2104 'modul' => $first['modul'],
2105 'dd' => $first['dd'],
2106 'mode' => 'merge',
2107 'reset' => 1,
2108 ));
2109 $actions['sync'] = $this->openwin($syncUrl, 'bi bi-arrow-down-up', 'DB nach DD synchronisieren', 1000, 700);
2110
2111 $ddToDbUrl = $this->build_url('db', 'sync_dd_to_db', array(
2112 'modul' => $first['modul'],
2113 'dd' => $first['dd'],
2114 'mode' => $ddToDbMode,
2115 'reset' => 1,
2116 ));
2117 $actions['sync_dd_to_db'] = $this->openwin($ddToDbUrl, 'bi bi-arrow-repeat', 'DB anhand DD synchronisieren', 1000, 700);
2118
2119 $mapUrl = $this->build_url('db', 'mapping', array(
2120 'kind' => 'db_to_dd',
2121 'server' => $server,
2122 'table' => $table,
2123 'modul' => $first['modul'],
2124 'dd' => $first['dd'],
2125 ));
2126 $actions['map'] = $this->openwin($mapUrl, 'bi bi-diagram-3', 'Schema-Mapping bearbeiten', 1280, 880);
2127 } else {
2128 $syncUrl = $this->build_url('db', 'sync_db_to_dd', array(
2129 'server' => $server,
2130 'table' => $table,
2131 ));
2132 $actions['sync'] = $this->openwin($syncUrl, 'bi bi-file-earmark-plus', 'DD aus DB-Tabelle erstellen', 980, 720);
2133 }
2134
2135 $transferUrl = $this->build_url('db', 'transfer', array(
2136 'source_server' => $server,
2137 'source_table' => $table,
2138 ));
2139 $actions['transfer'] = $this->openwin($transferUrl, 'bi bi-box-arrow-right', 'DB-Tabelle transferieren', 980, 720);
2140
2141 return $actions;
2142 }
2143
2150 private function get_db_records($ddRecords) {
2151 $records = array();
2152 $oDB = dbx()->get_system_obj('dbxDB');
2153 $oDD = dbx()->get_system_obj('dbxDD');
2154 $ddIndex = $this->get_dd_index_by_db($ddRecords);
2155 $servers = $this->get_server_options();
2156
2157 foreach ($servers as $server => $label) {
2158 $tables = $oDB->get_db_tables($server);
2159 if (!is_array($tables)) {
2160 $tables = array();
2161 }
2162
2163 $hasTables = false;
2164
2165 foreach ($tables as $tableRec) {
2166 $table = $tableRec['name'] ?? '';
2167 if (!$table) {
2168 continue;
2169 }
2170
2171 $hasTables = true;
2172
2173 $dds = $this->get_dd_records_for_db($ddIndex, $server, $table);
2174 $ddLabels = array();
2175 $sync = $this->badge('kein DD', 'secondary');
2176 $syncInfo = '';
2177
2178 foreach ($dds as $ddRec) {
2179 $ddLabels[] = $this->esc($ddRec['modul'] . '|' . $ddRec['dd']);
2180 }
2181
2182 if ($dds) {
2183 $sync = $dds[0]['sync'];
2184 $syncInfo = $dds[0]['sync_info'] ?? '';
2185 }
2186
2187 $dbFields = $oDD->get_db_fields($server, $table);
2188 $ddFields = array();
2189
2190 if ($dds) {
2191 $first = $dds[0];
2192 $model = $oDD->get_dd_model($this->dd_ref($first['modul'], $first['dd']));
2193 $ddFields = array_values((array)($model['fields'] ?? array()));
2194 }
2195
2196 $actions = $this->db_actions($server, $table, $dds);
2197
2198 $records[] = array(
2199 'rid' => $this->encode_db_rid($server, $table),
2200 'modul' => $this->get_server_module($server),
2201 'server' => $server,
2202 'database' => $this->get_database_label($server),
2203 'path' => $this->get_database_path_label($server),
2204 'table' => $table,
2205 'db_fields' => $this->field_tooltip_count($dbFields, 'DB-Felder'),
2206 'dd_fields' => $this->field_tooltip_count($ddFields, 'DD-Felder'),
2207 'count' => $tableRec['count'] ?? '-',
2208 'dd' => implode('<br>', $ddLabels),
2209 'sync' => $sync,
2210 'sync_info' => $syncInfo,
2211 'act_sync' => $actions['sync'] ?? '',
2212 'act_dd_sync' => $actions['sync_dd_to_db'] ?? '',
2213 'act_map' => $actions['map'] ?? '',
2214 'act_move' => $actions['transfer'] ?? '',
2215 'dd_edit_modul' => $dds[0]['modul'] ?? '',
2216 'dd_edit_dd' => $dds[0]['dd'] ?? '',
2217 );
2218 }
2219
2220 if (!$hasTables) {
2221 $records[] = array(
2222 'rid' => $this->encode_db_rid($server, ''),
2223 'modul' => $this->get_server_module($server),
2224 'server' => $server,
2225 'database' => $this->get_database_label($server),
2226 'path' => $this->get_database_path_label($server),
2227 'table' => $this->badge('keine Tabellen', 'secondary'),
2228 'db_fields' => $this->field_tooltip_count(array(), 'DB-Felder'),
2229 'dd_fields' => $this->field_tooltip_count(array(), 'DD-Felder'),
2230 'count' => '-',
2231 'dd' => '',
2232 'sync' => $this->badge('keine Tabellen', 'secondary'),
2233 'sync_info' => '',
2234 'act_sync' => '',
2235 'act_dd_sync' => '',
2236 'act_map' => '',
2237 'act_move' => '',
2238 'dd_edit_modul' => '',
2239 'dd_edit_dd' => '',
2240 );
2241 }
2242 }
2243
2244 usort($records, function($a, $b) {
2245 return strcmp(strip_tags($a['modul'] . '|' . $a['server'] . '|' . $a['table']), strip_tags($b['modul'] . '|' . $b['server'] . '|' . $b['table']));
2246 });
2247
2248 return $records;
2249 }
2250
2258 private function backup_table($server, $table) {
2259 $oDB = dbx()->get_system_obj('dbxDB');
2260 $oDD = dbx()->get_system_obj('dbxDD');
2261
2262 if (!$server || !$table || !$oDD->get_table_exist($server, $table)) {
2263 return array('ok' => 0, 'msg' => 'Tabelle nicht gefunden.');
2264 }
2265
2266 $fields = $oDD->get_db_fields($server, $table);
2267 if (!$fields) {
2268 return array('ok' => 0, 'msg' => 'Felder konnten nicht gelesen werden.');
2269 }
2270
2271 $indexes = $oDD->get_db_indexes($server, $table);
2272 $rows = $oDB->rawQuery($server, 'SELECT * FROM ' . $this->quote_db_ident($server, $table));
2273 if (!is_array($rows)) {
2274 return array('ok' => 0, 'msg' => 'Daten konnten nicht gelesen werden.');
2275 }
2276
2277 $created = date('Ymd-His');
2278 $file = $created . '__' . $this->backup_name_part($server) . '__' . $this->backup_name_part($table) . '.json';
2279 $path = $this->backup_dir() . $file;
2280 $tmp = $path . '.tmp';
2281
2282 $payload = array(
2283 'version' => 1,
2284 'created_at' => date('Y-m-d H:i:s'),
2285 'server' => $server,
2286 'table' => $table,
2287 'db_type' => $oDB->get_db_type($server),
2288 'count' => count($rows),
2289 'fields' => $fields,
2290 'indexes' => $indexes,
2291 'rows' => $rows,
2292 );
2293
2294 $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
2295 if ($json === false || file_put_contents($tmp, $json) === false || !@rename($tmp, $path)) {
2296 @unlink($tmp);
2297 return array('ok' => 0, 'msg' => 'Backup-Datei konnte nicht geschrieben werden.');
2298 }
2299
2300 return array('ok' => 1, 'file' => $file, 'path' => $path, 'count' => count($rows));
2301 }
2302
2309 private function backup_meta($file) {
2310 $path = $this->backup_file_path($file);
2311 if (!$path) {
2312 return array();
2313 }
2314
2315 $raw = file_get_contents($path);
2316 $data = $raw !== false ? json_decode($raw, true) : null;
2317 if (!is_array($data)) {
2318 return array();
2319 }
2320
2321 $data['_file'] = basename($path);
2322 $data['_path'] = $path;
2323 return $data;
2324 }
2325
2333 private function table_backups($server, $table) {
2334 $items = array();
2335 foreach (glob($this->backup_dir() . '*.json') as $file) {
2336 $meta = $this->backup_meta(basename($file));
2337 if (($meta['server'] ?? '') === $server && ($meta['table'] ?? '') === $table) {
2338 $items[] = $meta;
2339 }
2340 }
2341
2342 usort($items, function($a, $b) {
2343 return strcmp((string)($b['created_at'] ?? ''), (string)($a['created_at'] ?? ''));
2344 });
2345
2346 return $items;
2347 }
2348
2356 private function latest_backup($server, $table) {
2357 $items = $this->table_backups($server, $table);
2358 return $items[0] ?? array();
2359 }
2360
2368 private function backup_label($server, $table) {
2369 $meta = $this->latest_backup($server, $table);
2370 if (!$meta) {
2371 return $this->badge('kein Backup', 'secondary');
2372 }
2373
2374 $created = (string)($meta['created_at'] ?? '');
2375 $count = (int)($meta['count'] ?? 0);
2376 return $this->esc($created . ' / ' . $count . ' DS');
2377 }
2378
2385 private function restore_table_from_backup($file) {
2386 $meta = $this->backup_meta($file);
2387 if (!$meta) {
2388 return array('ok' => 0, 'msg' => 'Backup-Datei ist ungueltig.');
2389 }
2390
2391 $server = (string)($meta['server'] ?? '');
2392 $table = (string)($meta['table'] ?? '');
2393 $fields = is_array($meta['fields'] ?? null) ? $meta['fields'] : array();
2394 $indexes = is_array($meta['indexes'] ?? null) ? $meta['indexes'] : array();
2395 $rows = is_array($meta['rows'] ?? null) ? $meta['rows'] : array();
2396
2397 if (!$server || !$table || !$fields) {
2398 return array('ok' => 0, 'msg' => 'Backup-Metadaten sind unvollstaendig.');
2399 }
2400
2401 $oDB = dbx()->get_system_obj('dbxDB');
2402 $oDD = dbx()->get_system_obj('dbxDD');
2403
2404 $oDD->drop_db_tab($server, $table);
2405 if (!$oDD->create_db_tab_from_fields($server, $table, $fields, $indexes)) {
2406 return array('ok' => 0, 'msg' => 'Tabelle konnte nicht neu angelegt werden.');
2407 }
2408
2409 if (!$rows) {
2410 return array('ok' => 1, 'count' => 0, 'file' => basename((string)$file));
2411 }
2412
2413 $names = array();
2414 foreach ($fields as $field) {
2415 $name = (string)($field['name'] ?? '');
2416 if ($name !== '') {
2417 $names[] = $name;
2418 }
2419 }
2420
2421 if (!$names) {
2422 return array('ok' => 0, 'msg' => 'Keine Ziel-Felder gefunden.');
2423 }
2424
2425 $qNames = array_map(function($name) use ($server) {
2426 return $this->quote_db_ident($server, $name);
2427 }, $names);
2428
2429 foreach ($rows as $row) {
2430 $values = array();
2431 foreach ($names as $name) {
2432 $values[] = $this->sql_db_value($server, is_array($row) && array_key_exists($name, $row) ? $row[$name] : null);
2433 }
2434
2435 $sql = 'INSERT INTO ' . $this->quote_db_ident($server, $table)
2436 . ' (' . implode(',', $qNames) . ') VALUES (' . implode(',', $values) . ')';
2437 $ok = $oDB->rawQuery($server, $sql);
2438 if (!is_array($ok) && (int)$ok === 0 && $oDB->get_error_status() !== '') {
2439 return array('ok' => 0, 'msg' => 'Daten konnten nicht eingetragen werden.');
2440 }
2441 }
2442
2443 return array('ok' => 1, 'count' => count($rows), 'file' => basename((string)$file));
2444 }
2445
2453 private function filter_rows($rows, $search) {
2454 $search = trim((string) $search);
2455 if ($search === '') {
2456 return $rows;
2457 }
2458
2459 $filtered = array();
2460 foreach ($rows as $row) {
2461 $haystack = strtolower(strip_tags(implode(' ', $row)));
2462 if (strpos($haystack, strtolower($search)) !== false) {
2463 $filtered[] = $row;
2464 }
2465 }
2466
2467 return $filtered;
2468 }
2469
2475 private function has_config_db_servers() {
2476 $config = dbx()->get_config('dbx', 'db');
2477 if (!is_array($config) || count($config) <= 0) {
2478 return false;
2479 }
2480
2481 $moduleFiles = $this->get_module_db_files();
2482 $moduleFileIndex = array();
2483
2484 foreach ($moduleFiles as $server => $db) {
2485 $real = realpath((string)($db['file'] ?? ''));
2486 if ($real) {
2487 $moduleFileIndex[strtolower(str_replace('\\', '/', $real))] = 1;
2488 }
2489 }
2490
2491 foreach ($config as $server => $data) {
2492 if (isset($moduleFiles[$server])) {
2493 continue;
2494 }
2495
2496 $type = strtolower((string)($data['type'] ?? ''));
2497 $host = (string)($data['host'] ?? '');
2498 $name = (string)($data['dbname'] ?? ($data['name'] ?? ''));
2499 $isSqlite = ($type == 'sqlite' || $type == 'sqlite3' || preg_match('/\.(db3|sqlite|sqlite3)$/i', $name));
2500
2501 if ($isSqlite && ($host !== '' || $name !== '')) {
2502 $file = dbx_os_path_file($host . $name);
2503 $real = realpath($file);
2504
2505 if ($real) {
2506 $real = strtolower(str_replace('\\', '/', $real));
2507 if (isset($moduleFileIndex[$real])) {
2508 continue;
2509 }
2510 }
2511 }
2512
2513 return true;
2514 }
2515
2516 return false;
2517 }
2518
2526 private function get_module_filter_options($rows, $includeSys = false) {
2527 $options = array('0' => '[Alle Module]');
2528
2529 if ($includeSys) {
2530 $options['sys'] = 'sys';
2531 }
2532
2533 foreach ($rows as $row) {
2534 $modul = trim((string)($row['modul'] ?? ''));
2535 if ($modul !== '') {
2536 $options[$modul] = $modul;
2537 }
2538 }
2539
2540 ksort($options);
2541 return array('0' => '[Alle Module]') + $options;
2542 }
2543
2551 private function filter_rows_by_module($rows, $modul) {
2552 $modul = trim((string)$modul);
2553 if ($modul === '' || $modul === '0') {
2554 return $rows;
2555 }
2556
2557 $filtered = array();
2558 foreach ($rows as $row) {
2559 if (strcasecmp((string)($row['modul'] ?? ''), $modul) === 0) {
2560 $filtered[] = $row;
2561 }
2562 }
2563
2564 return $filtered;
2565 }
2566
2578 private function run_schema_report($mode, $rows, $flds, $action, $batchOptions, $message = '') {
2579 $oReport = dbx()->get_system_obj('dbxReport');
2580 $fidMap = array(
2581 'dd' => 'report-dd-sync',
2582 'db' => 'report-db-sync',
2583 'backup' => 'report-db-backup',
2584 'restore' => 'report-db-restore',
2585 );
2586 $fid = $fidMap[$mode] ?? 'report-db-sync';
2587 $moduleOptions = $this->get_module_filter_options($rows, $mode != 'dd' && $this->has_config_db_servers());
2588 $isSchemaList = in_array($mode, array('dd', 'db'), true);
2589
2590 $oReport->init($fid, 'report-schema');
2591 $oReport->set_callback_owner($this);
2592 $oReport->set_callback('row_action_data', 'schema_row_action_data');
2593 $oReport->_action = $action;
2594 $oReport->_rflds = $flds;
2595 $oReport->_mode = 'table';
2596 $oReport->_pages = true;
2597 $oReport->_rrows = 25;
2598 $oReport->_rpos = 0;
2599 $oReport->_but_pagination = 7;
2600 $oReport->_create_row_select = true;
2601 $oReport->_create_row_edit = $isSchemaList;
2602 $oReport->_create_row_show = $isSchemaList;
2603 $oReport->_create_row_delete = $isSchemaList;
2604 $oReport->_create_sel_flds = false;
2605 $oReport->_fld_id = 'rid';
2606 $oReport->_table_buttons = 'left';
2607 $oReport->_msg_confirm_delete = ($mode == 'dd')
2608 ? 'DD-Datei wirklich loeschen? Die DB-Tabelle bleibt bestehen.'
2609 : 'DB-Tabelle wirklich loeschen? Zugehoerige DD-Dateien bleiben bestehen.';
2610 $oReport->_msg_info = (string)$message;
2611 $oReport->_tabel_tpls['tpl_row_edit'] = 'modul|schema_row_edit';
2612 $oReport->_tabel_tpls['tpl_row_show'] = 'modul|schema_row_show';
2613 $oReport->_tabel_tpls['tpl_row_delete'] = 'modul|schema_row_delete';
2614
2615 $oReport->add_action('rows_select', 'action_button_select', '&dbx_do=multi_select');
2616 $oReport->add_action('rows_deselect', 'action_button_deselect', '&dbx_do=multi_deselect');
2617
2618 $oReport->add_fld('dbx_rmodul', 'select-single-label', label: 'Modul', rules: 'parameter', options: $moduleOptions);
2619 $oReport->add_fld('dbx_rwhere', 'dbx|search', label: 'Suche', rules: 'sqlsearch|max=80');
2620 $oReport->add_fld('dbx_rrows', 'integer-label', label: 'Zeilen', rules: 'int');
2621 $oReport->add_fld('maction_select', 'select-single', label: 'Batch', rules: 'parameter', options: $batchOptions);
2622 $oReport->add_obj('maction_submit', 'dbx|button-submit', data: 'label=Start');
2623
2624 if ($oReport->submit()) {
2625 $act = $oReport->get_fld_val('maction_select', '0', 'parameter');
2626 if ($act && $act != '0') {
2627 $selects = array_keys($oReport->get_multi_selects());
2628 if ($selects) {
2629 return $this->start_batch($act, $selects);
2630 }
2631
2632 $oReport->_msg_warning = 'Keine Zeilen ausgewaehlt';
2633 }
2634 }
2635
2636 $modul = $oReport->get_fld_val('dbx_rmodul', '0', 'parameter');
2637 $search = $oReport->get_fld_val('dbx_rwhere', '', 'sqlsearch|max=80');
2638 $rrows = $oReport->get_fld_val('dbx_rrows', 25, 'int');
2639 $rpos = $oReport->get_fld_val('dbx_rpos', 0, 'int');
2640 if ($rrows <= 0) {
2641 $rrows = 25;
2642 }
2643
2644 $rows = $this->filter_rows_by_module($rows, $modul);
2645 $rows = $this->filter_rows($rows, $search);
2646 $oReport->_rrows = $rrows;
2647 $oReport->_rpos = $rpos;
2648 $oReport->_rcount = count($rows);
2649 $oReport->_rdata = $oReport->data_rows($rows, $rpos, $rrows);
2650
2651 return $oReport->run();
2652 }
2653
2661 public function schema_row_action_data($report, $data) {
2662 if (!is_array($data) || !isset($data['data']) || !is_array($data['data'])) {
2663 return $data;
2664 }
2665
2666 $type = (string)($data['type'] ?? '');
2667 if (!in_array($type, array('edit', 'show', 'delete'), true)) {
2668 return $data;
2669 }
2670
2671 $record = is_array($data['record'] ?? null) ? $data['record'] : array();
2672 $rid = (string)($data['data']['rid'] ?? ($record['rid'] ?? ''));
2673 $mode = str_starts_with($rid, 'db_') ? 'db' : 'dd';
2674 $base = $this->build_url($mode, $mode == 'dd' ? 'list_dd' : 'list_db');
2675
2676 if ($type == 'edit') {
2677 $modul = '';
2678 $dd = '';
2679
2680 if ($mode == 'dd') {
2681 [$modul, $dd] = $this->split_dd_rid($rid);
2682 } else {
2683 $modul = (string)($record['dd_edit_modul'] ?? '');
2684 $dd = (string)($record['dd_edit_dd'] ?? '');
2685 }
2686
2687 if ($modul && $dd) {
2688 $editUrl = '?dbx_modul=dbxAdmin&dbx_run1=edit_dd&modul=' . rawurlencode($modul) . '&dd=' . rawurlencode($dd);
2689 $editTitle = 'DD bearbeiten: ' . $modul . '/' . $dd;
2690 $data['data']['edit_url'] = $this->esc($editUrl);
2691 $data['data']['edit_title'] = $this->esc($editTitle);
2692 $data['data']['edit_data_dBx'] = 'data-url="' . $this->esc($editUrl) . '" data-title="' . $this->esc($editTitle) . '"';
2693 } else {
2694 $data['data']['edit_url'] = '#';
2695 $data['data']['edit_title'] = $this->esc('Kein DD zugeordnet');
2696 $data['data']['edit_data_dBx'] = '';
2697 $data['data']['class'] = trim((string)($data['data']['class'] ?? '') . ' disabled text-muted');
2698 }
2699
2700 return $data;
2701 }
2702
2703 $showUrl = $this->append_url_params($base, array(
2704 'dbx_do' => 'row_show',
2705 'rid' => $rid,
2706 ));
2707 $deleteUrl = $this->append_url_params($base, array(
2708 'dbx_do' => 'row_delete',
2709 'rid' => $rid,
2710 ));
2711
2712 $data['data']['show_url'] = $this->esc($showUrl);
2713 $data['data']['delete_url'] = $this->esc($deleteUrl);
2714
2715 return $data;
2716 }
2717
2724 private function handle_dd_row_action(&$message) {
2725 $do = dbx()->get_modul_var('dbx_do', '', 'parameter');
2726 if (!in_array($do, array('row_show', 'row_delete'), true)) {
2727 return '';
2728 }
2729
2730 $rid = dbx()->get_modul_var('rid', '', 'parameter');
2731 [$modul, $dd] = $this->split_dd_rid($rid);
2732 if (!$modul || !$dd) {
2733 return '<div class="alert alert-danger">DD-Auswahl ist ungueltig.</div>';
2734 }
2735
2736 $ddRef = $this->dd_ref($modul, $dd);
2737
2738 if ($do == 'row_show') {
2739 $params = array('rid' => $rid);
2740 return $this->run_data_report(
2741 $ddRef,
2742 'DD Daten: ' . $modul . '|' . $dd,
2743 $this->build_url('dd', 'data_read', $params),
2744 $this->build_url('dd', 'data_save', $params),
2745 $this->build_url('dd', 'data_insert', $params),
2746 $this->build_url('dd', 'data_delete', $params)
2747 );
2748 }
2749
2750 if ($this->delete_dd_file($modul, $dd)) {
2751 $message = 'DD-Datei geloescht: ' . $modul . '|' . $dd;
2752 } else {
2753 $message = 'DD-Datei konnte nicht geloescht werden: ' . $modul . '|' . $dd;
2754 }
2755
2756 return '';
2757 }
2758
2765 private function handle_db_row_action(&$message) {
2766 $do = dbx()->get_modul_var('dbx_do', '', 'parameter');
2767 if (!in_array($do, array('row_show', 'row_delete'), true)) {
2768 return '';
2769 }
2770
2771 $rid = dbx()->get_modul_var('rid', '', 'parameter');
2772 [$server, $table] = $this->decode_db_rid($rid);
2773 if (!$server || !$table) {
2774 return '<div class="alert alert-danger">DB-Auswahl ist ungueltig.</div>';
2775 }
2776
2777 if ($do == 'row_show') {
2778 $ddRef = $this->ensure_db_view_dd($server, $table);
2779 if (!$ddRef) {
2780 return '<div class="alert alert-danger">Ansichts-DD konnte nicht erzeugt werden.</div>';
2781 }
2782
2783 $params = array('rid' => $rid);
2784 return $this->run_data_report(
2785 $ddRef,
2786 'DB Daten: ' . $server . '|' . $table,
2787 $this->build_url('db', 'data_read', $params),
2788 $this->build_url('db', 'data_save', $params),
2789 $this->build_url('db', 'data_insert', $params),
2790 $this->build_url('db', 'data_delete', $params)
2791 );
2792 }
2793
2794 $oDD = dbx()->get_system_obj('dbxDD');
2795 if ($oDD->drop_db_tab($server, $table)) {
2796 $message = 'DB-Tabelle geloescht: ' . $server . '|' . $table;
2797 } else {
2798 $message = 'DB-Tabelle konnte nicht geloescht werden: ' . $server . '|' . $table;
2799 }
2800
2801 return '';
2802 }
2803
2809 private function report_dd() {
2810 return $this->report_db();
2811 }
2812
2818 private function report_db() {
2819 $message = '';
2820 $content = $this->handle_db_row_action($message);
2821 if ($content !== '') {
2822 return $content;
2823 }
2824
2825 $flds = array(
2826 'modul' => 'Modul',
2827 'server' => 'Server',
2828 'table' => 'Tabelle',
2829 'db_fields' => 'DB Felder',
2830 'dd_fields' => 'DD Felder',
2831 'count' => 'Datensaetze',
2832 'dd' => 'DD',
2833 'sync' => 'Status',
2834 'sync_info' => 'Details',
2835 'act_sync' => $this->header_icon('bi bi-arrow-down-up', 'DB nach DD synchronisieren'),
2836 'act_dd_sync' => $this->header_icon('bi bi-arrow-repeat', 'DB anhand DD synchronisieren'),
2837 'act_map' => $this->header_icon('bi bi-diagram-3', 'Schema-Mapping bearbeiten'),
2838 'act_move' => $this->header_icon('bi bi-box-arrow-right', 'DB-Tabelle transferieren'),
2839 );
2840
2841 $batch = array(
2842 '0' => '[Batch-Aktion]',
2843 'batch_db_to_dd' => 'DB -> DD synchronisieren',
2844 'batch_dd_to_db' => 'DD -> DB synchronisieren',
2845 'batch_dd_to_db_force' => 'DD -> DB rebuild/force',
2846 );
2847
2848 return $this->run_schema_report('db', $this->get_db_records($this->get_dd_records()), $flds, $this->build_url('db', 'list_db'), $batch, $message);
2849 }
2855 private function backup_restore_rows() {
2856 $rows = $this->get_db_records($this->get_dd_records());
2857
2858 foreach ($rows as $no => $row) {
2859 [$server, $table] = $this->decode_db_rid((string)($row['rid'] ?? ''));
2860
2861 if (!$server) {
2862 $server = (string)($row['server'] ?? '');
2863 }
2864
2865 if (!$table) {
2866 $rows[$no]['backup'] = $this->badge('keine Tabelle', 'secondary');
2867 $rows[$no]['act_backup'] = '';
2868 $rows[$no]['act_restore'] = '';
2869 continue;
2870 }
2871
2872 $rows[$no]['backup'] = $this->backup_label($server, $table);
2873
2874 $backupUrl = $this->build_url('dd', 'backup_db_table', array(
2875 'server' => $server,
2876 'table' => $table,
2877 'reset' => 1,
2878 ));
2879 $rows[$no]['act_backup'] = $this->openwin($backupUrl, 'bi bi-download', 'Backup erstellen', 980, 700);
2880
2881 $restoreUrl = $this->build_url('dd', 'restore_db_table', array(
2882 'server' => $server,
2883 'table' => $table,
2884 ));
2885 $rows[$no]['act_restore'] = $this->openwin($restoreUrl, 'bi bi-upload', 'Backup auswaehlen und wiederherstellen', 980, 760);
2886 }
2887
2888 return $rows;
2889 }
2890
2896 private function report_backup_db() {
2897 $flds = array(
2898 'modul' => 'Modul',
2899 'server' => 'Server',
2900 'database' => 'DB',
2901 'table' => 'Tabelle',
2902 'count' => 'Datensaetze',
2903 'path' => 'Pfad',
2904 'backup' => 'Letztes Backup',
2905 'act_backup' => $this->header_icon('bi bi-download', 'Backup erstellen'),
2906 );
2907
2908 $batch = array(
2909 '0' => '[Batch-Aktion]',
2910 'batch_backup_db' => 'Auswahl sichern',
2911 );
2912
2913 return $this->run_schema_report('backup', $this->backup_restore_rows(), $flds, $this->build_url('dd', 'backup_db'), $batch, '');
2914 }
2915
2921 private function report_restore_db() {
2922 $flds = array(
2923 'modul' => 'Modul',
2924 'server' => 'Server',
2925 'database' => 'DB',
2926 'table' => 'Tabelle',
2927 'count' => 'Datensaetze',
2928 'path' => 'Pfad',
2929 'backup' => 'Letztes Backup',
2930 'act_restore' => $this->header_icon('bi bi-upload', 'Backup auswaehlen'),
2931 );
2932
2933 $batch = array(
2934 '0' => '[Batch-Aktion]',
2935 'batch_restore_latest_db' => 'Letztes Backup der Auswahl restoren',
2936 );
2937
2938 return $this->run_schema_report('restore', $this->backup_restore_rows(), $flds, $this->build_url('dd', 'restore_db'), $batch, '');
2939 }
2940
2948 private function append_url_params($url, $params = array()) {
2949 if (!$url) {
2950 return '';
2951 }
2952
2953 foreach ($params as $key => $value) {
2954 $url .= (strpos($url, '?') === false ? '?' : '&')
2955 . rawurlencode((string)$key) . '=' . rawurlencode((string)$value);
2956 }
2957
2958 return $url;
2959 }
2960
2967 private function process_status_label($status) {
2968 $map = array(
2969 'new' => 'Neu',
2970 'reset' => 'Neu',
2971 'running' => 'Laeuft',
2972 'paused' => 'Angehalten',
2973 'canceled' => 'Abgebrochen',
2974 'finished' => 'Fertig',
2975 'error' => 'Fehler',
2976 );
2977
2978 return $map[$status] ?? $status;
2979 }
2980
2987 private function process_status_class($status) {
2988 $map = array(
2989 'running' => 'bg-primary',
2990 'paused' => 'bg-warning text-dark',
2991 'canceled' => 'bg-danger',
2992 'finished' => 'bg-success',
2993 'error' => 'bg-danger',
2994 'new' => 'bg-secondary',
2995 'reset' => 'bg-secondary',
2996 );
2997
2998 return $map[$status] ?? 'bg-secondary';
2999 }
3000
3007 private function process_status_icon($status) {
3008 $map = array(
3009 'running' => 'bi bi-play-fill',
3010 'paused' => 'bi bi-pause-fill',
3011 'canceled' => 'bi bi-x-lg',
3012 'finished' => 'bi bi-check-lg',
3013 'error' => 'bi bi-exclamation-triangle',
3014 'new' => 'bi bi-circle',
3015 'reset' => 'bi bi-arrow-clockwise',
3016 );
3017
3018 return $map[$status] ?? 'bi bi-circle';
3019 }
3020
3027 private function process_type_label($type) {
3028 $map = array(
3029 'sync_dd_to_db' => 'DD -> DB',
3030 'sync_db_to_dd' => 'DB -> DD',
3031 'transfer_table' => 'Transfer',
3032 'backup' => 'Backup',
3033 'restore' => 'Restore',
3034 'schema_batch' => 'Batch',
3035 );
3036
3037 return $map[$type] ?? ($type ?: 'Prozess');
3038 }
3039
3046 private function process_task_label($phase) {
3047 $phase = (string)$phase;
3048
3049 if (strpos($phase, 'backup') !== false) return 'Aufgabe Backup';
3050 if (strpos($phase, 'restore') !== false) return 'Aufgabe Restore';
3051 if (strpos($phase, 'create') !== false || strpos($phase, 'rename') !== false || strpos($phase, 'drop') !== false) return 'Aufgabe Struktur';
3052 if (strpos($phase, 'add_') === 0) return 'Aufgabe Schema';
3053 if (strpos($phase, 'read') !== false || strpos($phase, 'merge') !== false || strpos($phase, 'write') !== false) return 'Aufgabe DD';
3054 if (strpos($phase, 'prepare') !== false) return 'Aufgabe Vorbereitung';
3055 if (strpos($phase, 'batch') !== false) return 'Aufgabe Batch';
3056
3057 return 'Aufgabe';
3058 }
3059
3066 private function process_step_label($phase) {
3067 $map = array(
3068 'prepare' => 'Vorbereiten',
3069 'prepare_target' => 'Ziel vorbereiten',
3070 'create_table' => 'Tabelle anlegen',
3071 'add_fields' => 'Felder ergaenzen',
3072 'add_indexes' => 'Indizes ergaenzen',
3073 'backup_old' => 'Backup erstellen',
3074 'backup_source' => 'Quelle sichern',
3075 'rename_old' => 'Alte Tabelle umbenennen',
3076 'create_new' => 'Neue Tabelle anlegen',
3077 'restore_new' => 'Daten einlesen',
3078 'restore_target' => 'Ziel einlesen',
3079 'drop_old' => 'Alte Tabelle entfernen',
3080 'read_schema' => 'Schema lesen',
3081 'merge_meta' => 'DD zusammenfuehren',
3082 'write_dd' => 'DD schreiben',
3083 'batch_step' => 'Eintrag bearbeiten',
3084 );
3085
3086 return $map[$phase] ?? ($phase ?: 'Schritt');
3087 }
3088
3095 private function process_message_label($message) {
3096 $message = (string)$message;
3097 $map = array(
3098 'process paused' => 'Prozess angehalten',
3099 'process resumed' => 'Prozess fortgesetzt',
3100 'process continued' => 'Prozess fortgesetzt',
3101 'process canceled' => 'Prozess abgebrochen',
3102 'process restarted' => 'Prozess neu gestartet',
3103 'sync initialized' => 'Synchronisierung vorbereitet',
3104 'transfer initialized' => 'Transfer vorbereitet',
3105 'schema loaded' => 'Schema gelesen',
3106 'meta merged' => 'DD-Informationen zusammengefuehrt',
3107 'sync dd -> db finished' => 'DD -> DB abgeschlossen',
3108 'sync dd -> db rebuild finished' => 'DD -> DB Rebuild abgeschlossen',
3109 'sync db -> dd finished' => 'DB -> DD abgeschlossen',
3110 'transfer finished' => 'Transfer abgeschlossen',
3111 'batch initialized' => 'Batch vorbereitet',
3112 'batch finished' => 'Batch abgeschlossen',
3113 'backup finished' => 'Backup abgeschlossen',
3114 'restore finished' => 'Restore abgeschlossen',
3115 );
3116
3117 return $map[$message] ?? $message;
3118 }
3119
3129 private function render_process($title, $state, $nextUrl = '', $backUrl = '') {
3130 $oTPL = dbx()->get_system_obj('dbxTPL');
3131 $status = $state['status'] ?? 'running';
3132 $percent = (int)($state['percent'] ?? 0);
3133 if ($percent < 0) $percent = 0;
3134 if ($percent > 100) $percent = 100;
3135
3136 $stepPercent = (int)($state['step_percent'] ?? $percent);
3137 if ($stepPercent < 0) $stepPercent = 0;
3138 if ($stepPercent > 100) $stepPercent = 100;
3139
3140 $barClass = 'bg-primary';
3141 if ($status == 'finished') $barClass = 'bg-success';
3142 if ($status == 'error' || $status == 'canceled') $barClass = 'bg-danger';
3143 if ($status == 'paused') $barClass = 'bg-warning';
3144
3145 $restartUrl = $nextUrl ? $this->append_url_params($nextUrl, array('reset' => 1, 'proc_cmd' => 'restart')) : '';
3146 $targetId = 'dbx_process_' . substr(md5((string)($state['proc_key'] ?? $title)), 0, 14);
3147 $phase = (string)($state['phase'] ?? '');
3148 $procType = (string)($state['proc_type'] ?? ($state['act'] ?? 'schema_batch'));
3149 $hasControls = $nextUrl ? 1 : 0;
3150 $autostart = ($nextUrl && $status == 'running') ? 1 : 0;
3151
3152 $data = array(
3153 'target_id' => $this->esc($targetId),
3154 'title' => $this->esc($title),
3155 'status_key' => $this->esc($status),
3156 'status_label' => $this->esc($this->process_status_label($status)),
3157 'status_class' => $this->esc($this->process_status_class($status)),
3158 'status_icon' => $this->esc($this->process_status_icon($status)),
3159 'message' => $this->esc($this->process_message_label($state['message'] ?? '')),
3160 'percent' => $percent,
3161 'step_percent' => $stepPercent,
3162 'bar_class' => $this->esc($barClass),
3163 'task_label' => $this->esc($this->process_task_label($phase)),
3164 'step_label' => $this->esc($this->process_step_label($phase)),
3165 'process_label' => $this->esc($this->process_type_label($procType)),
3166 'updated_at' => $this->esc($state['updated_at'] ?? ''),
3167 'next_url' => $this->esc($nextUrl),
3168 'pause_url' => $this->esc($this->append_url_params($nextUrl, array('proc_cmd' => 'pause'))),
3169 'resume_url' => $this->esc($this->append_url_params($nextUrl, array('proc_cmd' => 'resume'))),
3170 'continue_url' => $this->esc($this->append_url_params($nextUrl, array('proc_cmd' => 'continue'))),
3171 'cancel_url' => $this->esc($this->append_url_params($nextUrl, array('proc_cmd' => 'cancel'))),
3172 'restart_url' => $this->esc($restartUrl),
3173 'autostart' => $autostart,
3174 'interval' => 800,
3175 'pause_visible' => $hasControls ? 'running' : '_none',
3176 'resume_visible' => $hasControls ? 'paused' : '_none',
3177 'continue_visible' => $hasControls ? 'canceled' : '_none',
3178 'restart_visible' => $restartUrl ? 'paused,canceled,error,finished' : '_none',
3179 'cancel_visible' => $hasControls ? 'running,paused' : '_none',
3180 'back_url' => $this->esc($backUrl),
3181 );
3182
3183 return $oTPL->get_tpl('dbxAdmin|schema-process', $data);
3184 }
3185
3191 private function run_sync_dd_to_db() {
3192 $modul = dbx()->get_modul_var('modul', 'dbx', 'parameter');
3193 $dd = dbx()->get_modul_var('dd', '', 'parameter');
3194 $mode = dbx()->get_modul_var('mode', 'apply', 'parameter');
3195 $reset = dbx()->get_modul_var('reset', 0, 'int');
3196 $cmd = dbx()->get_modul_var('proc_cmd', '', 'parameter');
3197 $oDD = dbx()->get_system_obj('dbxDD');
3198
3199 if (!$dd) {
3200 return '<div class="alert alert-danger">DD fehlt.</div>';
3201 }
3202
3203 if ($reset) {
3204 $oDD->sync_dd_to_db($modul, $dd, 'reset');
3205 }
3206
3207 $nextUrl = $this->build_url('dd', 'sync_dd_to_db', array(
3208 'modul' => $modul,
3209 'dd' => $dd,
3210 'mode' => $mode,
3211 ));
3212
3213 if ($cmd && in_array($cmd, array('pause', 'resume', 'continue', 'cancel'), true)) {
3214 $state = $oDD->sync_dd_to_db($modul, $dd, $cmd);
3215 return $this->render_process('DD -> DB: ' . $modul . '|' . $dd, $state, $nextUrl, $this->build_url('dd', 'list_dd'));
3216 }
3217
3218 $state = $oDD->sync_dd_to_db($modul, $dd, $mode);
3219
3220 $content = $this->render_process('DD -> DB: ' . $modul . '|' . $dd, $state, $nextUrl, $this->build_url('dd', 'list_dd'));
3221
3222 if (($state['status'] ?? '') == 'error' && strpos((string)($state['message'] ?? ''), 'rebuild needed') !== false) {
3223 $force = $this->build_url('dd', 'sync_dd_to_db', array(
3224 'modul' => $modul,
3225 'dd' => $dd,
3226 'mode' => 'force',
3227 'reset' => 1,
3228 ));
3229 $content .= '<div class="mt-3"><a class="btn btn-danger" href="' . $this->esc($force) . '">Rebuild starten</a></div>';
3230 }
3231
3232 return $content;
3233 }
3234
3240 private function run_sync_db_to_dd() {
3241 $server = dbx()->get_modul_var('server', '', 'parameter');
3242 $table = dbx()->get_modul_var('table', '', 'parameter');
3243 $modul = dbx()->get_modul_var('modul', '', 'parameter');
3244 $dd = dbx()->get_modul_var('dd', '', 'parameter');
3245 $mode = dbx()->get_modul_var('mode', 'merge', 'parameter');
3246 $reset = dbx()->get_modul_var('reset', 0, 'int');
3247 $cmd = dbx()->get_modul_var('proc_cmd', '', 'parameter');
3248
3249 if (!$server || !$table) {
3250 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3251 }
3252
3253 if (!$modul || !$dd) {
3254 return $this->form_db_to_dd($server, $table);
3255 }
3256
3257 $oDD = dbx()->get_system_obj('dbxDD');
3258 if ($reset) {
3259 $oDD->sync_db_to_dd($modul, $dd, 'reset', $server, $table);
3260 }
3261
3262 $nextUrl = $this->build_url('db', 'sync_db_to_dd', array(
3263 'server' => $server,
3264 'table' => $table,
3265 'modul' => $modul,
3266 'dd' => $dd,
3267 'mode' => $mode,
3268 ));
3269
3270 if ($cmd && in_array($cmd, array('pause', 'resume', 'continue', 'cancel'), true)) {
3271 $state = $oDD->sync_db_to_dd($modul, $dd, $cmd, $server, $table);
3272 return $this->render_process('DB -> DD: ' . $server . '|' . $table . ' -> ' . $modul . '|' . $dd, $state, $nextUrl, $this->build_url('db', 'list_db'));
3273 }
3274
3275 $state = $oDD->sync_db_to_dd($modul, $dd, $mode, $server, $table);
3276
3277 return $this->render_process('DB -> DD: ' . $server . '|' . $table . ' -> ' . $modul . '|' . $dd, $state, $nextUrl, $this->build_url('db', 'list_db'));
3278 }
3279
3287 private function form_db_to_dd($server, $table) {
3288 $oForm = dbx()->get_system_obj('dbxForm');
3289 $oForm->init('form-schema-action');
3290 $oForm->_data = array(
3291 'server' => $server,
3292 'table' => $table,
3293 'modul' => 'dbx',
3294 'dd' => $this->sanitize_dd_name($table),
3295 );
3296 $oForm->_action = $this->build_url('db', 'sync_db_to_dd', array(
3297 'server' => $server,
3298 'table' => $table,
3299 ));
3300 $oForm->_msg_info = 'DD aus DB-Tabelle erzeugen oder vorhandenes DD synchronisieren.';
3301
3302 $oForm->add_fld('server', 'hidden', rules: 'parameter');
3303 $oForm->add_fld('table', 'hidden', rules: 'parameter');
3304 $oForm->add_fld('modul', 'select-single-label', label: 'Modul', rules: 'parameter', options: $this->get_module_options());
3305 $oForm->add_fld('dd', 'text-label', label: 'DD', rules: 'parameter|min=1');
3306
3307 if ($oForm->submit() && !$oForm->errors()) {
3308 $modul = $oForm->get_post('modul', 'dbx', 'parameter');
3309 $dd = $this->sanitize_dd_name($oForm->get_post('dd', $table, 'parameter'));
3310
3311 dbx()->set_modul_var('modul', $modul);
3312 dbx()->set_modul_var('dd', $dd);
3313 dbx()->set_modul_var('mode', 'merge');
3314 dbx()->set_modul_var('reset', 1);
3315
3316 return $this->run_sync_db_to_dd();
3317 }
3318
3319 return $oForm->run();
3320 }
3321
3327 private function run_mapping_editor() {
3328 $kind = dbx()->get_modul_var('kind', 'dd_to_db', 'parameter');
3329 $modul = dbx()->get_modul_var('modul', '', 'parameter');
3330 $dd = dbx()->get_modul_var('dd', '', 'parameter');
3331 $server = dbx()->get_modul_var('server', '', 'parameter');
3332 $table = dbx()->get_modul_var('table', '', 'parameter');
3333
3334 if (!$dd && $kind !== 'transfer') {
3335 return '<div class="alert alert-danger">DD fehlt.</div>';
3336 }
3337
3338 $oDD = dbx()->get_system_obj('dbxDD');
3339 $context = array(
3340 'modul' => $modul,
3341 'dd' => $dd,
3342 'server' => $server,
3343 'table' => $table,
3344 );
3345
3346 $model = $oDD->build_schema_mapping($kind, $context);
3347
3348 $oForm = dbx()->get_system_obj('dbxForm');
3349 $oForm->init('schema-mapping');
3350 $oForm->_msg_info = '';
3351 $oForm->_action = $this->build_url(($kind == 'db_to_dd') ? 'db' : 'dd', 'mapping', array(
3352 'kind' => $kind,
3353 'modul' => $modul,
3354 'dd' => $dd,
3355 'server' => $server,
3356 'table' => $table,
3357 ));
3358
3359 $oForm->_data = array(
3360 'kind' => $kind,
3361 'modul' => $modul,
3362 'dd' => $dd,
3363 'server' => $server,
3364 'table' => $table,
3365 );
3366
3367 $oForm->add_fld('kind', 'hidden', rules: 'parameter');
3368 $oForm->add_fld('modul', 'hidden', rules: 'parameter');
3369 $oForm->add_fld('dd', 'hidden', rules: 'parameter');
3370 $oForm->add_fld('server', 'hidden', rules: 'parameter');
3371 $oForm->add_fld('table', 'hidden', rules: 'parameter');
3372
3373 if ($oForm->submit() && !$oForm->errors()) {
3374 $mapping = $this->posted_mapping_from_model($model);
3375 $ok = $oDD->save_schema_mapping($kind, $model['context'] ?? $context, $mapping);
3376
3377 if ($ok) {
3378 $oForm->_msg_success = 'Mapping gespeichert.';
3379 $model = $oDD->build_schema_mapping($kind, $model['context'] ?? $context);
3380 } else {
3381 $oForm->_msg_error = 'Mapping konnte nicht gespeichert werden.';
3382 }
3383 }
3384
3385 $oForm->add_obj('mapping_board', 'obj-value', $this->render_mapping_board($model));
3386 $oForm->add_obj('save_button', 'obj-value',
3387 '<button type="submit" class="btn btn-primary" title="Speichern"><i class="bi bi-save"></i></button>'
3388 );
3389 $oForm->add_obj('back_button', 'obj-value',
3390 '<a class="btn btn-secondary" href="' . $this->esc($this->build_url(($kind == 'db_to_dd') ? 'db' : 'dd', ($kind == 'db_to_dd') ? 'list_db' : 'list_dd')) . '" title="Zurueck"><i class="bi bi-arrow-left"></i></a>'
3391 );
3392
3393 return $oForm->run();
3394 }
3395
3401 private function run_transfer() {
3402 $sourceServer = dbx()->get_modul_var('source_server', '', 'parameter');
3403 $sourceTable = dbx()->get_modul_var('source_table', '', 'parameter');
3404 $start = dbx()->get_modul_var('start', 0, 'int');
3405
3406 if (!$sourceServer || !$sourceTable) {
3407 return '<div class="alert alert-danger">Quelle fehlt.</div>';
3408 }
3409
3410 if (!$start) {
3411 return $this->form_transfer($sourceServer, $sourceTable);
3412 }
3413
3414 $targetServer = dbx()->get_modul_var('target_server', '', 'parameter');
3415 $targetTable = dbx()->get_modul_var('target_table', $sourceTable, 'parameter');
3416 $createTarget = dbx()->get_modul_var('create_target', 1, 'int');
3417 $truncate = dbx()->get_modul_var('truncate_target', 1, 'int');
3418 $reset = dbx()->get_modul_var('reset', 0, 'int');
3419 $cmd = dbx()->get_modul_var('proc_cmd', '', 'parameter');
3420 $oDD = dbx()->get_system_obj('dbxDD');
3421
3422 if ($reset) {
3423 $oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable, 'reset', $createTarget, $truncate);
3424 }
3425
3426 $nextUrl = $this->build_url('db', 'transfer', array(
3427 'source_server' => $sourceServer,
3428 'source_table' => $sourceTable,
3429 'target_server' => $targetServer,
3430 'target_table' => $targetTable,
3431 'create_target' => $createTarget,
3432 'truncate_target' => $truncate,
3433 'start' => 1,
3434 ));
3435
3436 if ($cmd && in_array($cmd, array('pause', 'resume', 'continue', 'cancel'), true)) {
3437 $state = $oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable, $cmd, $createTarget, $truncate);
3438 return $this->render_process('Transfer: ' . $sourceServer . '|' . $sourceTable . ' -> ' . $targetServer . '|' . $targetTable, $state, $nextUrl, $this->build_url('db', 'list_db'));
3439 }
3440
3441 $state = $oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable, 'step', $createTarget, $truncate);
3442
3443 return $this->render_process('Transfer: ' . $sourceServer . '|' . $sourceTable . ' -> ' . $targetServer . '|' . $targetTable, $state, $nextUrl, $this->build_url('db', 'list_db'));
3444 }
3445
3453 private function form_transfer($sourceServer, $sourceTable) {
3454 $oForm = dbx()->get_system_obj('dbxForm');
3455 $servers = $this->get_server_options();
3456
3457 $defaultTarget = '';
3458 foreach ($servers as $server => $label) {
3459 if ($server != $sourceServer) {
3460 $defaultTarget = $server;
3461 break;
3462 }
3463 }
3464 if (!$defaultTarget) {
3465 $defaultTarget = $sourceServer;
3466 }
3467
3468 $oForm->init('form-schema-action');
3469 $oForm->_data = array(
3470 'source_server' => $sourceServer,
3471 'source_table' => $sourceTable,
3472 'target_server' => $defaultTarget,
3473 'target_table' => $sourceTable,
3474 'create_target' => 1,
3475 'truncate_target' => 1,
3476 );
3477 $oForm->_action = $this->build_url('db', 'transfer', array(
3478 'source_server' => $sourceServer,
3479 'source_table' => $sourceTable,
3480 ));
3481 $oForm->_msg_info = 'DB-Tabelle serveruebergreifend transferieren.';
3482
3483 $yesNo = array(1 => 'Ja', 0 => 'Nein');
3484 $oForm->add_fld('source_server', 'text-label', label: 'Quelle Server', rules: 'parameter');
3485 $oForm->add_fld('source_table', 'text-label', label: 'Quelle Tabelle', rules: 'parameter');
3486 $oForm->add_fld('target_server', 'select-single-label', label: 'Ziel Server', rules: 'parameter', options: $servers);
3487 $oForm->add_fld('target_table', 'text-label', label: 'Ziel Tabelle', rules: 'parameter|min=1');
3488 $oForm->add_fld('create_target', 'select-single-label', label: 'Ziel anlegen', rules: 'int', options: $yesNo);
3489 $oForm->add_fld('truncate_target', 'select-single-label', label: 'Ziel leeren', rules: 'int', options: $yesNo);
3490
3491 if ($oForm->submit() && !$oForm->errors()) {
3492 dbx()->set_modul_var('target_server', $oForm->get_post('target_server', $defaultTarget, 'parameter'));
3493 dbx()->set_modul_var('target_table', $oForm->get_post('target_table', $sourceTable, 'parameter'));
3494 dbx()->set_modul_var('create_target', $oForm->get_post('create_target', 1, 'int'));
3495 dbx()->set_modul_var('truncate_target', $oForm->get_post('truncate_target', 1, 'int'));
3496 dbx()->set_modul_var('start', 1);
3497 dbx()->set_modul_var('reset', 1);
3498
3499 return $this->run_transfer();
3500 }
3501
3502 return $oForm->run();
3503 }
3504
3510 private function run_backup_db_table() {
3511 $server = dbx()->get_modul_var('server', '', 'parameter');
3512 $table = dbx()->get_modul_var('table', '', 'parameter');
3513 if (!$server || !$table) {
3514 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3515 }
3516
3517 $result = $this->backup_table($server, $table);
3518 $ok = !empty($result['ok']);
3519 $state = array(
3520 'proc_type' => 'backup',
3521 'proc_key' => 'backup_' . $server . '_' . $table,
3522 'status' => $ok ? 'finished' : 'error',
3523 'phase' => 'backup_source',
3524 'message' => $ok ? 'backup finished' : ($result['msg'] ?? 'backup error'),
3525 'percent' => $ok ? 100 : 0,
3526 'step_percent' => $ok ? 100 : 0,
3527 'updated_at' => date('Y-m-d H:i:s'),
3528 );
3529
3530 if ($ok) {
3531 $state['message'] = 'backup finished: ' . $this->backup_relative_file($result['file'] ?? '');
3532 }
3533
3534 return $this->render_process('Backup: ' . $server . '|' . $table, $state, '', $this->build_url('dd', 'backup_db'));
3535 }
3536
3544 private function form_restore_db_table($server, $table) {
3545 $backups = $this->table_backups($server, $table);
3546 if (!$backups) {
3547 return '<div class="alert alert-warning">Kein Backup fuer ' . $this->esc($server . '|' . $table) . ' vorhanden.</div>';
3548 }
3549
3550 $options = array('latest' => 'Letztes Backup');
3551 foreach ($backups as $backup) {
3552 $file = (string)($backup['_file'] ?? '');
3553 if ($file === '') {
3554 continue;
3555 }
3556 $options[$file] = (string)($backup['created_at'] ?? $file) . ' / ' . (int)($backup['count'] ?? 0) . ' DS';
3557 }
3558
3559 $oForm = dbx()->get_system_obj('dbxForm');
3560 $oForm->init('form-schema-restore');
3561 $oForm->_data = array(
3562 'server' => $server,
3563 'table' => $table,
3564 'backup_file' => 'latest',
3565 );
3566 $oForm->_action = $this->build_url('dd', 'restore_db_table', array(
3567 'server' => $server,
3568 'table' => $table,
3569 ));
3570 $oForm->_msg_info = 'Restore loescht die DB-Tabelle, legt sie aus dem Backup neu an und spielt die Daten ein.';
3571
3572 $oForm->add_fld('server', 'text-label', label: 'Server', rules: 'parameter');
3573 $oForm->add_fld('table', 'text-label', label: 'Tabelle', rules: 'parameter');
3574 $oForm->add_fld('backup_file', 'select-single-label', label: 'Backup', rules: 'parameter+.-_', options: $options);
3575 $oForm->add_obj('restore_submit', 'dbx|button-submit', data: 'label=Restore starten');
3576
3577 if ($oForm->submit() && !$oForm->errors()) {
3578 dbx()->set_modul_var('backup_file', $oForm->get_post('backup_file', 'latest', 'parameter+.-_'));
3579 dbx()->set_modul_var('start', 1);
3580 return $this->run_restore_db_table();
3581 }
3582
3583 return $oForm->run();
3584 }
3585
3591 private function run_restore_db_table() {
3592 $server = dbx()->get_modul_var('server', '', 'parameter');
3593 $table = dbx()->get_modul_var('table', '', 'parameter');
3594 $start = dbx()->get_modul_var('start', 0, 'int');
3595 if (!$server || !$table) {
3596 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3597 }
3598
3599 if (!$start) {
3600 return $this->form_restore_db_table($server, $table);
3601 }
3602
3603 $file = dbx()->get_modul_var('backup_file', 'latest', 'parameter+.-_');
3604 if ($file === 'latest') {
3605 $latest = $this->latest_backup($server, $table);
3606 $file = (string)($latest['_file'] ?? '');
3607 }
3608
3609 $result = $file ? $this->restore_table_from_backup($file) : array('ok' => 0, 'msg' => 'Kein Backup gefunden.');
3610 $ok = !empty($result['ok']);
3611 $state = array(
3612 'proc_type' => 'restore',
3613 'proc_key' => 'restore_' . $server . '_' . $table,
3614 'status' => $ok ? 'finished' : 'error',
3615 'phase' => 'restore_target',
3616 'message' => $ok ? 'restore finished' : ($result['msg'] ?? 'restore error'),
3617 'percent' => $ok ? 100 : 0,
3618 'step_percent' => $ok ? 100 : 0,
3619 'updated_at' => date('Y-m-d H:i:s'),
3620 );
3621
3622 if ($ok) {
3623 $state['message'] = 'restore finished: ' . $this->backup_relative_file($result['file'] ?? $file);
3624 }
3625
3626 return $this->render_process('Restore: ' . $server . '|' . $table, $state, '', $this->build_url('dd', 'restore_db'));
3627 }
3628
3636 private function start_batch($act, $selects) {
3637 $oDD = dbx()->get_system_obj('dbxDD');
3638
3639 foreach ($selects as $selected) {
3640 if ($act == 'batch_dd_to_db' || $act == 'batch_dd_to_db_force') {
3641 $parts = $this->selected_dd_parts($selected);
3642 if (count($parts) != 2) {
3643 continue;
3644 }
3645
3646 $oDD->sync_dd_to_db($parts[0], $parts[1], 'reset');
3647 }
3648
3649 if ($act == 'batch_db_to_dd') {
3650 $parts = $this->decode_db_rid($selected);
3651 if (!$parts[0] || !$parts[1]) {
3652 continue;
3653 }
3654
3655 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3656 $dds = $this->get_dd_records_for_db($ddIndex, $parts[0], $parts[1]);
3657
3658 if ($dds) {
3659 $oDD->sync_db_to_dd($dds[0]['modul'], $dds[0]['dd'], 'reset', $parts[0], $parts[1]);
3660 } else {
3661 $oDD->sync_db_to_dd('dbx', $this->sanitize_dd_name($parts[1]), 'reset', $parts[0], $parts[1]);
3662 }
3663 }
3664 }
3665
3666 $back = 'db';
3667 if ($act == 'batch_backup_db') $back = 'backup';
3668 if ($act == 'batch_restore_latest_db') $back = 'restore';
3669
3670 $state = array(
3671 'proc_type' => 'schema_batch',
3672 'proc_key' => 'schema_batch',
3673 'act' => $act,
3674 'selects' => array_values($selects),
3675 'pos' => 0,
3676 'status' => 'running',
3677 'phase' => 'batch_step',
3678 'message' => 'batch initialized',
3679 'percent' => 0,
3680 'step_percent' => 0,
3681 'back' => $back,
3682 );
3683
3684 dbx()->set_remember_var('schema_batch', $state, 'dbxAdmin');
3685 return $this->run_batch();
3686 }
3687
3694 private function selected_dd_parts($selected) {
3695 $selected = (string)$selected;
3696
3697 if (str_starts_with($selected, 'db_')) {
3698 [$server, $table] = $this->decode_db_rid($selected);
3699 if (!$server || !$table) {
3700 return array();
3701 }
3702
3703 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3704 $dds = $this->get_dd_records_for_db($ddIndex, $server, $table);
3705 if (!$dds) {
3706 return array();
3707 }
3708
3709 return array($dds[0]['modul'], $dds[0]['dd']);
3710 }
3711
3712 $parts = explode('|', $selected, 2);
3713 return (count($parts) == 2) ? $parts : array();
3714 }
3715
3722 private function reset_batch_children($state) {
3723 if (!is_array($state) || empty($state['selects'])) {
3724 return;
3725 }
3726
3727 $oDD = dbx()->get_system_obj('dbxDD');
3728 $act = $state['act'] ?? '';
3729
3730 foreach ($state['selects'] as $selected) {
3731 if ($act == 'batch_dd_to_db' || $act == 'batch_dd_to_db_force') {
3732 $parts = $this->selected_dd_parts($selected);
3733 if (count($parts) != 2) {
3734 continue;
3735 }
3736
3737 $oDD->sync_dd_to_db($parts[0], $parts[1], 'reset');
3738 continue;
3739 }
3740
3741 if ($act == 'batch_db_to_dd') {
3742 $parts = $this->decode_db_rid($selected);
3743 if (!$parts[0] || !$parts[1]) {
3744 continue;
3745 }
3746
3747 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3748 $dds = $this->get_dd_records_for_db($ddIndex, $parts[0], $parts[1]);
3749 $modul = $dds ? $dds[0]['modul'] : 'dbx';
3750 $dd = $dds ? $dds[0]['dd'] : $this->sanitize_dd_name($parts[1]);
3751 $oDD->sync_db_to_dd($modul, $dd, 'reset', $parts[0], $parts[1]);
3752 }
3753 }
3754 }
3755
3762 private function batch_back_url($backRun) {
3763 if ($backRun == 'db') {
3764 return $this->build_url('db', 'list_db');
3765 }
3766 if ($backRun == 'backup') {
3767 return $this->build_url('dd', 'backup_db');
3768 }
3769 if ($backRun == 'restore') {
3770 return $this->build_url('dd', 'restore_db');
3771 }
3772 return $this->build_url('dd', 'list_dd');
3773 }
3774
3782 private function control_batch($state, $cmd) {
3783 $cmd = strtolower(trim((string)$cmd));
3784 $status = $state['status'] ?? 'running';
3785
3786 if ($cmd == 'pause' && !in_array($status, array('finished', 'error', 'canceled'), true)) {
3787 $state['status'] = 'paused';
3788 $state['message'] = 'process paused';
3789 } elseif (($cmd == 'resume' || $cmd == 'continue') && in_array($status, array('paused', 'canceled'), true)) {
3790 $state['status'] = 'running';
3791 $state['message'] = ($cmd == 'continue') ? 'process continued' : 'process resumed';
3792 } elseif ($cmd == 'cancel' && !in_array($status, array('finished', 'error'), true)) {
3793 $state['status'] = 'canceled';
3794 $state['message'] = 'process canceled';
3795 } elseif ($cmd == 'restart') {
3796 $this->reset_batch_children($state);
3797 $state['pos'] = 0;
3798 $state['status'] = 'running';
3799 $state['phase'] = 'batch_step';
3800 $state['percent'] = 0;
3801 $state['step_percent'] = 0;
3802 $state['message'] = 'process restarted';
3803 }
3804
3805 $state['updated_at'] = date('Y-m-d H:i:s');
3806 dbx()->set_remember_var('schema_batch', $state, 'dbxAdmin');
3807 return $state;
3808 }
3809
3815 private function run_batch() {
3816 $state = dbx()->get_remember_var('schema_batch', array(), 'dbxAdmin');
3817 if (!is_array($state) || empty($state['selects'])) {
3818 return '<div class="alert alert-warning">Kein Batch aktiv.</div>';
3819 }
3820
3821 $cmd = dbx()->get_modul_var('proc_cmd', '', 'parameter');
3822 if ($cmd && in_array($cmd, array('pause', 'resume', 'continue', 'cancel', 'restart'), true)) {
3823 $state = $this->control_batch($state, $cmd);
3824 }
3825
3826 $total = count($state['selects']);
3827 $pos = (int)($state['pos'] ?? 0);
3828 $backRun = (string)($state['back'] ?? 'dd');
3829 if (!in_array($backRun, array('dd', 'db', 'backup', 'restore'), true)) {
3830 $backRun = 'dd';
3831 }
3832 $nextUrl = $this->build_url('dd', 'batch');
3833 $backUrl = $this->batch_back_url($backRun);
3834
3835 if (in_array(($state['status'] ?? ''), array('paused', 'canceled', 'error'), true)) {
3836 return $this->render_process('Batch', $state, $nextUrl, $backUrl);
3837 }
3838
3839 if ($pos >= $total) {
3840 $state['status'] = 'finished';
3841 $state['message'] = 'batch finished';
3842 $state['percent'] = 100;
3843 $state['step_percent'] = 100;
3844 dbx()->set_remember_var('schema_batch', array(), 'dbxAdmin');
3845 return $this->render_process('Batch', $state, '', $backUrl);
3846 }
3847
3848 $current = $state['selects'][$pos];
3849 $oDD = dbx()->get_system_obj('dbxDD');
3850 $act = $state['act'] ?? '';
3851
3852 $parts = in_array($act, array('batch_db_to_dd', 'batch_backup_db', 'batch_restore_latest_db'), true)
3853 ? $this->decode_db_rid($current)
3854 : $this->selected_dd_parts($current);
3855
3856 if (($act == 'batch_dd_to_db' || $act == 'batch_dd_to_db_force') && count($parts) == 2) {
3857 $mode = ($act == 'batch_dd_to_db_force') ? 'force' : 'apply';
3858 $step = $oDD->sync_dd_to_db($parts[0], $parts[1], $mode);
3859 $state['step_percent'] = (int)($step['percent'] ?? 0);
3860
3861 if (($step['status'] ?? '') == 'finished') {
3862 $state['pos'] = $pos + 1;
3863 $state['step_percent'] = 100;
3864 } elseif (($step['status'] ?? '') == 'error') {
3865 $state['status'] = 'error';
3866 $state['message'] = $current . ': ' . ($step['message'] ?? 'error');
3867 } else {
3868 $state['message'] = $current . ': ' . ($step['message'] ?? 'running');
3869 }
3870 } elseif ($act == 'batch_db_to_dd' && count($parts) == 2 && $parts[0] && $parts[1]) {
3871 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3872 $dds = $this->get_dd_records_for_db($ddIndex, $parts[0], $parts[1]);
3873 $modul = $dds ? $dds[0]['modul'] : 'dbx';
3874 $dd = $dds ? $dds[0]['dd'] : $this->sanitize_dd_name($parts[1]);
3875 $step = $oDD->sync_db_to_dd($modul, $dd, 'merge', $parts[0], $parts[1]);
3876 $state['step_percent'] = (int)($step['percent'] ?? 0);
3877
3878 if (($step['status'] ?? '') == 'finished') {
3879 $state['pos'] = $pos + 1;
3880 $state['step_percent'] = 100;
3881 } elseif (($step['status'] ?? '') == 'error') {
3882 $state['status'] = 'error';
3883 $state['message'] = $current . ': ' . ($step['message'] ?? 'error');
3884 } else {
3885 $state['message'] = $current . ': ' . ($step['message'] ?? 'running');
3886 }
3887 } elseif ($act == 'batch_backup_db' && count($parts) == 2 && $parts[0] && $parts[1]) {
3888 $result = $this->backup_table($parts[0], $parts[1]);
3889 $state['step_percent'] = 100;
3890 if (!empty($result['ok'])) {
3891 $state['pos'] = $pos + 1;
3892 $state['message'] = $parts[0] . '|' . $parts[1] . ': Backup geschrieben';
3893 } else {
3894 $state['status'] = 'error';
3895 $state['message'] = $parts[0] . '|' . $parts[1] . ': ' . ($result['msg'] ?? 'backup error');
3896 }
3897 } elseif ($act == 'batch_restore_latest_db' && count($parts) == 2 && $parts[0] && $parts[1]) {
3898 $latest = $this->latest_backup($parts[0], $parts[1]);
3899 $file = (string)($latest['_file'] ?? '');
3900 $result = $file ? $this->restore_table_from_backup($file) : array('ok' => 0, 'msg' => 'Kein Backup gefunden');
3901 $state['step_percent'] = 100;
3902 if (!empty($result['ok'])) {
3903 $state['pos'] = $pos + 1;
3904 $state['message'] = $parts[0] . '|' . $parts[1] . ': Restore abgeschlossen';
3905 } else {
3906 $state['status'] = 'error';
3907 $state['message'] = $parts[0] . '|' . $parts[1] . ': ' . ($result['msg'] ?? 'restore error');
3908 }
3909 } elseif (in_array($act, array('batch_db_to_dd', 'batch_backup_db', 'batch_restore_latest_db'), true) && count($parts) == 2 && $parts[0] && !$parts[1]) {
3910 $state['pos'] = $pos + 1;
3911 $state['step_percent'] = 100;
3912 $state['message'] = $parts[0] . ': keine Tabelle, Schritt uebersprungen';
3913 } else {
3914 $state['status'] = 'error';
3915 $state['message'] = 'ungueltige Batch-Aktion';
3916 }
3917
3918 $done = min($total, (int)$state['pos']);
3919 $partial = (($state['status'] ?? '') == 'running') ? max(0, min(100, (int)($state['step_percent'] ?? 0))) / 100 : 0;
3920 $state['percent'] = (int)floor((min($total, $done + $partial) / max(1, $total)) * 100);
3921 $state['updated_at'] = date('Y-m-d H:i:s');
3922 dbx()->set_remember_var('schema_batch', $state, 'dbxAdmin');
3923
3924 $next = (($state['status'] ?? '') == 'running') ? $nextUrl : '';
3925 return $this->render_process('Batch', $state, $next, $backUrl);
3926 }
3927
3934 public function run($mode = '') {
3935 $run2 = dbx()->get_modul_var('dbx_run2', '', 'parameter');
3936 if (!$mode) {
3937 $mode = dbx()->get_modul_var('dbx_run1', 'dd', 'parameter');
3938 }
3939
3940 if ($run2 == 'sync_dd_to_db') {
3941 return $this->run_sync_dd_to_db();
3942 }
3943
3944 if ($run2 == 'sync_db_to_dd') {
3945 return $this->run_sync_db_to_dd();
3946 }
3947
3948 if ($run2 == 'transfer') {
3949 return $this->run_transfer();
3950 }
3951
3952 if ($run2 == 'backup_db_table') {
3953 return $this->run_backup_db_table();
3954 }
3955
3956 if ($run2 == 'restore_db_table') {
3957 return $this->run_restore_db_table();
3958 }
3959
3960 if ($run2 == 'mapping') {
3961 return $this->run_mapping_editor();
3962 }
3963
3964 if ($run2 == 'fields') {
3965 return $this->run_dd_fields_grid();
3966 }
3967
3968 if ($run2 == 'data_read') {
3969 return $this->run_data_read();
3970 }
3971
3972 if ($run2 == 'data_save') {
3973 return $this->run_data_save();
3974 }
3975
3976 if ($run2 == 'data_insert') {
3977 return $this->run_data_insert();
3978 }
3979
3980 if ($run2 == 'data_delete') {
3981 return $this->run_data_delete();
3982 }
3983
3984 if ($run2 == 'fields_read') {
3985 return $this->run_dd_fields_read();
3986 }
3987
3988 if ($run2 == 'fields_save') {
3989 return $this->run_dd_fields_save();
3990 }
3991
3992 if ($run2 == 'fields_insert') {
3993 return $this->run_dd_fields_insert();
3994 }
3995
3996 if ($run2 == 'fields_delete') {
3997 return $this->run_dd_fields_delete();
3998 }
3999
4000 if ($run2 == 'batch') {
4001 return $this->run_batch();
4002 }
4003
4004 if ($run2 == 'backup_db') {
4005 return $this->report_backup_db();
4006 }
4007
4008 if ($run2 == 'restore_db') {
4009 return $this->report_restore_db();
4010 }
4011
4012 if ($mode == 'db') {
4013 return $this->report_db();
4014 }
4015
4016 return $this->report_dd();
4017 }
4018}
4019?>
$table['server']
Definition .dd.php:6
$indexes[]
Definition .dd.php:167
$index['name']
Definition .dd.php:162
schema_row_action_data($report, $data)
Erweitert Report-Zeilen um Daten fuer Row-Aktions-Templates.
run($mode='')
Zentraler Einstiegspunkt des dbxSchema-Moduls.
Gemeinsame Basisklasse fuer dbXapp-System-, Modul- und Include-Objekte.
Definition dbxKernel.php:63
$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