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/seaside.pacim.de/web/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/seaside.pacim.de/web/booking-storno.php
<?php
/**
 * booking-storno.php – Storno / Storno-Rückgängig einer Buchung (Admin) inkl.
 * optionalem Mailversand mit Vorschau-/Bearbeiten-Schritt.
 *
 * Aktionen (action):
 *   cancel    POST invoice_id            -> invoice_status = 3
 *   uncancel  POST invoice_id            -> invoice_status = 0
 *   preview   GET/POST invoice_id, kind(storno|confirm) -> {to, subject, html, attachments[]}
 *   send      POST invoice_id, kind, to, subject, html  -> versendet (mailservice)
 *
 * Die eigentliche Storno-Aktion (cancel/uncancel) wird sofort ausgeführt; der Mailversand
 * ist ein getrennter, optionaler Schritt mit Vorschau (Inhalt editierbar).
 */
require_once( __DIR__ . '/includes/config.inc.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
ob_start();

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

if ( ! function_exists( 'is_admin' ) || ! is_admin() ) { http_response_code( 403 ); bs_json( array( 'ok' => false, 'error' => 'forbidden' ) ); }

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

if ( $invoice <= 0 ) bs_fail( 'Ungültige Rechnung.' );

/* ---- Status setzen ---- */
if ( $action === 'cancel' || $action === 'uncancel' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bs_fail( 'method' );
    $first = $db->rawQueryOne( "SELECT booking_id, booking_no FROM pacim_booking WHERE booking_invoice = ? ORDER BY booking_id ASC LIMIT 1", array( $invoice ) );
    $bid   = $first ? (int) $first['booking_id'] : 0;
    $logBefore = $bid > 0 ? apilog::snapshot( $db, $bid ) : null;
    $db->where( 'invoice_id', $invoice );
    $db->update( 'pacim_invoice', array( 'invoice_status' => ( $action === 'cancel' ? 3 : 0 ) ) );
    if ( $bid > 0 ) {
        apilog::record( array(
            'key_name' => trim( (string) ( $_SESSION['user_name'] ?? 'Administrator' ) ),
            'ip'       => $_SERVER['REMOTE_ADDR'] ?? '', 'method' => 'POST',
            'resource' => 'booking', 'action' => ( $action === 'cancel' ? 'storno' : 'unstorno' ), 'kind' => 'write',
            'booking'  => $bid, 'invoice' => $invoice,
            'target'   => ( $action === 'cancel' ? 'Buchung storniert' : 'Storno zurückgenommen' ) . ( $first ? ' (' . $first['booking_no'] . ')' : '' ),
            'changes'  => apilog::diff( $logBefore, apilog::snapshot( $db, $bid ) ),
        ) );
    }
    bs_json( array( 'ok' => true ) );
}

/* ---- Mailvorlage des passenden Typs laden ---- */
function bs_template( $db, $kind ) {
    $key = ( $kind === 'confirm' ) ? 'booking_confirmation' : 'cancellation';
    $t = $db->rawQueryOne( "SELECT * FROM pacim_templates WHERE template_key = ?", array( $key ) );
    return $t ? (object) $t : null;
}

/* ---- Vorschau: gerenderte Mail zurückgeben ---- */
if ( $action === 'preview' ) {
    $kind = ( ( $_GET['kind'] ?? ( $_POST['kind'] ?? 'storno' ) ) === 'confirm' ) ? 'confirm' : 'storno';
    $tpl  = bs_template( $db, $kind );
    if ( ! $tpl ) bs_fail( 'Vorlage nicht gefunden.' );
    $ctx = mailrenderer::context( $invoice );
    $r   = mailrenderer::render( $tpl, $ctx, false );
    $att = mailrenderer::resolveAttachments( (int) $tpl->template_id, $ctx );
    $names = array();
    foreach ( $att['files'] as $f ) $names[] = $f['name'];
    bs_json( array(
        'ok' => true,
        'to' => trim( (string) ( $ctx['customer_email'] ?? '' ) ),
        'subject' => $r['subject'],
        'html' => $r['html'],
        'attachments' => $names,
    ) );
}

/* ---- Senden (bearbeiteter Inhalt) ----
 * Storno-/Bestätigungsmail läuft bewusst AN DEN REGELN VORBEI (manuelle Aktion, kein
 * Regel-Trigger). Trotzdem wird sie – wie die übrige Mail-Pipeline – als Snapshot in
 * pacim_messages protokolliert (erscheint im E-Mail-Tab der Buchung) und erhält eine
 * Kopie (BCC) an die Firmenadresse. Dedupe: pro Buchung wird je Vorlage nur EINMAL
 * versendet (Storno-Mail darf nur einmal pro Buchung rausgehen). */
if ( $action === 'send' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bs_fail( 'method' );
    $kind    = ( ( $_POST['kind'] ?? 'storno' ) === 'confirm' ) ? 'confirm' : 'storno';
    $to      = trim( (string) ( $_POST['to'] ?? '' ) );
    $subject = (string) ( $_POST['subject'] ?? '' );
    $html    = (string) ( $_POST['html'] ?? '' );
    if ( ! filter_var( $to, FILTER_VALIDATE_EMAIL ) ) bs_fail( 'Bitte eine gültige Empfängeradresse angeben.' );
    if ( trim( $html ) === '' ) bs_fail( 'Kein Inhalt zum Versenden.' );

    $tpl   = bs_template( $db, $kind );
    $tplId = $tpl ? (int) $tpl->template_id : 0;
    $files = array();
    if ( $tpl ) {
        $ctx = mailrenderer::context( $invoice );
        $att = mailrenderer::resolveAttachments( $tplId, $ctx );
        $files = $att['files'];
    }

    // Buchung/Kunde für Protokoll + Dedupe ermitteln
    $cust = $db->rawQueryOne( "SELECT invoice_customer FROM pacim_invoice WHERE invoice_id=?", array( $invoice ) );
    $bk   = $db->rawQueryOne( "SELECT booking_id FROM pacim_booking WHERE booking_invoice=? ORDER BY booking_id ASC LIMIT 1", array( $invoice ) );
    $bookingId = (int) ( $bk['booking_id'] ?? 0 );

    // Dedupe: je Vorlage + Buchung nur ein Versand (bereits gesendet ODER in Queue wartend)
    if ( $tplId > 0 && $bookingId > 0 ) {
        $dup = $db->rawQueryOne(
            "SELECT message_id FROM pacim_messages WHERE message_template=? AND message_booking=? AND message_status IN (1,5) LIMIT 1",
            array( $tplId, $bookingId )
        );
        if ( $dup ) bs_fail( $kind === 'confirm'
            ? 'Für diese Buchung wurde bereits eine Bestätigung versendet.'
            : 'Für diese Buchung wurde bereits eine Storno-Mail versendet.' );
    }

    // Konto & BCC (Kopie): vorhandene Regel zur Vorlage nutzen, sonst Firmenabsender als Kopie.
    $opts = array(); $ruleAccount = 0; $ruleBcc = null;
    if ( $tplId > 0 ) {
        $rule = $db->rawQueryOne( "SELECT rule_account, rule_bcc FROM pacim_mail_rules WHERE rule_template=? AND rule_active=1 ORDER BY rule_id ASC LIMIT 1", array( $tplId ) );
        if ( $rule ) {
            if ( (int) $rule['rule_account'] > 0 ) { $ruleAccount = (int) $rule['rule_account']; $opts['account'] = $ruleAccount; }
            if ( trim( (string) $rule['rule_bcc'] ) !== '' ) $ruleBcc = trim( (string) $rule['rule_bcc'] );
        }
    }
    $cfg = mailservice::config();
    if ( $ruleBcc === null || $ruleBcc === '' ) {
        $fallback = trim( (string) $cfg['from_email'] );           // Firmenadresse als Kopie, wenn keine Regel-BCC
        if ( $fallback !== '' && filter_var( $fallback, FILTER_VALIDATE_EMAIL ) ) $ruleBcc = $fallback;
    }
    if ( $ruleBcc !== null && $ruleBcc !== '' ) $opts['bcc'] = $ruleBcc;

    // Snapshot anlegen (erscheint in der E-Mail-Liste der Buchung). message_rule bleibt NULL
    // -> der Queue-Versand prüft KEINE Regel (Storno-Mail soll trotz stornierter Rechnung raus).
    $live  = mailservice::smtpEnabled();
    $msgId = $db->insert( 'pacim_messages', array(
        'message_status'   => 5, 'message_type' => 'system', 'message_count' => 0,
        'message_template' => $tplId, 'message_customer' => (int) ( $cust['invoice_customer'] ?? 0 ),
        'message_invoice'  => $invoice, 'message_booking' => $bookingId,
        'message_subject'  => $subject, 'message_sender' => $cfg['from_email'], 'message_receiver' => $to,
        'message_account'  => $ruleAccount, 'message_bcc' => $ruleBcc,
        'message_content'  => $html, 'message_token' => pacim_sign( 'msg:' . uniqid( '', true ) ),
        'message_datetime' => date( 'Y-m-d H:i:s' ),
    ) );

    if ( ! $live ) {
        bs_json( array( 'ok' => true, 'queued' => true, 'message' => 'E-Mail in die Warteschlange gelegt – sie wird versendet, sobald der SMTP-Versand aktiviert ist.' ) );
    }

    $res = mailservice::send( $to, '', $subject, $html, $files, $opts );
    if ( ! empty( $res['ok'] ) ) {
        $db->where( 'message_id', (int) $msgId );
        $db->update( 'pacim_messages', array( 'message_status' => 1, 'message_count' => 1, 'message_error' => null ) );
        bs_json( array( 'ok' => true, 'message' => 'E-Mail versendet.' ) );
    }
    // Fehler: Snapshot bleibt in der Queue (Cron versucht erneut)
    $db->where( 'message_id', (int) $msgId );
    $db->update( 'pacim_messages', array( 'message_count' => 1, 'message_error' => mb_substr( (string) ( $res['error'] ?? 'unbekannt' ), 0, 500 ) ) );
    bs_fail( 'Versand fehlgeschlagen: ' . ( $res['error'] ?? 'unbekannt' ) . '. Die E-Mail bleibt in der Warteschlange und wird automatisch erneut versucht.' );
}

bs_fail( 'unknown_action' );

Youez - 2016 - github.com/yon3zu
LinuXploit