- Werkzeug-Klarnamen statt interner IDs: "IP-Scanner — 46 Geräte im Netz …" statt "[netzwerk] ipscan — …". Bewusst eine kurze Zuordnung in netdiagToolName() statt eines zweiten Satzes Sprachschlüssel — die kanonischen Namen stehen in der App, doppelte Pflege wäre eine Fehlerquelle. Enthält auch dhcpcheck/wifiscan: in der App gibt es sie nicht mehr, in der PRODUKTIONSDATENBANK stehen dazu aber noch Messungen (4 bzw. 2). Die Gegenprüfung hielt den Punkt für gegenstandslos, hatte dabei aber nur die Testdatenbank angesehen. - Messparameter anzeigen (Prod-Messung #126): unter jeder Messung steht jetzt "Ziel: 192.168.1.1 · Dauer (s): 300" in Karte und PDF. Vorher stand das Ergebnis ohne Bezugspunkt da — man sah nicht, wogegen gemessen wurde. - Listenseite: Ampel je Protokoll (schlechteste Einzelmessung) plus Anzahl, dazu ein Filter "nur mit Befund". Status 3 "nicht messbar" geht bewusst NICHT ins Maximum ein — er ist keine Aussage über das Kundennetz — sondern wird separat als "n.m." ausgewiesen. Im Browser geprüft: der Filter liefert ausschließlich Protokolle mit Warnung oder Fehler. - N+1-Queries behoben: fetchAllByProtocol() las nur die rowids und setzte je Zeile ein eigenes fetch() ab. Jetzt eine Abfrage mit setVarsFromFetchObj(), zusätzlich mit Entity-Filter (fehlte bisher ganz). Nachgemessen über SHOW SESSION STATUS: 46 Geräte + 7 Messungen brauchen statt 55 Abfragen noch eine. - Standort aus der Kundenadresse vorbelegen, wenn der Techniker nichts eingetragen hat; eine vorhandene Angabe wird nie überschrieben. Über die echte API geprüft. Offen bleibt aus Phase 5 nur der Vergleich zweier Protokolle (Geräte-Diff nach MAC) — eigenes Feature mit eigener Ansicht.
457 lines
16 KiB
PHP
457 lines
16 KiB
PHP
<?php
|
||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||
*
|
||
* 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 <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
/**
|
||
* \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');
|
||
/*
|
||
* Spaltenbreiten (Summe muss 180 mm bleiben, sonst passt die Leerzeile
|
||
* unten nicht mehr zum Kopf): Gerätetyp hatte 15 mm, der Text durfte aber
|
||
* 10 Zeichen lang sein — „Chromecast/TV" lief dadurch über den rechten
|
||
* Tabellenrahmen HINAUS in den Druckrand (nachgemessen an ND2026-0015:
|
||
* 199,0 mm bei 195 mm Tabellenkante). Cell() schneidet nicht ab, es
|
||
* schreibt einfach weiter. MAC und Hostname sind in der Praxis oft leer
|
||
* (ARP ist ab Android 10 gesperrt), von dort kommt der Platz.
|
||
*/
|
||
$cols = array(30, 34, 44, 28, 24, 20);
|
||
$heads = array("IpAddress", "MacAddress", "Hostname", "NetDiagVendor", "DeviceType", "NetDiagOpenPorts");
|
||
netdiagPdfTableHead($pdf, $cols, $heads, $outputlangs);
|
||
foreach ($devices as $dev) {
|
||
netdiagPdfEnsureSpace($pdf, 5, function ($p) use ($cols, $heads, $outputlangs) {
|
||
netdiagPdfTableHead($p, $cols, $heads, $outputlangs);
|
||
});
|
||
// Anzeigename wie in der App: eigener Name vor mDNS-/Host-/NetBIOS-Name.
|
||
// Ein Drucker ist per DNS oft namenlos, meldet sich per mDNS aber als
|
||
// "Brother HL-L2350DW" — im Kundenprotokoll stand bisher nur die IP.
|
||
$anzeige = '';
|
||
foreach (array($dev->custom_name, $dev->mdns_name, $dev->hostname, $dev->netbios_name) as $kandidat) {
|
||
if (!empty($kandidat)) {
|
||
$anzeige = $kandidat;
|
||
break;
|
||
}
|
||
}
|
||
$pdf->Cell($cols[0], 5, dol_trunc($dev->ip, 18), 1, 0, 'L');
|
||
$pdf->Cell($cols[1], 5, dol_trunc($dev->mac, 20), 1, 0, 'L');
|
||
$pdf->Cell($cols[2], 5, dol_trunc($anzeige, 26), 1, 0, 'L');
|
||
$pdf->Cell($cols[3], 5, dol_trunc($dev->vendor, 16), 1, 0, 'L');
|
||
$pdf->Cell($cols[4], 5, dol_trunc($dev->devicetype, 14), 1, 0, 'L');
|
||
$pdf->Cell($cols[5], 5, dol_trunc((string) $dev->open_ports, 12), 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);
|
||
}
|
||
// Titelzeile und Ergebnis zusammenhalten: sonst steht am Seitenende die
|
||
// Überschrift samt Ampel allein und darunter ein leerer, unten offener
|
||
// Rahmen — das Ergebnis beginnt erst auf der nächsten Seite. Bei einem
|
||
// Testlauf über 61 Umbruchlagen trat das in 5 Fällen auf und liest sich
|
||
// wie ein abgebrochenes Dokument.
|
||
netdiagPdfEnsureSpace($pdf, 11);
|
||
$pdf->SetFont('', 'B', 9);
|
||
// Klarname statt interner ID — „ipscan" sagt dem Kunden nichts
|
||
$title = netdiagToolName($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);
|
||
// Messparameter: ohne sie steht das Ergebnis ohne Bezug da — man sieht
|
||
// nicht, gegen welches Ziel gemessen wurde (Prod-Messung #126).
|
||
$params = netdiagFormatParams($m->params);
|
||
if ($params !== '') {
|
||
$pdf->SetFont('', 'I', 7);
|
||
$pdf->MultiCell(180, 4, $params, 1, 'L');
|
||
$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 netdiagKundentext((string) $json);
|
||
}
|
||
$parts = array();
|
||
foreach ($data as $key => $val) {
|
||
$feld = netdiagFeldFuerKunde((string) $key, $val);
|
||
if ($feld !== null) {
|
||
$parts[] = $feld;
|
||
}
|
||
}
|
||
return empty($parts) ? '-' : implode(' | ', $parts);
|
||
}
|
||
|
||
/**
|
||
* Sorgt dafür, dass die nächsten `$hoeheMm` Millimeter noch auf die aktuelle
|
||
* Seite passen — sonst wird vorher umgebrochen und optional eine
|
||
* Wiederholungszeile (z.B. der Tabellenkopf) ausgegeben.
|
||
*
|
||
* Der automatische Seitenumbruch von TCPDF reicht dafür nicht: er greift erst,
|
||
* wenn die Zelle schon geschrieben wird, und zerreißt dabei zusammengehörige
|
||
* Blöcke (Tabellenkopf/Datenzeilen, Messungstitel/Messergebnis).
|
||
*
|
||
* @param TCPDF $pdf PDF-Objekt
|
||
* @param float $hoeheMm benötigte Resthöhe in mm
|
||
* @param callable|null $nachUmbruch wird nach einem Seitenwechsel aufgerufen
|
||
* @return bool true, wenn umgebrochen wurde
|
||
*/
|
||
function netdiagPdfEnsureSpace($pdf, $hoeheMm, $nachUmbruch = null)
|
||
{
|
||
$rest = ($pdf->getPageHeight() - $pdf->getBreakMargin()) - $pdf->GetY();
|
||
if ($rest >= $hoeheMm) {
|
||
return false;
|
||
}
|
||
$pdf->AddPage();
|
||
if (is_callable($nachUmbruch)) {
|
||
$nachUmbruch($pdf);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Kopfzeile einer Tabelle ausgeben (blau hinterlegt) und danach Schrift/Farbe
|
||
* für die Datenzeilen wiederherstellen. Als eigene Funktion, damit dieselbe
|
||
* Zeile nach einem Seitenumbruch wiederholt werden kann — vorher standen die
|
||
* Spalten ab Seite 2 ohne jede Beschriftung da.
|
||
*
|
||
* @param TCPDF $pdf PDF-Objekt
|
||
* @param array<int,int> $cols Spaltenbreiten in mm
|
||
* @param array<int,string> $heads Sprachschlüssel der Überschriften
|
||
* @param Translate $outputlangs Sprache
|
||
* @return void
|
||
*/
|
||
function netdiagPdfTableHead($pdf, $cols, $heads, $outputlangs)
|
||
{
|
||
$pdf->SetFillColor(0, 70, 130);
|
||
$pdf->SetTextColor(255, 255, 255);
|
||
$pdf->SetFont('', 'B', 8);
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* 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');
|
||
}
|
||
}
|