| 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
/**
* booking-mail.php – „E-Mail erstellen" in der Buchungsansicht (Admin).
* Liefert auswählbare Templates als Karten (mit Vorschau, Platzhalter bereits durch
* die echten Daten der Buchung ersetzt) und versendet die im Modal bearbeitete Mail.
* Anhänge werden automatisch aufgelöst; beim Versand greifen Konto & BCC der zum
* Template angelegten Regel.
*
* Aktionen:
* templates GET invoice_id -> { to, templates:[{id,name,type,subject,html,attachments[]}] }
* send POST invoice_id, template_id, to, subject, html -> versendet + protokolliert
*/
require_once( __DIR__ . '/includes/config.inc.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
ob_start();
function bm_json( $a ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); echo json_encode( $a ); exit(); }
function bm_fail( $m ) { bm_json( array( 'ok' => false, 'error' => $m ) ); }
if ( ! function_exists( 'is_admin' ) || ! is_admin() ) { http_response_code( 403 ); bm_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 ) bm_fail( 'Ungültige Rechnung.' );
/* ---- Auswählbare Templates inkl. gerenderter Vorschau (echte Daten) ---- */
if ( $action === 'templates' ) {
$ctx = mailrenderer::context( $invoice );
$db->rawQuery( "ALTER TABLE pacim_templates ADD COLUMN IF NOT EXISTS template_booking TINYINT(1) NOT NULL DEFAULT 0" );
$rows = $db->rawQuery( "SELECT template_id, template_key, template_name, template_type FROM pacim_templates
WHERE template_active=1 AND template_booking=1 ORDER BY template_name" );
$out = array();
foreach ( (array) $rows as $t ) {
$tpl = (object) $db->rawQueryOne( "SELECT * FROM pacim_templates WHERE template_id=?", array( (int) $t['template_id'] ) );
$r = mailrenderer::render( $tpl, $ctx, false );
$att = mailrenderer::resolveAttachments( (int) $t['template_id'], $ctx );
$names = array();
foreach ( $att['files'] as $f ) $names[] = $f['name'];
$out[] = array(
'id' => (int) $t['template_id'], 'name' => $t['template_name'], 'type' => $t['template_key'],
'subject' => $r['subject'], 'html' => $r['html'], 'attachments' => $names,
);
}
bm_json( array( 'ok' => true, 'to' => trim( (string) ( $ctx['customer_email'] ?? '' ) ), 'templates' => $out ) );
}
/* ---- Eigenes Dokument hochladen (PDF) für den Versand ---- *
* Speichert die Datei in datas/incoming/mailupload unter einem zufälligen Token und gibt
* Token + bereinigten Anzeigenamen zurück. Beim Senden wird das Token (s. u.) angehängt.
* Nur Admin (oben geprüft), nur PDF, max. 15 MB; alte Uploads (>24 h) werden mitaufgeräumt. */
if ( $action === 'upload' ) {
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bm_fail( 'method' );
if ( ! isset( $_FILES['file'] ) || ! is_uploaded_file( (string) ( $_FILES['file']['tmp_name'] ?? '' ) ) ) bm_fail( 'Keine Datei empfangen.' );
$f = $_FILES['file'];
if ( (int) $f['error'] !== UPLOAD_ERR_OK ) bm_fail( 'Upload fehlgeschlagen (Code ' . (int) $f['error'] . ').' );
if ( (int) $f['size'] <= 0 || (int) $f['size'] > 15 * 1024 * 1024 ) bm_fail( 'Datei ist leer oder zu groß (max. 15 MB).' );
$ext = strtolower( pathinfo( (string) $f['name'], PATHINFO_EXTENSION ) );
$mime = function_exists( 'finfo_open' ) ? @finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $f['tmp_name'] ) : 'application/pdf';
if ( $ext !== 'pdf' || stripos( (string) $mime, 'pdf' ) === false ) bm_fail( 'Nur PDF-Dateien sind erlaubt.' );
$dir = rtrim( PATH_DATAS, '/' ) . '/incoming/mailupload';
if ( ! is_dir( $dir ) ) @mkdir( $dir, 0775, true );
// Aufräumen: Uploads älter als 24 h entfernen
foreach ( (array) @glob( $dir . '/*.pdf' ) as $old ) { if ( is_file( $old ) && filemtime( $old ) < time() - 86400 ) @unlink( $old ); }
$token = bin2hex( random_bytes( 16 ) ) . '.pdf';
if ( ! @move_uploaded_file( $f['tmp_name'], $dir . '/' . $token ) ) bm_fail( 'Datei konnte nicht gespeichert werden.' );
$name = preg_replace( '/[^\w.\- ]+/u', '', (string) $f['name'] );
if ( $name === '' || strtolower( pathinfo( $name, PATHINFO_EXTENSION ) ) !== 'pdf' ) $name = 'Dokument.pdf';
bm_json( array( 'ok' => true, 'token' => $token, 'name' => $name, 'size' => (int) $f['size'] ) );
}
/* ---- Versenden ---- *
* Konsistent zur gated Mail-Pipeline: Die (im Modal bearbeitete) Mail wird als Snapshot
* in pacim_messages abgelegt. Ist der lokale SMTP-Versand aktiv, wird sofort versendet
* (Status 1), sonst landet sie in der Queue (Status 5) und geht nach Aktivierung raus.
* Konto & BCC stammen aus der zum Template angelegten aktiven Regel. */
if ( $action === 'send' ) {
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bm_fail( 'method' );
$tplId = (int) ( $_POST['template_id'] ?? 0 );
$to = trim( (string) ( $_POST['to'] ?? '' ) );
$subject = (string) ( $_POST['subject'] ?? '' );
$html = (string) ( $_POST['html'] ?? '' );
if ( ! filter_var( $to, FILTER_VALIDATE_EMAIL ) ) bm_fail( 'Bitte eine gültige Empfängeradresse angeben.' );
if ( trim( $html ) === '' ) bm_fail( 'Kein Inhalt zum Versenden.' );
$ctx = mailrenderer::context( $invoice );
$files = array();
if ( $tplId > 0 ) { $att = mailrenderer::resolveAttachments( $tplId, $ctx ); $files = $att['files']; }
// Eigene hochgeladene Anhänge (Tokens aus action=upload); optional automatische Anhänge ersetzen.
if ( ! empty( $_POST['skip_auto'] ) ) $files = array();
$uploads = json_decode( (string) ( $_POST['uploads'] ?? '' ), true );
if ( is_array( $uploads ) ) {
$udir = rtrim( PATH_DATAS, '/' ) . '/incoming/mailupload';
foreach ( $uploads as $u ) {
$tok = isset( $u['token'] ) ? (string) $u['token'] : '';
if ( ! preg_match( '/^[a-f0-9]{32}\.pdf$/', $tok ) ) continue; // nur eigene Token-Dateien
$path = $udir . '/' . $tok;
if ( ! is_file( $path ) ) continue;
$nm = isset( $u['name'] ) ? preg_replace( '/[^\w.\- ]+/u', '', (string) $u['name'] ) : '';
if ( $nm === '' || strtolower( pathinfo( $nm, PATHINFO_EXTENSION ) ) !== 'pdf' ) $nm = 'Dokument.pdf';
$files[] = array( 'path' => $path, 'name' => $nm );
}
}
// Konto & BCC aus der zum Template angelegten (aktiven) Regel übernehmen
$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 = (string) $rule['rule_bcc']; $opts['bcc'] = $ruleBcc; }
}
}
// Snapshot anlegen (erscheint in der E-Mail-Liste der Buchung)
$cfg = mailservice::config();
$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 ) );
$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' => (int) ( $bk['booking_id'] ?? 0 ),
'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 ) {
bm_json( array( 'ok' => true, 'queued' => true, 'message' => 'E-Mail in die Warteschlange gelegt – sie wird versendet, sobald der SMTP-Versand aktiviert ist.' ) );
}
// SMTP aktiv -> sofort versenden
$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 ) );
bm_json( array( 'ok' => true, 'message' => 'E-Mail versendet.' ) );
}
// Fehler: in Queue belassen (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 ) ) );
bm_fail( 'Versand fehlgeschlagen: ' . ( $res['error'] ?? 'unbekannt' ) . '. Die E-Mail bleibt in der Warteschlange und wird automatisch erneut versucht.' );
}
bm_fail( 'unknown_action' );