8 private function action_url($url) {
9 $sep = (strpos((
string)$url,
'?') ===
false) ?
'?' :
'&';
10 return $url . $sep .
'dbx_token=' . rawurlencode(
dbx()->action_token(
'dbxAdmin.trace'));
13 private function check_action_token($work, $oReport) {
14 if (!$work)
return true;
15 $token =
dbx()->get_modul_var(
'dbx_token',
'',
'varchar|max=128');
16 if (
dbx()->check_action_token(
'dbxAdmin.trace', $token))
return true;
17 $oReport->_msg_err =
'Aktion abgelehnt: Sicherheits-Token fehlt oder ist ungueltig.';
18 dbx()->sys_msg(
'security',
'dbxAdmin',
dbx()->user(),
'invalid admin trace action token',
$_SERVER[
'REQUEST_URI'] ??
'');
22 private function delete_table_action():
string {
23 return '<a class="btn btn-sm btn-outline-danger dbxAjax dbxConfirm"'
24 .
' href="' .
dbx()->esc($this->action_url(
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=list_trace&dbx_do=delete_tab')) .
'"'
25 .
' title="Trace leeren und optimieren"'
26 .
' data-confirm-title="<i class=\'bi bi-trash3\'></i> Trace leeren und optimieren"'
27 .
' data-confirm="Alle Trace-Eintraege wirklich loeschen und die Tabelle anschliessend optimieren?"'
28 .
' data-confirm-hint="<small>Die Tabelle dbxTrace wird komplett geleert und danach optimiert. Dieser Vorgang kann nicht rueckgaengig gemacht werden.</small>"'
29 .
' data-confirm-buttons="yesno"'
30 .
' role="button"><i class="bi bi-trash3"></i></a>';
33 private $_trace_sys_fields = array(
41 private function value_to_text($value) {
42 if ($value ===
null) {
46 if (is_bool($value)) {
47 return $value ?
'true' :
'false';
50 if (is_array($value) || is_object($value)) {
51 return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
54 return (
string) $value;
57 private function is_empty_value($value) {
58 if ($value ===
null) {
62 if (is_string($value) && trim($value) ===
'') {
69 private function is_trace_sys_field($key) {
70 return isset($this->_trace_sys_fields[$key]);
73 private function get_visible_values($data, $hideEmpty =
true) {
77 if (!is_array($data)) {
78 return array($values, $hiddenSys);
81 foreach ($data as $key => $value) {
82 if ($this->is_trace_sys_field($key)) {
87 if ($hideEmpty && $this->is_empty_value($value)) {
91 $values[$key] = $value;
94 return array($values, $hiddenSys);
97 private function make_trace_summary($json, $action) {
98 $data = json_decode((
string) $json,
true);
100 if (!is_array($data)) {
101 return array(
'summary' =>
'Rohdaten',
'fields' =>
'',
'detail' => (
string) $json);
104 $traceAction = $data[
'action'] ?? $action;
105 $before = $data[
'before'] ??
null;
106 $delta = $data[
'delta'] ??
null;
109 if ($traceAction ==
'delete') {
110 list($values, $hiddenSys) = $this->get_visible_values($before);
113 foreach ($values as $key => $value) {
114 $detail[] = $key .
': ' . $this->value_to_text($value);
118 'summary' =>
$count ?
$count .
' Werte geloescht' : ($hiddenSys ?
'nur Systemfelder' :
'keine Werte'),
119 'fields' => implode(
', ', array_slice(array_keys($values), 0, 4)),
120 'detail' => implode(
"\n", $detail),
124 if ($traceAction ==
'insert') {
125 list($values, $hiddenSys) = $this->get_visible_values($delta);
128 foreach ($values as $key => $value) {
129 $detail[] = $key .
': ' . $this->value_to_text($value);
133 'summary' =>
$count ?
$count .
' Werte gesetzt' : ($hiddenSys ?
'nur Systemfelder' :
'keine Werte'),
134 'fields' => implode(
', ', array_slice(array_keys($values), 0, 4)),
135 'detail' => implode(
"\n", $detail),
139 if (!is_array($delta)) {
140 return array(
'summary' =>
'keine Aenderung',
'fields' =>
'',
'detail' =>
'');
143 if (!is_array($before)) {
150 foreach ($delta as $key => $newValue) {
151 if ($this->is_trace_sys_field($key)) {
157 $oldValue = array_key_exists($key, $before) ? $before[$key] :
null;
158 $detail[] = $key .
': ' . $this->value_to_text($oldValue) .
' -> ' . $this->value_to_text($newValue);
164 'summary' =>
$count ?
$count .
' Felder geaendert' : ($hiddenSys ?
'nur Systemfelder' :
'keine Aenderung'),
165 'fields' => implode(
', ', array_slice(
$fields, 0, 4)),
166 'detail' => implode(
"\n", $detail),
170 private function render_trace_json($json, $action) {
171 $summary = $this->make_trace_summary($json, $action);
172 $title = $summary[
'detail'];
173 $payload = json_decode((
string) $json,
true);
175 if (is_array($payload)) {
178 if (!empty($payload[
'source'])) {
179 $context[] =
'source: ' . $payload[
'source'];
182 if (!empty($payload[
'modul'])) {
183 $run = $payload[
'modul'];
185 if (!empty($payload[
'run1'])) $run .=
'/' . $payload[
'run1'];
186 if (!empty($payload[
'run2'])) $run .=
'/' . $payload[
'run2'];
187 if (!empty($payload[
'run3'])) $run .=
'/' . $payload[
'run3'];
189 $context[] =
'run: ' . $run;
193 $title = implode(
"\n", $context) . ($title ?
"\n\n" . $title :
'');
198 $title = $summary[
'summary'];
201 $html =
'<span class="dbx-trace-summary" title="' .
dbx()->esc($title) .
'">';
202 $html .=
'<strong>' .
dbx()->esc($summary[
'summary']) .
'</strong>';
204 if ($summary[
'fields']) {
205 $html .=
'<span class="dbx-trace-summary-fields">' .
dbx()->esc($summary[
'fields']) .
'</span>';
214 if (!is_array($data) || ($data[
'type'] ??
'') !=
'show') {
218 $record = $data[
'record'] ?? array();
220 if (!is_array($record)) {
224 $dd = isset($record[
'dd']) ? urlencode((
string) $record[
'dd']) :
'';
225 $rid = isset($record[
'record_id']) ? urlencode((
string) $record[
'record_id']) :
'';
227 $data[
'data'][
'action'] =
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=record_history&trace_dd=' . $dd .
'&trace_rid=' . $rid;
228 $data[
'data'][
'class'] =
'openWin';
229 $data[
'data'][
'tooltip'] =
'Historie anzeigen';
235 $record = $oObj->get_record();
236 $action = $record[
'action'] ??
'';
238 if (isset($record[
'action'])) {
239 $badge =
'secondary';
241 if ($action ==
'insert') $badge =
'success';
242 if ($action ==
'update') $badge =
'primary';
243 if ($action ==
'delete') $badge =
'danger';
245 $record[
'action'] =
'<span class="badge bg-' . $badge .
'">' .
dbx()->esc($action) .
'</span>';
248 if (isset($record[
'data_json'])) {
249 $record[
'data_json'] = $this->render_trace_json($record[
'data_json'], $action);
252 $oObj->_class_body[
'data_json'] =
'dbx-trace-data';
253 $oObj->set_record($record);
258 private function get_trace_payload($json) {
259 $data = json_decode((
string) $json,
true);
261 return is_array($data) ? $data : array();
264 private function render_history_payload($row) {
265 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
266 $before = $payload[
'before'] ?? array();
267 $delta = $payload[
'delta'] ?? array();
268 $action = $payload[
'action'] ?? ($row[
'action'] ??
'');
270 if (!is_array($before)) {
274 if (!is_array($delta)) {
278 $html =
'<div class="dbx-trace-history-detail">';
280 if ($action ==
'update') {
281 foreach ($delta as $key => $newValue) {
282 $oldValue = array_key_exists($key, $before) ? $before[$key] :
'';
283 $html .=
'<div class="dbx-trace-history-row">';
284 $html .=
'<strong>' . dbx()->esc($key) .
'</strong>';
285 $html .=
'<span class="dbx-trace-old">' . dbx()->esc($this->value_to_text($oldValue)) .
'</span>';
286 $html .=
'<span class="dbx-trace-arrow">→</span>';
287 $html .=
'<span class="dbx-trace-new">' . dbx()->esc($this->value_to_text($newValue)) .
'</span>';
291 $values = ($action ==
'delete') ? $before : $delta;
293 foreach ($values as $key => $value) {
294 if ($value ===
null || (is_string($value) && trim($value) ===
'')) {
298 $html .=
'<div class="dbx-trace-history-row">';
299 $html .=
'<strong>' . dbx()->esc($key) .
'</strong>';
300 $html .=
'<span>' . dbx()->esc($this->value_to_text($value)) .
'</span>';
310 private function get_compare_fields($before, $after) {
313 if (is_array($before)) {
314 foreach ($before as $key => $value) {
319 if (is_array($after)) {
320 foreach ($after as $key => $value) {
328 private function render_compare_value($value) {
329 $text = $this->value_to_text($value);
332 return '<span class="dbx-trace-empty">leer</span>';
335 return dbx()->esc($text);
338 private function trace_detail($trace_id) {
339 $row = $this->get_trace_row($trace_id);
342 return '<div class="container-fluid dbxReport dbx-trace-detail"><div class="alert alert-danger">Trace-Eintrag nicht gefunden.</div></div>';
345 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
346 $action = $payload[
'action'] ?? ($row[
'action'] ??
'');
347 $before = $payload[
'before'] ?? array();
348 $after = $payload[
'delta'] ?? array();
349 $source = $payload[
'source'] ??
'';
350 $modul = $payload[
'modul'] ??
'';
351 $run1 = $payload[
'run1'] ??
'';
352 $run2 = $payload[
'run2'] ??
'';
353 $run3 = $payload[
'run3'] ??
'';
355 if (!is_array($before)) {
359 if (!is_array($after)) {
363 $fields = $this->get_compare_fields($before, $after);
365 $html =
'<div class="container-fluid dbxReport dbx-trace-detail">';
366 $html .=
'<h3>' . dbx()->esc($row[
'dd'] ??
'') .
' #' . dbx()->esc($row[
'record_id'] ??
'') .
' - ' . dbx()->esc($action) .
'</h3>';
368 if ($run1) $runPath .=
'/' . $run1;
369 if ($run2) $runPath .=
'/' . $run2;
370 if ($run3) $runPath .=
'/' . $run3;
372 $html .=
'<div class="mb-2 text-muted">' . dbx()->esc($row[
'create_date'] ??
'');
374 if ($source || $runPath) {
376 if ($source) $html .=
'source: ' . dbx()->esc($source);
377 if ($source && $runPath) $html .=
' | ';
378 if ($runPath) $html .=
'run: ' . dbx()->esc($runPath);
382 $html .=
'<table class="table table-striped table-bordered table-light table-hover">';
383 $html .=
'<thead><tr><th>Feld</th><th>Vorher</th><th>Nachher</th></tr></thead><tbody>';
386 $oldValue = array_key_exists(
$field, $before) ? $before[
$field] :
'';
387 $newValue = array_key_exists(
$field, $after) ? $after[
$field] :
'';
389 if ($action ==
'delete') {
394 $html .=
'<th class="text-nowrap">' . dbx()->esc(
$field) .
'</th>';
395 $html .=
'<td class="dbx-trace-before">' . $this->render_compare_value($oldValue) .
'</td>';
396 $html .=
'<td class="dbx-trace-after">' . $this->render_compare_value($newValue) .
'</td>';
401 $html .=
'<tr><td colspan="3">Keine Feldwerte vorhanden.</td></tr>';
404 $html .=
'</tbody></table>';
405 $html .=
'<div class="d-flex gap-2 justify-content-end">';
407 if ($action ==
'delete') {
408 $html .=
'<a class="btn btn-primary" href="?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=undelete&trace_id=' . dbx()->esc($row[
'id'] ??
'') .
'">Undelete</a>';
409 } elseif ($action ==
'update') {
410 $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>';
419 private function get_trace_row($trace_id) {
420 $db = dbx()->get_system_obj(
'dbxDB');
421 $rows =
$db->select(
'dbxTrace', array(
'id' => $trace_id),
'*',
'id',
'DESC',
'', 1, 0);
423 if (is_array($rows) && isset($rows[0])) {
430 private function restore_trace_before($trace_id) {
431 $db = dbx()->get_system_obj(
'dbxDB');
432 $row = $this->get_trace_row($trace_id);
435 return '<div class="alert alert-danger">Trace-Eintrag nicht gefunden.</div>';
438 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
439 $before = $payload[
'before'] ?? array();
440 $dd = $row[
'dd'] ??
'';
441 $rid = $row[
'record_id'] ?? 0;
443 if (!$dd || !$rid || !is_array($before) || !count($before)) {
444 return '<div class="alert alert-warning">Dieser Stand kann nicht wiederhergestellt werden.</div>';
447 $pk =
$db->get_dd_primary($dd);
452 $ok =
$db->update($dd, $before, array($pk => $rid));
455 return '<div class="alert alert-success">Stand wurde wiederhergestellt.</div>' . $this->record_history($dd, $rid);
458 return '<div class="alert alert-danger">Stand konnte nicht wiederhergestellt werden.</div>' . $this->record_history($dd, $rid);
461 private function undelete_trace_row($trace_id) {
462 $db = dbx()->get_system_obj(
'dbxDB');
463 $row = $this->get_trace_row($trace_id);
466 return '<div class="alert alert-danger">Trace-Eintrag nicht gefunden.</div>';
469 $payload = $this->get_trace_payload($row[
'data_json'] ??
'');
470 $before = $payload[
'before'] ?? array();
471 $dd = $row[
'dd'] ??
'';
472 $rid = $row[
'record_id'] ?? 0;
474 if (($row[
'action'] ??
'') !=
'delete' || !$dd || !is_array($before) || !count($before)) {
475 return '<div class="alert alert-warning">Dieser Datensatz kann nicht per Undelete reaktiviert werden.</div>';
478 $ok =
$db->insert($dd, $before);
481 return '<div class="alert alert-success">Datensatz wurde reaktiviert.</div>' . $this->record_history($dd, $rid);
484 return '<div class="alert alert-danger">Datensatz konnte nicht reaktiviert werden.</div>' . $this->record_history($dd, $rid);
487 private function record_history($dd =
'', $rid = 0) {
488 $db = dbx()->get_system_obj(
'dbxDB');
491 $dd = dbx()->get_modul_var(
'trace_dd',
'',
'parameter');
495 $rid = dbx()->get_modul_var(
'trace_rid', 0,
'int');
503 $rows =
$db->select(
'dbxTrace', $where,
'*',
'create_date',
'DESC',
'', 200, 0);
505 if (!is_array($rows)) {
509 $html =
'<div class="container-fluid dbxReport dbx-trace-history">';
510 $html .=
'<h3>Historie ' . dbx()->esc($dd) .
' #' . dbx()->esc($rid) .
'</h3>';
511 $html .=
'<table class="table table-striped table-bordered table-light table-hover">';
512 $html .=
'<thead><tr><th></th><th>Zeit</th><th>Aktion</th><th>Aenderung</th></tr></thead><tbody>';
514 foreach ($rows as $row) {
515 $action = $row[
'action'] ??
'';
516 $detailUrl =
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=trace_detail&trace_id=' . urlencode((
string) ($row[
'id'] ??
''));
519 $html .=
'<td class="td-show text-center align-middle">';
520 $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>';
522 $html .=
'<td class="text-nowrap">' . dbx()->esc($row[
'create_date'] ??
'') .
'</td>';
523 $html .=
'<td>' . dbx()->esc($action) .
'</td>';
524 $html .=
'<td>' . $this->render_history_payload($row) .
'</td>';
529 $html .=
'<tr><td colspan="4">Keine Historie gefunden.</td></tr>';
532 $html .=
'</tbody></table></div>';
537 private function report_trace() {
539 $db = dbx()->get_system_obj(
'dbxDB');
540 $oReport = dbx()->get_system_obj(
'dbxReport');
542 $oReport->init(
'report-trace');
544 $oReport->set_tabel_tpl(
'tpl_row_show',
'dbxAdmin|table_row_trace_show');
545 $oReport->add_rep(
'bar_actions', $this->delete_table_action());
547 $work = dbx()->get_modul_var(
'dbx_do',
'',
'parameter');
548 $rid = dbx()->get_modul_var(
'rid', 0,
'int');
549 $actionAllowed = $this->check_action_token($work, $oReport);
551 if ($actionAllowed && $work ==
'row_delete' && $rid) {
552 $ok = $oReport->del_selected($rid);
553 $ok =
$db->delete($dd, $rid);
555 if ( $ok) $oReport->_msg_success =
'Zeile geloescht';
556 if (!$ok) $oReport->_msg_err =
'Zeile konnte nicht geloescht werden';
559 if ($actionAllowed && $work ==
'multi_delete') {
560 $result = $oReport->delete_multi_selected_records($dd);
561 $oReport->apply_multi_delete_result(
$result);
564 if ($actionAllowed && $work ==
'delete_tab') {
565 $ok =
$db->delete_tab($dd);
566 $optimizeOk = $ok ?
$db->optimize_tab($dd) : 0;
567 dbx()->debug(
"##delete tab dd=($dd) ok=($ok)");
568 dbx()->debug(
"##optimize tab dd=($dd) ok=($optimizeOk)");
569 if ($ok && $optimizeOk) $oReport->_msg_success =
'Datenbank-Tabelle geleert und optimiert';
570 if ($ok && !$optimizeOk) $oReport->_msg_success =
'Datenbank-Tabelle geleert. Optimierung konnte nicht ausgefuehrt werden.';
571 if (!$ok) $oReport->_msg_err =
'Datenbank-Tabelle konnte nicht geleert werden';
575 $flds[
'create_date'] =
'Create';
576 $flds[
'action'] =
'Action';
578 $flds[
'record_id'] =
'Record';
579 $flds[
'owner'] =
'Owner';
580 $flds[
'data_json'] =
'Aenderung';
582 $rformat[
'create_date'] =
'php-datetime-usr';
583 $rformat[
'action'] =
'html';
584 $rformat[
'data_json'] =
'html';
585 $oReport->_rpt_format = $rformat;
587 $oReport->_action =
'?dbx_modul=dbxAdmin&dbx_run1=trace&dbx_run2=list_trace';
588 $oReport->_action = $this->action_url($oReport->_action);
589 $oReport->_pages =
true;
590 $oReport->_create_row_select =
true;
591 $oReport->_create_row_edit =
false;
592 $oReport->_create_row_show =
true;
593 $oReport->_create_row_delete =
true;
594 $oReport->_create_sel_flds =
true;
595 $oReport->_but_pagination = 5;
596 $oReport->set_callback_owner($this);
597 $oReport->set_body_callback(
'report_trace_body');
598 $oReport->set_callback(
'row_action_data',
'report_trace_row_action_data');
600 $oReport->enable_delete_tab($dd);
602 $oReport->add_action(
'rows_delete' ,
'action_button_delete' ,
'&dbx_do=multi_delete');
604 $oReport->create_selection_fields(
'dbxAdmin|rpt-trace-selection');
606 if ($work !=
'delete_tab' && $work !=
'multi_delete' && $work !=
'row_delete' && $oReport->submit()) {
607 if (!$oReport->errors()) {
608 $oReport->_msg_success =
'';
610 $oReport->_msg_err =
'Pruefen sie bitte ihre Eingaben';
614 $rwhere = $oReport->get_fld_val(
'dbx_rwhere' ,
'' ,
'sqlsearch|max=64');
615 $rrows = $oReport->get_fld_val(
'dbx_rrows' , 15 ,
'int');
616 $rpos = $oReport->get_fld_val(
'dbx_rpos' , 0 ,
'int');
617 $rsort = $oReport->get_fld_val(
'dbx_rsort' ,
'create_date',
'parameter');
618 $rdesc = $oReport->get_fld_val(
'dbx_rdesc' ,
'DESC' ,
'parameter');
619 $select = $oReport->get_fld_val(
'dbx_rselect', 0 ,
'int');
626 'like' => array(
'action',
'dd',
'data_json'),
627 'equal' => array(
'record_id',
'owner'),
628 'mode' =>
'contains',
634 if (is_array($rwhere)) {
635 $rwhere =
$db->normalize_where($dd, $rwhere);
638 $rwhere = $oReport->add_rwhere_select($rwhere);
641 $oReport->_rflds = $flds;
642 $oReport->_rrows = $rrows;
643 $oReport->_rpos = $rpos;
644 $oReport->_count_all =
$db->count($dd);
645 $oReport->_rcount =
$db->count($dd, $rwhere);
646 $oReport->_rdata =
$db->select($dd, $rwhere, $flds, $rsort, $rdesc, $rgroup, $rrows, $rpos);
648 return $oReport->run();
652 $run2 =
dbx()->get_modul_var(
'dbx_run2',
'',
'parameter');
654 if ($run2 ==
'record_history') {
655 return $this->record_history();
658 if ($run2 ==
'trace_detail') {
659 $trace_id =
dbx()->get_modul_var(
'trace_id', 0,
'int');
660 return $this->trace_detail($trace_id);
663 if ($run2 ==
'restore') {
664 $trace_id =
dbx()->get_modul_var(
'trace_id', 0,
'int');
665 return $this->restore_trace_before($trace_id);
668 if ($run2 ==
'undelete') {
669 $trace_id =
dbx()->get_modul_var(
'trace_id', 0,
'int');
670 return $this->undelete_trace_row($trace_id);
673 return $this->report_trace();