| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/seaside.pacim.de/web/ |
Upload File : |
<?php
/**
* terminals-api.php – Backoffice-Endpunkt für Terminals (Einstellungen → Terminals). Nur Admins.
* Aktionen (action=): list | get | create | update | delete | toggle | qr | accesses
*/
require_once( __DIR__ . '/includes/config.inc.php' );
require_once( __DIR__ . '/includes/mobileaccess.php' );
require_once( __DIR__ . '/includes/terminals.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
function tm_json( $a ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); echo json_encode( $a ); exit(); }
function tm_fail( $m ) { tm_json( array( 'ok' => false, 'error' => $m ) ); }
if ( ! function_exists( 'is_admin' ) || ! is_admin() ) tm_fail( 'Nicht berechtigt.' );
$action = (string) ( $_GET['action'] ?? ( $_POST['action'] ?? '' ) );
$db = new MysqliDb( MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB );
pacim_terminal_ensure_table( $db );
pacim_ma_ensure_table( $db );
function tm_url( $hash ) { return SITE_URL . '/terminal.php?h=' . $hash; }
function tm_qr_png( $url, $size = 8 ) {
require_once( PATH_SYSTEM . '/includes/plugins/tcpdf/tcpdf_barcodes_2d.php' );
$bc = new TCPDF2DBarcode( (string) $url, 'QRCODE,M' );
return $bc->getBarcodePngData( $size, $size, array( 13, 44, 68 ) );
}
function tm_access_item( $m ) {
return array(
'id' => (int) $m['ma_id'], 'label' => (string) $m['ma_label'],
'date' => substr( (string) $m['ma_date'], 0, 10 ),
'has_pin' => ( (string) $m['ma_pin'] !== '' ),
'summary' => function_exists( 'pacim_ma_summary' ) ? pacim_ma_summary( $m ) : array(),
);
}
function tm_result( $db, $id ) {
$t = $db->rawQueryOne( "SELECT * FROM pacim_terminals WHERE terminal_id = ?", array( (int) $id ) );
if ( ! $t ) return array( 'ok' => false, 'error' => 'Terminal nicht gefunden.' );
$url = tm_url( $t['terminal_hash'] );
$acc = array(); foreach ( pacim_terminal_accesses( $db, $id ) as $m ) $acc[] = tm_access_item( $m );
$logins = array();
foreach ( pacim_terminal_logins( $db, $id, false ) as $lg ) $logins[] = array(
'id' => (int) $lg['tl_id'], 'name' => (string) $lg['tl_name'], 'email' => (string) $lg['tl_email'],
'expires' => ! empty( $lg['tl_expires'] ) ? date( 'Y-m-d\TH:i', strtotime( $lg['tl_expires'] ) ) : '',
'active' => (int) $lg['tl_active'], 'has_pin' => ( (string) $lg['tl_pin'] !== '' ),
);
return array(
'ok' => true, 'id' => (int) $t['terminal_id'], 'hash' => $t['terminal_hash'],
'label' => (string) $t['terminal_label'], 'email' => (string) $t['terminal_email'],
'active' => (int) $t['terminal_active'], 'has_pin' => ( (string) $t['terminal_pin'] !== '' ),
'url' => $url, 'qr' => 'data:image/png;base64,' . base64_encode( tm_qr_png( $url, 8 ) ),
'accesses' => $acc, 'accessIds' => pacim_terminal_access_ids( $db, $id ),
'logins' => $logins,
);
}
/* ---- QR (Bildquelle) ---- */
if ( $action === 'qr' ) {
$hash = preg_replace( '/[^a-f0-9]/', '', strtolower( (string) ( $_GET['hash'] ?? '' ) ) );
if ( $hash === '' ) { http_response_code( 404 ); exit(); }
while ( ob_get_level() > 0 ) ob_end_clean();
header( 'Content-Type: image/png' ); header( 'Cache-Control: public, max-age=3600' );
echo tm_qr_png( tm_url( $hash ), 8 ); exit();
}
/* ---- Verfügbare Mobile-Zugänge (zum Zuordnen) ---- */
if ( $action === 'accesses' ) {
$rows = (array) $db->rawQuery( "SELECT * FROM pacim_mobile_access WHERE ma_active = 1 ORDER BY ma_date DESC, ma_id DESC LIMIT 500" );
$out = array(); foreach ( $rows as $m ) $out[] = tm_access_item( $m );
tm_json( array( 'ok' => true, 'accesses' => $out ) );
}
/* ---- Liste ---- */
if ( $action === 'list' ) {
$rows = (array) $db->rawQuery( "SELECT * FROM pacim_terminals ORDER BY terminal_active DESC, terminal_id DESC" );
$items = array();
foreach ( $rows as $t ) {
$cnt = $db->rawQueryOne( "SELECT COUNT(*) c FROM pacim_terminal_access WHERE terminal_id = ?", array( (int) $t['terminal_id'] ) );
$items[] = array(
'id' => (int) $t['terminal_id'], 'hash' => $t['terminal_hash'],
'label' => (string) $t['terminal_label'], 'email' => (string) $t['terminal_email'],
'active' => (int) $t['terminal_active'], 'has_pin' => ( (string) $t['terminal_pin'] !== '' ),
'url' => tm_url( $t['terminal_hash'] ), 'qr_url' => SITE_URL . '/terminals-api.php?action=qr&hash=' . $t['terminal_hash'],
'count' => $cnt ? (int) $cnt['c'] : 0,
);
}
tm_json( array( 'ok' => true, 'terminals' => $items ) );
}
/* ---- Einzeln (für Editor) ---- */
if ( $action === 'get' ) {
tm_json( tm_result( $db, (int) ( $_GET['id'] ?? 0 ) ) );
}
/* ---- Anlegen ---- */
if ( $action === 'create' ) {
$label = trim( mb_substr( (string) ( $_POST['label'] ?? '' ), 0, 190 ) );
$email = trim( mb_substr( (string) ( $_POST['email'] ?? '' ), 0, 190 ) );
if ( $label === '' ) tm_fail( 'Bitte eine Bezeichnung angeben.' );
if ( $email !== '' && ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) tm_fail( 'Ungültige E-Mail-Adresse.' );
// Master-PIN optional: leer = Terminal nutzt stattdessen Login-Zugänge (oder ist offen).
$pin = preg_replace( '/\D/', '', (string) ( $_POST['pin'] ?? '' ) );
if ( $pin !== '' && ( strlen( $pin ) < 4 || strlen( $pin ) > 10 ) ) tm_fail( 'PIN muss 4–10 Ziffern haben.' );
$hash = '';
for ( $i = 0; $i < 6; $i++ ) { $cand = bin2hex( random_bytes( 12 ) );
if ( ! $db->rawQueryOne( "SELECT 1 x FROM pacim_terminals WHERE terminal_hash = ?", array( $cand ) ) ) { $hash = $cand; break; } }
if ( $hash === '' ) tm_fail( 'Hash-Erzeugung fehlgeschlagen.' );
$ok = $db->insert( 'pacim_terminals', array(
'terminal_hash' => $hash, 'terminal_label' => $label, 'terminal_email' => $email,
'terminal_pin' => ( $pin !== '' ? pacim_terminal_pin_hash( $hash, $pin ) : '' ),
'terminal_active' => ( isset( $_POST['active'] ) && empty( $_POST['active'] ) ) ? 0 : 1,
'terminal_created' => date( 'Y-m-d H:i:s' ),
'terminal_created_by' => function_exists( 'current_user_id' ) ? current_user_id() : 0,
'terminal_created_name' => trim( (string) ( $_SESSION['user_name'] ?? '' ) ),
) );
if ( ! $ok ) tm_fail( 'Konnte nicht angelegt werden.' );
$ids = isset( $_POST['ids'] ) ? array_map( 'intval', explode( ',', (string) $_POST['ids'] ) ) : array();
pacim_terminal_set_access( $db, (int) $ok, $ids );
tm_json( tm_result( $db, (int) $ok ) );
}
/* ---- Bearbeiten ---- */
if ( $action === 'update' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$t = $db->rawQueryOne( "SELECT * FROM pacim_terminals WHERE terminal_id = ?", array( $id ) );
if ( ! $t ) tm_fail( 'Terminal nicht gefunden.' );
$label = trim( mb_substr( (string) ( $_POST['label'] ?? '' ), 0, 190 ) );
$email = trim( mb_substr( (string) ( $_POST['email'] ?? '' ), 0, 190 ) );
if ( $label === '' ) tm_fail( 'Bitte eine Bezeichnung angeben.' );
if ( $email !== '' && ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) tm_fail( 'Ungültige E-Mail-Adresse.' );
$upd = array( 'terminal_label' => $label, 'terminal_email' => $email );
// PIN: leer = unverändert; 4–10 Ziffern = neue PIN.
$pin = preg_replace( '/\D/', '', (string) ( $_POST['pin'] ?? '' ) );
if ( $pin !== '' ) { if ( strlen( $pin ) < 4 || strlen( $pin ) > 10 ) tm_fail( 'PIN muss 4–10 Ziffern haben.' ); $upd['terminal_pin'] = pacim_terminal_pin_hash( $t['terminal_hash'], $pin ); }
$db->where( 'terminal_id', $id );
if ( $db->update( 'pacim_terminals', $upd ) === false ) tm_fail( 'Konnte nicht gespeichert werden.' );
if ( isset( $_POST['ids'] ) ) pacim_terminal_set_access( $db, $id, array_map( 'intval', array_filter( explode( ',', (string) $_POST['ids'] ) ) ) );
tm_json( tm_result( $db, $id ) );
}
/* ---- Aktiv/Inaktiv ---- */
if ( $action === 'toggle' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$t = $db->rawQueryOne( "SELECT terminal_active FROM pacim_terminals WHERE terminal_id = ?", array( $id ) );
if ( ! $t ) tm_fail( 'Terminal nicht gefunden.' );
$new = (int) $t['terminal_active'] ? 0 : 1;
$db->where( 'terminal_id', $id ); $db->update( 'pacim_terminals', array( 'terminal_active' => $new ) );
tm_json( array( 'ok' => true, 'active' => $new ) );
}
/* ---- Löschen ---- */
if ( $action === 'delete' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$db->where( 'terminal_id', $id ); $db->delete( 'pacim_terminals' );
$db->where( 'terminal_id', $id ); $db->delete( 'pacim_terminal_access' );
tm_json( array( 'ok' => true ) );
}
/* ---- Login-Zugang anlegen/bearbeiten ---- */
if ( $action === 'login_save' ) {
$tid = (int) ( $_POST['terminal'] ?? 0 );
$t = $db->rawQueryOne( "SELECT * FROM pacim_terminals WHERE terminal_id = ?", array( $tid ) );
if ( ! $t ) tm_fail( 'Terminal nicht gefunden.' );
$lid = (int) ( $_POST['id'] ?? 0 );
$name = trim( mb_substr( (string) ( $_POST['name'] ?? '' ), 0, 120 ) );
$email = trim( mb_substr( (string) ( $_POST['email'] ?? '' ), 0, 190 ) );
if ( $name === '' ) tm_fail( 'Bitte einen Anzeigenamen angeben.' );
if ( $email !== '' && ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) tm_fail( 'Ungültige E-Mail-Adresse.' );
$exp = null; $exRaw = trim( (string) ( $_POST['expires'] ?? '' ) );
if ( $exRaw !== '' ) { $ts = strtotime( str_replace( 'T', ' ', $exRaw ) ); if ( $ts ) $exp = date( 'Y-m-d H:i:s', $ts ); }
$active = ( isset( $_POST['active'] ) && empty( $_POST['active'] ) ) ? 0 : 1;
$pin = preg_replace( '/\D/', '', (string) ( $_POST['pin'] ?? '' ) );
if ( $lid > 0 ) {
$lg = pacim_terminal_login_get( $db, $lid );
if ( ! $lg || (int) $lg['tl_terminal'] !== $tid ) tm_fail( 'Login nicht gefunden.' );
$upd = array( 'tl_name' => $name, 'tl_email' => $email, 'tl_expires' => $exp, 'tl_active' => $active );
if ( $pin !== '' ) { if ( strlen( $pin ) < 4 || strlen( $pin ) > 10 ) tm_fail( 'PIN muss 4–10 Ziffern haben.' ); $upd['tl_pin'] = pacim_terminal_login_pin_hash( $t['terminal_hash'], $lid, $pin ); }
$db->where( 'tl_id', $lid ); if ( $db->update( 'pacim_terminal_logins', $upd ) === false ) tm_fail( 'Konnte nicht gespeichert werden.' );
} else {
if ( strlen( $pin ) < 4 || strlen( $pin ) > 10 ) tm_fail( 'PIN muss 4–10 Ziffern haben.' );
$newId = $db->insert( 'pacim_terminal_logins', array(
'tl_terminal' => $tid, 'tl_name' => $name, 'tl_email' => $email, 'tl_expires' => $exp,
'tl_active' => $active, 'tl_pin' => '', 'tl_created' => date( 'Y-m-d H:i:s' ) ) );
if ( ! $newId ) tm_fail( 'Konnte nicht angelegt werden.' );
$db->where( 'tl_id', (int) $newId ); $db->update( 'pacim_terminal_logins', array( 'tl_pin' => pacim_terminal_login_pin_hash( $t['terminal_hash'], (int) $newId, $pin ) ) );
}
tm_json( tm_result( $db, $tid ) );
}
/* ---- Login-Zugang löschen ---- */
if ( $action === 'login_delete' ) {
$lid = (int) ( $_POST['id'] ?? 0 ); $tid = (int) ( $_POST['terminal'] ?? 0 );
$db->where( 'tl_id', $lid ); $db->where( 'tl_terminal', $tid ); $db->delete( 'pacim_terminal_logins' );
tm_json( tm_result( $db, $tid ) );
}
/* ==================================================================================
* Session-/Zugriffs-Tracking (Sitzungen, Fehl-Logins, Verlauf, Land/Whois)
* ================================================================================== */
if ( function_exists( 'pacim_ts_ensure_tables' ) ) pacim_ts_ensure_tables( $db );
/** Alle Sitzungs-IDs derselben (Terminal, Login, IP, Gerät, Status)-Gruppe wie die übergebene Sitzung. */
function tm_group_ids( $db, $sid ) {
$s = $db->rawQueryOne( "SELECT ts_terminal, ts_login, ts_ip, ts_ua, ts_ok FROM pacim_terminal_sessions WHERE ts_id = ?", array( (int) $sid ) );
if ( ! $s ) return array();
$rows = (array) $db->rawQuery(
"SELECT ts_id FROM pacim_terminal_sessions WHERE ts_terminal = ? AND ts_login = ? AND ts_ip = ? AND ts_ua = ? AND ts_ok = ?",
array( (int) $s['ts_terminal'], (int) $s['ts_login'], (string) $s['ts_ip'], (string) $s['ts_ua'], (int) $s['ts_ok'] ) );
$ids = array(); foreach ( $rows as $r ) $ids[] = (int) $r['ts_id'];
return $ids;
}
/* ---- Liste der Sitzungen (gruppiert nach Terminal · Login · IP · Gerät) + Fehlversuche ---- */
if ( $action === 'sessions' ) {
$tid = (int) ( $_GET['terminal'] ?? 0 );
$only = (string) ( $_GET['filter'] ?? '' ); // '' | 'ok' | 'fail'
$limit = max( 1, min( 500, (int) ( $_GET['limit'] ?? 200 ) ) );
$where = array(); $args = array();
if ( $tid > 0 ) { $where[] = 's.ts_terminal = ?'; $args[] = $tid; }
if ( $only === 'ok' ) $where[] = 's.ts_ok = 1';
if ( $only === 'fail' ) $where[] = 's.ts_ok = 0';
$wsql = $where ? ( 'WHERE ' . implode( ' AND ', $where ) ) : '';
// Gleiche IP + gleicher Kontext (Terminal/Login/Gerät/Status) → EIN Eintrag, Aktivität zusammengefasst.
$rows = (array) $db->rawQuery(
"SELECT MAX(s.ts_id) AS ts_id, s.ts_terminal, s.ts_login, s.ts_ip, s.ts_ua, s.ts_ok,
MAX(t.terminal_label) AS terminal_label, MAX(s.ts_name) AS ts_name,
MIN(s.ts_started) AS started, MAX(s.ts_last_seen) AS last_seen,
SUM(s.ts_events) AS events, COUNT(*) AS sessions,
MAX(NULLIF(s.ts_country,'')) AS ts_country, MAX(NULLIF(s.ts_geo,'')) AS ts_geo
FROM pacim_terminal_sessions s
LEFT JOIN pacim_terminals t ON t.terminal_id = s.ts_terminal
$wsql
GROUP BY s.ts_terminal, s.ts_login, s.ts_ip, s.ts_ua, s.ts_ok
ORDER BY last_seen DESC LIMIT $limit", $args );
// Fehlende Länder lazy nachziehen – je eindeutiger IP nur einmal, max. 15 externe Abfragen pro Aufruf,
// Ergebnis für alle Sitzungen dieser IP cachen.
$lookups = 0; $ipCache = array();
foreach ( $rows as &$r ) {
if ( trim( (string) $r['ts_country'] ) === '' && trim( (string) $r['ts_geo'] ) === '' ) {
$ip = (string) $r['ts_ip']; $g = null;
if ( isset( $ipCache[ $ip ] ) ) $g = $ipCache[ $ip ];
elseif ( $lookups < 15 ) { $lookups++; $g = pacim_ts_geo( $ip ); $ipCache[ $ip ] = $g; }
if ( $g !== null ) {
$geo = $g['geo'] !== '' ? $g['geo'] : '—';
$db->rawQuery( "UPDATE pacim_terminal_sessions SET ts_country = ?, ts_geo = ? WHERE ts_ip = ? AND ts_country = '' AND ts_geo = ''", array( $g['cc'], $geo, $ip ) );
$r['ts_country'] = $g['cc']; $r['ts_geo'] = $geo;
}
}
}
unset( $r );
$items = array();
foreach ( $rows as $r ) {
$items[] = array(
'id' => (int) $r['ts_id'],
'terminal' => (int) $r['ts_terminal'],
'terminal_label' => (string) ( $r['terminal_label'] ?? '' ),
'name' => (string) $r['ts_name'],
'ok' => (int) $r['ts_ok'],
'ip' => (string) $r['ts_ip'],
'ua' => (string) $r['ts_ua'],
'country' => strtoupper( (string) $r['ts_country'] ),
'geo' => (string) $r['ts_geo'],
'started' => (string) $r['started'],
'last_seen' => (string) $r['last_seen'],
'events' => (int) $r['events'],
'sessions' => (int) $r['sessions'],
);
}
tm_json( array( 'ok' => true, 'sessions' => $items ) );
}
/* ---- Einzelne Sitzung: Metadaten + Kennzahlen (Statistik/Diagramm). Der Verlauf kommt via session_events. ---- */
if ( $action === 'session' ) {
$sid = (int) ( $_GET['id'] ?? 0 );
$s = $db->rawQueryOne(
"SELECT s.*, t.terminal_label FROM pacim_terminal_sessions s
LEFT JOIN pacim_terminals t ON t.terminal_id = s.ts_terminal WHERE s.ts_id = ?", array( $sid ) );
if ( ! $s ) tm_fail( 'Sitzung nicht gefunden.' );
// Gesamte Gruppe (gleiche IP + gleicher Kontext) → zusammengefasste Metadaten & Verlauf.
$ids = tm_group_ids( $db, $sid ); if ( ! $ids ) $ids = array( $sid );
$in = implode( ',', $ids );
$meta = $db->rawQueryOne( "SELECT MIN(ts_started) started, MAX(ts_last_seen) last_seen, SUM(ts_events) events, COUNT(*) sessions FROM pacim_terminal_sessions WHERE ts_id IN ($in)" );
// Land/Ort aus der Gruppe (irgendeine gesetzte), sonst lazy auflösen und für die ganze IP cachen.
$geoRow = $db->rawQueryOne( "SELECT MAX(NULLIF(ts_country,'')) cc, MAX(NULLIF(ts_geo,'')) geo FROM pacim_terminal_sessions WHERE ts_id IN ($in)" );
$cc = (string) ( $geoRow['cc'] ?? '' ); $geo = (string) ( $geoRow['geo'] ?? '' );
if ( $cc === '' && $geo === '' ) {
$g = pacim_ts_geo( (string) $s['ts_ip'] ); $cc = $g['cc']; $geo = $g['geo'] !== '' ? $g['geo'] : '—';
$db->rawQuery( "UPDATE pacim_terminal_sessions SET ts_country = ?, ts_geo = ? WHERE ts_ip = ? AND ts_country = '' AND ts_geo = ''", array( $cc, $geo, (string) $s['ts_ip'] ) );
}
// Roh-Aggregat (Tag × Aktion × Stunde) über ALLE Sitzungen der Gruppe – der Client leitet daraus
// Kennzahlen ab und filtert nach Tag (Statistiken/Diagramm).
$agg = array();
foreach ( (array) $db->rawQuery( "SELECT DATE(te_at) d, te_type, HOUR(te_at) h, COUNT(*) c FROM pacim_terminal_events WHERE te_session IN ($in) GROUP BY DATE(te_at), te_type, HOUR(te_at)" ) as $r ) {
$agg[] = array( 'date' => (string) $r['d'], 'type' => (string) $r['te_type'], 'hour' => (int) $r['h'], 'count' => (int) $r['c'] );
}
$labels = array();
foreach ( (array) $db->rawQuery( "SELECT DISTINCT te_type FROM pacim_terminal_events WHERE te_session IN ($in)" ) as $r ) {
$labels[ (string) $r['te_type'] ] = function_exists( 'pacim_ts_event_label' ) ? pacim_ts_event_label( $r['te_type'] ) : (string) $r['te_type'];
}
tm_json( array( 'ok' => true, 'session' => array(
'id' => (int) $s['ts_id'],
'terminal_label' => (string) ( $s['terminal_label'] ?? '' ),
'name' => (string) $s['ts_name'],
'ok' => (int) $s['ts_ok'],
'ip' => (string) $s['ts_ip'],
'ua' => (string) $s['ts_ua'],
'country' => strtoupper( $cc ),
'geo' => $geo,
'started' => (string) ( $meta['started'] ?? $s['ts_started'] ),
'last_seen' => (string) ( $meta['last_seen'] ?? $s['ts_last_seen'] ),
'events' => (int) ( $meta['events'] ?? 0 ),
'sessions' => (int) ( $meta['sessions'] ?? 1 ),
), 'agg' => $agg, 'labels' => $labels ) );
}
/* ---- Verlauf einer Sitzung: gefiltert (Aktion/Zeitraum) + paginiert ---- */
if ( $action === 'session_events' ) {
$sid = (int) ( $_GET['id'] ?? 0 );
$gids = tm_group_ids( $db, $sid );
if ( ! $gids ) tm_fail( 'Sitzung nicht gefunden.' );
$gin = implode( ',', $gids );
$type = trim( (string) ( $_GET['type'] ?? '' ) );
$from = trim( (string) ( $_GET['from'] ?? '' ) );
$to = trim( (string) ( $_GET['to'] ?? '' ) );
$per = in_array( (int) ( $_GET['per'] ?? 20 ), array( 20, 50, 100, 200 ), true ) ? (int) $_GET['per'] : 20;
$page = max( 1, (int) ( $_GET['page'] ?? 1 ) );
$w = array( "e.te_session IN ($gin)" ); $a = array();
if ( $type !== '' ) { $w[] = 'e.te_type = ?'; $a[] = $type; }
if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $from ) ) { $w[] = 'e.te_at >= ?'; $a[] = $from . ' 00:00:00'; }
if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $to ) ) { $w[] = 'e.te_at < ?'; $a[] = date( 'Y-m-d 00:00:00', strtotime( $to . ' +1 day' ) ); }
$wsql = implode( ' AND ', $w );
$tot = $db->rawQueryOne( "SELECT COUNT(*) c FROM pacim_terminal_events e WHERE $wsql", $a );
$total = $tot ? (int) $tot['c'] : 0;
$pages = max( 1, (int) ceil( $total / $per ) );
if ( $page > $pages ) $page = $pages;
$off = ( $page - 1 ) * $per;
$rows = (array) $db->rawQuery(
"SELECT e.*, b.booking_no, license_plate(b.booking_serial) AS plate,
c.customer_lastname, c.customer_firstname, c.customer_name AS company
FROM pacim_terminal_events e
LEFT JOIN pacim_booking b ON b.booking_id = e.te_booking
LEFT JOIN pacim_customer c ON c.customer_id = b.booking_customer
WHERE $wsql ORDER BY e.te_at DESC, e.te_id DESC LIMIT $per OFFSET $off", $a );
$events = array();
foreach ( $rows as $e ) {
$ln = trim( (string) ( $e['customer_lastname'] ?? '' ) ); $fn = trim( (string) ( $e['customer_firstname'] ?? '' ) );
$cust = ( $ln !== '' || $fn !== '' ) ? trim( $ln . ( $fn !== '' ? ', ' . $fn : '' ) ) : trim( (string) ( $e['company'] ?? '' ) );
$events[] = array(
'at' => (string) $e['te_at'],
'type' => (string) $e['te_type'],
'label' => function_exists( 'pacim_ts_event_label' ) ? pacim_ts_event_label( $e['te_type'] ) : (string) $e['te_type'],
'booking' => (int) $e['te_booking'],
'invoice' => (int) $e['te_invoice'],
'booking_no' => (string) ( $e['booking_no'] ?? '' ),
'plate' => trim( (string) ( $e['plate'] ?? '' ) ),
'customer'=> $cust,
'detail' => (string) $e['te_detail'],
'link' => ( (int) $e['te_invoice'] > 0 ) ? ( SITE_URL . '/booking/view/?invoice_id=' . (int) $e['te_invoice'] ) : '',
);
}
tm_json( array( 'ok' => true, 'events' => $events, 'total' => $total, 'page' => $page, 'per' => $per, 'pages' => $pages ) );
}
/* ---- Whois-/RIR-Abfrage zu einer IP ---- */
if ( $action === 'whois' ) {
$ip = trim( (string) ( $_GET['ip'] ?? '' ) );
tm_json( pacim_ts_whois( $ip ) );
}
/* ---- Sitzungen löschen (einzeln / Fehl-Logins / alle) ---- */
if ( $action === 'sessions_delete' ) {
$sid = (int) ( $_POST['id'] ?? 0 );
$scope = (string) ( $_POST['scope'] ?? '' ); // '' (einzeln) | 'fails' | 'all'
if ( $scope === 'all' ) {
$db->rawQuery( "DELETE FROM pacim_terminal_events" );
$db->rawQuery( "DELETE FROM pacim_terminal_sessions" );
} elseif ( $scope === 'fails' ) {
$db->rawQuery( "DELETE FROM pacim_terminal_sessions WHERE ts_ok = 0" );
} elseif ( $sid > 0 ) {
// Ganze Gruppe (gleiche IP + Kontext) löschen, wie in der Liste dargestellt.
$gids = tm_group_ids( $db, $sid ); if ( ! $gids ) $gids = array( $sid );
$gin = implode( ',', $gids );
$db->rawQuery( "DELETE FROM pacim_terminal_events WHERE te_session IN ($gin)" );
$db->rawQuery( "DELETE FROM pacim_terminal_sessions WHERE ts_id IN ($gin)" );
} else { tm_fail( 'Nichts zu löschen.' ); }
tm_json( array( 'ok' => true ) );
}
tm_fail( 'Unbekannte Aktion.' );