31 private function esc($value) {
32 return htmlspecialchars((
string) $value, ENT_QUOTES,
'UTF-8');
43 return strtolower((
string)
$server) .
'|' . strtolower((
string)
$table);
54 $json = json_encode(array((
string)
$server, (
string)
$table), JSON_UNESCAPED_SLASHES);
55 return 'db_' . rtrim(strtr(base64_encode($json),
'+/',
'-_'),
'=');
64 private function decode_db_rid($rid) {
67 if (str_starts_with($rid,
'db_')) {
68 $raw = substr($rid, 3);
69 $pad = strlen($raw) % 4;
71 $raw .= str_repeat(
'=', 4 - $pad);
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]);
81 $parts = explode(
'|', $rid, 2);
82 return count($parts) == 2 ? $parts : array(
'',
'');
92 private function dd_ref($modul, $dd) {
93 return ($modul && $modul !=
'dbx') ? $modul .
'|' . $dd : $dd;
102 private function path_rel($path) {
103 $path = str_replace(
'\\',
'/', (
string)$path);
106 if (
$base !==
'' && str_starts_with($path,
$base)) {
107 $path = substr($path, strlen(
$base));
110 $path = ltrim($path,
'/');
111 if (str_starts_with($path,
'dbx/modules/')) {
112 $path = substr($path, strlen(
'dbx/modules/'));
126 private function build_url($run1, $run2, $params = array()) {
127 $url =
'?dbx_modul=dbxAdmin&dbx_run1=' . rawurlencode($run1) .
'&dbx_run2=' . rawurlencode($run2);
129 foreach ($params as $key => $value) {
130 $url .=
'&' . rawurlencode((
string) $key) .
'=' . rawurlencode((
string) $value);
147 private function openwin($url, $icon, $title, $width = 1200, $height = 780, $class =
'btn-inline') {
148 $titleEsc = $this->esc($title);
149 $urlEsc = $this->esc($url);
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>';
163 private function badge($label, $class =
'secondary') {
164 return '<span class="badge bg-' . $this->esc($class) .
'">' . $this->esc($label) .
'</span>';
174 private function header_icon($icon, $title) {
175 return '<i class="' . $this->esc($icon) .
'" title="' . $this->esc($title) .
'" aria-label="' . $this->esc($title) .
'"></i>';
184 private function sanitize_dd_name($name) {
185 $name = preg_replace(
'/[^A-Za-z0-9_]+/',
'_', (
string) $name);
186 $name = trim($name,
'_');
192 if (preg_match(
'/^[0-9]/', $name)) {
193 $name =
'dd_' . $name;
205 private function sanitize_db_view_part($name) {
206 $name = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'_', (
string) $name);
207 $name = trim($name,
'._-');
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';
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);
239 if ($dbType ===
'mysql') {
240 return '`' . str_replace(
'`',
'``', $name) .
'`';
243 if ($dbType ===
'sqlsrv') {
244 return '[' . str_replace(
']',
']]', $name) .
']';
247 return '"' . str_replace(
'"',
'""', $name) .
'"';
257 private function sql_db_value(
$server, $value) {
258 if ($value ===
null) {
262 if (is_bool($value)) {
263 return $value ?
'1' :
'0';
266 if (is_array($value) || is_object($value)) {
267 $value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
270 $oDB =
dbx()->get_system_obj(
'dbxDB');
271 return "'" . $oDB->escape((
string)$value,
$server) .
"'";
279 private function backup_dir() {
282 @mkdir(
$dir, 0775,
true);
284 return rtrim(str_replace(
'\\',
'/',
$dir),
'/') .
'/';
293 private function backup_file_path($file) {
294 $file = basename((
string)$file);
295 if ($file ===
'' || !preg_match(
'/\.json$/i', $file)) {
299 $path = $this->backup_dir() . $file;
300 $realDir = realpath($this->backup_dir());
301 $realFile = realpath($path);
303 if (!$realDir || !$realFile) {
307 $realDir = rtrim(str_replace(
'\\',
'/', $realDir),
'/') .
'/';
308 $realFile = str_replace(
'\\',
'/', $realFile);
310 return str_starts_with($realFile, $realDir) ? $realFile :
'';
319 private function backup_relative_file($file) {
320 return 'files/db/backup/' . basename((
string)$file);
331 return $this->sanitize_db_view_part(
$server) .
'.' . $this->sanitize_db_view_part(
$table);
340 private function split_dd_rid($rid) {
341 $parts = explode(
'|', (
string)$rid, 2);
342 if (count($parts) != 2) {
343 return array(
'',
'');
346 $modul = trim($parts[0]);
347 $dd = trim($parts[1]);
349 if (!preg_match(
'/^[A-Za-z0-9_.-]+$/', $modul) || !preg_match(
'/^[A-Za-z0-9_.-]+$/', $dd)) {
350 return array(
'',
'');
353 return array($modul, $dd);
363 private function dd_file_path($modul, $dd) {
374 private function dd_editor_file($modul, $dd) {
375 return dbx()->editor_file_path($this->dd_file_path($modul, $dd));
385 private function delete_dd_file($modul, $dd) {
386 $file = $this->dd_file_path($modul, $dd);
388 $realBase = realpath(
$base);
389 $realFile = realpath($file);
391 if (!$realBase || !$realFile || !str_starts_with(str_replace(
'\\',
'/', $realFile), rtrim(str_replace(
'\\',
'/', $realBase),
'/') .
'/')) {
395 if (!is_file($realFile)) {
399 $ok = @unlink($realFile) ? 1 : 0;
401 $oDD =
dbx()->get_system_obj(
'dbxDD');
402 $oDD->clear_dd_cache($modul .
'|' . $dd);
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'] ??
''));
421 $oldByName[$name] =
$field;
425 $oDD =
dbx()->get_system_obj(
'dbxDD');
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);
449 $oDD =
dbx()->get_system_obj(
'dbxDD');
455 $ddRef =
'dbx|' . $ddName;
456 $old = file_exists($this->dd_file_path(
'dbx', $ddName)) ? $oDD->get_dd_model($ddRef) : array();
458 $tableMeta = is_array($old[
'table'] ??
null) ? $old[
'table'] : array();
459 $tableMeta[
'server'] =
$server;
460 $tableMeta[
'table'] =
$table;
461 $tableMeta[
'datadic'] = $ddName;
470 return $oDD->save_dd(
'dbx', $ddName, $tableMeta,
$fields,
$indexes) ? $ddRef :
'';
479 private function data_report_fields($ddRef) {
480 $oDD =
dbx()->get_system_obj(
'dbxDD');
481 $fields = $oDD->get_dd_fields($ddRef);
485 $name = (string)(
$field[
'name'] ??
'');
490 $label = trim((
string)(
$field[
'label'] ??
''));
491 $out[$name] = $label !==
'' ? $label : $name;
504 private function data_grid_cols($ddRef, $primary) {
505 $oDB =
dbx()->get_system_obj(
'dbxDB');
506 $fields = $oDB->get_dd_fields($ddRef);
510 $name = (string)(
$field[
'name'] ??
'');
511 if ($name ===
'' || str_starts_with($name,
'_')) {
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';
521 if ($protect ===
'2') {
525 $flag = ($name === $primary || $protect ===
'1') ?
'p' :
'';
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';
537 $col = $name .
'[' . $label .
']:' . $gridType;
542 $col .=
':' . implode(
';', $options);
548 return implode(
',', $cols);
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');
567 [$modul, $dd] = $this->split_dd_rid($rid);
568 if (!$modul || !$dd) {
569 [$modul, $dd] = $this->dd_params_from_request();
572 $ddRef = $this->dd_ref($modul, $dd);
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';
580 if (!$primary || !isset($flds[$primary])) {
581 $primary = isset($flds[
'id']) ?
'id' :
'';
584 return array($ddRef, $flds, $primary);
595 private function data_row_by_id($ddRef, $primary, $id) {
596 if (!$ddRef || !$primary || $id ===
'' || $id ===
null) {
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])) {
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);
626 if (!$flds || !$primary || !isset($flds[$primary])) {
627 return '<div class="alert alert-warning">Keine editierbare Felddefinition mit Primaerschluessel vorhanden.</div>';
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));
647 return $oReport->run();
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.'));
661 $oDB =
dbx()->get_system_obj(
'dbxDB');
662 $rows = $oDB->select($ddRef,
'', array_keys($flds),
'',
'ASC',
'', 0, 0, 0);
663 if (!is_array($rows)) {
668 foreach (array_values($rows) as $pos => $row) {
669 if (is_array($row)) {
670 $row[
'_dbx_rownum'] = $pos + 1;
675 $count = $oDB->count($ddRef,
'');
676 $this->json_response(array(
680 'server_time' => date(
'Y-m-d H:i:s'),
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();
694 if (!$ddRef || !$flds || !$primary) {
695 $this->json_response(array(
'ok' => 0,
'msg' =>
'Keine editierbare Felddefinition vorhanden.'));
698 $oDB =
dbx()->get_system_obj(
'dbxDB');
701 foreach ($rows as $row) {
702 if (!is_array($row)) {
706 $id = $row[$primary] ?? ($row[
'id'] ??
null);
707 if ($id ===
null || $id ===
'') {
712 foreach ($flds as
$field => $label) {
713 if (
$field === $primary || !array_key_exists(
$field, $row)) {
723 $ok = $oDB->update($ddRef, $values, array($primary => $id), 1, 1, 1, 1);
724 if ($ok === 1 && $oDB->_update_count > 0) {
729 $this->json_response(array(
'ok' => 1,
'success' =>
true,
'count' => $okCount));
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.'));
743 $oDB =
dbx()->get_system_obj(
'dbxDB');
745 if ($oDB->insert($ddRef, array(), 1, 1, 1, 1) === 1) {
746 $id = $oDB->get_insert_id();
749 $this->json_response(array(
'ok' => 0,
'msg' =>
'Datensatz konnte nicht angelegt werden.'));
752 $row = $this->data_row_by_id($ddRef, $primary, $id);
753 $this->json_response(array(
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;
770 if (!$ddRef || !$flds || !$primary || $id ===
null || $id ===
'') {
771 $this->json_response(array(
'ok' => 0,
'msg' =>
'Datensatz-ID fehlt.'));
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));
785 private function json_response($data) {
786 header(
'Content-Type: application/json; charset=utf-8');
787 echo json_encode($data, JSON_UNESCAPED_UNICODE);
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();
807 private function dd_params_from_request() {
808 $modul =
dbx()->get_modul_var(
'modul',
'',
'parameter');
809 $dd =
dbx()->get_modul_var(
'dd',
'',
'parameter');
811 if ((!$modul || !$dd) && strpos((
string)$dd,
'|') !==
false) {
812 [$modul, $dd] = $this->split_dd_rid($dd);
815 if (!$modul && $dd) {
819 if (!preg_match(
'/^[A-Za-z0-9_.-]+$/', (
string)$modul) || !preg_match(
'/^[A-Za-z0-9_.-]+$/', (
string)$dd)) {
820 return array(
'',
'');
823 return array($modul, $dd);
831 private function dd_field_grid_keys() {
832 $oDD =
dbx()->get_system_obj(
'dbxDD');
833 return $oDD->dd_field_schema_keys();
843 private function dd_field_grid_row(
$field, $id) {
846 '_pos' => (
int)$id + 1,
849 foreach ($this->dd_field_grid_keys() as $key) {
850 $value =
$field[$key] ??
'';
851 $row[$key] = is_array($value) ? implode(
',', $value) : (string)$value;
863 private function dd_field_grid_rows(
$fields) {
866 $rows[] = $this->dd_field_grid_row((array)
$field, $id);
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',
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',
917 private function dd_field_model($modul, $dd) {
918 if (!$modul || !$dd) {
922 $oDD =
dbx()->get_system_obj(
'dbxDD');
923 return $oDD->get_dd_model($this->dd_ref($modul, $dd));
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());
947 private function normalize_dd_field_grid_post($row, $old = array()) {
948 $field = is_array($old) ? $old : array();
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]);
957 $field[
'type'] = strtolower(trim((
string)(
$field[
'type'] ??
'varchar')));
959 if (
$field[
'type'] ===
'') {
960 $field[
'type'] =
'varchar';
963 if (!isset(
$field[
'protect']) || !in_array((
string)
$field[
'protect'], array(
'0',
'1',
'2'),
true)) {
977 private function validate_dd_fields(
$fields, &$message =
'') {
981 $name = trim((
string)(
$field[
'name'] ??
''));
982 if ($name ===
'' || !preg_match(
'/^[A-Za-z_][A-Za-z0-9_]*$/', $name)) {
983 $message =
'Ungueltiger Feldname: ' . $name;
987 $key = strtolower($name);
988 if (isset($names[$key])) {
989 $message =
'Feldname doppelt: ' . $name;
1004 private function run_dd_fields_grid() {
1005 [$modul, $dd] = $this->dd_params_from_request();
1006 $model = $this->dd_field_model($modul, $dd);
1009 return '<div class="alert alert-danger">DD nicht gefunden.</div>';
1012 $baseParams = array(
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)));
1031 return $oReport->run();
1039 private function run_dd_fields_read() {
1040 [$modul, $dd] = $this->dd_params_from_request();
1041 $model = $this->dd_field_model($modul, $dd);
1044 $this->json_response(array(
'ok' => 0,
'rows' => array(),
'msg' =>
'DD nicht gefunden'));
1047 $rows = $this->dd_field_grid_rows($model[
'fields'] ?? array());
1048 $this->json_response(array(
1050 'count' => count($rows),
1052 'server_time' => date(
'Y-m-d H:i:s'),
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();
1068 $this->json_response(array(
'ok' => 0,
'msg' =>
'DD nicht gefunden'));
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])) {
1078 $fields[$id] = $this->normalize_dd_field_grid_post($row,
$fields[$id]);
1082 if (!$this->validate_dd_fields(
$fields, $message)) {
1083 $this->json_response(array(
'ok' => 0,
'msg' => $message));
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)));
1095 private function run_dd_fields_insert() {
1096 [$modul, $dd] = $this->dd_params_from_request();
1097 $model = $this->dd_field_model($modul, $dd);
1100 $this->json_response(array(
'ok' => 0,
'msg' =>
'DD nicht gefunden'));
1103 $fields = array_values((array)($model[
'fields'] ?? array()));
1106 $used[strtolower((
string)(
$field[
'name'] ??
''))] =
true;
1109 $name =
'new_field';
1111 while (isset($used[strtolower($name)])) {
1113 $name =
'new_field_' . $i;
1116 $oDD =
dbx()->get_system_obj(
'dbxDD');
1117 $field = $oDD->normalize_dd_field(array(
1119 'type' =>
'varchar',
1122 'label' => str_replace(
'_',
' ', ucfirst($name)),
1124 'tpl' =>
'text-label',
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),
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();
1147 $this->json_response(array(
'ok' => 0,
'msg' =>
'DD nicht gefunden'));
1150 if (!array_key_exists(
'id', $payload)) {
1151 $this->json_response(array(
'ok' => 0,
'msg' =>
'Feld-ID fehlt'));
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'));
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));
1170 private function get_module_options() {
1174 foreach (glob(
$base, GLOB_ONLYDIR) as
$dir) {
1175 $modul = basename(
$dir);
1176 if (is_dir(
$dir .
'/dd')) {
1177 $options[$modul] = $modul;
1181 if (!isset($options[
'dbx'])) {
1182 $options[
'dbx'] =
'dbx';
1194 private function get_server_options() {
1197 $moduleFiles = $this->get_module_db_files();
1198 $moduleFileIndex = array();
1203 $real = realpath((
string)(
$db[
'file'] ??
''));
1205 $moduleFileIndex[strtolower(str_replace(
'\\',
'/', $real))] = 1;
1211 if (isset($options[
$server])) {
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));
1220 if ($isSqlite && ($host !==
'' || $name !==
'')) {
1222 $real = realpath($file);
1225 $real = strtolower(str_replace(
'\\',
'/', $real));
1226 if (isset($moduleFileIndex[$real])) {
1246 private function get_module_db_files() {
1250 foreach (glob(
$base) as $file) {
1251 if (!is_file($file) || !preg_match(
'/\.(db3|sqlite|sqlite3)$/i', $file)) {
1255 $norm = str_replace(
'\\',
'/', $file);
1256 if (!preg_match(
'#/dbx/modules/([^/]+)/db/([^/]+)$#', $norm, $match)) {
1262 $server = ($modul ==
'dbx') ? $name : $modul .
'|' . $name;
1269 'path' => $this->path_rel($norm),
1270 'label' => $this->path_rel($norm),
1284 private function resolve_module_db_file(
$server) {
1286 if (!preg_match(
'/\.(db3|sqlite|sqlite3)$/i',
$server)) {
1293 if (strpos(
$server,
'|') !==
false) {
1294 $parts = explode(
'|',
$server, 2);
1295 $modul = trim($parts[0]) ?:
'dbx';
1296 $name = trim($parts[1]);
1300 if (file_exists($file)) {
1301 return str_replace(
'\\',
'/', $file);
1304 if ($modul !=
'dbx') {
1306 if (file_exists($file)) {
1307 return str_replace(
'\\',
'/', $file);
1320 private function get_database_label(
$server) {
1321 $moduleFile = $this->resolve_module_db_file(
$server);
1323 return $this->path_rel($moduleFile);
1330 $name =
$db[
'dbname'] ?? (
$db[
'name'] ??
'');
1331 $host =
$db[
'host'] ??
'';
1332 $type = strtolower((
string)(
$db[
'type'] ??
''));
1334 if ($name && $host) {
1336 if ($type ==
'sqlite' || preg_match(
'/[\/\\\\]/', (
string)$host)) {
1337 return $this->path_rel($full);
1340 return rtrim((
string)$host,
'/') .
'/' . $name;
1361 private function get_database_path_label(
$server) {
1362 $moduleFile = $this->resolve_module_db_file(
$server);
1364 return $this->path_rel($moduleFile);
1373 $type = strtolower((
string)(
$db[
'type'] ??
''));
1374 $host = (string)(
$db[
'host'] ??
'');
1375 $name = (string)(
$db[
'dbname'] ?? (
$db[
'name'] ??
''));
1377 if ($type ==
'sqlite' && ($host !==
'' || $name !==
'')) {
1391 private function get_server_module(
$server) {
1393 static $moduleFiles =
null;
1394 if ($moduleFiles ===
null) {
1395 $moduleFiles = $this->get_module_db_files();
1398 if (isset($moduleFiles[
$server])) {
1399 return $moduleFiles[
$server][
'modul'] ??
'';
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));
1410 if ($isSqlite && ($host !==
'' || $name !==
'')) {
1414 $configFile = strtolower(str_replace(
'\\',
'/', $configFile));
1416 foreach ($moduleFiles as $moduleDb) {
1417 $moduleFile = realpath((
string)($moduleDb[
'file'] ??
''));
1419 if ($moduleFile && strtolower(str_replace(
'\\',
'/', $moduleFile)) === $configFile) {
1420 return $moduleDb[
'modul'] ??
'';
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'] ??
''));
1446 $label .=
'(' . $len .
')';
1449 $label .=
' / ' . $idx;
1462 private function field_tooltip_count(
$fields, $title =
'Felder') {
1466 $plain = $title .
': ' .
$count;
1467 $html =
'<div class="dbx-tooltip-fields">';
1468 $html .=
'<div class="dbx-tooltip-title">' . $this->esc($title) .
'</div>';
1471 $plain .=
"\nKeine Felder";
1472 $html .=
'<div class="dbx-tooltip-empty">Keine Felder</div>';
1474 $html .=
'<table class="dbx-tooltip-table">';
1476 $name = trim((
string)(
$field[
'name'] ??
''));
1481 $type = $this->field_type_label(
$field);
1482 $plain .=
"\n" . $name .
': ' . $type;
1485 $html .=
'<td><code>' . $this->esc($name) .
'</code></td>';
1486 $html .=
'<td>' . $this->esc($type) .
'</td>';
1489 $html .=
'</table>';
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)) .
'">'
1508 private function mapping_status_label($status) {
1510 'exact' => array(
'Direkt',
'success'),
1511 'mapped' => array(
'Mapping',
'primary'),
1512 'type_conflict' => array(
'Typ pruefen',
'warning text-dark'),
1513 'new' => array(
'Neu',
'secondary'),
1516 $item = $map[$status] ?? array($status,
'secondary');
1517 return '<span class="badge bg-' . $this->esc($item[1]) .
'">' . $this->esc($item[0]) .
'</span>';
1526 private function mapping_kind_label($kind) {
1528 'dd_to_db' =>
'DD -> DB',
1529 'db_to_dd' =>
'DB -> DD',
1530 'transfer' =>
'Transfer',
1533 return $map[$kind] ??
'Schema-Mapping';
1542 private function posted_mapping_from_model($model) {
1543 $posted = $_POST[
'schema_map'] ?? array();
1544 if (!is_array($posted)) {
1549 foreach ($posted as $target => $source) {
1550 $target = trim((
string)$target);
1551 $source = trim((
string)$source);
1553 if ($target ===
'' || $source ===
'') {
1557 $mapping[$source] = $target;
1560 $oDD =
dbx()->get_system_obj(
'dbxDD');
1561 return $oDD->normalize_schema_mapping(
1563 $model[
'source_fields'] ?? array(),
1564 $model[
'target_fields'] ?? array()
1575 private function source_options_html($sources, $selected) {
1576 $html =
'<option value=""></option>';
1578 foreach ($sources as $source) {
1579 $name = (string)($source[
'name'] ??
'');
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 :
''))
1601 private function render_source_items($sources, $mapping) {
1603 foreach ($mapping as $source => $target) {
1604 $mapped[strtolower((
string)$source)] = $target;
1608 foreach ($sources as $source) {
1609 $name = (string)($source[
'name'] ??
'');
1614 $target = $mapped[strtolower($name)] ??
'';
1615 $classes =
'dbx-mapping-source';
1617 $classes .=
' is-used';
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>'
1629 $html =
'<div class="dbx-mapping-empty">Keine Quellfelder</div>';
1641 private function render_mapping_rows($model) {
1642 $sources = $model[
'source_fields'] ?? array();
1645 foreach (($model[
'target_rows'] ?? array()) as $row) {
1646 $target = $row[
'target'] ?? array();
1647 $targetName = (string)($row[
'target_name'] ?? ($target[
'name'] ??
''));
1648 if ($targetName ===
'') {
1652 $sourceName = (string)($row[
'source_name'] ??
'');
1653 $status = (string)($row[
'status'] ??
'new');
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);
1662 $html .=
'<span class="dbx-mapping-field-meta">' . $this->esc($this->field_type_label($target)) .
'</span>';
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>';
1678 $html =
'<div class="dbx-mapping-empty">Keine Zielfelder</div>';
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'] ??
'');
1700 $html =
'<div class="dbx-schema-mapping" data-dbx="lib=mapping" data-mapping-root="1">';
1702 $html .=
'<div class="dbx-mapping-head">';
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>'
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>';
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>';
1725 if ($fileLabel !==
'') {
1726 $html .=
'<span class="dbx-mapping-file">' . $this->esc($fileLabel) .
'</span>';
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>';
1736 $html .=
'<div class="dbx-mapping-canvas" aria-hidden="true"><svg data-mapping-lines></svg></div>';
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>';
1755 private function sync_label_from_plan($plan) {
1756 if (!is_array($plan) || empty($plan[
'ok'])) {
1757 return $this->badge(
'DD Fehler',
'danger');
1760 if (empty($plan[
'table_exists'])) {
1761 return $this->badge(
'DB fehlt',
'warning');
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());
1769 if (!empty($plan[
'rebuild_needed'])) {
1770 return $this->badge(
'Rebuild',
'danger');
1773 if ($adds || $idx) {
1774 return $this->badge(
'Sync offen',
'warning');
1777 return $this->badge(
'Synchron',
'success');
1786 private function sync_detail_from_plan($plan) {
1787 if (!is_array($plan) || empty($plan[
'ok']) || empty($plan[
'table_exists'])) {
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());
1796 if (!empty($plan[
'rebuild_needed'])) {
1797 return $this->esc($conf .
' Typ / ' . $miss .
' Extra');
1800 if ($adds || $idx) {
1801 return $this->esc($adds .
' Feld / ' . $idx .
' Index');
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');
1824 private function get_dd_records() {
1826 $oDD =
dbx()->get_system_obj(
'dbxDD');
1829 foreach (glob(
$base) as $file) {
1830 $norm = str_replace(
'\\',
'/', $file);
1832 if (!preg_match(
'#/dbx/modules/([^/]+)/dd/([^/]+)\.dd\.php$#', $norm, $match)) {
1838 if ($dd ===
'new') {
1842 $ddRef = $this->dd_ref($modul, $dd);
1843 $model = $oDD->get_dd_model($ddRef);
1848 $server = $model[
'table'][
'server'] ??
'';
1849 $table = $model[
'table'][
'table'] ??
'';
1850 if (str_starts_with(strtolower((
string)
$table),
'sqlite_')) {
1854 $count = $exists ? $oDD->count($ddRef) :
'-';
1855 $plan = $this->get_sync_plan($modul, $dd);
1856 $actions = $this->dd_actions($modul, $dd,
$server,
$table, $plan);
1858 $ddFields = array_values((array)($model[
'fields'] ?? array()));
1862 'rid' => $modul .
'|' . $dd,
1865 'path' => $this->path_rel($norm),
1867 'database' => $this->get_database_label(
$server),
1869 'dd_fields' => $this->field_tooltip_count($ddFields,
'DD-Felder'),
1870 'db_fields' => $this->field_tooltip_count($dbFields,
'DB-Felder'),
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'] ??
'',
1881 usort($records,
function($a, $b) {
1882 return strcmp($a[
'modul'] .
'|' . $a[
'dd'], $b[
'modul'] .
'|' . $b[
'dd']);
1894 private function db_server_aliases(
$server) {
1902 $add =
function($value) use (&$aliases) {
1903 $value = trim(str_replace(
'\\',
'/', (
string)$value));
1904 if ($value !==
'') {
1905 $aliases[$value] =
true;
1911 $norm = str_replace(
'\\',
'/',
$server);
1916 if (strpos($norm,
'/') !==
false) {
1917 $add(basename($norm));
1920 if (strpos(
$server,
'|') !==
false) {
1921 $parts = explode(
'|',
$server, 2);
1922 $modul = trim((
string)($parts[0] ??
''));
1923 $name = trim((
string)($parts[1] ??
''));
1929 if ($modul !==
'' && $name !==
'') {
1930 $add($modul .
'|' . $name);
1931 $add($modul .
'/db/' . $name);
1935 $moduleFile = $this->resolve_module_db_file(
$server);
1937 $file = str_replace(
'\\',
'/', $moduleFile);
1939 $add($this->path_rel($file));
1940 $add(basename($file));
1942 if (preg_match(
'#/dbx/modules/([^/]+)/db/([^/]+)$#', $file, $match)) {
1944 $add($match[1] .
'|' . $match[2]);
1945 $add($match[1] .
'/db/' . $match[2]);
1949 return array_keys($aliases);
1960 private function get_dd_index_by_db($ddRecords) {
1963 foreach ($ddRecords as $record) {
1964 $server = $record[
'server'] ??
'';
1965 $table = $record[
'table'] ??
'';
1971 $rid = (string)($record[
'rid'] ??
'');
1973 $rid = (string)($record[
'modul'] ??
'') .
'|' . (string)($record[
'dd'] ??
'');
1976 foreach ($this->db_server_aliases(
$server) as $alias) {
1977 $key = $this->norm_key($alias,
$table);
1978 if (!isset(
$index[$key])) {
1982 $index[$key][$rid] = $record;
1986 foreach (
$index as $key => $records) {
1987 $index[$key] = array_values($records);
2001 private function get_dd_records_for_db($ddIndex,
$server,
$table) {
2008 foreach ($this->db_server_aliases(
$server) as $alias) {
2009 $key = $this->norm_key($alias,
$table);
2010 if (!isset($ddIndex[$key])) {
2014 foreach ($ddIndex[$key] as $record) {
2015 $rid = (string)($record[
'rid'] ??
'');
2017 $rid = (string)($record[
'modul'] ??
'') .
'|' . (string)($record[
'dd'] ??
'');
2020 $records[$rid] = $record;
2024 return array_values($records);
2039 private function dd_actions($modul, $dd,
$server,
$table, $plan) {
2046 $mode = (!empty($plan[
'rebuild_needed'])) ?
'force' :
'apply';
2048 $syncUrl = $this->build_url(
'dd',
'sync_dd_to_db', array(
2054 $actions[
'sync'] = $this->openwin($syncUrl,
'bi bi-arrow-repeat',
'DD nach DB synchronisieren', 1000, 700);
2056 $mapUrl = $this->build_url(
'dd',
'mapping', array(
2057 'kind' =>
'dd_to_db',
2061 $actions[
'map'] = $this->openwin($mapUrl,
'bi bi-diagram-3',
'Schema-Mapping bearbeiten', 1280, 880);
2064 $transferUrl = $this->build_url(
'dd',
'transfer', array(
2066 'source_table' =>
$table,
2068 $actions[
'transfer'] = $this->openwin($transferUrl,
'bi bi-box-arrow-right',
'DB-Tabelle transferieren', 980, 720);
2071 $fieldsUrl = $this->build_url(
'dd',
'fields', array(
2075 $actions[
'fields'] = $this->openwin($fieldsUrl,
'bi bi-list-columns',
'DD-Felder bearbeiten', 1400, 860);
2091 'sync_dd_to_db' =>
'',
2096 if (is_array($dds) && count($dds)) {
2098 $plan = $this->get_sync_plan($first[
'modul'], $first[
'dd']);
2099 $ddToDbMode = (!empty($plan[
'rebuild_needed'])) ?
'force' :
'apply';
2101 $syncUrl = $this->build_url(
'db',
'sync_db_to_dd', array(
2104 'modul' => $first[
'modul'],
2105 'dd' => $first[
'dd'],
2109 $actions[
'sync'] = $this->openwin($syncUrl,
'bi bi-arrow-down-up',
'DB nach DD synchronisieren', 1000, 700);
2111 $ddToDbUrl = $this->build_url(
'db',
'sync_dd_to_db', array(
2112 'modul' => $first[
'modul'],
2113 'dd' => $first[
'dd'],
2114 'mode' => $ddToDbMode,
2117 $actions[
'sync_dd_to_db'] = $this->openwin($ddToDbUrl,
'bi bi-arrow-repeat',
'DB anhand DD synchronisieren', 1000, 700);
2119 $mapUrl = $this->build_url(
'db',
'mapping', array(
2120 'kind' =>
'db_to_dd',
2123 'modul' => $first[
'modul'],
2124 'dd' => $first[
'dd'],
2126 $actions[
'map'] = $this->openwin($mapUrl,
'bi bi-diagram-3',
'Schema-Mapping bearbeiten', 1280, 880);
2128 $syncUrl = $this->build_url(
'db',
'sync_db_to_dd', array(
2132 $actions[
'sync'] = $this->openwin($syncUrl,
'bi bi-file-earmark-plus',
'DD aus DB-Tabelle erstellen', 980, 720);
2135 $transferUrl = $this->build_url(
'db',
'transfer', array(
2137 'source_table' =>
$table,
2139 $actions[
'transfer'] = $this->openwin($transferUrl,
'bi bi-box-arrow-right',
'DB-Tabelle transferieren', 980, 720);
2150 private function get_db_records($ddRecords) {
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();
2157 foreach ($servers as
$server => $label) {
2158 $tables = $oDB->get_db_tables(
$server);
2159 if (!is_array($tables)) {
2165 foreach ($tables as $tableRec) {
2166 $table = $tableRec[
'name'] ??
'';
2173 $dds = $this->get_dd_records_for_db($ddIndex,
$server,
$table);
2174 $ddLabels = array();
2175 $sync = $this->badge(
'kein DD',
'secondary');
2178 foreach ($dds as $ddRec) {
2179 $ddLabels[] = $this->esc($ddRec[
'modul'] .
'|' . $ddRec[
'dd']);
2183 $sync = $dds[0][
'sync'];
2184 $syncInfo = $dds[0][
'sync_info'] ??
'';
2188 $ddFields = array();
2192 $model = $oDD->get_dd_model($this->dd_ref($first[
'modul'], $first[
'dd']));
2193 $ddFields = array_values((array)($model[
'fields'] ?? array()));
2200 'modul' => $this->get_server_module(
$server),
2202 'database' => $this->get_database_label(
$server),
2203 'path' => $this->get_database_path_label(
$server),
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),
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'] ??
'',
2222 'rid' => $this->encode_db_rid(
$server,
''),
2223 'modul' => $this->get_server_module(
$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'),
2232 'sync' => $this->badge(
'keine Tabellen',
'secondary'),
2235 'act_dd_sync' =>
'',
2238 'dd_edit_modul' =>
'',
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']));
2259 $oDB =
dbx()->get_system_obj(
'dbxDB');
2260 $oDD =
dbx()->get_system_obj(
'dbxDD');
2263 return array(
'ok' => 0,
'msg' =>
'Tabelle nicht gefunden.');
2268 return array(
'ok' => 0,
'msg' =>
'Felder konnten nicht gelesen werden.');
2273 if (!is_array($rows)) {
2274 return array(
'ok' => 0,
'msg' =>
'Daten konnten nicht gelesen werden.');
2278 $file =
$created .
'__' . $this->backup_name_part(
$server) .
'__' . $this->backup_name_part(
$table) .
'.json';
2279 $path = $this->backup_dir() . $file;
2280 $tmp = $path .
'.tmp';
2284 'created_at' => date(
'Y-m-d H:i:s'),
2287 'db_type' => $oDB->get_db_type(
$server),
2288 'count' => count($rows),
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)) {
2297 return array(
'ok' => 0,
'msg' =>
'Backup-Datei konnte nicht geschrieben werden.');
2300 return array(
'ok' => 1,
'file' => $file,
'path' => $path,
'count' => count($rows));
2309 private function backup_meta($file) {
2310 $path = $this->backup_file_path($file);
2315 $raw = file_get_contents($path);
2316 $data = $raw !==
false ? json_decode($raw,
true) :
null;
2317 if (!is_array($data)) {
2321 $data[
'_file'] = basename($path);
2322 $data[
'_path'] = $path;
2335 foreach (glob($this->backup_dir() .
'*.json') as $file) {
2336 $meta = $this->backup_meta(basename($file));
2337 if (($meta[
'server'] ??
'') ===
$server && ($meta[
'table'] ??
'') ===
$table) {
2342 usort($items,
function($a, $b) {
2343 return strcmp((
string)($b[
'created_at'] ??
''), (
string)($a[
'created_at'] ??
''));
2358 return $items[0] ?? array();
2371 return $this->badge(
'kein Backup',
'secondary');
2374 $created = (string)($meta[
'created_at'] ??
'');
2375 $count = (int)($meta[
'count'] ?? 0);
2385 private function restore_table_from_backup($file) {
2386 $meta = $this->backup_meta($file);
2388 return array(
'ok' => 0,
'msg' =>
'Backup-Datei ist ungueltig.');
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();
2398 return array(
'ok' => 0,
'msg' =>
'Backup-Metadaten sind unvollstaendig.');
2401 $oDB =
dbx()->get_system_obj(
'dbxDB');
2402 $oDD =
dbx()->get_system_obj(
'dbxDD');
2406 return array(
'ok' => 0,
'msg' =>
'Tabelle konnte nicht neu angelegt werden.');
2410 return array(
'ok' => 1,
'count' => 0,
'file' => basename((
string)$file));
2415 $name = (string)(
$field[
'name'] ??
'');
2422 return array(
'ok' => 0,
'msg' =>
'Keine Ziel-Felder gefunden.');
2425 $qNames = array_map(
function($name) use (
$server) {
2426 return $this->quote_db_ident(
$server, $name);
2429 foreach ($rows as $row) {
2431 foreach ($names as $name) {
2432 $values[] = $this->sql_db_value(
$server, is_array($row) && array_key_exists($name, $row) ? $row[$name] :
null);
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.');
2443 return array(
'ok' => 1,
'count' => count($rows),
'file' => basename((
string)$file));
2453 private function filter_rows($rows, $search) {
2454 $search = trim((
string) $search);
2455 if ($search ===
'') {
2459 $filtered = array();
2460 foreach ($rows as $row) {
2461 $haystack = strtolower(strip_tags(implode(
' ', $row)));
2462 if (strpos($haystack, strtolower($search)) !==
false) {
2475 private function has_config_db_servers() {
2481 $moduleFiles = $this->get_module_db_files();
2482 $moduleFileIndex = array();
2485 $real = realpath((
string)(
$db[
'file'] ??
''));
2487 $moduleFileIndex[strtolower(str_replace(
'\\',
'/', $real))] = 1;
2492 if (isset($moduleFiles[
$server])) {
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));
2501 if ($isSqlite && ($host !==
'' || $name !==
'')) {
2503 $real = realpath($file);
2506 $real = strtolower(str_replace(
'\\',
'/', $real));
2507 if (isset($moduleFileIndex[$real])) {
2526 private function get_module_filter_options($rows, $includeSys =
false) {
2527 $options = array(
'0' =>
'[Alle Module]');
2530 $options[
'sys'] =
'sys';
2533 foreach ($rows as $row) {
2534 $modul = trim((
string)($row[
'modul'] ??
''));
2535 if ($modul !==
'') {
2536 $options[$modul] = $modul;
2541 return array(
'0' =>
'[Alle Module]') + $options;
2551 private function filter_rows_by_module($rows, $modul) {
2552 $modul = trim((
string)$modul);
2553 if ($modul ===
'' || $modul ===
'0') {
2557 $filtered = array();
2558 foreach ($rows as $row) {
2559 if (strcasecmp((
string)($row[
'modul'] ??
''), $modul) === 0) {
2578 private function run_schema_report($mode, $rows, $flds, $action, $batchOptions, $message =
'') {
2579 $oReport =
dbx()->get_system_obj(
'dbxReport');
2581 'dd' =>
'report-dd-sync',
2582 'db' =>
'report-db-sync',
2583 'backup' =>
'report-db-backup',
2584 'restore' =>
'report-db-restore',
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);
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';
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');
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');
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());
2629 return $this->start_batch($act, $selects);
2632 $oReport->_msg_warning =
'Keine Zeilen ausgewaehlt';
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');
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);
2651 return $oReport->run();
2662 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
2666 $type = (string)($data[
'type'] ??
'');
2667 if (!in_array($type, array(
'edit',
'show',
'delete'),
true)) {
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');
2676 if ($type ==
'edit') {
2680 if ($mode ==
'dd') {
2681 [$modul, $dd] = $this->split_dd_rid($rid);
2683 $modul = (string)($record[
'dd_edit_modul'] ??
'');
2684 $dd = (string)($record[
'dd_edit_dd'] ??
'');
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) .
'"';
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');
2703 $showUrl = $this->append_url_params(
$base, array(
2704 'dbx_do' =>
'row_show',
2707 $deleteUrl = $this->append_url_params(
$base, array(
2708 'dbx_do' =>
'row_delete',
2712 $data[
'data'][
'show_url'] = $this->esc($showUrl);
2713 $data[
'data'][
'delete_url'] = $this->esc($deleteUrl);
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)) {
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>';
2736 $ddRef = $this->dd_ref($modul, $dd);
2738 if ($do ==
'row_show') {
2739 $params = array(
'rid' => $rid);
2740 return $this->run_data_report(
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)
2750 if ($this->delete_dd_file($modul, $dd)) {
2751 $message =
'DD-Datei geloescht: ' . $modul .
'|' . $dd;
2753 $message =
'DD-Datei konnte nicht geloescht werden: ' . $modul .
'|' . $dd;
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)) {
2771 $rid = dbx()->get_modul_var(
'rid',
'',
'parameter');
2774 return '<div class="alert alert-danger">DB-Auswahl ist ungueltig.</div>';
2777 if ($do ==
'row_show') {
2780 return '<div class="alert alert-danger">Ansichts-DD konnte nicht erzeugt werden.</div>';
2783 $params = array(
'rid' => $rid);
2784 return $this->run_data_report(
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)
2794 $oDD = dbx()->get_system_obj(
'dbxDD');
2796 $message =
'DB-Tabelle geloescht: ' .
$server .
'|' .
$table;
2798 $message =
'DB-Tabelle konnte nicht geloescht werden: ' .
$server .
'|' .
$table;
2809 private function report_dd() {
2810 return $this->report_db();
2818 private function report_db() {
2820 $content = $this->handle_db_row_action($message);
2821 if ($content !==
'') {
2827 'server' =>
'Server',
2828 'table' =>
'Tabelle',
2829 'db_fields' =>
'DB Felder',
2830 'dd_fields' =>
'DD Felder',
2831 'count' =>
'Datensaetze',
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'),
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',
2848 return $this->run_schema_report(
'db', $this->get_db_records($this->get_dd_records()), $flds, $this->build_url(
'db',
'list_db'), $batch, $message);
2855 private function backup_restore_rows() {
2856 $rows = $this->get_db_records($this->get_dd_records());
2858 foreach ($rows as $no => $row) {
2859 [
$server,
$table] = $this->decode_db_rid((
string)($row[
'rid'] ??
''));
2862 $server = (string)($row[
'server'] ??
'');
2866 $rows[$no][
'backup'] = $this->badge(
'keine Tabelle',
'secondary');
2867 $rows[$no][
'act_backup'] =
'';
2868 $rows[$no][
'act_restore'] =
'';
2872 $rows[$no][
'backup'] = $this->backup_label(
$server,
$table);
2874 $backupUrl = $this->build_url(
'dd',
'backup_db_table', array(
2879 $rows[$no][
'act_backup'] = $this->openwin($backupUrl,
'bi bi-download',
'Backup erstellen', 980, 700);
2881 $restoreUrl = $this->build_url(
'dd',
'restore_db_table', array(
2885 $rows[$no][
'act_restore'] = $this->openwin($restoreUrl,
'bi bi-upload',
'Backup auswaehlen und wiederherstellen', 980, 760);
2896 private function report_backup_db() {
2899 'server' =>
'Server',
2901 'table' =>
'Tabelle',
2902 'count' =>
'Datensaetze',
2904 'backup' =>
'Letztes Backup',
2905 'act_backup' => $this->header_icon(
'bi bi-download',
'Backup erstellen'),
2909 '0' =>
'[Batch-Aktion]',
2910 'batch_backup_db' =>
'Auswahl sichern',
2913 return $this->run_schema_report(
'backup', $this->backup_restore_rows(), $flds, $this->build_url(
'dd',
'backup_db'), $batch,
'');
2921 private function report_restore_db() {
2924 'server' =>
'Server',
2926 'table' =>
'Tabelle',
2927 'count' =>
'Datensaetze',
2929 'backup' =>
'Letztes Backup',
2930 'act_restore' => $this->header_icon(
'bi bi-upload',
'Backup auswaehlen'),
2934 '0' =>
'[Batch-Aktion]',
2935 'batch_restore_latest_db' =>
'Letztes Backup der Auswahl restoren',
2938 return $this->run_schema_report(
'restore', $this->backup_restore_rows(), $flds, $this->build_url(
'dd',
'restore_db'), $batch,
'');
2948 private function append_url_params($url, $params = array()) {
2953 foreach ($params as $key => $value) {
2954 $url .= (strpos($url,
'?') ===
false ?
'?' :
'&')
2955 . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)$value);
2967 private function process_status_label($status) {
2971 'running' =>
'Laeuft',
2972 'paused' =>
'Angehalten',
2973 'canceled' =>
'Abgebrochen',
2974 'finished' =>
'Fertig',
2975 'error' =>
'Fehler',
2978 return $map[$status] ?? $status;
2987 private function process_status_class($status) {
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',
2998 return $map[$status] ??
'bg-secondary';
3007 private function process_status_icon($status) {
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',
3018 return $map[$status] ??
'bi bi-circle';
3027 private function process_type_label($type) {
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',
3037 return $map[$type] ?? ($type ?:
'Prozess');
3046 private function process_task_label($phase) {
3047 $phase = (string)$phase;
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';
3066 private function process_step_label($phase) {
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',
3086 return $map[$phase] ?? ($phase ?:
'Schritt');
3095 private function process_message_label($message) {
3096 $message = (string)$message;
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',
3117 return $map[$message] ?? $message;
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;
3136 $stepPercent = (int)($state[
'step_percent'] ?? $percent);
3137 if ($stepPercent < 0) $stepPercent = 0;
3138 if ($stepPercent > 100) $stepPercent = 100;
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';
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;
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,
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),
3183 return $oTPL->get_tpl(
'dbxAdmin|schema-process', $data);
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');
3200 return '<div class="alert alert-danger">DD fehlt.</div>';
3204 $oDD->sync_dd_to_db($modul, $dd,
'reset');
3207 $nextUrl = $this->build_url(
'dd',
'sync_dd_to_db', array(
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'));
3218 $state = $oDD->sync_dd_to_db($modul, $dd, $mode);
3220 $content = $this->render_process(
'DD -> DB: ' . $modul .
'|' . $dd, $state, $nextUrl, $this->build_url(
'dd',
'list_dd'));
3222 if (($state[
'status'] ??
'') ==
'error' && strpos((
string)($state[
'message'] ??
''),
'rebuild needed') !==
false) {
3223 $force = $this->build_url(
'dd',
'sync_dd_to_db', array(
3229 $content .=
'<div class="mt-3"><a class="btn btn-danger" href="' . $this->esc($force) .
'">Rebuild starten</a></div>';
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');
3250 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3253 if (!$modul || !$dd) {
3257 $oDD = dbx()->get_system_obj(
'dbxDD');
3262 $nextUrl = $this->build_url(
'db',
'sync_db_to_dd', array(
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'));
3275 $state = $oDD->sync_db_to_dd($modul, $dd, $mode,
$server,
$table);
3277 return $this->render_process(
'DB -> DD: ' .
$server .
'|' .
$table .
' -> ' . $modul .
'|' . $dd, $state, $nextUrl, $this->build_url(
'db',
'list_db'));
3288 $oForm = dbx()->get_system_obj(
'dbxForm');
3289 $oForm->init(
'form-schema-action');
3290 $oForm->_data = array(
3294 'dd' => $this->sanitize_dd_name(
$table),
3296 $oForm->_action = $this->build_url(
'db',
'sync_db_to_dd', array(
3300 $oForm->_msg_info =
'DD aus DB-Tabelle erzeugen oder vorhandenes DD synchronisieren.';
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');
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'));
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);
3316 return $this->run_sync_db_to_dd();
3319 return $oForm->run();
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');
3334 if (!$dd && $kind !==
'transfer') {
3335 return '<div class="alert alert-danger">DD fehlt.</div>';
3338 $oDD = dbx()->get_system_obj(
'dbxDD');
3346 $model = $oDD->build_schema_mapping($kind, $context);
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(
3359 $oForm->_data = array(
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');
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);
3378 $oForm->_msg_success =
'Mapping gespeichert.';
3379 $model = $oDD->build_schema_mapping($kind, $model[
'context'] ?? $context);
3381 $oForm->_msg_error =
'Mapping konnte nicht gespeichert werden.';
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>'
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>'
3393 return $oForm->run();
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');
3406 if (!$sourceServer || !$sourceTable) {
3407 return '<div class="alert alert-danger">Quelle fehlt.</div>';
3411 return $this->form_transfer($sourceServer, $sourceTable);
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');
3423 $oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable,
'reset', $createTarget, $truncate);
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,
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'));
3441 $state = $oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable,
'step', $createTarget, $truncate);
3443 return $this->render_process(
'Transfer: ' . $sourceServer .
'|' . $sourceTable .
' -> ' . $targetServer .
'|' . $targetTable, $state, $nextUrl, $this->build_url(
'db',
'list_db'));
3453 private function form_transfer($sourceServer, $sourceTable) {
3454 $oForm = dbx()->get_system_obj(
'dbxForm');
3455 $servers = $this->get_server_options();
3457 $defaultTarget =
'';
3458 foreach ($servers as
$server => $label) {
3459 if (
$server != $sourceServer) {
3464 if (!$defaultTarget) {
3465 $defaultTarget = $sourceServer;
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,
3477 $oForm->_action = $this->build_url(
'db',
'transfer', array(
3478 'source_server' => $sourceServer,
3479 'source_table' => $sourceTable,
3481 $oForm->_msg_info =
'DB-Tabelle serveruebergreifend transferieren.';
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);
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);
3499 return $this->run_transfer();
3502 return $oForm->run();
3510 private function run_backup_db_table() {
3511 $server = dbx()->get_modul_var(
'server',
'',
'parameter');
3512 $table = dbx()->get_modul_var(
'table',
'',
'parameter');
3514 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3520 'proc_type' =>
'backup',
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'),
3531 $state[
'message'] =
'backup finished: ' . $this->backup_relative_file(
$result[
'file'] ??
'');
3534 return $this->render_process(
'Backup: ' .
$server .
'|' .
$table, $state,
'', $this->build_url(
'dd',
'backup_db'));
3547 return '<div class="alert alert-warning">Kein Backup fuer ' . $this->esc(
$server .
'|' .
$table) .
' vorhanden.</div>';
3550 $options = array(
'latest' =>
'Letztes Backup');
3551 foreach ($backups as $backup) {
3552 $file = (string)($backup[
'_file'] ??
'');
3556 $options[$file] = (string)($backup[
'created_at'] ?? $file) .
' / ' . (int)($backup[
'count'] ?? 0) .
' DS';
3559 $oForm = dbx()->get_system_obj(
'dbxForm');
3560 $oForm->init(
'form-schema-restore');
3561 $oForm->_data = array(
3564 'backup_file' =>
'latest',
3566 $oForm->_action = $this->build_url(
'dd',
'restore_db_table', array(
3570 $oForm->_msg_info =
'Restore loescht die DB-Tabelle, legt sie aus dem Backup neu an und spielt die Daten ein.';
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');
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();
3583 return $oForm->run();
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');
3596 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3603 $file = dbx()->get_modul_var(
'backup_file',
'latest',
'parameter+.-_');
3604 if ($file ===
'latest') {
3606 $file = (string)($latest[
'_file'] ??
'');
3609 $result = $file ? $this->restore_table_from_backup($file) : array(
'ok' => 0,
'msg' =>
'Kein Backup gefunden.');
3612 'proc_type' =>
'restore',
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'),
3623 $state[
'message'] =
'restore finished: ' . $this->backup_relative_file(
$result[
'file'] ?? $file);
3626 return $this->render_process(
'Restore: ' .
$server .
'|' .
$table, $state,
'', $this->build_url(
'dd',
'restore_db'));
3636 private function start_batch($act, $selects) {
3637 $oDD = dbx()->get_system_obj(
'dbxDD');
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) {
3646 $oDD->sync_dd_to_db($parts[0], $parts[1],
'reset');
3649 if ($act ==
'batch_db_to_dd') {
3650 $parts = $this->decode_db_rid($selected);
3651 if (!$parts[0] || !$parts[1]) {
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]);
3659 $oDD->sync_db_to_dd($dds[0][
'modul'], $dds[0][
'dd'],
'reset', $parts[0], $parts[1]);
3661 $oDD->sync_db_to_dd(
'dbx', $this->sanitize_dd_name($parts[1]),
'reset', $parts[0], $parts[1]);
3667 if ($act ==
'batch_backup_db') $back =
'backup';
3668 if ($act ==
'batch_restore_latest_db') $back =
'restore';
3671 'proc_type' =>
'schema_batch',
3672 'proc_key' =>
'schema_batch',
3674 'selects' => array_values($selects),
3676 'status' =>
'running',
3677 'phase' =>
'batch_step',
3678 'message' =>
'batch initialized',
3680 'step_percent' => 0,
3684 dbx()->set_remember_var(
'schema_batch', $state,
'dbxAdmin');
3685 return $this->run_batch();
3694 private function selected_dd_parts($selected) {
3695 $selected = (string)$selected;
3697 if (str_starts_with($selected,
'db_')) {
3703 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3704 $dds = $this->get_dd_records_for_db($ddIndex,
$server,
$table);
3709 return array($dds[0][
'modul'], $dds[0][
'dd']);
3712 $parts = explode(
'|', $selected, 2);
3713 return (count($parts) == 2) ? $parts : array();
3722 private function reset_batch_children($state) {
3723 if (!is_array($state) || empty($state[
'selects'])) {
3727 $oDD = dbx()->get_system_obj(
'dbxDD');
3728 $act = $state[
'act'] ??
'';
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) {
3737 $oDD->sync_dd_to_db($parts[0], $parts[1],
'reset');
3741 if ($act ==
'batch_db_to_dd') {
3742 $parts = $this->decode_db_rid($selected);
3743 if (!$parts[0] || !$parts[1]) {
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]);
3762 private function batch_back_url($backRun) {
3763 if ($backRun ==
'db') {
3764 return $this->build_url(
'db',
'list_db');
3766 if ($backRun ==
'backup') {
3767 return $this->build_url(
'dd',
'backup_db');
3769 if ($backRun ==
'restore') {
3770 return $this->build_url(
'dd',
'restore_db');
3772 return $this->build_url(
'dd',
'list_dd');
3782 private function control_batch($state, $cmd) {
3783 $cmd = strtolower(trim((
string)$cmd));
3784 $status = $state[
'status'] ??
'running';
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);
3798 $state[
'status'] =
'running';
3799 $state[
'phase'] =
'batch_step';
3800 $state[
'percent'] = 0;
3801 $state[
'step_percent'] = 0;
3802 $state[
'message'] =
'process restarted';
3805 $state[
'updated_at'] = date(
'Y-m-d H:i:s');
3806 dbx()->set_remember_var(
'schema_batch', $state,
'dbxAdmin');
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>';
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);
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)) {
3832 $nextUrl = $this->build_url(
'dd',
'batch');
3833 $backUrl = $this->batch_back_url($backRun);
3835 if (in_array(($state[
'status'] ??
''), array(
'paused',
'canceled',
'error'),
true)) {
3836 return $this->render_process(
'Batch', $state, $nextUrl, $backUrl);
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);
3848 $current = $state[
'selects'][$pos];
3849 $oDD = dbx()->get_system_obj(
'dbxDD');
3850 $act = $state[
'act'] ??
'';
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);
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);
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');
3868 $state[
'message'] = $current .
': ' . ($step[
'message'] ??
'running');
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);
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');
3885 $state[
'message'] = $current .
': ' . ($step[
'message'] ??
'running');
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;
3891 $state[
'pos'] = $pos + 1;
3892 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': Backup geschrieben';
3894 $state[
'status'] =
'error';
3895 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': ' . (
$result[
'msg'] ??
'backup error');
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;
3903 $state[
'pos'] = $pos + 1;
3904 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': Restore abgeschlossen';
3906 $state[
'status'] =
'error';
3907 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': ' . (
$result[
'msg'] ??
'restore error');
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';
3914 $state[
'status'] =
'error';
3915 $state[
'message'] =
'ungueltige Batch-Aktion';
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');
3924 $next = (($state[
'status'] ??
'') ==
'running') ? $nextUrl :
'';
3925 return $this->render_process(
'Batch', $state, $next, $backUrl);
3934 public function run($mode =
'') {
3935 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
3937 $mode =
dbx()->get_modul_var(
'dbx_run1',
'dd',
'parameter');
3940 if ($run2 ==
'sync_dd_to_db') {
3941 return $this->run_sync_dd_to_db();
3944 if ($run2 ==
'sync_db_to_dd') {
3945 return $this->run_sync_db_to_dd();
3948 if ($run2 ==
'transfer') {
3949 return $this->run_transfer();
3952 if ($run2 ==
'backup_db_table') {
3953 return $this->run_backup_db_table();
3956 if ($run2 ==
'restore_db_table') {
3957 return $this->run_restore_db_table();
3960 if ($run2 ==
'mapping') {
3961 return $this->run_mapping_editor();
3964 if ($run2 ==
'fields') {
3965 return $this->run_dd_fields_grid();
3968 if ($run2 ==
'data_read') {
3969 return $this->run_data_read();
3972 if ($run2 ==
'data_save') {
3973 return $this->run_data_save();
3976 if ($run2 ==
'data_insert') {
3977 return $this->run_data_insert();
3980 if ($run2 ==
'data_delete') {
3981 return $this->run_data_delete();
3984 if ($run2 ==
'fields_read') {
3985 return $this->run_dd_fields_read();
3988 if ($run2 ==
'fields_save') {
3989 return $this->run_dd_fields_save();
3992 if ($run2 ==
'fields_insert') {
3993 return $this->run_dd_fields_insert();
3996 if ($run2 ==
'fields_delete') {
3997 return $this->run_dd_fields_delete();
4000 if ($run2 ==
'batch') {
4001 return $this->run_batch();
4004 if ($run2 ==
'backup_db') {
4005 return $this->report_backup_db();
4008 if ($run2 ==
'restore_db') {
4009 return $this->report_restore_db();
4012 if ($mode ==
'db') {
4013 return $this->report_db();
4016 return $this->report_dd();