* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file netdiag/lib/netdiag_pdf.lib.php * \ingroup netdiag * \brief PDF-Generator für Diagnose-Protokolle */ require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php'; require_once __DIR__.'/netdiag.lib.php'; require_once __DIR__.'/../class/netdiagdevice.class.php'; require_once __DIR__.'/../class/netdiagmeasurement.class.php'; /** * Ein Diagnose-Protokoll als PDF erzeugen und im Dokumentenverzeichnis ablegen. * * @param DoliDB $db Datenbank-Handler * @param NetDiagProtocol $protocol Protokoll-Objekt (geladen) * @param Translate $outputlangs Sprache der Ausgabe * @return int >0 wenn OK, <=0 bei Fehler */ function netdiagGeneratePdf($db, $protocol, $outputlangs) { global $conf, $mysoc, $langs; $outputlangs->loadLangs(array("netdiag@netdiag", "main", "companies", "orders")); $dir = netdiagGetOutputDir().'/'.dol_sanitizeFileName($protocol->ref); if (!dol_is_dir($dir)) { if (dol_mkdir($dir) < 0) { $protocol->error = 'Konnte Verzeichnis nicht anlegen: '.$dir; return -1; } } $file = $dir.'/'.dol_sanitizeFileName($protocol->ref).'.pdf'; // Daten laden $soc = new Societe($db); if ($protocol->fk_soc > 0) { $soc->fetch($protocol->fk_soc); } $devObj = new NetDiagDevice($db); $devices = $devObj->fetchAllByProtocol($protocol->id); $measObj = new NetDiagMeasurement($db); $measurements = $measObj->fetchAllByProtocol($protocol->id); // PDF aufbauen $pdf = pdf_getInstance('A4', 'mm', 'P'); $default_font_size = pdf_getPDFFontSize($outputlangs); $pdf->SetAutoPageBreak(true, 20); $pdf->SetMargins(15, 15, 15); $pdf->SetTitle($protocol->ref); $pdf->SetSubject($outputlangs->transnoentities("NetDiagProtocol")); $pdf->SetCreator("Dolibarr ".DOL_VERSION." - NetDiag"); $pdf->SetAuthor($mysoc->name); $pdf->AddPage(); // Kopf $pdf->SetFont('', 'B', 16); $pdf->SetTextColor(0, 70, 130); $pdf->Cell(0, 8, $outputlangs->transnoentities("NetDiagProtocol").' '.$protocol->ref, 0, 1, 'L'); $pdf->SetTextColor(0, 0, 0); $pdf->SetFont('', '', 9); $pdf->Cell(0, 5, $mysoc->name, 0, 1, 'L'); $pdf->Ln(3); // Stammdaten $pdf->SetFont('', '', 10); $infos = array( $outputlangs->transnoentities("ThirdParty") => ($soc->id > 0 ? $soc->name : '-'), $outputlangs->transnoentities("DateDiag") => dol_print_date($protocol->date_diag, 'dayhour', false, $outputlangs), $outputlangs->transnoentities("Location") => ($protocol->standort ? $protocol->standort : '-'), $outputlangs->transnoentities("Subnet") => ($protocol->subnet ? $protocol->subnet : '-'), ); foreach ($infos as $k => $v) { $pdf->SetFont('', 'B', 10); $pdf->Cell(40, 6, $k.':', 0, 0, 'L'); $pdf->SetFont('', '', 10); $pdf->Cell(0, 6, $v, 0, 1, 'L'); } if (!empty($protocol->note)) { $pdf->Ln(1); $pdf->SetFont('', 'I', 9); $pdf->MultiCell(0, 5, $outputlangs->transnoentities("Note").': '.$protocol->note, 0, 'L'); } $pdf->Ln(4); // Geräteliste $pdf->SetFont('', 'B', 12); $pdf->Cell(0, 7, $outputlangs->transnoentities("NetDiagDevices").' ('.count($devices).')', 0, 1, 'L'); $pdf->SetFillColor(0, 70, 130); $pdf->SetTextColor(255, 255, 255); $pdf->SetFont('', 'B', 8); $cols = array(35, 40, 50, 40, 15); $heads = array("IpAddress", "MacAddress", "Hostname", "Vendor", "DeviceType"); foreach ($heads as $idx => $h) { $pdf->Cell($cols[$idx], 6, $outputlangs->transnoentities($h), 1, 0, 'L', true); } $pdf->Ln(); $pdf->SetTextColor(0, 0, 0); $pdf->SetFont('', '', 8); foreach ($devices as $dev) { $pdf->Cell($cols[0], 5, dol_trunc($dev->ip, 22), 1, 0, 'L'); $pdf->Cell($cols[1], 5, dol_trunc($dev->mac, 24), 1, 0, 'L'); $pdf->Cell($cols[2], 5, dol_trunc($dev->hostname, 32), 1, 0, 'L'); $pdf->Cell($cols[3], 5, dol_trunc($dev->vendor, 26), 1, 0, 'L'); $pdf->Cell($cols[4], 5, dol_trunc($dev->devicetype, 10), 1, 1, 'L'); } if (empty($devices)) { $pdf->Cell(array_sum($cols), 5, '-', 1, 1, 'C'); } $pdf->Ln(4); // Messungen $pdf->SetFont('', 'B', 12); $pdf->Cell(0, 7, $outputlangs->transnoentities("NetDiagMeasurements").' ('.count($measurements).')', 0, 1, 'L'); // Status 3 = "nicht messbar": der Test konnte nicht durchgeführt werden. // Bewusst grau und mit eigenem Zeichen — weder grün (wäre gelogen) noch rot // (wäre eine Aussage über das Kundennetz, die die Messung nicht hergibt). $statuslabels = array( 0 => $outputlangs->transnoentities("NetDiagMeasureOk"), 1 => $outputlangs->transnoentities("NetDiagMeasureWarn"), 2 => $outputlangs->transnoentities("NetDiagMeasureFail"), 3 => $outputlangs->transnoentities("NetDiagMeasureUnmeasurable"), ); // Zusätzlich ein Zeichen, damit die Ampel auch im Schwarz-Weiß-Ausdruck lesbar // bleibt. Bei Status 0 bewusst leer: das Label heißt dort bereits "OK", sonst // stünde im PDF doppelt "OK OK". $statusmarks = array(0 => '', 1 => '! ', 2 => 'X ', 3 => '? '); foreach ($measurements as $m) { $st = (int) $m->measure_status; if ($st < 0 || $st > 3) { $st = 1; // unbekannter Wert -> als Warnung behandeln, nie als OK } if ($st == 2) { $pdf->SetFillColor(230, 130, 130); } elseif ($st == 1) { $pdf->SetFillColor(245, 215, 130); } elseif ($st == 3) { $pdf->SetFillColor(210, 210, 210); } else { $pdf->SetFillColor(170, 215, 170); } $pdf->SetFont('', 'B', 9); $title = ($m->category ? '['.$m->category.'] ' : '').$m->tool.($m->label ? ' — '.$m->label : ''); $pdf->Cell(150, 6, dol_trunc($title, 80), 1, 0, 'L'); $pdf->Cell(30, 6, $statusmarks[$st].$statuslabels[$st], 1, 1, 'C', true); $pdf->SetFont('', '', 8); if ($m->tool === 'stresstest') { netdiagPdfStressTest($pdf, $m->result); } elseif ($m->tool === 'wifikanal') { netdiagPdfWifiKanal($pdf, $m->result); } else { $pdf->MultiCell(180, 5, netdiagPdfFlattenResult($m->result), 1, 'L'); } } if (empty($measurements)) { $pdf->SetFont('', '', 9); $pdf->Cell(180, 5, '-', 1, 1, 'C'); } // Fuß $pdf->Ln(6); $pdf->SetFont('', 'I', 8); $pdf->SetTextColor(120, 120, 120); $pdf->Cell(0, 5, $outputlangs->transnoentities("NetDiagProtocol").' '.$protocol->ref.' — '.dol_print_date(dol_now(), 'dayhour', false, $outputlangs), 0, 1, 'L'); // Speichern $pdf->Output($file, 'F'); dolChmod($file); return 1; } /** * Mess-Ergebnis (JSON) als einzeiligen Text für PDF-Ausgabe. * * @param string $json JSON-String * @return string Lesbarer Text */ function netdiagPdfFlattenResult($json) { if (empty($json)) { return '-'; } $data = json_decode($json, true); if (!is_array($data)) { return (string) $json; } $parts = array(); foreach ($data as $key => $val) { if (is_array($val)) { $flat = array(); foreach ($val as $item) { $flat[] = is_array($item) ? json_encode($item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : (string) $item; } $val = implode(', ', $flat); } elseif (is_bool($val)) { $val = $val ? 'ja' : 'nein'; } $parts[] = ucfirst((string) $key).': '.$val; } return implode(' | ', $parts); } /** * Dauer-/Stresstest strukturiert ins PDF schreiben statt als Fließtext. * * netdiagPdfFlattenResult() würde Kennzahlen, Ausfallliste und Zeitreihe zu * einem einzigen langen Absatz zusammenkleben — bei einer Stunde Laufzeit mit * 60 Minuten-Buckets kaum lesbar. Hier stattdessen eine kompakte * Kennzahlenzeile plus eine echte Ausfalltabelle. Die Zeitreihe (`verlauf`) * wird bewusst NICHT gedruckt — sie ist als Diagramm in der App nützlich, * als reiner Text auf Papier nicht. * * @param TCPDF $pdf PDF-Objekt (Cursor steht direkt hinter der Titelzeile) * @param string $json result-JSON der Messung * @return void */ function netdiagPdfStressTest($pdf, $json) { $data = json_decode((string) $json, true); if (!is_array($data)) { $pdf->MultiCell(180, 5, (string) $json, 1, 'L'); return; } if (!empty($data['hinweis']) || !empty($data['fehler'])) { $pdf->MultiCell(180, 5, (string) ($data['hinweis'] ?? $data['fehler']), 1, 'L'); return; } $sum = array(); if (isset($data['host'])) { $sum[] = 'Ziel: '.$data['host']; } if (isset($data['dauerSekunden'])) { $sum[] = 'Dauer: '.round($data['dauerSekunden'] / 60).' min'; } if (isset($data['intervallSek'])) { $sum[] = 'Takt: '.$data['intervallSek'].' s'; } if (isset($data['gesendet']) && isset($data['empfangen'])) { $sum[] = 'Proben: '.$data['empfangen'].'/'.$data['gesendet']; } if (isset($data['verlustProzent'])) { $sum[] = 'Verlust: '.$data['verlustProzent'].' %'; } if (isset($data['avgMs']) && $data['avgMs'] !== null) { $sum[] = 'Ø '.$data['avgMs'].' ms'; } if (isset($data['minMs']) && isset($data['maxMs']) && $data['minMs'] !== null) { $sum[] = 'Min/Max '.$data['minMs'].'/'.$data['maxMs'].' ms'; } if (isset($data['p95Ms']) && $data['p95Ms'] !== null) { $sum[] = 'p95 '.$data['p95Ms'].' ms'; } if (isset($data['laengsterAusfallSek']) && $data['laengsterAusfallSek'] !== null) { $sum[] = 'längster Ausfall '.$data['laengsterAusfallSek'].' s'; } $pdf->MultiCell(180, 5, implode(' | ', $sum), 1, 'L'); // Ausfalltabelle — je Zeile "HH:MM:SS – HH:MM:SS (Xs)", vorformatiert von // der App (dieselben Zeilen, die auch in der Messungen-Liste in der App // stehen — eine Quelle der Wahrheit statt zwei Formatierungen). $ausfaelle = (isset($data['ausfaelle']) && is_array($data['ausfaelle'])) ? $data['ausfaelle'] : array(); if (empty($ausfaelle)) { $pdf->SetFont('', 'I', 8); $pdf->Cell(180, 5, 'Kein Ausfall während des Laufs.', 1, 1, 'L'); $pdf->SetFont('', '', 8); return; } $pdf->SetFont('', 'B', 8); $pdf->Cell(180, 5, 'Ausfälle ('.count($ausfaelle).')', 1, 1, 'L'); $pdf->SetFont('', '', 8); foreach ($ausfaelle as $line) { $pdf->Cell(180, 5, dol_trunc((string) $line, 120), 1, 1, 'L'); } } /** * WLAN-Kanal-Momentaufnahme im PDF als Tabelle ausgeben. * * Wie netdiagPdfStressTest ein eigener Zweig, weil die generische * Flach-Darstellung die Netzliste (Array von Objekten) unlesbar macht. * * @param TCPDF $pdf PDF-Objekt * @param string $json JSON-String des Ergebnisses * @return void */ function netdiagPdfWifiKanal($pdf, $json) { $data = json_decode($json, true); if (!is_array($data)) { $pdf->MultiCell(180, 5, netdiagPdfFlattenResult($json), 1, 'L'); return; } // Aus dem Demomodus der App — im PDF besonders wichtig zu kennzeichnen, // weil das Dokument beim Kunden landet. if (!empty($data['demodaten'])) { $pdf->SetFont('', 'B', 8); $pdf->MultiCell(180, 5, 'DEMODATEN — keine echte Messung', 1, 'L'); $pdf->SetFont('', '', 8); } // Kopfzeile: Anzahl, eigenes Netz, Empfehlung $sum = array(); if (isset($data['anzahlNetze'])) { $sum[] = $data['anzahlNetze'].' Netze sichtbar'; } if (!empty($data['eigenesNetz']) && is_array($data['eigenesNetz'])) { $e = $data['eigenesNetz']; $teil = 'Eigenes Netz: '.($e['ssid'] ?? '?').' Kanal '.($e['kanal'] ?? '?'); if (isset($e['rssi'])) { $teil .= ', '.$e['rssi'].' dBm'; } if (!empty($e['bewertung'])) { $teil .= ' ('.$e['bewertung'].')'; } $sum[] = $teil; } if (!empty($data['empfehlung24GHz']) && is_array($data['empfehlung24GHz'])) { $best = reset($data['empfehlung24GHz']); if (is_array($best) && isset($best['kanal'])) { $sum[] = 'Störungsärmster 2,4-GHz-Kanal: '.$best['kanal']; } } if (!empty($sum)) { $pdf->MultiCell(180, 5, implode(' | ', $sum), 1, 'L'); } if (!empty($data['warnungen']) && is_array($data['warnungen'])) { foreach ($data['warnungen'] as $w) { $pdf->MultiCell(180, 5, '! '.dol_trunc((string) $w, 160), 1, 'L'); } } $netze = (isset($data['netze']) && is_array($data['netze'])) ? $data['netze'] : array(); if (empty($netze)) { return; } // Tabellenkopf $pdf->SetFont('', 'B', 7); $pdf->Cell(60, 5, 'SSID', 1, 0, 'L'); $pdf->Cell(18, 5, 'Kanal', 1, 0, 'C'); $pdf->Cell(22, 5, 'Band', 1, 0, 'L'); $pdf->Cell(20, 5, 'Breite', 1, 0, 'C'); $pdf->Cell(35, 5, 'Standard', 1, 0, 'L'); $pdf->Cell(25, 5, 'Pegel', 1, 1, 'R'); $pdf->SetFont('', '', 7); foreach ($netze as $n) { if (!is_array($n)) { continue; } $pdf->Cell(60, 5, dol_trunc((string) ($n['ssid'] ?? ''), 34), 1, 0, 'L'); $pdf->Cell(18, 5, (string) ($n['kanal'] ?? ''), 1, 0, 'C'); $pdf->Cell(22, 5, (string) ($n['band'] ?? ''), 1, 0, 'L'); $pdf->Cell(20, 5, (isset($n['breiteMhz']) && $n['breiteMhz'] !== null) ? $n['breiteMhz'].' MHz' : '-', 1, 0, 'C'); $pdf->Cell(35, 5, dol_trunc((string) ($n['standard'] ?? '-'), 20), 1, 0, 'L'); $pdf->Cell(25, 5, (string) ($n['rssi'] ?? '').' dBm', 1, 1, 'R'); } }