30 private function schema_texts() {
31 if ($this->schemaTexts) {
32 return $this->schemaTexts;
34 dbx()->get_system_obj(
'dbxForm',
'use');
36 $texts->set_form_help_enabled(
false);
37 $texts->_fd =
'dbxAdmin|schema-report';
38 $texts->load_fd_messages();
39 $this->schemaTexts =
$texts;
40 return $this->schemaTexts;
49 private function esc(
$value) {
50 return htmlspecialchars((
string)
$value, ENT_QUOTES,
'UTF-8');
61 return strtolower((
string)
$server) .
'|' . strtolower((
string)
$table);
73 return 'db_' . rtrim(strtr(base64_encode(
$json),
'+/',
'-_'),
'=');
82 private function decode_db_rid($rid) {
85 if (str_starts_with($rid,
'db_')) {
86 $raw = substr($rid, 3);
87 $pad = strlen($raw) % 4;
89 $raw .= str_repeat(
'=', 4 - $pad);
92 $json = base64_decode(strtr($raw,
'-_',
'+/'),
true);
93 $data =
$json !==
false ? json_decode(
$json,
true) :
null;
94 if (is_array($data) && count($data) >= 2) {
95 return array((
string)$data[0], (
string)$data[1]);
99 $parts = explode(
'|', $rid, 2);
100 return count($parts) == 2 ? $parts : array(
'',
'');
110 private function dd_ref($modul,
$dd) {
111 return ($modul && $modul !=
'dbx') ? $modul .
'|' .
$dd :
$dd;
120 private function path_rel($path) {
121 $path = str_replace(
'\\',
'/', (
string)$path);
124 if (
$base !==
'' && str_starts_with($path,
$base)) {
125 $path = substr($path, strlen(
$base));
128 $path = ltrim($path,
'/');
129 if (str_starts_with($path,
'dbx/modules/')) {
130 $path = substr($path, strlen(
'dbx/modules/'));
144 private function build_url($run1, $run2, $params = array()) {
145 $url =
'?dbx_modul=dbxAdmin&dbx_run1=' . rawurlencode($run1) .
'&dbx_run2=' . rawurlencode($run2);
147 foreach ($params as $key =>
$value) {
148 $url .=
'&' . rawurlencode((
string) $key) .
'=' . rawurlencode((
string)
$value);
165 private function openwin(
$url, $icon, $title, $width = 1200, $height = 780,
$class =
'btn-inline') {
166 $titleEsc = $this->esc($title);
167 $urlEsc = $this->esc(
$url);
169 return '<a class="' .
$class .
' dbx-win" href="' . $urlEsc .
'" title="' . $titleEsc .
'" '
170 .
'data-url="' . $urlEsc .
'" data-title="' . $titleEsc
171 .
'" role="button"><i class="' . $this->esc($icon) .
'"></i></a>';
181 private function badge($label,
$class =
'secondary') {
182 return '<span class="badge bg-' . $this->esc(
$class) .
'">' . $this->esc($label) .
'</span>';
192 private function header_icon($icon, $title) {
193 return '<i class="' . $this->esc($icon) .
'" title="' . $this->esc($title) .
'" aria-label="' . $this->esc($title) .
'"></i>';
202 private function sanitize_dd_name($name) {
203 $name = preg_replace(
'/[^A-Za-z0-9_]+/',
'_', (
string) $name);
204 $name = trim($name,
'_');
210 if (preg_match(
'/^[0-9]/', $name)) {
211 $name =
'dd_' . $name;
223 private function sanitize_db_view_part($name) {
224 $name = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'_', (
string) $name);
225 $name = trim($name,
'._-');
240 private function backup_name_part($name) {
241 $name = preg_replace(
'/[^A-Za-z0-9_.-]+/',
'_', (
string)$name);
242 $name = trim($name,
'._-');
243 return $name !==
'' ? $name :
'db';
253 private function quote_db_ident(
$server, $name) {
254 $dbType =
dbx()->get_system_obj(
'dbxDB')->get_db_type(
$server);
255 $name = str_replace(array(
'`',
'"',
']'),
'', (
string)$name);
257 if ($dbType ===
'mysql') {
258 return '`' . str_replace(
'`',
'``', $name) .
'`';
261 if ($dbType ===
'sqlsrv') {
262 return '[' . str_replace(
']',
']]', $name) .
']';
265 return '"' . str_replace(
'"',
'""', $name) .
'"';
281 return $value ?
'1' :
'0';
285 $value = json_encode(
$value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
288 $oDB =
dbx()->get_system_obj(
'dbxDB');
297 private function backup_dir() {
300 @mkdir(
$dir, 0775,
true);
302 return rtrim(str_replace(
'\\',
'/',
$dir),
'/') .
'/';
311 private function backup_file_path(
$file) {
313 if (
$file ===
'' || !preg_match(
'/\.json$/i',
$file)) {
317 $path = $this->backup_dir() .
$file;
318 $realDir = realpath($this->backup_dir());
319 $realFile = realpath($path);
321 if (!$realDir || !$realFile) {
325 $realDir = rtrim(str_replace(
'\\',
'/', $realDir),
'/') .
'/';
326 $realFile = str_replace(
'\\',
'/', $realFile);
328 return str_starts_with($realFile, $realDir) ? $realFile :
'';
337 private function backup_relative_file(
$file) {
338 return 'files/db/backup/' . basename((
string)
$file);
349 return $this->sanitize_db_view_part(
$server) .
'.' . $this->sanitize_db_view_part(
$table);
358 private function split_dd_rid($rid) {
359 $parts = explode(
'|', (
string)$rid, 2);
360 if (count($parts) != 2) {
361 return array(
'',
'');
364 $modul = trim($parts[0]);
365 $dd = trim($parts[1]);
367 if (!preg_match(
'/^[A-Za-z0-9_.-]+$/', $modul) || !preg_match(
'/^[A-Za-z0-9_.-]+$/',
$dd)) {
368 return array(
'',
'');
371 return array($modul,
$dd);
381 private function dd_file_path($modul,
$dd) {
392 private function dd_editor_file($modul,
$dd) {
393 return dbx()->editor_file_path($this->dd_file_path($modul,
$dd));
403 private function delete_dd_file($modul,
$dd) {
404 $file = $this->dd_file_path($modul,
$dd);
406 $realBase = realpath(
$base);
407 $realFile = realpath(
$file);
409 if (!$realBase || !$realFile || !str_starts_with(str_replace(
'\\',
'/', $realFile), rtrim(str_replace(
'\\',
'/', $realBase),
'/') .
'/')) {
413 if (!is_file($realFile)) {
417 $ok = @unlink($realFile) ? 1 : 0;
419 $oDD =
dbx()->get_system_obj(
'dbxDD');
420 $oDD->clear_dd_cache($modul .
'|' .
$dd);
434 private function merge_auto_dd_fields($oldFields, $dbFields,
$server =
'') {
435 $oldByName = array();
436 foreach ((array)$oldFields as
$field) {
437 $name = strtolower((
string)(
$field[
'name'] ??
''));
439 $oldByName[$name] =
$field;
443 $oDD =
dbx()->get_system_obj(
'dbxDD');
447 foreach ((array)$dbFields as
$field) {
448 $name = strtolower((
string)(
$field[
'name'] ??
''));
449 if ($name !==
'' && isset($oldByName[$name])) {
450 $field =
$oDD->merge_dd_field_with_db_field($oldByName[$name],
$field, $dbType);
467 $oDD =
dbx()->get_system_obj(
'dbxDD');
473 $ddRef =
'dbx|' . $ddName;
474 $old = file_exists($this->dd_file_path(
'dbx', $ddName)) ?
$oDD->get_dd_model($ddRef) : array();
476 $tableMeta = is_array($old[
'table'] ??
null) ? $old[
'table'] : array();
477 $tableMeta[
'server'] =
$server;
478 $tableMeta[
'table'] =
$table;
479 $tableMeta[
'datadic'] = $ddName;
497 private function data_report_fields($ddRef) {
498 $oDD =
dbx()->get_system_obj(
'dbxDD');
503 $name = (string)(
$field[
'name'] ??
'');
508 $label = trim((
string)(
$field[
'label'] ??
''));
509 $out[$name] = $label !==
'' ? $label : $name;
522 private function data_grid_cols($ddRef, $primary) {
523 $oDB =
dbx()->get_system_obj(
'dbxDB');
524 $fields = $oDB->get_dd_fields($ddRef);
528 $name = (string)(
$field[
'name'] ??
'');
529 if ($name ===
'' || str_starts_with($name,
'_')) {
533 $label = trim((
string)(
$field[
'label'] ??
''));
534 $label = str_replace(array(
':',
'[',
']'),
'-', $label ?: $name);
535 $fieldType = strtolower(trim((
string)(
$field[
'type'] ??
'')));
536 $gridType = $oDB->map_dd_type_to_grid_type($fieldType ?:
'text');
537 $protect = isset(
$field[
'protect']) ? (string)
$field[
'protect'] :
'0';
539 if ($protect ===
'2') {
543 $flag = ($name === $primary || $protect ===
'1') ?
'p' :
'';
546 if ($name ===
'content' || in_array($fieldType, array(
'mediumtext',
'longtext'),
true)) {
555 $col = $name .
'[' . $label .
']:' . $gridType;
560 $col .=
':' . implode(
';',
$options);
566 return implode(
',', $cols);
574 private function data_context_from_request() {
575 $mode =
dbx()->get_modul_var(
'dbx_run1',
'dd',
'parameter');
576 $rid =
dbx()->get_modul_var(
'rid',
'',
'parameter');
585 [$modul,
$dd] = $this->split_dd_rid($rid);
586 if (!$modul || !
$dd) {
587 [$modul,
$dd] = $this->dd_params_from_request();
590 $ddRef = $this->dd_ref($modul,
$dd);
594 $oDB =
dbx()->get_system_obj(
'dbxDB');
595 $flds = $ddRef ? $this->data_report_fields($ddRef) : array();
596 $primary = $ddRef ? $oDB->get_dd_primary($ddRef) :
'id';
598 if (!$primary || !isset($flds[$primary])) {
599 $primary = isset($flds[
'id']) ?
'id' :
'';
602 return array($ddRef, $flds, $primary);
613 private function data_row_by_id($ddRef, $primary, $id) {
614 if (!$ddRef || !$primary || $id ===
'' || $id ===
null) {
618 $oDB =
dbx()->get_system_obj(
'dbxDB');
619 $rows = $oDB->select($ddRef, array($primary => $id),
'',
'',
'ASC',
'', 1, 0, 0);
638 private function run_data_report($ddRef, $title, $readUrl, $saveUrl, $insertUrl,
$deleteUrl) {
639 $oDB =
dbx()->get_system_obj(
'dbxDB');
640 $oReport =
dbx()->get_system_obj(
'dbxReport');
641 $flds = $this->data_report_fields($ddRef);
642 $primary = $oDB->get_dd_primary($ddRef);
644 if (!$flds || !$primary || !isset($flds[$primary])) {
645 return '<div class="alert alert-warning">Keine editierbare Felddefinition mit Primaerschluessel vorhanden.</div>';
648 $oReport->init(
'report-schema-data-' . substr(md5($ddRef), 0, 10),
'schema-data-report');
649 $oReport->_rflds = $flds;
650 $oReport->_mode =
'tabulurator';
651 $oReport->_rrows =
'auto';
652 $oReport->_grid_id =
'schema_data_' . substr(md5($ddRef), 0, 10);
653 $oReport->_grid_cols = $this->data_grid_cols($ddRef, $primary);
654 $oReport->_grid_layout =
'fitData';
655 $oReport->_grid_read_url = $readUrl;
656 $oReport->_grid_save_url = $saveUrl;
657 $oReport->_grid_insert_url = $insertUrl;
659 $oReport->add_obj(
'title',
'obj-value', $this->esc($title));
660 $oReport->add_obj(
'subtitle',
'obj-value', $this->esc(
'Direkt editierbares Grid'));
661 $oReport->add_rep(
'bar_title', $title);
662 $oReport->add_rep(
'bar_subtitle',
'Direkt editierbares Grid');
663 $oReport->add_obj(
'primary',
'obj-value', $this->esc($primary));
665 return $oReport->run();
673 private function run_data_read() {
674 [$ddRef, $flds, $primary] = $this->data_context_from_request();
675 if (!$ddRef || !$flds || !$primary) {
676 $this->json_response(array(
'ok' => 0,
'count' => 0,
'rows' => array(),
'msg' =>
'Keine Felddefinition vorhanden.'));
679 $oDB =
dbx()->get_system_obj(
'dbxDB');
680 $rows = $oDB->select($ddRef,
'', array_keys($flds),
'',
'ASC',
'', 0, 0, 0);
681 if (!is_array(
$rows)) {
686 foreach (array_values(
$rows) as $pos => $row) {
687 if (is_array($row)) {
688 $row[
'_dbx_rownum'] = $pos + 1;
693 $count = $oDB->count($ddRef,
'');
694 $this->json_response(array(
698 'server_time' => date(
'Y-m-d H:i:s'),
707 private function run_data_save() {
708 [$ddRef, $flds, $primary] = $this->data_context_from_request();
709 $payload = $this->request_json();
710 $rows = is_array($payload[
'rows'] ??
null) ? $payload[
'rows'] : array();
712 if (!$ddRef || !$flds || !$primary) {
713 $this->json_response(array(
'ok' => 0,
'msg' =>
'Keine editierbare Felddefinition vorhanden.'));
716 $oDB =
dbx()->get_system_obj(
'dbxDB');
719 foreach (
$rows as $row) {
720 if (!is_array($row)) {
724 $id = $row[$primary] ?? ($row[
'id'] ??
null);
725 if ($id ===
null || $id ===
'') {
730 foreach ($flds as
$field => $label) {
731 if (
$field === $primary || !array_key_exists(
$field, $row)) {
741 $ok = $oDB->update($ddRef, $values, array($primary => $id), 1, 1, 1, 1);
742 if (
$ok === 1 && $oDB->_update_count > 0) {
747 $this->json_response(array(
'ok' => 1,
'success' =>
true,
'count' => $okCount));
755 private function run_data_insert() {
756 [$ddRef, $flds, $primary] = $this->data_context_from_request();
757 if (!$ddRef || !$flds || !$primary) {
758 $this->json_response(array(
'ok' => 0,
'msg' =>
'Keine editierbare Felddefinition vorhanden.'));
761 $oDB =
dbx()->get_system_obj(
'dbxDB');
763 if ($oDB->insert($ddRef, array(), 1, 1, 1, 1) === 1) {
764 $id = $oDB->get_insert_id();
767 $this->json_response(array(
'ok' => 0,
'msg' =>
'Datensatz konnte nicht angelegt werden.'));
770 $row = $this->data_row_by_id($ddRef, $primary, $id);
771 $this->json_response(array(
783 private function run_data_delete() {
784 [$ddRef, $flds, $primary] = $this->data_context_from_request();
785 $payload = $this->request_json();
786 $id = $payload[
'id'] ??
null;
788 if (!$ddRef || !$flds || !$primary || $id ===
null || $id ===
'') {
789 $this->json_response(array(
'ok' => 0,
'msg' =>
'Datensatz-ID fehlt.'));
792 $oDB =
dbx()->get_system_obj(
'dbxDB');
793 $ok = $oDB->delete($ddRef, array($primary => $id), 1, 1);
794 $this->json_response(array(
'ok' =>
$ok > 0 ? 1 : 0,
'success' =>
$ok > 0));
803 private function json_response($data) {
804 header(
'Content-Type: application/json; charset=utf-8');
805 echo json_encode($data, JSON_UNESCAPED_UNICODE);
814 private function request_json() {
815 $raw = file_get_contents(
'php://input');
816 $data = $raw ? json_decode($raw,
true) : array();
817 return is_array($data) ? $data : array();
825 private function dd_params_from_request() {
826 $modul =
dbx()->get_modul_var(
'modul',
'',
'parameter');
827 $dd =
dbx()->get_modul_var(
'dd',
'',
'parameter');
829 if ((!$modul || !
$dd) && strpos((
string)
$dd,
'|') !==
false) {
830 [$modul,
$dd] = $this->split_dd_rid(
$dd);
833 if (!$modul &&
$dd) {
837 if (!preg_match(
'/^[A-Za-z0-9_.-]+$/', (
string)$modul) || !preg_match(
'/^[A-Za-z0-9_.-]+$/', (
string)
$dd)) {
838 return array(
'',
'');
841 return array($modul,
$dd);
849 private function dd_field_grid_keys() {
850 $oDD =
dbx()->get_system_obj(
'dbxDD');
851 return $oDD->dd_field_schema_keys();
861 private function dd_field_grid_row(
$field, $id) {
864 '_pos' => (
int)$id + 1,
867 foreach ($this->dd_field_grid_keys() as $key) {
881 private function dd_field_grid_rows(
$fields) {
884 $rows[] = $this->dd_field_grid_row((array)
$field, $id);
895 private function dd_field_grid_cols() {
896 $types = implode(
'~', array(
897 'varchar',
'char',
'text',
'mediumtext',
'longtext',
898 'int',
'integer',
'bigint',
'smallint',
'mediumint',
'tinyint',
899 'decimal',
'float',
'double',
900 'date',
'datetime',
'timestamp',
'time',
901 'bool',
'boolean',
'json',
'blob',
904 return implode(
',', array(
905 '_pos[#]:number:p:width=62;hozAlign=center;headerHozAlign=center',
906 'name[Name]:text::width=150',
907 'type[Type]:text::editor=list;values=' . $types .
';width=120',
908 'index[Index]:text::editor=list;values==ohne~PRI=Primaer~MU=Mehrfach~INDEX=Index~UNIQUE=Unique;width=115',
909 'length[Laenge]:text::width=88',
910 'default[Default]:text::width=120',
911 'label[Label]:text::width=160',
912 'rules[Regeln]:text::width=180',
913 'tooltip[Tooltip]:text::editor=textarea;width=220',
914 'errormsg[Fehlermeldung]:text::editor=textarea;width=220',
915 'placeholder[Platzhalter]:text::width=160',
916 'convert[Convert]:text::width=120',
917 'protect[Schutz]:text::editor=list;values=0=editierbar~1=geschuetzt~2=versteckt;width=120',
918 'group[Gruppe]:text::width=130',
919 'mask[Maske]:text::width=130',
920 'data[Data]:text::editor=textarea;width=220',
921 'options[Optionen]:text::editor=textarea;width=220',
922 'tpl[TPL]:text::width=160',
923 'js[JS]:text::editor=textarea;width=180',
924 'prompt[Prompt]:text::editor=textarea;width=220',
935 private function dd_field_model($modul,
$dd) {
936 if (!$modul || !
$dd) {
940 $oDD =
dbx()->get_system_obj(
'dbxDD');
941 return $oDD->get_dd_model($this->dd_ref($modul,
$dd));
953 private function save_dd_field_model($modul,
$dd, $model,
$fields) {
954 $oDD =
dbx()->get_system_obj(
'dbxDD');
955 return $oDD->save_dd($modul,
$dd, $model[
'table'] ?? array(), array_values(
$fields), $model[
'indexes'] ?? array());
965 private function normalize_dd_field_grid_post($row, $old = array()) {
966 $field = is_array($old) ? $old : array();
968 foreach ($this->dd_field_grid_keys() as $key) {
969 if (array_key_exists($key, $row)) {
970 $field[$key] = is_array($row[$key]) ? implode(
',', $row[$key]) : trim((
string)$row[$key]);
975 $field[
'type'] = strtolower(trim((
string)(
$field[
'type'] ??
'varchar')));
977 if (
$field[
'type'] ===
'') {
978 $field[
'type'] =
'varchar';
981 if (!isset(
$field[
'protect']) || !in_array((
string)
$field[
'protect'], array(
'0',
'1',
'2'),
true)) {
995 private function validate_dd_fields(
$fields, &$message =
'') {
999 $name = trim((
string)(
$field[
'name'] ??
''));
1000 if ($name ===
'' || !preg_match(
'/^[A-Za-z_][A-Za-z0-9_]*$/', $name)) {
1001 $message =
'Ungueltiger Feldname: ' . $name;
1005 $key = strtolower($name);
1006 if (isset($names[$key])) {
1007 $message =
'Feldname doppelt: ' . $name;
1011 $names[$key] =
true;
1022 private function run_dd_fields_grid() {
1023 [$modul,
$dd] = $this->dd_params_from_request();
1024 $model = $this->dd_field_model($modul,
$dd);
1027 return '<div class="alert alert-danger">DD nicht gefunden.</div>';
1030 $baseParams = array(
1035 $oReport =
dbx()->get_system_obj(
'dbxReport');
1036 $oReport->init(
'report-dd-fields-grid',
'schema-fields-grid');
1037 $oReport->_mode =
'tabulurator';
1038 $oReport->_rrows = 620;
1039 $oReport->_grid_id =
'dd_fields_' . substr(md5($modul .
'|' .
$dd), 0, 10);
1040 $oReport->_grid_cols = $this->dd_field_grid_cols();
1041 $oReport->_grid_layout =
'fitDataStretch';
1042 $oReport->_grid_read_url = $this->build_url(
'dd',
'fields_read', $baseParams);
1043 $oReport->_grid_save_url = $this->build_url(
'dd',
'fields_save', $baseParams);
1044 $oReport->_grid_insert_url = $this->build_url(
'dd',
'fields_insert', $baseParams);
1045 $oReport->_grid_delete_url = $this->build_url(
'dd',
'fields_delete', $baseParams);
1046 $oReport->add_rep(
'bar_title',
'DD-Felder: ' . $modul .
'|' .
$dd);
1047 $oReport->add_rep(
'bar_subtitle', $this->path_rel($this->dd_file_path($modul,
$dd)));
1049 return $oReport->run();
1057 private function run_dd_fields_read() {
1058 [$modul,
$dd] = $this->dd_params_from_request();
1059 $model = $this->dd_field_model($modul,
$dd);
1062 $this->json_response(array(
'ok' => 0,
'rows' => array(),
'msg' =>
'DD nicht gefunden'));
1065 $rows = $this->dd_field_grid_rows($model[
'fields'] ?? array());
1066 $this->json_response(array(
1068 'count' => count(
$rows),
1070 'server_time' => date(
'Y-m-d H:i:s'),
1079 private function run_dd_fields_save() {
1080 [$modul,
$dd] = $this->dd_params_from_request();
1081 $model = $this->dd_field_model($modul,
$dd);
1082 $payload = $this->request_json();
1083 $rows = is_array($payload[
'rows'] ??
null) ? $payload[
'rows'] : array();
1086 $this->json_response(array(
'ok' => 0,
'msg' =>
'DD nicht gefunden'));
1089 $fields = array_values((array)($model[
'fields'] ?? array()));
1090 foreach (
$rows as $row) {
1091 $id = isset($row[
'id']) ? (int)$row[
'id'] : -1;
1092 if ($id < 0 || !isset(
$fields[$id])) {
1096 $fields[$id] = $this->normalize_dd_field_grid_post($row,
$fields[$id]);
1100 if (!$this->validate_dd_fields(
$fields, $message)) {
1101 $this->json_response(array(
'ok' => 0,
'msg' => $message));
1104 $ok = $this->save_dd_field_model($modul,
$dd, $model,
$fields);
1105 $this->json_response(array(
'ok' =>
$ok ? 1 : 0,
'success' =>
$ok ?
true :
false,
'rows' => $this->dd_field_grid_rows(
$fields)));
1113 private function run_dd_fields_insert() {
1114 [$modul,
$dd] = $this->dd_params_from_request();
1115 $model = $this->dd_field_model($modul,
$dd);
1118 $this->json_response(array(
'ok' => 0,
'msg' =>
'DD nicht gefunden'));
1121 $fields = array_values((array)($model[
'fields'] ?? array()));
1124 $used[strtolower((
string)(
$field[
'name'] ??
''))] =
true;
1127 $name =
'new_field';
1129 while (isset($used[strtolower($name)])) {
1131 $name =
'new_field_' . $i;
1134 $oDD =
dbx()->get_system_obj(
'dbxDD');
1137 'type' =>
'varchar',
1140 'label' => str_replace(
'_',
' ', ucfirst($name)),
1142 'tpl' =>
'text-label',
1146 $ok = $this->save_dd_field_model($modul,
$dd, $model,
$fields);
1147 $this->json_response(array(
1148 'ok' =>
$ok ? 1 : 0,
1149 'success' =>
$ok ?
true :
false,
1150 'row' => $this->dd_field_grid_row(
$field, count(
$fields) - 1),
1159 private function run_dd_fields_delete() {
1160 [$modul,
$dd] = $this->dd_params_from_request();
1161 $model = $this->dd_field_model($modul,
$dd);
1162 $payload = $this->request_json();
1165 $this->json_response(array(
'ok' => 0,
'msg' =>
'DD nicht gefunden'));
1168 if (!array_key_exists(
'id', $payload)) {
1169 $this->json_response(array(
'ok' => 0,
'msg' =>
'Feld-ID fehlt'));
1172 $id = (int)$payload[
'id'];
1173 $fields = array_values((array)($model[
'fields'] ?? array()));
1174 if ($id < 0 || !isset(
$fields[$id])) {
1175 $this->json_response(array(
'ok' => 0,
'msg' =>
'Feld-ID ungueltig'));
1178 array_splice(
$fields, $id, 1);
1179 $ok = $this->save_dd_field_model($modul,
$dd, $model,
$fields);
1180 $this->json_response(array(
'ok' =>
$ok ? 1 : 0,
'success' =>
$ok ?
true :
false));
1188 private function get_module_options() {
1192 foreach (glob(
$base, GLOB_ONLYDIR) as
$dir) {
1193 $modul = basename(
$dir);
1194 if (is_dir(
$dir .
'/dd')) {
1212 private function get_server_options() {
1215 $moduleFiles = $this->get_module_db_files();
1216 $moduleFileIndex = array();
1221 $real = realpath((
string)(
$db[
'file'] ??
''));
1223 $moduleFileIndex[strtolower(str_replace(
'\\',
'/', $real))] = 1;
1233 $type = strtolower((
string)($data[
'type'] ??
''));
1234 $host = (string)($data[
'host'] ??
'');
1235 $name = (string)($data[
'dbname'] ?? ($data[
'name'] ??
''));
1236 $isSqlite = ($type ==
'sqlite' || $type ==
'sqlite3' || preg_match(
'/\.(db3|sqlite|sqlite3)$/i', $name));
1238 if ($isSqlite && ($host !==
'' || $name !==
'')) {
1239 $file =
dbx()->os_path($host . $name);
1240 $real = realpath(
$file);
1243 $real = strtolower(str_replace(
'\\',
'/', $real));
1244 if (isset($moduleFileIndex[$real])) {
1264 private function get_module_db_files() {
1269 if (!is_file(
$file) || !preg_match(
'/\.(db3|sqlite|sqlite3)$/i',
$file)) {
1273 $norm = str_replace(
'\\',
'/',
$file);
1274 if (!preg_match(
'#/dbx/modules/([^/]+)/db/([^/]+)$#', $norm, $match)) {
1280 $server = ($modul ==
'dbx') ? $name : $modul .
'|' . $name;
1287 'path' => $this->path_rel($norm),
1288 'label' => $this->path_rel($norm),
1302 private function resolve_module_db_file(
$server) {
1304 if (!preg_match(
'/\.(db3|sqlite|sqlite3)$/i',
$server)) {
1311 if (strpos(
$server,
'|') !==
false) {
1312 $parts = explode(
'|',
$server, 2);
1313 $modul = trim($parts[0]) ?:
'dbx';
1314 $name = trim($parts[1]);
1318 if (file_exists(
$file)) {
1319 return str_replace(
'\\',
'/',
$file);
1322 if ($modul !=
'dbx') {
1324 if (file_exists(
$file)) {
1325 return str_replace(
'\\',
'/',
$file);
1338 private function get_database_label(
$server) {
1339 $moduleFile = $this->resolve_module_db_file(
$server);
1341 return $this->path_rel($moduleFile);
1348 $name =
$db[
'dbname'] ?? (
$db[
'name'] ??
'');
1349 $host =
$db[
'host'] ??
'';
1350 $type = strtolower((
string)(
$db[
'type'] ??
''));
1352 if ($name && $host) {
1353 $full =
dbx()->os_path($host . $name);
1354 if ($type ==
'sqlite' || preg_match(
'/[\/\\\\]/', (
string)$host)) {
1355 return $this->path_rel($full);
1358 return rtrim((
string)$host,
'/') .
'/' . $name;
1379 private function get_database_path_label(
$server) {
1380 $moduleFile = $this->resolve_module_db_file(
$server);
1382 return $this->path_rel($moduleFile);
1391 $type = strtolower((
string)(
$db[
'type'] ??
''));
1392 $host = (string)(
$db[
'host'] ??
'');
1393 $name = (string)(
$db[
'dbname'] ?? (
$db[
'name'] ??
''));
1395 if ($type ==
'sqlite' && ($host !==
'' || $name !==
'')) {
1396 return $this->path_rel(
dbx()->
os_path($host . $name));
1409 private function get_server_module(
$server) {
1411 static $moduleFiles =
null;
1412 if ($moduleFiles ===
null) {
1413 $moduleFiles = $this->get_module_db_files();
1416 if (isset($moduleFiles[
$server])) {
1417 return $moduleFiles[
$server][
'modul'] ??
'';
1423 $type = strtolower((
string)(
$db[
'type'] ??
''));
1424 $host = (string)(
$db[
'host'] ??
'');
1425 $name = (string)(
$db[
'dbname'] ?? (
$db[
'name'] ??
''));
1426 $isSqlite = ($type ==
'sqlite' || $type ==
'sqlite3' || preg_match(
'/\.(db3|sqlite|sqlite3)$/i', $name));
1428 if ($isSqlite && ($host !==
'' || $name !==
'')) {
1429 $configFile = realpath(
dbx()->
os_path($host . $name));
1432 $configFile = strtolower(str_replace(
'\\',
'/', $configFile));
1434 foreach ($moduleFiles as $moduleDb) {
1435 $moduleFile = realpath((
string)($moduleDb[
'file'] ??
''));
1437 if ($moduleFile && strtolower(str_replace(
'\\',
'/', $moduleFile)) === $configFile) {
1438 return $moduleDb[
'modul'] ??
'';
1457 private function field_type_label(
$field) {
1458 $type = trim((
string)(
$field[
'type'] ??
''));
1459 $len = trim((
string)(
$field[
'length'] ??
''));
1460 $idx = trim((
string)(
$field[
'index'] ??
''));
1464 $label .=
'(' . $len .
')';
1467 $label .=
' / ' . $idx;
1480 private function field_tooltip_count(
$fields, $title =
'') {
1481 $texts = $this->schema_texts();
1482 if ($title ===
'') {
1483 $title =
$texts->get_fd_message(
'fields');
1488 $plain = $title .
': ' .
$count;
1489 $html =
'<div class="dbx-tooltip-fields">';
1490 $html .=
'<div class="dbx-tooltip-title">' . $this->esc($title) .
'</div>';
1493 $plain .=
"\n" .
$texts->get_fd_message(
'no_fields');
1494 $html .=
'<div class="dbx-tooltip-empty">' . $this->esc(
$texts->get_fd_message(
'no_fields')) .
'</div>';
1496 $html .=
'<table class="dbx-tooltip-table">';
1498 $name = trim((
string)(
$field[
'name'] ??
''));
1503 $type = $this->field_type_label(
$field);
1504 $plain .=
"\n" . $name .
': ' . $type;
1507 $html .=
'<td><code>' . $this->esc($name) .
'</code></td>';
1508 $html .=
'<td>' . $this->esc($type) .
'</td>';
1511 $html .=
'</table>';
1516 return '<span class="dbx-schema-field-count badge bg-light text-dark border" '
1517 .
'title="' . $this->esc($plain) .
'" '
1518 .
'data-dbx-tooltip="' . $this->esc(str_replace(
'"',
"'",
$html)) .
'">'
1530 private function mapping_status_label($status) {
1531 $texts = $this->schema_texts();
1534 $texts->get_fd_message(
'mapping_status_direct'),
1538 $texts->get_fd_message(
'mapping_status_mapped'),
1541 'type_conflict' => array(
1542 $texts->get_fd_message(
'mapping_status_type_check'),
1543 'warning text-dark',
1546 $texts->get_fd_message(
'mapping_status_new'),
1551 $item = $map[$status] ?? array($status,
'secondary');
1552 return '<span class="badge bg-' . $this->esc($item[1]) .
'">' . $this->esc($item[0]) .
'</span>';
1561 private function mapping_kind_label($kind) {
1563 'dd_to_db' =>
'DD -> DB',
1564 'db_to_dd' =>
'DB -> DD',
1565 'transfer' =>
'Transfer',
1568 return $map[$kind] ??
'Schema-Mapping';
1577 private function posted_mapping_from_model($model) {
1578 $posted =
$_POST[
'schema_map'] ?? array();
1579 if (!is_array($posted)) {
1595 $oDD =
dbx()->get_system_obj(
'dbxDD');
1596 return $oDD->normalize_schema_mapping(
1598 $model[
'source_fields'] ?? array(),
1599 $model[
'target_fields'] ?? array()
1610 private function source_options_html($sources, $selected) {
1611 $html =
'<option value=""></option>';
1613 foreach ($sources as
$source) {
1614 $name = (string)(
$source[
'name'] ??
'');
1619 $sel = (strcasecmp($name, (
string)$selected) === 0) ?
' selected' :
'';
1620 $meta = $this->field_type_label(
$source);
1621 $html .=
'<option value="' . $this->esc($name) .
'"' . $sel .
'>'
1622 . $this->esc($name . ($meta ?
' - ' . $meta :
''))
1636 private function render_source_items($sources, $mapping) {
1637 $texts = $this->schema_texts();
1644 foreach ($sources as
$source) {
1645 $name = (string)(
$source[
'name'] ??
'');
1650 $target = $mapped[strtolower($name)] ??
'';
1651 $classes =
'dbx-mapping-source';
1653 $classes .=
' is-used';
1656 $html .=
'<button type="button" class="' . $classes .
'" draggable="true" '
1657 .
'data-mapping-source="' . $this->esc($name) .
'" '
1658 .
'title="' . $this->esc($name) .
'">'
1659 .
'<span class="dbx-mapping-field-name">' . $this->esc($name) .
'</span>'
1660 .
'<span class="dbx-mapping-field-meta">' . $this->esc($this->field_type_label(
$source)) .
'</span>'
1665 $html =
'<div class="dbx-mapping-empty">'
1666 . $this->esc(
$texts->get_fd_message(
'mapping_no_source_fields'))
1679 private function render_mapping_rows($model) {
1680 $texts = $this->schema_texts();
1681 $sources = $model[
'source_fields'] ?? array();
1684 foreach (($model[
'target_rows'] ?? array()) as $row) {
1685 $target = $row[
'target'] ?? array();
1686 $targetName = (string)($row[
'target_name'] ?? (
$target[
'name'] ??
''));
1687 if ($targetName ===
'') {
1691 $sourceName = (string)($row[
'source_name'] ??
'');
1692 $status = (string)($row[
'status'] ??
'new');
1694 $html .=
'<div class="dbx-mapping-target" data-mapping-target="' . $this->esc($targetName) .
'" '
1695 .
'data-mapping-status="' . $this->esc($status) .
'">';
1696 $html .=
'<div class="dbx-mapping-target-main">';
1697 $html .=
'<div class="dbx-mapping-target-title">';
1698 $html .=
'<span class="dbx-mapping-field-name">' . $this->esc($targetName) .
'</span>';
1699 $html .= $this->mapping_status_label($status);
1701 $html .=
'<span class="dbx-mapping-field-meta">' . $this->esc($this->field_type_label(
$target)) .
'</span>';
1704 $html .=
'<div class="dbx-mapping-drop" data-mapping-drop="' . $this->esc($targetName) .
'">';
1705 $html .=
'<select class="form-select form-select-sm" name="schema_map[' . $this->esc($targetName) .
']" '
1706 .
'data-mapping-select data-target="' . $this->esc($targetName) .
'" '
1707 .
'data-auto-source="' . $this->esc($sourceName) .
'">';
1708 $html .= $this->source_options_html($sources, $sourceName);
1709 $html .=
'</select>';
1710 $html .=
'<button type="button" class="btn btn-sm btn-outline-secondary" data-mapping-clear-row title="'
1711 . $this->esc(
$texts->get_fd_message(
'mapping_clear_assignment')) .
'">'
1712 .
'<i class="bi bi-x-lg"></i></button>';
1718 $html =
'<div class="dbx-mapping-empty">'
1719 . $this->esc(
$texts->get_fd_message(
'mapping_no_target_fields'))
1732 private function render_mapping_board($model) {
1733 $texts = $this->schema_texts();
1734 $kind = (string)($model[
'kind'] ??
'dd_to_db');
1735 $mapping = $model[
'mapping'] ?? array();
1736 $sourceCount = count($model[
'source_fields'] ?? array());
1737 $targetCount = count($model[
'target_fields'] ?? array());
1738 $mappedCount = count($mapping);
1739 $file = (string)($model[
'file'] ??
'');
1740 $fileLabel =
$file !==
'' ? $this->path_rel(
$file) :
'';
1741 $updated = (string)($model[
'updated_at'] ??
'');
1743 $html =
'<div class="dbx-schema-mapping" data-dbx="lib=mapping" data-mapping-root="1">';
1745 $html .=
'<div class="dbx-mapping-head">';
1747 $html .=
'<h3>' . $this->esc($this->mapping_kind_label($kind)) .
'</h3>';
1748 $html .=
'<div class="dbx-mapping-subtitle">'
1749 .
'<span>' . $this->esc($model[
'source_label'] ??
'') .
'</span>'
1750 .
'<i class="bi bi-arrow-right"></i>'
1751 .
'<span>' . $this->esc($model[
'target_label'] ??
'') .
'</span>'
1755 $html .=
'<div class="dbx-mapping-tools">';
1756 $html .=
'<span class="badge bg-secondary" data-mapping-count="mapped">' . $this->esc($mappedCount .
' / ' . $targetCount) .
'</span>';
1757 $html .=
'<button type="button" class="btn btn-outline-primary" data-mapping-action="auto" title="'
1758 . $this->esc(
$texts->get_fd_message(
'mapping_auto'))
1759 .
'"><i class="bi bi-magic"></i></button>';
1760 $html .=
'<button type="button" class="btn btn-outline-secondary" data-mapping-action="clear" title="'
1761 . $this->esc(
$texts->get_fd_message(
'mapping_clear'))
1762 .
'"><i class="bi bi-eraser"></i></button>';
1766 $html .=
'<div class="dbx-mapping-meta">';
1767 $html .=
'<span>' . $this->esc(
$texts->get_fd_message(
'mapping_source'))
1768 .
': ' . $this->esc((
string)$sourceCount) .
'</span>';
1769 $html .=
'<span>' . $this->esc(
$texts->get_fd_message(
'mapping_target'))
1770 .
': ' . $this->esc((
string)$targetCount) .
'</span>';
1772 $html .=
'<span>' . $this->esc(
$texts->get_fd_message(
'mapping_saved_at'))
1773 .
': ' . $this->esc(
$updated) .
'</span>';
1775 if ($fileLabel !==
'') {
1776 $html .=
'<span class="dbx-mapping-file">' . $this->esc($fileLabel) .
'</span>';
1780 $html .=
'<div class="dbx-mapping-workbench">';
1781 $html .=
'<section class="dbx-mapping-panel dbx-mapping-panel-source">';
1782 $html .=
'<header><span>'
1783 . $this->esc(
$texts->get_fd_message(
'mapping_source'))
1784 .
'</span><span class="badge bg-light text-dark">'
1785 . $this->esc((
string)$sourceCount) .
'</span></header>';
1786 $html .=
'<div class="dbx-mapping-source-list">' . $this->render_source_items($model[
'source_fields'] ?? array(), $mapping) .
'</div>';
1787 $html .=
'</section>';
1789 $html .=
'<div class="dbx-mapping-canvas" aria-hidden="true"><svg data-mapping-lines></svg></div>';
1791 $html .=
'<section class="dbx-mapping-panel dbx-mapping-panel-target">';
1792 $html .=
'<header><span>'
1793 . $this->esc(
$texts->get_fd_message(
'mapping_target'))
1794 .
'</span><span class="badge bg-light text-dark">'
1795 . $this->esc((
string)$targetCount) .
'</span></header>';
1796 $html .=
'<div class="dbx-mapping-target-list">' . $this->render_mapping_rows($model) .
'</div>';
1797 $html .=
'</section>';
1811 private function sync_label_from_plan(
$plan) {
1812 $texts = $this->schema_texts();
1813 if (!is_array(
$plan) || empty(
$plan[
'ok'])) {
1814 return $this->badge(
$texts->get_fd_message(
'status_dd_error'),
'danger');
1817 if (empty(
$plan[
'table_exists'])) {
1818 return $this->badge(
$texts->get_fd_message(
'status_db_missing'),
'warning');
1821 $adds = count(
$plan[
'add_fields'] ?? array());
1822 $idx = count(
$plan[
'add_indexes'] ?? array());
1823 $miss = count(
$plan[
'missing_in_dd'] ?? array());
1824 $conf = count(
$plan[
'type_conflicts'] ?? array());
1826 if (!empty(
$plan[
'rebuild_needed'])) {
1827 return $this->badge(
$texts->get_fd_message(
'status_rebuild'),
'danger');
1830 if ($adds || $idx) {
1831 return $this->badge(
$texts->get_fd_message(
'status_sync_open'),
'warning');
1834 return $this->badge(
$texts->get_fd_message(
'status_synced'),
'success');
1843 private function sync_detail_from_plan(
$plan) {
1844 if (!is_array(
$plan) || empty(
$plan[
'ok']) || empty(
$plan[
'table_exists'])) {
1848 $adds = count(
$plan[
'add_fields'] ?? array());
1849 $idx = count(
$plan[
'add_indexes'] ?? array());
1850 $miss = count(
$plan[
'missing_in_dd'] ?? array());
1851 $conf = count(
$plan[
'type_conflicts'] ?? array());
1853 if (!empty(
$plan[
'rebuild_needed'])) {
1855 $this->schema_texts()->format_fd_message(
'detail_type', array(
'count' => $conf))
1857 . $this->schema_texts()->format_fd_message(
'detail_extra', array(
'count' => $miss))
1861 if ($adds || $idx) {
1863 $this->schema_texts()->format_fd_message(
'detail_field', array(
'count' => $adds))
1865 . $this->schema_texts()->format_fd_message(
'detail_index', array(
'count' => $idx))
1879 private function get_sync_plan($modul,
$dd) {
1880 $oDD =
dbx()->get_system_obj(
'dbxDD');
1881 return $oDD->sync_dd_to_db($modul,
$dd,
'plan');
1889 private function get_dd_records() {
1891 $oDD =
dbx()->get_system_obj(
'dbxDD');
1895 $norm = str_replace(
'\\',
'/',
$file);
1897 if (!preg_match(
'#/dbx/modules/([^/]+)/dd/([^/]+)\.dd\.php$#', $norm, $match)) {
1903 if (
$dd ===
'new') {
1907 $ddRef = $this->dd_ref($modul,
$dd);
1908 $model =
$oDD->get_dd_model($ddRef);
1913 $server = $model[
'table'][
'server'] ??
'';
1914 $table = $model[
'table'][
'table'] ??
'';
1915 if (str_starts_with(strtolower((
string)
$table),
'sqlite_')) {
1919 $count = $exists ?
$oDD->count($ddRef) :
'-';
1920 $plan = $this->get_sync_plan($modul,
$dd);
1923 $ddFields = array_values((array)($model[
'fields'] ?? array()));
1927 'rid' => $modul .
'|' .
$dd,
1930 'path' => $this->path_rel($norm),
1932 'database' => $this->get_database_label(
$server),
1934 'dd_fields' => $this->field_tooltip_count($ddFields, $this->schema_texts()->get_fd_message(
'dd_fields')),
1935 'db_fields' => $this->field_tooltip_count($dbFields, $this->schema_texts()->get_fd_message(
'db_fields')),
1937 'sync' => $this->sync_label_from_plan(
$plan),
1938 'sync_info' => $this->sync_detail_from_plan(
$plan),
1939 'act_sync' =>
$actions[
'sync'] ??
'',
1940 'act_map' =>
$actions[
'map'] ??
'',
1941 'act_move' =>
$actions[
'transfer'] ??
'',
1942 'act_flds' =>
$actions[
'fields'] ??
'',
1946 usort($records,
function($a, $b) {
1947 return strcmp($a[
'modul'] .
'|' . $a[
'dd'], $b[
'modul'] .
'|' . $b[
'dd']);
1959 private function db_server_aliases(
$server) {
1967 $add =
function(
$value) use (&$aliases) {
1976 $norm = str_replace(
'\\',
'/',
$server);
1981 if (strpos($norm,
'/') !==
false) {
1982 $add(basename($norm));
1985 if (strpos(
$server,
'|') !==
false) {
1986 $parts = explode(
'|',
$server, 2);
1987 $modul = trim((
string)($parts[0] ??
''));
1988 $name = trim((
string)($parts[1] ??
''));
1994 if ($modul !==
'' && $name !==
'') {
1995 $add($modul .
'|' . $name);
1996 $add($modul .
'/db/' . $name);
2000 $moduleFile = $this->resolve_module_db_file(
$server);
2002 $file = str_replace(
'\\',
'/', $moduleFile);
2004 $add($this->path_rel(
$file));
2005 $add(basename(
$file));
2007 if (preg_match(
'#/dbx/modules/([^/]+)/db/([^/]+)$#',
$file, $match)) {
2009 $add($match[1] .
'|' . $match[2]);
2010 $add($match[1] .
'/db/' . $match[2]);
2014 return array_keys($aliases);
2025 private function get_dd_index_by_db($ddRecords) {
2028 foreach ($ddRecords as $record) {
2029 $server = $record[
'server'] ??
'';
2030 $table = $record[
'table'] ??
'';
2036 $rid = (string)($record[
'rid'] ??
'');
2038 $rid = (string)($record[
'modul'] ??
'') .
'|' . (string)($record[
'dd'] ??
'');
2041 foreach ($this->db_server_aliases(
$server) as $alias) {
2042 $key = $this->norm_key($alias,
$table);
2043 if (!isset(
$index[$key])) {
2047 $index[$key][$rid] = $record;
2051 foreach (
$index as $key => $records) {
2052 $index[$key] = array_values($records);
2066 private function get_dd_records_for_db($ddIndex,
$server,
$table) {
2073 foreach ($this->db_server_aliases(
$server) as $alias) {
2074 $key = $this->norm_key($alias,
$table);
2075 if (!isset($ddIndex[$key])) {
2079 foreach ($ddIndex[$key] as $record) {
2080 $rid = (string)($record[
'rid'] ??
'');
2082 $rid = (string)($record[
'modul'] ??
'') .
'|' . (string)($record[
'dd'] ??
'');
2085 $records[$rid] = $record;
2089 return array_values($records);
2111 $mode = (!empty(
$plan[
'rebuild_needed'])) ?
'force' :
'apply';
2113 $syncUrl = $this->build_url(
'dd',
'sync_dd_to_db', array(
2119 $actions[
'sync'] = $this->openwin($syncUrl,
'bi bi-arrow-repeat', $this->schema_texts()->get_fd_message(
'action_dd_to_db'), 1000, 700);
2121 $mapUrl = $this->build_url(
'dd',
'mapping', array(
2122 'kind' =>
'dd_to_db',
2126 $actions[
'map'] = $this->openwin($mapUrl,
'bi bi-diagram-3', $this->schema_texts()->get_fd_message(
'action_mapping'), 1280, 880);
2129 $transferUrl = $this->build_url(
'dd',
'transfer', array(
2131 'source_table' =>
$table,
2133 $actions[
'transfer'] = $this->openwin($transferUrl,
'bi bi-box-arrow-right', $this->schema_texts()->get_fd_message(
'action_transfer'), 980, 720);
2136 $fieldsUrl = $this->build_url(
'dd',
'fields', array(
2140 $actions[
'fields'] = $this->openwin($fieldsUrl,
'bi bi-list-columns', $this->schema_texts()->get_fd_message(
'action_dd_fields'), 1400, 860);
2156 'sync_dd_to_db' =>
'',
2161 if (is_array($dds) && count($dds)) {
2164 $ddToDbMode = (!empty(
$plan[
'rebuild_needed'])) ?
'force' :
'apply';
2166 $syncUrl = $this->build_url(
'db',
'sync_db_to_dd', array(
2169 'modul' =>
$first[
'modul'],
2174 $actions[
'sync'] = $this->openwin($syncUrl,
'bi bi-arrow-down-up', $this->schema_texts()->get_fd_message(
'action_db_to_dd'), 1000, 700);
2176 $ddToDbUrl = $this->build_url(
'db',
'sync_dd_to_db', array(
2177 'modul' =>
$first[
'modul'],
2179 'mode' => $ddToDbMode,
2182 $actions[
'sync_dd_to_db'] = $this->openwin($ddToDbUrl,
'bi bi-arrow-repeat', $this->schema_texts()->get_fd_message(
'action_db_from_dd'), 1000, 700);
2184 $mapUrl = $this->build_url(
'db',
'mapping', array(
2185 'kind' =>
'db_to_dd',
2188 'modul' =>
$first[
'modul'],
2191 $actions[
'map'] = $this->openwin($mapUrl,
'bi bi-diagram-3', $this->schema_texts()->get_fd_message(
'action_mapping'), 1280, 880);
2193 $syncUrl = $this->build_url(
'db',
'sync_db_to_dd', array(
2197 $actions[
'sync'] = $this->openwin($syncUrl,
'bi bi-file-earmark-plus', $this->schema_texts()->get_fd_message(
'action_create_dd'), 980, 720);
2200 $transferUrl = $this->build_url(
'db',
'transfer', array(
2202 'source_table' =>
$table,
2204 $actions[
'transfer'] = $this->openwin($transferUrl,
'bi bi-box-arrow-right', $this->schema_texts()->get_fd_message(
'action_transfer'), 980, 720);
2215 private function get_db_records($ddRecords) {
2217 $oDB =
dbx()->get_system_obj(
'dbxDB');
2218 $oDD =
dbx()->get_system_obj(
'dbxDD');
2219 $ddIndex = $this->get_dd_index_by_db($ddRecords);
2220 $servers = $this->get_server_options();
2223 $tables = $oDB->get_db_tables(
$server);
2224 if (!is_array($tables)) {
2230 foreach ($tables as $tableRec) {
2231 $table = $tableRec[
'name'] ??
'';
2238 $dds = $this->get_dd_records_for_db($ddIndex,
$server,
$table);
2239 $ddLabels = array();
2240 $sync = $this->badge($this->schema_texts()->get_fd_message(
'no_dd'),
'secondary');
2243 foreach ($dds as $ddRec) {
2244 $ddLabels[] = $this->esc($ddRec[
'modul'] .
'|' . $ddRec[
'dd']);
2248 $sync = $dds[0][
'sync'];
2249 $syncInfo = $dds[0][
'sync_info'] ??
'';
2253 $ddFields = array();
2258 $ddFields = array_values((array)($model[
'fields'] ?? array()));
2265 'modul' => $this->get_server_module(
$server),
2267 'database' => $this->get_database_label(
$server),
2268 'path' => $this->get_database_path_label(
$server),
2270 'db_fields' => $this->field_tooltip_count($dbFields, $this->schema_texts()->get_fd_message(
'db_fields')),
2271 'dd_fields' => $this->field_tooltip_count($ddFields, $this->schema_texts()->get_fd_message(
'dd_fields')),
2272 'count' => $tableRec[
'count'] ??
'-',
2273 'dd' => implode(
'<br>', $ddLabels),
2275 'sync_info' => $syncInfo,
2276 'act_sync' =>
$actions[
'sync'] ??
'',
2277 'act_dd_sync' =>
$actions[
'sync_dd_to_db'] ??
'',
2278 'act_map' =>
$actions[
'map'] ??
'',
2279 'act_move' =>
$actions[
'transfer'] ??
'',
2280 'dd_edit_modul' => $dds[0][
'modul'] ??
'',
2281 'dd_edit_dd' => $dds[0][
'dd'] ??
'',
2287 'rid' => $this->encode_db_rid(
$server,
''),
2288 'modul' => $this->get_server_module(
$server),
2290 'database' => $this->get_database_label(
$server),
2291 'path' => $this->get_database_path_label(
$server),
2292 'table' => $this->badge($this->schema_texts()->get_fd_message(
'no_tables'),
'secondary'),
2293 'db_fields' => $this->field_tooltip_count(array(), $this->schema_texts()->get_fd_message(
'db_fields')),
2294 'dd_fields' => $this->field_tooltip_count(array(), $this->schema_texts()->get_fd_message(
'dd_fields')),
2297 'sync' => $this->badge($this->schema_texts()->get_fd_message(
'no_tables'),
'secondary'),
2300 'act_dd_sync' =>
'',
2303 'dd_edit_modul' =>
'',
2309 usort($records,
function($a, $b) {
2310 return strcmp(strip_tags($a[
'modul'] .
'|' . $a[
'server'] .
'|' . $a[
'table']), strip_tags($b[
'modul'] .
'|' . $b[
'server'] .
'|' . $b[
'table']));
2324 $texts = $this->schema_texts();
2325 $oDB =
dbx()->get_system_obj(
'dbxDB');
2326 $oDD =
dbx()->get_system_obj(
'dbxDD');
2329 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'backup_table_not_found'));
2334 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'backup_fields_read_error'));
2339 if (!is_array(
$rows)) {
2340 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'backup_data_read_error'));
2345 $path = $this->backup_dir() .
$file;
2346 $tmp = $path .
'.tmp';
2350 'created_at' => date(
'Y-m-d H:i:s'),
2353 'db_type' => $oDB->get_db_type(
$server),
2354 'count' => count(
$rows),
2360 $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
2361 if (
$json ===
false || file_put_contents(
$tmp,
$json) ===
false || !@rename(
$tmp, $path)) {
2363 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'backup_write_error'));
2366 return array(
'ok' => 1,
'file' =>
$file,
'path' => $path,
'count' => count(
$rows));
2375 private function backup_meta(
$file) {
2376 $path = $this->backup_file_path(
$file);
2381 $raw = file_get_contents($path);
2382 $data = $raw !==
false ? json_decode($raw,
true) :
null;
2383 if (!is_array($data)) {
2387 $data[
'_file'] = basename($path);
2388 $data[
'_path'] = $path;
2401 foreach (glob($this->backup_dir() .
'*.json') as
$file) {
2402 $meta = $this->backup_meta(basename(
$file));
2403 if (($meta[
'server'] ??
'') ===
$server && ($meta[
'table'] ??
'') ===
$table) {
2408 usort(
$items,
function($a, $b) {
2409 return strcmp((
string)($b[
'created_at'] ??
''), (
string)($a[
'created_at'] ??
''));
2424 return $items[0] ?? array();
2435 $texts = $this->schema_texts();
2438 return $this->badge(
$texts->get_fd_message(
'no_backup'),
'secondary');
2441 $created = (string)($meta[
'created_at'] ??
'');
2442 $count = (int)($meta[
'count'] ?? 0);
2454 private function restore_table_from_backup(
$file) {
2455 $texts = $this->schema_texts();
2456 $meta = $this->backup_meta(
$file);
2458 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'backup_invalid_file'));
2461 $server = (string)($meta[
'server'] ??
'');
2462 $table = (string)($meta[
'table'] ??
'');
2463 $fields = is_array($meta[
'fields'] ??
null) ? $meta[
'fields'] : array();
2464 $indexes = is_array($meta[
'indexes'] ??
null) ? $meta[
'indexes'] : array();
2465 $rows = is_array($meta[
'rows'] ??
null) ? $meta[
'rows'] : array();
2468 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'backup_incomplete_metadata'));
2471 $oDB =
dbx()->get_system_obj(
'dbxDB');
2472 $oDD =
dbx()->get_system_obj(
'dbxDD');
2476 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'restore_create_table_error'));
2480 return array(
'ok' => 1,
'count' => 0,
'file' => basename((
string)
$file));
2485 $name = (string)(
$field[
'name'] ??
'');
2492 return array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'restore_no_target_fields'));
2495 $qNames = array_map(
function($name) use (
$server) {
2496 return $this->quote_db_ident(
$server, $name);
2499 foreach (
$rows as $row) {
2501 foreach ($names as $name) {
2502 $values[] = $this->sql_db_value(
$server, is_array($row) && array_key_exists($name, $row) ? $row[$name] :
null);
2505 $sql =
'INSERT INTO ' . $this->quote_db_ident(
$server,
$table)
2506 .
' (' . implode(
',', $qNames) .
') VALUES (' . implode(
',', $values) .
')';
2508 if (!is_array(
$ok) && (
int)
$ok === 0 && $oDB->get_error_status() !==
'') {
2509 return array(
'ok' => 0,
'msg' =>
'Daten konnten nicht eingetragen werden.');
2513 return array(
'ok' => 1,
'count' => count(
$rows),
'file' => basename((
string)
$file));
2523 private function filter_rows(
$rows, $search) {
2524 $search = trim((
string) $search);
2525 if ($search ===
'') {
2530 foreach (
$rows as $row) {
2531 $haystack = strtolower(strip_tags(implode(
' ', $row)));
2532 if (strpos($haystack, strtolower($search)) !==
false) {
2545 private function has_config_db_servers() {
2551 $moduleFiles = $this->get_module_db_files();
2552 $moduleFileIndex = array();
2555 $real = realpath((
string)(
$db[
'file'] ??
''));
2557 $moduleFileIndex[strtolower(str_replace(
'\\',
'/', $real))] = 1;
2562 if (isset($moduleFiles[
$server])) {
2566 $type = strtolower((
string)($data[
'type'] ??
''));
2567 $host = (string)($data[
'host'] ??
'');
2568 $name = (string)($data[
'dbname'] ?? ($data[
'name'] ??
''));
2569 $isSqlite = ($type ==
'sqlite' || $type ==
'sqlite3' || preg_match(
'/\.(db3|sqlite|sqlite3)$/i', $name));
2571 if ($isSqlite && ($host !==
'' || $name !==
'')) {
2572 $file =
dbx()->os_path($host . $name);
2573 $real = realpath(
$file);
2576 $real = strtolower(str_replace(
'\\',
'/', $real));
2577 if (isset($moduleFileIndex[$real])) {
2596 private function get_module_filter_options(
$rows, $includeSys =
false) {
2597 $options = array(
'0' => $this->schema_texts()->get_fd_message(
'all_modules'));
2603 foreach (
$rows as $row) {
2604 $modul = trim((
string)($row[
'modul'] ??
''));
2605 if ($modul !==
'') {
2611 return array(
'0' => $this->schema_texts()->get_fd_message(
'all_modules')) +
$options;
2621 private function filter_rows_by_module(
$rows, $modul) {
2622 $modul = trim((
string)$modul);
2623 if ($modul ===
'' || $modul ===
'0') {
2628 foreach (
$rows as $row) {
2629 if (strcasecmp((
string)($row[
'modul'] ??
''), $modul) === 0) {
2648 private function run_schema_report(
$mode,
$rows, $flds, $action, $batchOptions, $message =
'') {
2649 $oReport =
dbx()->get_system_obj(
'dbxReport');
2651 'dd' =>
'report-dd-sync',
2652 'db' =>
'report-db-sync',
2653 'backup' =>
'report-db-backup',
2654 'restore' =>
'report-db-restore',
2656 $fid = $fidMap[
$mode] ??
'report-db-sync';
2657 $moduleOptions = $this->get_module_filter_options(
$rows,
$mode !=
'dd' && $this->has_config_db_servers());
2658 $isSchemaList = in_array(
$mode, array(
'dd',
'db'),
true);
2660 $oReport->init($fid,
'report-schema');
2661 $oReport->_fd =
'dbxAdmin|schema-report';
2662 $oReport->load_fd_messages();
2663 $oReport->set_form_help_enabled(
false);
2664 $oReport->set_callback_owner($this);
2665 $oReport->set_callback(
'row_action_data',
'schema_row_action_data');
2666 $oReport->_action = $action;
2667 $oReport->_rflds = $flds;
2668 $oReport->_mode =
'table';
2669 $oReport->_pages =
true;
2670 $oReport->_rrows = 25;
2671 $oReport->_rpos = 0;
2672 $oReport->_but_pagination = 7;
2673 $oReport->_create_row_select =
true;
2674 $oReport->_create_row_edit = $isSchemaList;
2675 $oReport->_create_row_show = $isSchemaList;
2676 $oReport->_create_row_delete = $isSchemaList;
2677 $oReport->_create_sel_flds =
false;
2678 $oReport->_fld_id =
'rid';
2679 $oReport->_table_buttons =
'left';
2680 $texts = $this->schema_texts();
2681 $oReport->_msg_confirm_delete = (
$mode ==
'dd')
2682 ?
$texts->get_fd_message(
'confirm_delete_dd')
2683 :
$texts->get_fd_message(
'confirm_delete_db');
2684 $oReport->_msg_info = (string)$message;
2685 $oReport->_tabel_tpls[
'tpl_row_edit'] =
'modul|schema_row_edit';
2686 $oReport->_tabel_tpls[
'tpl_row_show'] =
'modul|schema_row_show';
2687 $oReport->_tabel_tpls[
'tpl_row_delete'] =
'modul|schema_row_delete';
2689 $oReport->add_action(
'rows_select',
'action_button_select',
'&dbx_do=multi_select');
2690 $oReport->add_action(
'rows_deselect',
'action_button_deselect',
'&dbx_do=multi_deselect');
2692 $barPrefix = in_array(
$mode, array(
'dd',
'backup',
'restore'),
true) ?
$mode :
'db';
2693 $oReport->add_rep(
'bar_title',
$texts->get_fd_message(
'bar_' . $barPrefix .
'_title'));
2694 $oReport->add_rep(
'bar_subtitle',
$texts->get_fd_message(
'bar_' . $barPrefix .
'_subtitle'));
2695 $oReport->add_fld(
'dbx_rmodul',
'select-single-label', label:
$texts->get_fd_message(
'label_module'), rules:
'parameter', options: $moduleOptions);
2696 $oReport->add_fld(
'dbx_rwhere',
'dbx|search', label:
$texts->get_fd_message(
'label_search'), rules:
'sqlsearch|max=80');
2697 $oReport->add_fld(
'dbx_rrows',
'integer-label', label:
$texts->get_fd_message(
'label_rows'), rules:
'int');
2698 $oReport->add_fld(
'maction_select',
'select-single', label:
$texts->get_fd_message(
'label_batch'), rules:
'parameter', options: $batchOptions);
2699 $oReport->add_obj(
'maction_submit',
'dbx|button-submit', data:
'label=' .
$texts->get_fd_message(
'action_start'));
2701 if ($oReport->submit()) {
2702 $act = $oReport->get_fld_val(
'maction_select',
'0',
'parameter');
2703 if ($act && $act !=
'0') {
2704 $selects = array_keys($oReport->get_multi_selects());
2706 return $this->start_batch($act, $selects);
2709 $oReport->_msg_warning =
$texts->get_fd_message(
'no_rows_selected');
2713 $modul = $oReport->get_fld_val(
'dbx_rmodul',
'0',
'parameter');
2714 $search = $oReport->get_fld_val(
'dbx_rwhere',
'',
'sqlsearch|max=80');
2715 $rrows = $oReport->get_fld_val(
'dbx_rrows', 25,
'int');
2716 $rpos = $oReport->get_fld_val(
'dbx_rpos', 0,
'int');
2721 $rows = $this->filter_rows_by_module(
$rows, $modul);
2723 $oReport->_rrows = $rrows;
2724 $oReport->_rpos = $rpos;
2725 $oReport->_rcount = count(
$rows);
2726 $oReport->_rdata = $oReport->data_rows(
$rows, $rpos, $rrows);
2728 return $oReport->run();
2739 if (!is_array($data) || !isset($data[
'data']) || !is_array($data[
'data'])) {
2743 $type = (string)($data[
'type'] ??
'');
2744 if (!in_array($type, array(
'edit',
'show',
'delete'),
true)) {
2748 $record = is_array($data[
'record'] ??
null) ? $data[
'record'] : array();
2749 $rid = (string)($data[
'data'][
'rid'] ?? ($record[
'rid'] ??
''));
2750 $mode = str_starts_with($rid,
'db_') ?
'db' :
'dd';
2751 $base = $this->build_url(
$mode,
$mode ==
'dd' ?
'list_dd' :
'list_db');
2753 if ($type ==
'edit') {
2757 if (
$mode ==
'dd') {
2758 [$modul,
$dd] = $this->split_dd_rid($rid);
2760 $modul = (string)($record[
'dd_edit_modul'] ??
'');
2761 $dd = (string)($record[
'dd_edit_dd'] ??
'');
2764 if ($modul &&
$dd) {
2765 $editUrl =
'?dbx_modul=dbxAdmin&dbx_run1=edit_dd&modul=' . rawurlencode($modul) .
'&dd=' . rawurlencode(
$dd);
2766 $editTitle = $this->schema_texts()->format_fd_message(
'edit_dd', array(
'module' => $modul,
'dd' =>
$dd));
2767 $data[
'data'][
'edit_url'] = $this->esc($editUrl);
2768 $data[
'data'][
'edit_title'] = $this->esc($editTitle);
2769 $data[
'data'][
'edit_data_dBx'] =
'data-url="' . $this->esc($editUrl) .
'" data-title="' . $this->esc($editTitle) .
'"';
2771 $data[
'data'][
'edit_url'] =
'#';
2772 $data[
'data'][
'edit_title'] = $this->esc($this->schema_texts()->get_fd_message(
'no_dd_assigned'));
2773 $data[
'data'][
'edit_data_dBx'] =
'';
2774 $data[
'data'][
'class'] = trim((
string)($data[
'data'][
'class'] ??
'') .
' disabled text-muted');
2780 $showUrl = $this->append_url_params(
$base, array(
2781 'dbx_do' =>
'row_show',
2785 'dbx_do' =>
'row_delete',
2789 $data[
'data'][
'show_url'] = $this->esc($showUrl);
2790 $data[
'data'][
'delete_url'] = $this->esc(
$deleteUrl);
2801 private function handle_dd_row_action(&$message) {
2802 $do =
dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
2803 if (!in_array($do, array(
'row_show',
'row_delete'),
true)) {
2807 $rid =
dbx()->get_modul_var(
'rid',
'',
'parameter');
2808 [$modul,
$dd] = $this->split_dd_rid($rid);
2809 if (!$modul || !
$dd) {
2810 return '<div class="alert alert-danger">DD-Auswahl ist ungueltig.</div>';
2813 $ddRef = $this->dd_ref($modul,
$dd);
2815 if ($do ==
'row_show') {
2816 $params = array(
'rid' => $rid);
2817 return $this->run_data_report(
2819 'DD Daten: ' . $modul .
'|' .
$dd,
2820 $this->build_url(
'dd',
'data_read', $params),
2821 $this->build_url(
'dd',
'data_save', $params),
2822 $this->build_url(
'dd',
'data_insert', $params),
2823 $this->build_url(
'dd',
'data_delete', $params)
2827 if ($this->delete_dd_file($modul,
$dd)) {
2828 $message =
'DD-Datei geloescht: ' . $modul .
'|' .
$dd;
2830 $message =
'DD-Datei konnte nicht geloescht werden: ' . $modul .
'|' .
$dd;
2842 private function handle_db_row_action(&$message) {
2843 $do =
dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
2844 if (!in_array($do, array(
'row_show',
'row_delete'),
true)) {
2848 $rid =
dbx()->get_modul_var(
'rid',
'',
'parameter');
2851 return '<div class="alert alert-danger">DB-Auswahl ist ungueltig.</div>';
2854 if ($do ==
'row_show') {
2857 return '<div class="alert alert-danger">Ansichts-DD konnte nicht erzeugt werden.</div>';
2860 $params = array(
'rid' => $rid);
2861 return $this->run_data_report(
2864 $this->build_url(
'db',
'data_read', $params),
2865 $this->build_url(
'db',
'data_save', $params),
2866 $this->build_url(
'db',
'data_insert', $params),
2867 $this->build_url(
'db',
'data_delete', $params)
2871 $oDD =
dbx()->get_system_obj(
'dbxDD');
2873 $message =
'DB-Tabelle geloescht: ' .
$server .
'|' .
$table;
2875 $message =
'DB-Tabelle konnte nicht geloescht werden: ' .
$server .
'|' .
$table;
2886 private function report_dd() {
2887 $texts = $this->schema_texts();
2889 $content = $this->handle_dd_row_action($message);
2895 'modul' =>
$texts->get_fd_message(
'column_module'),
2896 'dd' =>
$texts->get_fd_message(
'column_dd'),
2897 'path' =>
$texts->get_fd_message(
'column_path'),
2898 'server' =>
$texts->get_fd_message(
'column_server'),
2899 'database' =>
$texts->get_fd_message(
'column_database'),
2900 'table' =>
$texts->get_fd_message(
'column_table'),
2901 'dd_fields' =>
$texts->get_fd_message(
'column_dd_fields'),
2902 'db_fields' =>
$texts->get_fd_message(
'column_db_fields'),
2903 'count' =>
$texts->get_fd_message(
'column_records'),
2904 'sync' =>
$texts->get_fd_message(
'column_status'),
2905 'sync_info' =>
$texts->get_fd_message(
'column_details'),
2906 'act_sync' => $this->header_icon(
'bi bi-arrow-repeat',
$texts->get_fd_message(
'action_dd_to_db')),
2907 'act_map' => $this->header_icon(
'bi bi-diagram-3',
$texts->get_fd_message(
'action_mapping')),
2908 'act_move' => $this->header_icon(
'bi bi-box-arrow-right',
$texts->get_fd_message(
'action_transfer')),
2909 'act_flds' => $this->header_icon(
'bi bi-list-columns',
$texts->get_fd_message(
'action_dd_fields')),
2913 '0' =>
$texts->get_fd_message(
'batch_placeholder'),
2914 'batch_dd_to_db' =>
$texts->get_fd_message(
'batch_dd_to_db'),
2915 'batch_dd_to_db_force' =>
$texts->get_fd_message(
'batch_dd_to_db_force'),
2918 return $this->run_schema_report(
2920 $this->get_dd_records(),
2922 $this->build_url(
'dd',
'list_dd'),
2933 private function report_db() {
2934 $texts = $this->schema_texts();
2936 $content = $this->handle_db_row_action($message);
2942 'modul' =>
$texts->get_fd_message(
'column_module'),
2943 'server' =>
$texts->get_fd_message(
'column_server'),
2944 'table' =>
$texts->get_fd_message(
'column_table'),
2945 'db_fields' =>
$texts->get_fd_message(
'column_db_fields'),
2946 'dd_fields' =>
$texts->get_fd_message(
'column_dd_fields'),
2947 'count' =>
$texts->get_fd_message(
'column_records'),
2948 'dd' =>
$texts->get_fd_message(
'column_dd'),
2949 'sync' =>
$texts->get_fd_message(
'column_status'),
2950 'sync_info' =>
$texts->get_fd_message(
'column_details'),
2951 'act_sync' => $this->header_icon(
'bi bi-arrow-down-up',
$texts->get_fd_message(
'action_db_to_dd')),
2952 'act_dd_sync' => $this->header_icon(
'bi bi-arrow-repeat',
$texts->get_fd_message(
'action_db_from_dd')),
2953 'act_map' => $this->header_icon(
'bi bi-diagram-3',
$texts->get_fd_message(
'action_mapping')),
2954 'act_move' => $this->header_icon(
'bi bi-box-arrow-right',
$texts->get_fd_message(
'action_transfer')),
2958 '0' =>
$texts->get_fd_message(
'batch_placeholder'),
2959 'batch_db_to_dd' =>
$texts->get_fd_message(
'batch_db_to_dd'),
2960 'batch_dd_to_db' =>
$texts->get_fd_message(
'batch_dd_to_db'),
2961 'batch_dd_to_db_force' =>
$texts->get_fd_message(
'batch_dd_to_db_force'),
2964 return $this->run_schema_report(
'db', $this->get_db_records($this->get_dd_records()), $flds, $this->build_url(
'db',
'list_db'), $batch, $message);
2971 private function backup_restore_rows() {
2972 $rows = $this->get_db_records($this->get_dd_records());
2973 $texts = $this->schema_texts();
2975 foreach (
$rows as $no => $row) {
2976 [
$server,
$table] = $this->decode_db_rid((
string)($row[
'rid'] ??
''));
2979 $server = (string)($row[
'server'] ??
'');
2983 $rows[$no][
'backup'] = $this->badge(
$texts->get_fd_message(
'no_table'),
'secondary');
2984 $rows[$no][
'act_backup'] =
'';
2985 $rows[$no][
'act_restore'] =
'';
2991 $backupUrl = $this->build_url(
'dd',
'backup_db_table', array(
2996 $rows[$no][
'act_backup'] = $this->openwin(
2999 $texts->get_fd_message(
'action_backup_create'),
3004 $restoreUrl = $this->build_url(
'dd',
'restore_db_table', array(
3008 $rows[$no][
'act_restore'] = $this->openwin(
3011 $texts->get_fd_message(
'action_restore_select'),
3025 private function report_backup_db() {
3026 $texts = $this->schema_texts();
3028 'modul' =>
$texts->get_fd_message(
'column_module'),
3029 'server' =>
$texts->get_fd_message(
'column_server'),
3030 'database' =>
$texts->get_fd_message(
'column_database'),
3031 'table' =>
$texts->get_fd_message(
'column_table'),
3032 'count' =>
$texts->get_fd_message(
'column_records'),
3033 'path' =>
$texts->get_fd_message(
'column_path'),
3034 'backup' =>
$texts->get_fd_message(
'column_last_backup'),
3035 'act_backup' => $this->header_icon(
'bi bi-download',
$texts->get_fd_message(
'action_backup_create')),
3039 '0' =>
$texts->get_fd_message(
'batch_placeholder'),
3040 'batch_backup_db' =>
$texts->get_fd_message(
'batch_backup_selected'),
3043 return $this->run_schema_report(
'backup', $this->backup_restore_rows(), $flds, $this->build_url(
'dd',
'backup_db'), $batch,
'');
3051 private function report_restore_db() {
3052 $texts = $this->schema_texts();
3054 'modul' =>
$texts->get_fd_message(
'column_module'),
3055 'server' =>
$texts->get_fd_message(
'column_server'),
3056 'database' =>
$texts->get_fd_message(
'column_database'),
3057 'table' =>
$texts->get_fd_message(
'column_table'),
3058 'count' =>
$texts->get_fd_message(
'column_records'),
3059 'path' =>
$texts->get_fd_message(
'column_path'),
3060 'backup' =>
$texts->get_fd_message(
'column_last_backup'),
3061 'act_restore' => $this->header_icon(
'bi bi-upload',
$texts->get_fd_message(
'action_restore_choose')),
3065 '0' =>
$texts->get_fd_message(
'batch_placeholder'),
3066 'batch_restore_latest_db' =>
$texts->get_fd_message(
'batch_restore_selected'),
3069 return $this->run_schema_report(
'restore', $this->backup_restore_rows(), $flds, $this->build_url(
'dd',
'restore_db'), $batch,
'');
3079 private function append_url_params(
$url, $params = array()) {
3084 foreach ($params as $key =>
$value) {
3085 $url .= (strpos(
$url,
'?') ===
false ?
'?' :
'&')
3086 . rawurlencode((
string)$key) .
'=' . rawurlencode((
string)
$value);
3098 private function process_status_label($status) {
3102 'running' =>
'Laeuft',
3103 'paused' =>
'Angehalten',
3104 'canceled' =>
'Abgebrochen',
3105 'finished' =>
'Fertig',
3106 'error' =>
'Fehler',
3109 return $map[$status] ?? $status;
3118 private function process_status_class($status) {
3120 'running' =>
'bg-primary',
3121 'paused' =>
'bg-warning text-dark',
3122 'canceled' =>
'bg-danger',
3123 'finished' =>
'bg-success',
3124 'error' =>
'bg-danger',
3125 'new' =>
'bg-secondary',
3126 'reset' =>
'bg-secondary',
3129 return $map[$status] ??
'bg-secondary';
3138 private function process_status_icon($status) {
3140 'running' =>
'bi bi-play-fill',
3141 'paused' =>
'bi bi-pause-fill',
3142 'canceled' =>
'bi bi-x-lg',
3143 'finished' =>
'bi bi-check-lg',
3144 'error' =>
'bi bi-exclamation-triangle',
3145 'new' =>
'bi bi-circle',
3146 'reset' =>
'bi bi-arrow-clockwise',
3149 return $map[$status] ??
'bi bi-circle';
3158 private function process_type_label($type) {
3160 'sync_dd_to_db' =>
'DD -> DB',
3161 'sync_db_to_dd' =>
'DB -> DD',
3162 'transfer_table' =>
'Transfer',
3163 'backup' =>
'Backup',
3164 'restore' =>
'Restore',
3165 'schema_batch' =>
'Batch',
3168 return $map[$type] ?? ($type ?:
'Prozess');
3177 private function process_task_label($phase) {
3178 $phase = (string)$phase;
3180 if (strpos($phase,
'backup') !==
false)
return 'Aufgabe Backup';
3181 if (strpos($phase,
'restore') !==
false)
return 'Aufgabe Restore';
3182 if (strpos($phase,
'create') !==
false || strpos($phase,
'rename') !==
false || strpos($phase,
'drop') !==
false)
return 'Aufgabe Struktur';
3183 if (strpos($phase,
'add_') === 0)
return 'Aufgabe Schema';
3184 if (strpos($phase,
'read') !==
false || strpos($phase,
'merge') !==
false || strpos($phase,
'write') !==
false)
return 'Aufgabe DD';
3185 if (strpos($phase,
'prepare') !==
false)
return 'Aufgabe Vorbereitung';
3186 if (strpos($phase,
'batch') !==
false)
return 'Aufgabe Batch';
3197 private function process_step_label($phase) {
3199 'prepare' =>
'Vorbereiten',
3200 'prepare_target' =>
'Ziel vorbereiten',
3201 'create_table' =>
'Tabelle anlegen',
3202 'add_fields' =>
'Felder ergaenzen',
3203 'add_indexes' =>
'Indizes ergaenzen',
3204 'backup_old' =>
'Backup erstellen',
3205 'backup_source' =>
'Quelle sichern',
3206 'rename_old' =>
'Alte Tabelle umbenennen',
3207 'create_new' =>
'Neue Tabelle anlegen',
3208 'restore_new' =>
'Daten einlesen',
3209 'restore_target' =>
'Ziel einlesen',
3210 'drop_old' =>
'Alte Tabelle entfernen',
3211 'read_schema' =>
'Schema lesen',
3212 'merge_meta' =>
'DD zusammenfuehren',
3213 'write_dd' =>
'DD schreiben',
3214 'batch_step' =>
'Eintrag bearbeiten',
3217 return $map[$phase] ?? ($phase ?:
'Schritt');
3226 private function process_message_label($message) {
3227 $message = (string)$message;
3229 'process paused' =>
'Prozess angehalten',
3230 'process resumed' =>
'Prozess fortgesetzt',
3231 'process continued' =>
'Prozess fortgesetzt',
3232 'process canceled' =>
'Prozess abgebrochen',
3233 'process restarted' =>
'Prozess neu gestartet',
3234 'sync initialized' =>
'Synchronisierung vorbereitet',
3235 'transfer initialized' =>
'Transfer vorbereitet',
3236 'schema loaded' =>
'Schema gelesen',
3237 'meta merged' =>
'DD-Informationen zusammengefuehrt',
3238 'sync dd -> db finished' =>
'DD -> DB abgeschlossen',
3239 'sync dd -> db rebuild finished' =>
'DD -> DB Rebuild abgeschlossen',
3240 'sync db -> dd finished' =>
'DB -> DD abgeschlossen',
3241 'transfer finished' =>
'Transfer abgeschlossen',
3242 'batch initialized' =>
'Batch vorbereitet',
3243 'batch finished' =>
'Batch abgeschlossen',
3244 'backup finished' =>
'Backup abgeschlossen',
3245 'restore finished' =>
'Restore abgeschlossen',
3248 return $map[$message] ?? $message;
3260 private function render_process($title,
$state, $nextUrl =
'', $backUrl =
'') {
3261 $oTPL =
dbx()->get_system_obj(
'dbxTPL');
3262 $status =
$state[
'status'] ??
'running';
3263 $percent = (int)(
$state[
'percent'] ?? 0);
3264 if ($percent < 0) $percent = 0;
3265 if ($percent > 100) $percent = 100;
3267 $stepPercent = (int)(
$state[
'step_percent'] ?? $percent);
3268 if ($stepPercent < 0) $stepPercent = 0;
3269 if ($stepPercent > 100) $stepPercent = 100;
3271 $barClass =
'bg-primary';
3272 if ($status ==
'finished') $barClass =
'bg-success';
3273 if ($status ==
'error' || $status ==
'canceled') $barClass =
'bg-danger';
3274 if ($status ==
'paused') $barClass =
'bg-warning';
3276 $restartUrl = $nextUrl ? $this->append_url_params($nextUrl, array(
'reset' => 1,
'proc_cmd' =>
'restart')) :
'';
3277 $targetId =
'dbx_process_' . substr(md5((
string)(
$state[
'proc_key'] ?? $title)), 0, 14);
3278 $phase = (string)(
$state[
'phase'] ??
'');
3279 $procType = (string)(
$state[
'proc_type'] ?? (
$state[
'act'] ??
'schema_batch'));
3280 $hasControls = $nextUrl ? 1 : 0;
3281 $autostart = ($nextUrl && $status ==
'running') ? 1 : 0;
3284 'target_id' => $this->esc($targetId),
3285 'title' => $this->esc($title),
3286 'status_key' => $this->esc($status),
3287 'status_label' => $this->esc($this->process_status_label($status)),
3288 'status_class' => $this->esc($this->process_status_class($status)),
3289 'status_icon' => $this->esc($this->process_status_icon($status)),
3290 'message' => $this->esc($this->process_message_label(
$state[
'message'] ??
'')),
3291 'percent' => $percent,
3292 'step_percent' => $stepPercent,
3293 'bar_class' => $this->esc($barClass),
3294 'task_label' => $this->esc($this->process_task_label($phase)),
3295 'step_label' => $this->esc($this->process_step_label($phase)),
3296 'process_label' => $this->esc($this->process_type_label($procType)),
3297 'updated_at' => $this->esc(
$state[
'updated_at'] ??
''),
3298 'next_url' => $this->esc($nextUrl),
3299 'pause_url' => $this->esc($this->append_url_params($nextUrl, array(
'proc_cmd' =>
'pause'))),
3300 'resume_url' => $this->esc($this->append_url_params($nextUrl, array(
'proc_cmd' =>
'resume'))),
3301 'continue_url' => $this->esc($this->append_url_params($nextUrl, array(
'proc_cmd' =>
'continue'))),
3302 'cancel_url' => $this->esc($this->append_url_params($nextUrl, array(
'proc_cmd' =>
'cancel'))),
3303 'restart_url' => $this->esc($restartUrl),
3304 'autostart' => $autostart,
3306 'pause_visible' => $hasControls ?
'running' :
'_none',
3307 'resume_visible' => $hasControls ?
'paused' :
'_none',
3308 'continue_visible' => $hasControls ?
'canceled' :
'_none',
3309 'restart_visible' => $restartUrl ?
'paused,canceled,error,finished' :
'_none',
3310 'cancel_visible' => $hasControls ?
'running,paused' :
'_none',
3311 'back_url' => $this->esc($backUrl),
3314 return $oTPL->get_tpl(
'dbxAdmin|schema-process', $data);
3322 private function run_sync_dd_to_db() {
3323 $modul =
dbx()->get_modul_var(
'modul',
'dbx',
'parameter');
3324 $dd =
dbx()->get_modul_var(
'dd',
'',
'parameter');
3325 $mode =
dbx()->get_modul_var(
'mode',
'apply',
'parameter');
3326 $reset =
dbx()->get_modul_var(
'reset', 0,
'int');
3327 $cmd =
dbx()->get_modul_var(
'proc_cmd',
'',
'parameter');
3328 $oDD =
dbx()->get_system_obj(
'dbxDD');
3331 return '<div class="alert alert-danger">DD fehlt.</div>';
3335 $oDD->sync_dd_to_db($modul,
$dd,
'reset');
3338 $nextUrl = $this->build_url(
'dd',
'sync_dd_to_db', array(
3344 if ($cmd && in_array($cmd, array(
'pause',
'resume',
'continue',
'cancel'),
true)) {
3346 return $this->render_process(
'DD -> DB: ' . $modul .
'|' .
$dd,
$state, $nextUrl, $this->build_url(
'dd',
'list_dd'));
3351 $content = $this->render_process(
'DD -> DB: ' . $modul .
'|' .
$dd,
$state, $nextUrl, $this->build_url(
'dd',
'list_dd'));
3353 if ((
$state[
'status'] ??
'') ==
'error' && strpos((
string)(
$state[
'message'] ??
''),
'rebuild needed') !==
false) {
3354 $force = $this->build_url(
'dd',
'sync_dd_to_db', array(
3360 $content .=
'<div class="mt-3"><a class="btn btn-danger" href="' . $this->esc(
$force) .
'">Rebuild starten</a></div>';
3371 private function run_sync_db_to_dd() {
3372 $server =
dbx()->get_modul_var(
'server',
'',
'parameter');
3373 $table =
dbx()->get_modul_var(
'table',
'',
'parameter');
3374 $modul =
dbx()->get_modul_var(
'modul',
'',
'parameter');
3375 $dd =
dbx()->get_modul_var(
'dd',
'',
'parameter');
3376 $mode =
dbx()->get_modul_var(
'mode',
'merge',
'parameter');
3377 $reset =
dbx()->get_modul_var(
'reset', 0,
'int');
3378 $cmd =
dbx()->get_modul_var(
'proc_cmd',
'',
'parameter');
3381 return '<div class="alert alert-danger">Server oder Tabelle fehlt.</div>';
3384 if (!$modul || !
$dd) {
3388 $oDD =
dbx()->get_system_obj(
'dbxDD');
3393 $nextUrl = $this->build_url(
'db',
'sync_db_to_dd', array(
3401 if ($cmd && in_array($cmd, array(
'pause',
'resume',
'continue',
'cancel'),
true)) {
3403 return $this->render_process(
'DB -> DD: ' .
$server .
'|' .
$table .
' -> ' . $modul .
'|' .
$dd,
$state, $nextUrl, $this->build_url(
'db',
'list_db'));
3408 return $this->render_process(
'DB -> DD: ' .
$server .
'|' .
$table .
' -> ' . $modul .
'|' .
$dd,
$state, $nextUrl, $this->build_url(
'db',
'list_db'));
3419 $oForm =
dbx()->get_system_obj(
'dbxForm');
3420 $oForm->init(
'form-schema-action');
3421 $oForm->_fd =
'dbxAdmin|schema-report';
3422 $oForm->load_fd_messages();
3423 $oForm->_data = array(
3427 'dd' => $this->sanitize_dd_name(
$table),
3429 $oForm->_action = $this->build_url(
'db',
'sync_db_to_dd', array(
3433 $oForm->_msg_info = $oForm->get_fd_message(
'db_to_dd_info');
3435 $oForm->add_fld(
'server',
'hidden', rules:
'parameter');
3436 $oForm->add_fld(
'table',
'hidden', rules:
'parameter');
3439 'select-single-label',
3440 label: $oForm->get_fd_message(
'label_module'),
3442 options: $this->get_module_options()
3447 label: $oForm->get_fd_message(
'column_dd'),
3448 rules:
'parameter|min=1'
3451 if ($oForm->submit() && !$oForm->errors()) {
3452 $modul = $oForm->get_post(
'modul',
'dbx',
'parameter');
3453 $dd = $this->sanitize_dd_name($oForm->get_post(
'dd',
$table,
'parameter'));
3455 dbx()->set_modul_var(
'modul', $modul);
3456 dbx()->set_modul_var(
'dd',
$dd);
3457 dbx()->set_modul_var(
'mode',
'merge');
3458 dbx()->set_modul_var(
'reset', 1);
3460 return $this->run_sync_db_to_dd();
3463 return $oForm->run();
3471 private function run_mapping_editor() {
3472 $texts = $this->schema_texts();
3473 $kind =
dbx()->get_modul_var(
'kind',
'dd_to_db',
'parameter');
3474 $modul =
dbx()->get_modul_var(
'modul',
'',
'parameter');
3475 $dd =
dbx()->get_modul_var(
'dd',
'',
'parameter');
3476 $server =
dbx()->get_modul_var(
'server',
'',
'parameter');
3477 $table =
dbx()->get_modul_var(
'table',
'',
'parameter');
3479 if (!
$dd && $kind !==
'transfer') {
3480 return '<div class="alert alert-danger">'
3481 . $this->esc(
$texts->get_fd_message(
'missing_dd'))
3485 $oDD =
dbx()->get_system_obj(
'dbxDD');
3493 $model =
$oDD->build_schema_mapping($kind, $context);
3495 $oForm =
dbx()->get_system_obj(
'dbxForm');
3496 $oForm->init(
'schema-mapping');
3497 $oForm->_fd =
'dbxAdmin|schema-report';
3498 $oForm->load_fd_messages();
3499 $oForm->_msg_info =
'';
3500 $oForm->_action = $this->build_url(($kind ==
'db_to_dd') ?
'db' :
'dd',
'mapping', array(
3508 $oForm->_data = array(
3516 $oForm->add_fld(
'kind',
'hidden', rules:
'parameter');
3517 $oForm->add_fld(
'modul',
'hidden', rules:
'parameter');
3518 $oForm->add_fld(
'dd',
'hidden', rules:
'parameter');
3519 $oForm->add_fld(
'server',
'hidden', rules:
'parameter');
3520 $oForm->add_fld(
'table',
'hidden', rules:
'parameter');
3522 if ($oForm->submit() && !$oForm->errors()) {
3523 $mapping = $this->posted_mapping_from_model($model);
3524 $ok =
$oDD->save_schema_mapping($kind, $model[
'context'] ?? $context, $mapping);
3527 $oForm->_msg_success = $oForm->get_fd_message(
'mapping_saved');
3528 $model =
$oDD->build_schema_mapping($kind, $model[
'context'] ?? $context);
3530 $oForm->_msg_error = $oForm->get_fd_message(
'mapping_save_error');
3534 $oForm->add_obj(
'mapping_board',
'obj-value', $this->render_mapping_board($model));
3535 $oForm->add_obj(
'save_button',
'obj-value',
3536 '<button type="submit" class="btn btn-primary" title="'
3537 . $this->esc($oForm->get_fd_message(
'action_save'))
3538 .
'"><i class="bi bi-save"></i></button>'
3540 $oForm->add_obj(
'back_button',
'obj-value',
3541 '<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="'
3542 . $this->esc($oForm->get_fd_message(
'action_back'))
3543 .
'"><i class="bi bi-arrow-left"></i></a>'
3546 return $oForm->run();
3554 private function run_transfer() {
3555 $texts = $this->schema_texts();
3556 $sourceServer =
dbx()->get_modul_var(
'source_server',
'',
'parameter');
3557 $sourceTable =
dbx()->get_modul_var(
'source_table',
'',
'parameter');
3558 $start =
dbx()->get_modul_var(
'start', 0,
'int');
3560 if (!$sourceServer || !$sourceTable) {
3561 return '<div class="alert alert-danger">'
3562 . $this->esc(
$texts->get_fd_message(
'missing_source'))
3567 return $this->form_transfer($sourceServer, $sourceTable);
3570 $targetServer =
dbx()->get_modul_var(
'target_server',
'',
'parameter');
3571 $targetTable =
dbx()->get_modul_var(
'target_table', $sourceTable,
'parameter');
3572 $createTarget =
dbx()->get_modul_var(
'create_target', 1,
'int');
3573 $truncate =
dbx()->get_modul_var(
'truncate_target', 1,
'int');
3574 $reset =
dbx()->get_modul_var(
'reset', 0,
'int');
3575 $cmd =
dbx()->get_modul_var(
'proc_cmd',
'',
'parameter');
3576 $oDD =
dbx()->get_system_obj(
'dbxDD');
3579 $oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable,
'reset', $createTarget, $truncate);
3582 $nextUrl = $this->build_url(
'db',
'transfer', array(
3583 'source_server' => $sourceServer,
3584 'source_table' => $sourceTable,
3585 'target_server' => $targetServer,
3586 'target_table' => $targetTable,
3587 'create_target' => $createTarget,
3588 'truncate_target' => $truncate,
3592 if ($cmd && in_array($cmd, array(
'pause',
'resume',
'continue',
'cancel'),
true)) {
3593 $state =
$oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable, $cmd, $createTarget, $truncate);
3594 return $this->render_process(
'Transfer: ' . $sourceServer .
'|' . $sourceTable .
' -> ' . $targetServer .
'|' . $targetTable,
$state, $nextUrl, $this->build_url(
'db',
'list_db'));
3597 $state =
$oDD->transfer_table($sourceServer, $sourceTable, $targetServer, $targetTable,
'step', $createTarget, $truncate);
3599 return $this->render_process(
'Transfer: ' . $sourceServer .
'|' . $sourceTable .
' -> ' . $targetServer .
'|' . $targetTable,
$state, $nextUrl, $this->build_url(
'db',
'list_db'));
3609 private function form_transfer($sourceServer, $sourceTable) {
3610 $oForm =
dbx()->get_system_obj(
'dbxForm');
3611 $servers = $this->get_server_options();
3613 $defaultTarget =
'';
3615 if (
$server != $sourceServer) {
3620 if (!$defaultTarget) {
3621 $defaultTarget = $sourceServer;
3624 $oForm->init(
'form-schema-action');
3625 $oForm->_fd =
'dbxAdmin|schema-report';
3626 $oForm->load_fd_messages();
3627 $oForm->_data = array(
3628 'source_server' => $sourceServer,
3629 'source_table' => $sourceTable,
3630 'target_server' => $defaultTarget,
3631 'target_table' => $sourceTable,
3632 'create_target' => 1,
3633 'truncate_target' => 1,
3635 $oForm->_action = $this->build_url(
'db',
'transfer', array(
3636 'source_server' => $sourceServer,
3637 'source_table' => $sourceTable,
3639 $oForm->_msg_info = $oForm->get_fd_message(
'transfer_info');
3642 1 => $oForm->get_fd_message(
'yes'),
3643 0 => $oForm->get_fd_message(
'no'),
3645 $oForm->add_fld(
'source_server',
'text-label', label: $oForm->get_fd_message(
'label_source_server'), rules:
'parameter');
3646 $oForm->add_fld(
'source_table',
'text-label', label: $oForm->get_fd_message(
'label_source_table'), rules:
'parameter');
3647 $oForm->add_fld(
'target_server',
'select-single-label', label: $oForm->get_fd_message(
'label_target_server'), rules:
'parameter', options:
$servers);
3648 $oForm->add_fld(
'target_table',
'text-label', label: $oForm->get_fd_message(
'label_target_table'), rules:
'parameter|min=1');
3649 $oForm->add_fld(
'create_target',
'select-single-label', label: $oForm->get_fd_message(
'label_create_target'), rules:
'int', options: $yesNo);
3650 $oForm->add_fld(
'truncate_target',
'select-single-label', label: $oForm->get_fd_message(
'label_truncate_target'), rules:
'int', options: $yesNo);
3652 if ($oForm->submit() && !$oForm->errors()) {
3653 dbx()->set_modul_var(
'target_server', $oForm->get_post(
'target_server', $defaultTarget,
'parameter'));
3654 dbx()->set_modul_var(
'target_table', $oForm->get_post(
'target_table', $sourceTable,
'parameter'));
3655 dbx()->set_modul_var(
'create_target', $oForm->get_post(
'create_target', 1,
'int'));
3656 dbx()->set_modul_var(
'truncate_target', $oForm->get_post(
'truncate_target', 1,
'int'));
3657 dbx()->set_modul_var(
'start', 1);
3658 dbx()->set_modul_var(
'reset', 1);
3660 return $this->run_transfer();
3663 return $oForm->run();
3671 private function run_backup_db_table() {
3672 $texts = $this->schema_texts();
3673 $server =
dbx()->get_modul_var(
'server',
'',
'parameter');
3674 $table =
dbx()->get_modul_var(
'table',
'',
'parameter');
3676 return '<div class="alert alert-danger">' . $this->esc(
$texts->get_fd_message(
'missing_server_or_table')) .
'</div>';
3682 'proc_type' =>
'backup',
3684 'status' =>
$ok ?
'finished' :
'error',
3685 'phase' =>
'backup_source',
3686 'message' =>
$ok ?
'backup finished' : (
$result[
'msg'] ??
'backup error'),
3687 'percent' =>
$ok ? 100 : 0,
3688 'step_percent' =>
$ok ? 100 : 0,
3689 'updated_at' => date(
'Y-m-d H:i:s'),
3693 $state[
'message'] =
'backup finished: ' . $this->backup_relative_file(
$result[
'file'] ??
'');
3696 return $this->render_process(
'Backup: ' .
$server .
'|' .
$table,
$state,
'', $this->build_url(
'dd',
'backup_db'));
3707 $texts = $this->schema_texts();
3710 return '<div class="alert alert-warning">' . $this->esc(
3711 $texts->format_fd_message(
'no_backup_for_table', array(
'table' =>
$server .
'|' .
$table))
3715 $options = array(
'latest' =>
$texts->get_fd_message(
'latest_backup'));
3716 foreach ($backups as
$backup) {
3722 .
$texts->format_fd_message(
'record_count_short', array(
'count' => (
int)(
$backup[
'count'] ?? 0)));
3725 $oForm =
dbx()->get_system_obj(
'dbxForm');
3726 $oForm->init(
'form-schema-restore');
3727 $oForm->_fd =
'dbxAdmin|schema-report';
3728 $oForm->load_fd_messages();
3729 $oForm->_data = array(
3732 'backup_file' =>
'latest',
3734 $oForm->_action = $this->build_url(
'dd',
'restore_db_table', array(
3738 $oForm->_msg_info =
$texts->get_fd_message(
'restore_warning');
3740 $oForm->add_fld(
'server',
'text-label', label:
$texts->get_fd_message(
'column_server'), rules:
'parameter');
3741 $oForm->add_fld(
'table',
'text-label', label:
$texts->get_fd_message(
'column_table'), rules:
'parameter');
3742 $oForm->add_fld(
'backup_file',
'select-single-label', label:
$texts->get_fd_message(
'label_backup'), rules:
'parameter+.-_', options:
$options);
3743 $oForm->add_obj(
'restore_submit',
'dbx|button-submit', data:
'label=' .
$texts->get_fd_message(
'action_restore_start'));
3745 if ($oForm->submit() && !$oForm->errors()) {
3746 dbx()->set_modul_var(
'backup_file', $oForm->get_post(
'backup_file',
'latest',
'parameter+.-_'));
3747 dbx()->set_modul_var(
'start', 1);
3748 return $this->run_restore_db_table();
3751 return $oForm->run();
3759 private function run_restore_db_table() {
3760 $texts = $this->schema_texts();
3761 $server =
dbx()->get_modul_var(
'server',
'',
'parameter');
3762 $table =
dbx()->get_modul_var(
'table',
'',
'parameter');
3763 $start =
dbx()->get_modul_var(
'start', 0,
'int');
3765 return '<div class="alert alert-danger">' . $this->esc(
$texts->get_fd_message(
'missing_server_or_table')) .
'</div>';
3772 $file =
dbx()->get_modul_var(
'backup_file',
'latest',
'parameter+.-_');
3773 if (
$file ===
'latest') {
3775 $file = (string)($latest[
'_file'] ??
'');
3779 ? $this->restore_table_from_backup(
$file)
3780 : array(
'ok' => 0,
'msg' =>
$texts->get_fd_message(
'no_backup_found'));
3783 'proc_type' =>
'restore',
3785 'status' =>
$ok ?
'finished' :
'error',
3786 'phase' =>
'restore_target',
3787 'message' =>
$ok ?
'restore finished' : (
$result[
'msg'] ??
'restore error'),
3788 'percent' =>
$ok ? 100 : 0,
3789 'step_percent' =>
$ok ? 100 : 0,
3790 'updated_at' => date(
'Y-m-d H:i:s'),
3794 $state[
'message'] =
'restore finished: ' . $this->backup_relative_file(
$result[
'file'] ??
$file);
3797 return $this->render_process(
'Restore: ' .
$server .
'|' .
$table,
$state,
'', $this->build_url(
'dd',
'restore_db'));
3807 private function start_batch($act, $selects) {
3808 $oDD =
dbx()->get_system_obj(
'dbxDD');
3810 foreach ($selects as $selected) {
3811 if ($act ==
'batch_dd_to_db' || $act ==
'batch_dd_to_db_force') {
3812 $parts = $this->selected_dd_parts($selected);
3813 if (count($parts) != 2) {
3817 $oDD->sync_dd_to_db($parts[0], $parts[1],
'reset');
3820 if ($act ==
'batch_db_to_dd') {
3821 $parts = $this->decode_db_rid($selected);
3822 if (!$parts[0] || !$parts[1]) {
3826 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3827 $dds = $this->get_dd_records_for_db($ddIndex, $parts[0], $parts[1]);
3830 $oDD->sync_db_to_dd($dds[0][
'modul'], $dds[0][
'dd'],
'reset', $parts[0], $parts[1]);
3832 $oDD->sync_db_to_dd(
'dbx', $this->sanitize_dd_name($parts[1]),
'reset', $parts[0], $parts[1]);
3838 if ($act ==
'batch_backup_db') $back =
'backup';
3839 if ($act ==
'batch_restore_latest_db') $back =
'restore';
3842 'proc_type' =>
'schema_batch',
3843 'proc_key' =>
'schema_batch',
3845 'selects' => array_values($selects),
3847 'status' =>
'running',
3848 'phase' =>
'batch_step',
3849 'message' =>
'batch initialized',
3851 'step_percent' => 0,
3855 dbx()->set_remember_var(
'schema_batch',
$state,
'dbxAdmin');
3856 return $this->run_batch();
3865 private function selected_dd_parts($selected) {
3866 $selected = (string)$selected;
3868 if (str_starts_with($selected,
'db_')) {
3874 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3875 $dds = $this->get_dd_records_for_db($ddIndex,
$server,
$table);
3880 return array($dds[0][
'modul'], $dds[0][
'dd']);
3883 $parts = explode(
'|', $selected, 2);
3884 return (count($parts) == 2) ? $parts : array();
3893 private function reset_batch_children(
$state) {
3898 $oDD =
dbx()->get_system_obj(
'dbxDD');
3899 $act =
$state[
'act'] ??
'';
3901 foreach (
$state[
'selects'] as $selected) {
3902 if ($act ==
'batch_dd_to_db' || $act ==
'batch_dd_to_db_force') {
3903 $parts = $this->selected_dd_parts($selected);
3904 if (count($parts) != 2) {
3908 $oDD->sync_dd_to_db($parts[0], $parts[1],
'reset');
3912 if ($act ==
'batch_db_to_dd') {
3913 $parts = $this->decode_db_rid($selected);
3914 if (!$parts[0] || !$parts[1]) {
3918 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
3919 $dds = $this->get_dd_records_for_db($ddIndex, $parts[0], $parts[1]);
3920 $modul = $dds ? $dds[0][
'modul'] :
'dbx';
3921 $dd = $dds ? $dds[0][
'dd'] : $this->sanitize_dd_name($parts[1]);
3922 $oDD->sync_db_to_dd($modul,
$dd,
'reset', $parts[0], $parts[1]);
3933 private function batch_back_url($backRun) {
3934 if ($backRun ==
'db') {
3935 return $this->build_url(
'db',
'list_db');
3937 if ($backRun ==
'backup') {
3938 return $this->build_url(
'dd',
'backup_db');
3940 if ($backRun ==
'restore') {
3941 return $this->build_url(
'dd',
'restore_db');
3943 return $this->build_url(
'dd',
'list_dd');
3953 private function control_batch(
$state, $cmd) {
3954 $cmd = strtolower(trim((
string)$cmd));
3955 $status =
$state[
'status'] ??
'running';
3957 if ($cmd ==
'pause' && !in_array($status, array(
'finished',
'error',
'canceled'),
true)) {
3958 $state[
'status'] =
'paused';
3959 $state[
'message'] =
'process paused';
3960 } elseif (($cmd ==
'resume' || $cmd ==
'continue') && in_array($status, array(
'paused',
'canceled'),
true)) {
3961 $state[
'status'] =
'running';
3962 $state[
'message'] = ($cmd ==
'continue') ?
'process continued' :
'process resumed';
3963 } elseif ($cmd ==
'cancel' && !in_array($status, array(
'finished',
'error'),
true)) {
3964 $state[
'status'] =
'canceled';
3965 $state[
'message'] =
'process canceled';
3966 } elseif ($cmd ==
'restart') {
3967 $this->reset_batch_children(
$state);
3969 $state[
'status'] =
'running';
3970 $state[
'phase'] =
'batch_step';
3972 $state[
'step_percent'] = 0;
3973 $state[
'message'] =
'process restarted';
3976 $state[
'updated_at'] = date(
'Y-m-d H:i:s');
3977 dbx()->set_remember_var(
'schema_batch',
$state,
'dbxAdmin');
3986 private function run_batch() {
3987 $state =
dbx()->get_remember_var(
'schema_batch', array(),
'dbxAdmin');
3989 return '<div class="alert alert-warning">Kein Batch aktiv.</div>';
3992 $cmd =
dbx()->get_modul_var(
'proc_cmd',
'',
'parameter');
3993 if ($cmd && in_array($cmd, array(
'pause',
'resume',
'continue',
'cancel',
'restart'),
true)) {
3997 $total = count(
$state[
'selects']);
3998 $pos = (int)(
$state[
'pos'] ?? 0);
3999 $backRun = (string)(
$state[
'back'] ??
'dd');
4000 if (!in_array($backRun, array(
'dd',
'db',
'backup',
'restore'),
true)) {
4003 $nextUrl = $this->build_url(
'dd',
'batch');
4004 $backUrl = $this->batch_back_url($backRun);
4006 if (in_array((
$state[
'status'] ??
''), array(
'paused',
'canceled',
'error'),
true)) {
4007 return $this->render_process(
'Batch',
$state, $nextUrl, $backUrl);
4010 if ($pos >= $total) {
4011 $state[
'status'] =
'finished';
4012 $state[
'message'] =
'batch finished';
4014 $state[
'step_percent'] = 100;
4015 dbx()->set_remember_var(
'schema_batch', array(),
'dbxAdmin');
4016 return $this->render_process(
'Batch',
$state,
'', $backUrl);
4019 $current =
$state[
'selects'][$pos];
4020 $oDD =
dbx()->get_system_obj(
'dbxDD');
4021 $act =
$state[
'act'] ??
'';
4023 $parts = in_array($act, array(
'batch_db_to_dd',
'batch_backup_db',
'batch_restore_latest_db'),
true)
4024 ? $this->decode_db_rid($current)
4025 : $this->selected_dd_parts($current);
4027 if (($act ==
'batch_dd_to_db' || $act ==
'batch_dd_to_db_force') && count($parts) == 2) {
4028 $mode = ($act ==
'batch_dd_to_db_force') ?
'force' :
'apply';
4029 $step =
$oDD->sync_dd_to_db($parts[0], $parts[1],
$mode);
4030 $state[
'step_percent'] = (int)($step[
'percent'] ?? 0);
4032 if (($step[
'status'] ??
'') ==
'finished') {
4033 $state[
'pos'] = $pos + 1;
4034 $state[
'step_percent'] = 100;
4035 } elseif (($step[
'status'] ??
'') ==
'error') {
4036 $state[
'status'] =
'error';
4037 $state[
'message'] = $current .
': ' . ($step[
'message'] ??
'error');
4039 $state[
'message'] = $current .
': ' . ($step[
'message'] ??
'running');
4041 } elseif ($act ==
'batch_db_to_dd' && count($parts) == 2 && $parts[0] && $parts[1]) {
4042 $ddIndex = $this->get_dd_index_by_db($this->get_dd_records());
4043 $dds = $this->get_dd_records_for_db($ddIndex, $parts[0], $parts[1]);
4044 $modul = $dds ? $dds[0][
'modul'] :
'dbx';
4045 $dd = $dds ? $dds[0][
'dd'] : $this->sanitize_dd_name($parts[1]);
4046 $step =
$oDD->sync_db_to_dd($modul,
$dd,
'merge', $parts[0], $parts[1]);
4047 $state[
'step_percent'] = (int)($step[
'percent'] ?? 0);
4049 if (($step[
'status'] ??
'') ==
'finished') {
4050 $state[
'pos'] = $pos + 1;
4051 $state[
'step_percent'] = 100;
4052 } elseif (($step[
'status'] ??
'') ==
'error') {
4053 $state[
'status'] =
'error';
4054 $state[
'message'] = $current .
': ' . ($step[
'message'] ??
'error');
4056 $state[
'message'] = $current .
': ' . ($step[
'message'] ??
'running');
4058 } elseif ($act ==
'batch_backup_db' && count($parts) == 2 && $parts[0] && $parts[1]) {
4059 $result = $this->backup_table($parts[0], $parts[1]);
4060 $state[
'step_percent'] = 100;
4062 $state[
'pos'] = $pos + 1;
4063 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': Backup geschrieben';
4065 $state[
'status'] =
'error';
4066 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': ' . (
$result[
'msg'] ??
'backup error');
4068 } elseif ($act ==
'batch_restore_latest_db' && count($parts) == 2 && $parts[0] && $parts[1]) {
4069 $latest = $this->latest_backup($parts[0], $parts[1]);
4070 $file = (string)($latest[
'_file'] ??
'');
4071 $result =
$file ? $this->restore_table_from_backup(
$file) : array(
'ok' => 0,
'msg' =>
'Kein Backup gefunden');
4072 $state[
'step_percent'] = 100;
4074 $state[
'pos'] = $pos + 1;
4075 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': Restore abgeschlossen';
4077 $state[
'status'] =
'error';
4078 $state[
'message'] = $parts[0] .
'|' . $parts[1] .
': ' . (
$result[
'msg'] ??
'restore error');
4080 } elseif (in_array($act, array(
'batch_db_to_dd',
'batch_backup_db',
'batch_restore_latest_db'),
true) && count($parts) == 2 && $parts[0] && !$parts[1]) {
4081 $state[
'pos'] = $pos + 1;
4082 $state[
'step_percent'] = 100;
4083 $state[
'message'] = $parts[0] .
': keine Tabelle, Schritt uebersprungen';
4085 $state[
'status'] =
'error';
4086 $state[
'message'] =
'ungueltige Batch-Aktion';
4089 $done = min($total, (
int)
$state[
'pos']);
4090 $partial = ((
$state[
'status'] ??
'') ==
'running') ? max(0, min(100, (
int)(
$state[
'step_percent'] ?? 0))) / 100 : 0;
4091 $state[
'percent'] = (int)floor((min($total, $done + $partial) / max(1, $total)) * 100);
4092 $state[
'updated_at'] = date(
'Y-m-d H:i:s');
4093 dbx()->set_remember_var(
'schema_batch',
$state,
'dbxAdmin');
4095 $next = ((
$state[
'status'] ??
'') ==
'running') ? $nextUrl :
'';
4096 return $this->render_process(
'Batch',
$state, $next, $backUrl);
4106 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
4108 $mode =
dbx()->get_modul_var(
'dbx_run1',
'dd',
'parameter');
4111 if ($run2 ==
'sync_dd_to_db') {
4112 return $this->run_sync_dd_to_db();
4115 if ($run2 ==
'sync_db_to_dd') {
4116 return $this->run_sync_db_to_dd();
4119 if ($run2 ==
'transfer') {
4120 return $this->run_transfer();
4123 if ($run2 ==
'backup_db_table') {
4124 return $this->run_backup_db_table();
4127 if ($run2 ==
'restore_db_table') {
4128 return $this->run_restore_db_table();
4131 if ($run2 ==
'mapping') {
4132 return $this->run_mapping_editor();
4135 if ($run2 ==
'fields') {
4136 return $this->run_dd_fields_grid();
4139 if ($run2 ==
'data_read') {
4140 return $this->run_data_read();
4143 if ($run2 ==
'data_save') {
4144 return $this->run_data_save();
4147 if ($run2 ==
'data_insert') {
4148 return $this->run_data_insert();
4151 if ($run2 ==
'data_delete') {
4152 return $this->run_data_delete();
4155 if ($run2 ==
'fields_read') {
4156 return $this->run_dd_fields_read();
4159 if ($run2 ==
'fields_save') {
4160 return $this->run_dd_fields_save();
4163 if ($run2 ==
'fields_insert') {
4164 return $this->run_dd_fields_insert();
4167 if ($run2 ==
'fields_delete') {
4168 return $this->run_dd_fields_delete();
4171 if ($run2 ==
'batch') {
4172 return $this->run_batch();
4175 if ($run2 ==
'backup_db') {
4176 return $this->report_backup_db();
4179 if ($run2 ==
'restore_db') {
4180 return $this->report_restore_db();
4183 if (
$mode ==
'db') {
4184 return $this->report_db();
4187 return $this->report_dd();