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/seaside2.pacim.de/web/app/services/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/seaside2.pacim.de/web/app/services/EmailTemplateRenderer.php
<?php
/**
 * Rendert E-Mail-Templates: ersetzt Platzhalter durch konkrete Buchungs-Daten
 * und produziert Subject + Body + Recipient + Attachment-Pfade.
 *
 * Verwendung:
 *   $renderer = new EmailTemplateRenderer($db);
 *   $rendered = $renderer->renderForBooking($template, $bookingId);
 *   // → ['subject' => '…', 'body' => '…', 'to' => '…', 'attachments' => [['path' => '…', 'name' => '…']]]
 *
 * Platzhalter (zwischen geschweiften Klammern, ASCII-Variante {placeholder}):
 *   Kunde:   {customer.first_name}, {customer.last_name}, {customer.company},
 *            {customer.email}, {customer.phone}, {customer.greeting}
 *   Buchung: {booking.no}, {booking.travel_from}, {booking.travel_to}, {booking.duration_days},
 *            {booking.arrival_time}, {booking.license_plate}, {booking.total_gross},
 *            {booking.total_net}, {booking.event_title}, {booking.event_ship},
 *            {booking.event_terminal}, {booking.event_starts_at}, {booking.event_ends_at},
 *            {booking.status}, {booking.payment_status}, {booking.checkin_status}
 *   Sonst:   {invoice.no}, {company.name}, {company.email}
 *   Liste:   {products_list}      — formatierte HTML-Tabelle (Produkt · Menge · Preis · Summe)
 *            {products_list_text} — Plain-Text-Variante (pro Zeile: 1x Außen … 90,00 €)
 */
class EmailTemplateRenderer
{
    private PDO $pdo;
    private Booking $bookingRepo;
    private Invoice $invoiceRepo;
    private Settings $settingsRepo;

    public function __construct(Database $db, Booking $b, Invoice $i, Settings $s)
    {
        $this->pdo          = $db->getConnection();
        $this->bookingRepo  = $b;
        $this->invoiceRepo  = $i;
        $this->settingsRepo = $s;
    }

    public function renderForBooking(array $template, int $bookingId): array
    {
        $booking = $this->bookingRepo->find($bookingId);
        if (!$booking) {
            throw new RuntimeException('Buchung nicht gefunden.');
        }
        $items   = $this->bookingRepo->products($bookingId);
        $invoice = $this->invoiceRepo->findByBooking($bookingId);
        $vars    = $this->buildVars($booking, $items, $invoice);

        $subject = $this->replaceAll((string)$template['subject'],   $vars);
        $body    = $this->replaceAll((string)$template['body_html'], $vars);

        $attachments = [];
        if (!empty($template['attach_confirmation']) && !empty($booking['confirmation_path'])) {
            $abs = PdfRenderer::absolutePath((string)$booking['confirmation_path']);
            if ($abs && is_file($abs)) {
                $attachments[] = ['path' => $abs, 'name' => 'Buchungsbestaetigung-' . ($booking['booking_no'] ?? $bookingId) . (str_ends_with($abs, '.pdf') ? '.pdf' : '.html')];
            }
        }
        if (!empty($template['attach_invoice']) && $invoice) {
            $pdf = !empty($invoice['pdf_path']) ? PdfRenderer::absolutePath((string)$invoice['pdf_path']) : null;
            if ($pdf && is_file($pdf)) {
                $attachments[] = ['path' => $pdf, 'name' => 'Rechnung-' . ($invoice['invoice_no'] ?? $invoice['id']) . '.pdf'];
            } elseif (!empty($invoice['html_path'])) {
                $html = PdfRenderer::absolutePath((string)$invoice['html_path']);
                if ($html && is_file($html)) {
                    $attachments[] = ['path' => $html, 'name' => 'Rechnung-' . ($invoice['invoice_no'] ?? $invoice['id']) . '.html'];
                }
            }
        }

        return [
            'subject'     => $subject,
            'body'        => $body,
            'to'          => (string)($booking['c_email'] ?? ''),
            'cc'          => $this->splitAddresses((string)($template['cc_addresses']  ?? '')),
            'bcc'         => $this->splitAddresses((string)($template['bcc_addresses'] ?? '')),
            'attachments' => $attachments,
            'booking'     => $booking,
            'vars'        => $vars,
        ];
    }

    /**
     * Liefert die für die UI dokumentierte Liste der Platzhalter (Anzeige im Editor).
     * @return array<string,string> Map placeholder → kurze Beschreibung
     */
    public static function placeholderList(): array
    {
        return [
            '{customer.first_name}'  => 'Vorname',
            '{customer.last_name}'   => 'Nachname',
            '{customer.company}'     => 'Firma (falls vorhanden)',
            '{customer.email}'       => 'E-Mail',
            '{customer.phone}'       => 'Telefon',
            '{customer.greeting}'    => 'Anrede ("Sehr geehrter Herr X" o.ä.)',
            '{booking.no}'           => 'Buchungs-Nr.',
            '{booking.travel_from}'  => 'Reisebeginn (TT.MM.JJJJ)',
            '{booking.travel_to}'    => 'Reiseende',
            '{booking.duration_days}'=> 'Reisedauer in Tagen',
            '{booking.arrival_time}' => 'Anreisezeit',
            '{booking.license_plate}'=> 'Kennzeichen',
            '{booking.total_gross}'  => 'Gesamtbetrag (brutto)',
            '{booking.total_net}'    => 'Gesamtbetrag (netto)',
            '{booking.event_title}'  => 'Event-Titel',
            '{booking.event_ship}'   => 'Schiff',
            '{booking.event_terminal}'=> 'Terminal',
            '{booking.event_starts_at}'=> 'Event-Start',
            '{booking.event_ends_at}'  => 'Event-Ende',
            '{booking.status}'        => 'Buchungs-Status',
            '{booking.payment_status}'=> 'Zahlungs-Status',
            '{booking.checkin_status}'=> 'Check-in-Status',
            '{invoice.no}'            => 'Rechnungs-Nr. (falls vorhanden)',
            '{company.name}'          => 'Firmenname (aus Einstellungen)',
            '{company.email}'         => 'Firmen-E-Mail',
            '{products_list}'         => 'Tabelle der gebuchten Leistungen (HTML)',
            '{products_list_text}'    => 'Auflistung der Leistungen (Plain-Text)',
        ];
    }

    private function buildVars(array $b, array $items, ?array $invoice): array
    {
        $name    = trim(($b['c_first_name'] ?? '') . ' ' . ($b['c_last_name'] ?? ''));
        $greeting = $this->buildGreeting($b);
        $days    = 0;
        if (!empty($b['travel_from']) && !empty($b['travel_to'])) {
            $f = strtotime((string)$b['travel_from']);
            $t = strtotime((string)$b['travel_to']);
            if ($f && $t && $t >= $f) {
                $days = (int)round((strtotime(date('Y-m-d', $t) . ' 00:00:00') - strtotime(date('Y-m-d', $f) . ' 00:00:00')) / 86400);
            }
        }
        $company = $this->settingsRepo->company();
        return [
            'customer.first_name'  => (string)($b['c_first_name'] ?? ''),
            'customer.last_name'   => (string)($b['c_last_name']  ?? ''),
            'customer.company'     => (string)($b['c_company']    ?? ''),
            'customer.email'       => (string)($b['c_email']      ?? ''),
            'customer.phone'       => (string)($b['c_phone']      ?? ''),
            'customer.greeting'    => $greeting,
            'booking.no'              => (string)($b['booking_no'] ?? ''),
            'booking.travel_from'     => $this->fmtDate($b['travel_from']     ?? null),
            'booking.travel_to'       => $this->fmtDate($b['travel_to']       ?? null),
            'booking.duration_days'   => (string)$days,
            'booking.arrival_time'    => $this->fmtTime($b['arrival_time']    ?? null),
            'booking.license_plate'   => (string)($b['license_plate'] ?? ''),
            'booking.total_gross'     => $this->fmtMoney($b['total_gross'] ?? 0),
            'booking.total_net'       => $this->fmtMoney($b['total_net']   ?? 0),
            'booking.event_title'     => (string)($b['event_title']     ?? ''),
            'booking.event_ship'      => (string)($b['event_ship']      ?? ''),
            'booking.event_terminal'  => (string)($b['event_terminal']  ?? ''),
            'booking.event_starts_at' => $this->fmtDateTime($b['event_starts_at'] ?? null),
            'booking.event_ends_at'   => $this->fmtDateTime($b['event_ends_at']   ?? null),
            'booking.status'          => (string)($b['status'] ?? ''),
            'booking.payment_status'  => (string)($b['payment_status'] ?? ''),
            'booking.checkin_status'  => (string)($b['checkin_status'] ?? ''),
            'invoice.no'              => $invoice ? (string)($invoice['invoice_no'] ?? '') : '',
            'company.name'            => (string)($company['name']  ?? ''),
            'company.email'           => (string)($company['email'] ?? ''),
            'products_list'           => $this->renderProductsHtml($items),
            'products_list_text'      => $this->renderProductsText($items),
        ];
    }

    private function buildGreeting(array $b): string
    {
        $first = trim((string)($b['c_first_name'] ?? ''));
        $last  = trim((string)($b['c_last_name']  ?? ''));
        if ($last === '') {
            return 'Sehr geehrte Damen und Herren';
        }
        // Geschlecht ist im Booking-Row nicht zuverlässig — neutral grüßen.
        return 'Sehr geehrte/r ' . trim($first . ' ' . $last);
    }

    private function renderProductsHtml(array $items): string
    {
        if (empty($items)) return '<p><em>Keine Positionen.</em></p>';
        $rows = '';
        $sum  = 0.0;
        foreach ($items as $it) {
            $line = (float)$it['total_gross'];
            $sum += $line;
            $rows .= sprintf(
                '<tr><td style="padding:4px 8px;border-bottom:1px solid #e9ecef;">%s</td>'
                . '<td style="padding:4px 8px;text-align:right;border-bottom:1px solid #e9ecef;">%dx</td>'
                . '<td style="padding:4px 8px;text-align:right;border-bottom:1px solid #e9ecef;">%s</td>'
                . '<td style="padding:4px 8px;text-align:right;border-bottom:1px solid #e9ecef;font-weight:600;">%s</td></tr>',
                htmlspecialchars((string)($it['product_title'] ?? ''), ENT_QUOTES, 'UTF-8'),
                (int)$it['quantity'],
                $this->fmtMoney($it['price_gross'] ?? 0),
                $this->fmtMoney($line)
            );
        }
        return '<table cellpadding="0" cellspacing="0" style="border-collapse:collapse;font-family:inherit;width:100%;max-width:560px;">'
             . '<thead><tr>'
             .  '<th style="padding:6px 8px;text-align:left;border-bottom:2px solid #1a1a1a;">Leistung</th>'
             .  '<th style="padding:6px 8px;text-align:right;border-bottom:2px solid #1a1a1a;">Menge</th>'
             .  '<th style="padding:6px 8px;text-align:right;border-bottom:2px solid #1a1a1a;">Einzelpreis</th>'
             .  '<th style="padding:6px 8px;text-align:right;border-bottom:2px solid #1a1a1a;">Summe</th>'
             . '</tr></thead>'
             . '<tbody>' . $rows . '</tbody>'
             . '<tfoot><tr><td colspan="3" style="padding:8px;text-align:right;font-weight:700;">Gesamt</td>'
             . '<td style="padding:8px;text-align:right;font-weight:700;">' . $this->fmtMoney($sum) . '</td></tr></tfoot>'
             . '</table>';
    }

    private function renderProductsText(array $items): string
    {
        if (empty($items)) return 'Keine Positionen.';
        $lines = [];
        foreach ($items as $it) {
            $lines[] = sprintf('%dx %s · %s',
                (int)$it['quantity'],
                (string)($it['product_title'] ?? ''),
                $this->fmtMoney($it['total_gross'] ?? 0)
            );
        }
        return implode("\n", $lines);
    }

    private function replaceAll(string $template, array $vars): string
    {
        $search = $replace = [];
        foreach ($vars as $key => $val) {
            $search[]  = '{' . $key . '}';
            $replace[] = (string)$val;
        }
        return str_replace($search, $replace, $template);
    }

    private function splitAddresses(string $csv): array
    {
        return array_values(array_filter(array_map('trim', preg_split('/[,;\s]+/', $csv) ?: [])));
    }

    private function fmtDate($v): string
    {
        if (!$v) return '';
        $ts = strtotime((string)$v);
        return $ts ? date('d.m.Y', $ts) : (string)$v;
    }

    private function fmtTime($v): string
    {
        if (!$v) return '';
        $ts = strtotime((string)$v);
        return $ts ? date('H:i', $ts) . ' Uhr' : (string)$v;
    }

    private function fmtDateTime($v): string
    {
        if (!$v) return '';
        $ts = strtotime((string)$v);
        return $ts ? date('d.m.Y H:i', $ts) : (string)$v;
    }

    private function fmtMoney($v): string
    {
        return number_format((float)$v, 2, ',', '.') . ' €';
    }
}

Youez - 2016 - github.com/yon3zu
LinuXploit