8 private $_trace_sys_fields = array(
16 private function value_to_text(
$value) {
22 return $value ?
'true' :
'false';
26 return json_encode(
$value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
32 private function is_empty_value(
$value) {
44 private function is_trace_sys_field($key) {
45 return isset($this->_trace_sys_fields[$key]);
48 private function get_visible_values($data, $hideEmpty =
true) {
52 if (!is_array($data)) {
53 return array($values, $hiddenSys);
56 foreach ($data as $key =>
$value) {
57 if ($this->is_trace_sys_field($key)) {
62 if ($hideEmpty && $this->is_empty_value(
$value)) {
69 return array($values, $hiddenSys);
72 private function make_trace_summary(
$json, $action) {
73 $data = json_decode((
string)
$json,
true);
75 if (!is_array($data)) {
76 return array(
'summary' =>
'Rohdaten',
'fields' =>
'',
'detail' => (
string)
$json);
79 $traceAction = $data[
'action'] ?? $action;
80 $before = $data[
'before'] ??
null;
81 $delta = $data[
'delta'] ??
null;
84 if ($traceAction ==
'delete') {
85 list($values, $hiddenSys) = $this->get_visible_values($before);
88 foreach ($values as $key =>
$value) {
89 $detail[] = $key .
': ' . $this->value_to_text(
$value);
93 'summary' =>
$count ?
$count .
' Werte geloescht' : ($hiddenSys ?
'nur Systemfelder' :
'keine Werte'),
94 'fields' => implode(
', ', array_slice(array_keys($values), 0, 4)),
95 'detail' => implode(
"\n", $detail),
99 if ($traceAction ==
'insert') {
100 list($values, $hiddenSys) = $this->get_visible_values(
$delta);
103 foreach ($values as $key =>
$value) {
104 $detail[] = $key .
': ' . $this->value_to_text(
$value);
108 'summary' =>
$count ?
$count .
' Werte gesetzt' : ($hiddenSys ?
'nur Systemfelder' :
'keine Werte'),
109 'fields' => implode(
', ', array_slice(array_keys($values), 0, 4)),
110 'detail' => implode(
"\n", $detail),
115 return array(
'summary' =>
'keine Aenderung',
'fields' =>
'',
'detail' =>
'');
118 if (!is_array($before)) {
125 foreach (
$delta as $key => $newValue) {
126 if ($this->is_trace_sys_field($key)) {
132 $oldValue = array_key_exists($key, $before) ? $before[$key] :
null;
133 $detail[] = $key .
': ' . $this->value_to_text($oldValue) .
' -> ' . $this->value_to_text($newValue);
139 'summary' =>
$count ?
$count .
' Felder geaendert' : ($hiddenSys ?
'nur Systemfelder' :
'keine Aenderung'),
140 'fields' => implode(
', ', array_slice(
$fields, 0, 4)),
141 'detail' => implode(
"\n", $detail),
145 private function render_trace_json(
$json, $action) {
146 $summary = $this->make_trace_summary(
$json, $action);
147 $title = $summary[
'detail'];
148 $payload = json_decode((
string)
$json,
true);
150 if (is_array($payload)) {
153 if (!empty($payload[
'source'])) {
154 $context[] =
'source: ' . $payload[
'source'];
157 if (!empty($payload[
'modul'])) {
158 $run = $payload[
'modul'];
160 if (!empty($payload[
'run1']))
$run .=
'/' . $payload[
'run1'];
161 if (!empty($payload[
'run2']))
$run .=
'/' . $payload[
'run2'];
162 if (!empty($payload[
'run3']))
$run .=
'/' . $payload[
'run3'];
164 $context[] =
'run: ' .
$run;
168 $title = implode(
"\n", $context) . ($title ?
"\n\n" . $title :
'');
173 $title = $summary[
'summary'];
176 $html =
'<span class="dbx-trace-summary" title="' .
dbx()->esc($title) .
'">';
177 $html .=
'<strong>' .
dbx()->esc($summary[
'summary']) .
'</strong>';
179 if ($summary[
'fields']) {
180 $html .=
'<span class="dbx-trace-summary-fields">' .
dbx()->esc($summary[
'fields']) .
'</span>';
189 if (!is_array($data) || ($data[
'type'] ??
'') !=
'show') {
193 $record = $data[
'record'] ?? array();
195 if (!is_array($record)) {
199 $dd = isset($record[
'dd']) ? urlencode((
string) $record[
'dd']) :
'';
200 $rid = isset($record[
'record_id']) ? urlencode((
string) $record[
'record_id']) :
'';
202 $data[
'data'][
'action'] =
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=record_history&trace_dd=' .
$dd .
'&trace_rid=' . $rid;
203 $data[
'data'][
'class'] =
'openWin';
204 $data[
'data'][
'tooltip'] =
'Historie anzeigen';
210 $record = $oObj->get_record();
211 $action = $record[
'action'] ??
'';
213 if (isset($record[
'action'])) {
214 $badge =
'secondary';
216 if ($action ==
'insert') $badge =
'success';
217 if ($action ==
'update') $badge =
'primary';
218 if ($action ==
'delete') $badge =
'danger';
220 $record[
'action'] =
'<span class="badge bg-' . $badge .
'">' .
dbx()->esc($action) .
'</span>';
223 if (isset($record[
'data_json'])) {
224 $record[
'data_json'] = $this->render_trace_json($record[
'data_json'], $action);
227 $oObj->_class_body[
'data_json'] =
'dbx-trace-data';
228 $oObj->set_record($record);
233 private function get_trace_payload(
$json) {
234 $data = json_decode((
string)
$json,
true);
236 return is_array($data) ? $data : array();
239 private function render_history_payload($row) {
240 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
241 $before = $payload[
'before'] ?? array();
242 $delta = $payload[
'delta'] ?? array();
243 $action = $payload[
'action'] ?? ($row[
'action'] ??
'');
245 if (!is_array($before)) {
253 $html =
'<div class="dbx-trace-history-detail">';
255 if ($action ==
'update') {
256 foreach (
$delta as $key => $newValue) {
257 $oldValue = array_key_exists($key, $before) ? $before[$key] :
'';
258 $html .=
'<div class="dbx-trace-history-row">';
259 $html .=
'<strong>' .
dbx()->esc($key) .
'</strong>';
260 $html .=
'<span class="dbx-trace-old">' .
dbx()->esc($this->value_to_text($oldValue)) .
'</span>';
261 $html .=
'<span class="dbx-trace-arrow">→</span>';
262 $html .=
'<span class="dbx-trace-new">' .
dbx()->esc($this->value_to_text($newValue)) .
'</span>';
266 $values = ($action ==
'delete') ? $before :
$delta;
268 foreach ($values as $key =>
$value) {
273 $html .=
'<div class="dbx-trace-history-row">';
274 $html .=
'<strong>' .
dbx()->esc($key) .
'</strong>';
275 $html .=
'<span>' .
dbx()->esc($this->value_to_text(
$value)) .
'</span>';
285 private function get_compare_fields($before,
$after) {
288 if (is_array($before)) {
289 foreach ($before as $key =>
$value) {
303 private function render_compare_value(
$value) {
304 $text = $this->value_to_text(
$value);
307 return '<span class="dbx-trace-empty">leer</span>';
310 return dbx()->esc($text);
313 private function trace_detail($trace_id) {
314 $row = $this->get_trace_row($trace_id);
317 return '<div class="container-fluid dbxReport dbx-trace-detail"><div class="alert alert-danger">Trace-Eintrag nicht gefunden.</div></div>';
320 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
321 $action = $payload[
'action'] ?? ($row[
'action'] ??
'');
322 $before = $payload[
'before'] ?? array();
323 $after = $payload[
'delta'] ?? array();
324 $source = $payload[
'source'] ??
'';
325 $modul = $payload[
'modul'] ??
'';
326 $run1 = $payload[
'run1'] ??
'';
327 $run2 = $payload[
'run2'] ??
'';
328 $run3 = $payload[
'run3'] ??
'';
330 if (!is_array($before)) {
340 $html =
'<div class="container-fluid dbxReport dbx-trace-detail">';
341 $html .=
'<h3>' .
dbx()->esc($row[
'dd'] ??
'') .
' #' .
dbx()->esc($row[
'record_id'] ??
'') .
' - ' .
dbx()->esc($action) .
'</h3>';
343 if ($run1) $runPath .=
'/' . $run1;
344 if ($run2) $runPath .=
'/' . $run2;
345 if ($run3) $runPath .=
'/' . $run3;
347 $html .=
'<div class="mb-2 text-muted">' .
dbx()->esc($row[
'create_date'] ??
'');
353 if ($runPath)
$html .=
'run: ' .
dbx()->esc($runPath);
357 $html .=
'<table class="table table-striped table-bordered table-light table-hover">';
358 $html .=
'<thead><tr><th>Feld</th><th>Vorher</th><th>Nachher</th></tr></thead><tbody>';
361 $oldValue = array_key_exists(
$field, $before) ? $before[
$field] :
'';
364 if ($action ==
'delete') {
369 $html .=
'<th class="text-nowrap">' .
dbx()->esc(
$field) .
'</th>';
370 $html .=
'<td class="dbx-trace-before">' . $this->render_compare_value($oldValue) .
'</td>';
371 $html .=
'<td class="dbx-trace-after">' . $this->render_compare_value($newValue) .
'</td>';
376 $html .=
'<tr><td colspan="3">Keine Feldwerte vorhanden.</td></tr>';
379 $html .=
'</tbody></table>';
380 $html .=
'<div class="d-flex gap-2 justify-content-end">';
382 if ($action ==
'delete') {
383 $html .=
'<a class="btn btn-primary" href="?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=undelete&trace_id=' .
dbx()->esc($row[
'id'] ??
'') .
'">Undelete</a>';
384 } elseif ($action ==
'update') {
385 $html .=
'<a class="btn btn-primary" href="?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=restore&trace_id=' .
dbx()->esc($row[
'id'] ??
'') .
'">Vorherigen Stand wiederherstellen</a>';
394 private function get_trace_row($trace_id) {
395 $db =
dbx()->get_system_obj(
'dbxDB');
396 $rows =
$db->select(
'dbxTrace', array(
'id' => $trace_id),
'*',
'id',
'DESC',
'', 1, 0);
405 private function restore_trace_before($trace_id) {
406 $db =
dbx()->get_system_obj(
'dbxDB');
407 $row = $this->get_trace_row($trace_id);
410 return '<div class="alert alert-danger">Trace-Eintrag nicht gefunden.</div>';
413 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
414 $before = $payload[
'before'] ?? array();
415 $dd = $row[
'dd'] ??
'';
416 $rid = $row[
'record_id'] ?? 0;
418 if (!
$dd || !$rid || !is_array($before) || !count($before)) {
419 return '<div class="alert alert-warning">Dieser Stand kann nicht wiederhergestellt werden.</div>';
422 $pk =
$db->get_dd_primary(
$dd);
427 $ok =
$db->update(
$dd, $before, array($pk => $rid));
430 return '<div class="alert alert-success">Stand wurde wiederhergestellt.</div>' . $this->record_history(
$dd, $rid);
433 return '<div class="alert alert-danger">Stand konnte nicht wiederhergestellt werden.</div>' . $this->record_history(
$dd, $rid);
436 private function undelete_trace_row($trace_id) {
437 $db =
dbx()->get_system_obj(
'dbxDB');
438 $row = $this->get_trace_row($trace_id);
441 return '<div class="alert alert-danger">Trace-Eintrag nicht gefunden.</div>';
444 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
445 $before = $payload[
'before'] ?? array();
446 $dd = $row[
'dd'] ??
'';
447 $rid = $row[
'record_id'] ?? 0;
449 if (($row[
'action'] ??
'') !=
'delete' || !
$dd || !is_array($before) || !count($before)) {
450 return '<div class="alert alert-warning">Dieser Datensatz kann nicht per Undelete reaktiviert werden.</div>';
456 return '<div class="alert alert-success">Datensatz wurde reaktiviert.</div>' . $this->record_history(
$dd, $rid);
459 return '<div class="alert alert-danger">Datensatz konnte nicht reaktiviert werden.</div>' . $this->record_history(
$dd, $rid);
462 private function record_history(
$dd =
'', $rid = 0) {
463 $db =
dbx()->get_system_obj(
'dbxDB');
466 $dd =
dbx()->get_modul_var(
'trace_dd',
'',
'parameter');
470 $rid =
dbx()->get_modul_var(
'trace_rid', 0,
'int');
478 $rows =
$db->select(
'dbxTrace', $where,
'*',
'create_date',
'DESC',
'', 200, 0);
480 if (!is_array(
$rows)) {
484 $html =
'<div class="container-fluid dbxReport dbx-trace-history">';
485 $html .=
'<h3>Historie ' .
dbx()->esc(
$dd) .
' #' .
dbx()->esc($rid) .
'</h3>';
486 $html .=
'<table class="table table-striped table-bordered table-light table-hover">';
487 $html .=
'<thead><tr><th></th><th>Zeit</th><th>Aktion</th><th>Aenderung</th></tr></thead><tbody>';
489 foreach (
$rows as $row) {
490 $action = $row[
'action'] ??
'';
491 $detailUrl =
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=trace_detail&trace_id=' . urlencode((
string) ($row[
'id'] ??
''));
494 $html .=
'<td class="td-show text-center align-middle">';
495 $html .=
'<a class="btn-inline" href="' .
dbx()->esc($detailUrl) .
'" data-dbx-tooltip="Trace-Detail anzeigen" data-url="' .
dbx()->esc($detailUrl) .
'" data-title="Trace Detail" data-width="1200" role="button"><i class="bi bi-search"></i></a>';
497 $html .=
'<td class="text-nowrap">' .
dbx()->esc($row[
'create_date'] ??
'') .
'</td>';
498 $html .=
'<td>' .
dbx()->esc($action) .
'</td>';
499 $html .=
'<td>' . $this->render_history_payload($row) .
'</td>';
504 $html .=
'<tr><td colspan="4">Keine Historie gefunden.</td></tr>';
507 $html .=
'</tbody></table></div>';
512 private function report_trace() {
514 $db =
dbx()->get_system_obj(
'dbxDB');
515 $oReport =
dbx()->get_system_obj(
'dbxReport');
517 $oReport->init(
'report-trace');
518 $oReport->_fd =
'dbxAdmin|rpt-trace-selection';
519 $oReport->load_fd_messages();
520 $oReport->add_module_bar(
521 $oReport->get_fd_message(
'bar_title'),
523 $oReport->get_fd_message(
'bar_subtitle')
526 $oReport->set_tabel_tpl(
'tpl_row_show',
'dbxAdmin|table_row_trace_show');
528 $work =
dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
529 $rid =
dbx()->get_modul_var(
'rid', 0,
'int');
531 if (
$work ==
'row_delete' && $rid) {
532 $ok = $oReport->del_selected($rid);
536 $oReport->_msg_success = $oReport->get_fd_message(
540 $oReport->_msg_error = $oReport->get_fd_message(
546 if (
$work ==
'multi_delete') {
547 $result = $oReport->delete_multi_selected_records(
$dd);
548 $oReport->apply_multi_delete_result(
$result);
551 if (
$work ==
'delete_tab') {
553 $optimizeOk =
$ok ?
$db->optimize_tab(
$dd) : 0;
554 dbx()->debug(
"##delete tab dd=($dd) ok=($ok)");
555 dbx()->debug(
"##optimize tab dd=($dd) ok=($optimizeOk)");
556 if (
$ok && $optimizeOk) {
557 $oReport->_msg_success = $oReport->get_fd_message(
561 $oReport->_msg_success = $oReport->get_fd_message(
565 $oReport->_msg_error = $oReport->get_fd_message(
572 $flds[
'create_date'] = $oReport->get_fd_message(
'column_created');
573 $flds[
'action'] = $oReport->get_fd_message(
'column_action');
574 $flds[
'dd'] = $oReport->get_fd_message(
'column_dd');
575 $flds[
'record_id'] = $oReport->get_fd_message(
'column_record');
576 $flds[
'owner'] = $oReport->get_fd_message(
'column_owner');
577 $flds[
'data_json'] = $oReport->get_fd_message(
'column_change');
579 $rformat[
'create_date'] =
'php-datetime-usr';
580 $rformat[
'action'] =
'html';
581 $rformat[
'data_json'] =
'html';
582 $oReport->_rpt_format = $rformat;
584 $oReport->_action =
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=list_trace';
585 $oReport->_pages =
true;
586 $oReport->_create_row_select =
true;
587 $oReport->_create_row_edit =
false;
588 $oReport->_create_row_show =
true;
589 $oReport->_create_row_delete =
true;
590 $oReport->_create_sel_flds =
true;
591 $oReport->_but_pagination = 5;
592 $oReport->set_callback_owner($this);
593 $oReport->set_body_callback(
'report_trace_body');
594 $oReport->set_callback(
'row_action_data',
'report_trace_row_action_data');
596 $oReport->enable_delete_tab(
$dd);
598 $oReport->add_action(
'rows_delete' ,
'action_button_delete' ,
'&dbx_do=multi_delete');
600 $oReport->create_selection_fields(
'dbxAdmin|rpt-trace-selection');
601 $oReport->add_module_bar(
602 $oReport->get_fd_message(
'bar_title'),
604 $oReport->get_fd_message(
'bar_subtitle')
607 if (
$work !=
'delete_tab' &&
$work !=
'multi_delete' &&
$work !=
'row_delete' && $oReport->submit()) {
608 if (!$oReport->errors()) {
609 $oReport->_msg_success =
'';
611 $oReport->_msg_error = $oReport->get_fd_message(
617 $rwhere = $oReport->get_fld_val(
'dbx_rwhere' ,
'' ,
'sqlsearch|max=64');
618 $rrows = $oReport->get_fld_val(
'dbx_rrows' , 15 ,
'int');
619 $rpos = $oReport->get_fld_val(
'dbx_rpos' , 0 ,
'int');
620 $rsort = $oReport->get_fld_val(
'dbx_rsort' ,
'create_date',
'parameter');
621 $rdesc = $oReport->get_fld_val(
'dbx_rdesc' ,
'DESC' ,
'parameter');
622 $select = $oReport->get_fld_val(
'dbx_rselect', 0 ,
'int');
629 'like' => array(
'action',
'dd',
'data_json'),
630 'equal' => array(
'record_id',
'owner'),
631 'mode' =>
'contains',
637 if (is_array($rwhere)) {
638 $rwhere =
$db->normalize_where(
$dd, $rwhere);
641 $rwhere = $oReport->add_rwhere_select($rwhere);
644 $oReport->_rflds = $flds;
645 $oReport->_rrows = $rrows;
646 $oReport->_rpos = $rpos;
647 $oReport->_count_all =
$db->count(
$dd);
648 $oReport->_rcount =
$db->count(
$dd, $rwhere);
649 $oReport->_rdata =
$db->select(
$dd, $rwhere, $flds, $rsort, $rdesc, $rgroup, $rrows, $rpos);
651 return $oReport->run();
655 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
657 if ($run2 ==
'record_history') {
658 return $this->record_history();
661 if ($run2 ==
'trace_detail') {
662 $trace_id =
dbx()->get_modul_var(
'trace_id', 0,
'int');
663 return $this->trace_detail($trace_id);
666 if ($run2 ==
'restore') {
667 $trace_id =
dbx()->get_modul_var(
'trace_id', 0,
'int');
668 return $this->restore_trace_before($trace_id);
671 if ($run2 ==
'undelete') {
672 $trace_id =
dbx()->get_modul_var(
'trace_id', 0,
'int');
673 return $this->undelete_trace_row($trace_id);
676 return $this->report_trace();