Internet-Kette im PDF + Pfeile wurden als "?" gedruckt

netdiagPdfKette() fuer das neue App-Werkzeug: Fehlerglied als eigene
hervorgehobene Zeile, darunter die vier Stufen einzeln, dann Systemurteil
und Hinweis. Ohne eigenen Zweig stuenden die Stufen als |-Kette in einer
Zelle und das Fehlerglied - die eigentliche Aussage - mittendrin.

Dabei ein Fehler gefunden, der jedes Protokoll betrifft: pdf_getPDFFont()
liefert Helvetica, einen Core-Font mit WinAnsi-Kodierung. Alles ausserhalb
dieses Zeichenvorrats setzt TCPDF wortlos als "?" (KB #1025). Aufgefallen
an "www.google.de -> 142.250.185.67", das im Kundendokument als
"www.google.de ? 142.250.185.67" ankam. netdiagPdfText() ersetzt die
betroffenen Zeichen jetzt in ALLEN PDF-Zweigen, also auch fuer Altdaten.
Bewusst keine Unicode-Schrift: dejavusans wuerde Schriftbild und Dateigroesse
aller Protokolle aendern. Gedankenstrich, Mittelpunkt und Anfuehrungszeichen
sind in WinAnsi enthalten und bleiben unangetastet (nachgeprueft).

Feld-Whitelist um fehlerglied, stufen, systemUrteil, validated und
captivePortal erweitert. Die letzten beiden kommen aus dem LinkInfo-Objekt
und erscheinen damit auch beim IP-Test an der Dose.

Geprueft an vier Faellen gegen die lokale Testinstanz, zwei davon die echten
Emulator-Messungen dieser Nacht (Gutfall und Flugmodus).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-08-16 22:59:49 +02:00
parent 36dd18f53a
commit b2ab430ae7
4 changed files with 154 additions and 10 deletions

View file

@ -1,5 +1,33 @@
# Changelog NetDiag
## 1.1.3 — 2026-08-16
### Internet-Kette im PDF + ein Zeichenfehler, der jedes Protokoll betraf
- **`netdiagPdfKette()`** für das neue App-Werkzeug „Internet-Kette": Fehlerglied
als eigene hervorgehobene Zeile, darunter die vier geprüften Stufen einzeln,
dann Systemurteil und Hinweis. Ohne eigenen Zweig stünden die Stufen als
`|`-Kette in einer Zelle und das Fehlerglied — die eigentliche Aussage —
mittendrin.
- **`netdiagPdfText()`: Pfeile wurden als „?" gedruckt.** `pdf_getPDFFont()`
liefert hier Helvetica, einen Core-Font mit WinAnsi-Kodierung; alles außerhalb
dieses Zeichenvorrats setzt TCPDF wortlos als „?" (bekannt aus KB #1025).
Aufgefallen an „www.google.de → 142.250.185.67", das im Kundendokument als
„www.google.de ? 142.250.185.67" ankam — der Pfeil ist dort kein Schmuck,
er trennt Frage und Antwort. Die Ersetzung greift jetzt in **allen**
PDF-Zweigen, also auch für Altdaten und künftige Werkzeuge.
Bewusst keine Unicode-Schrift: `dejavusans` würde Schriftbild und Dateigröße
aller Protokolle ändern. Gedankenstrich, Mittelpunkt, Auslassungspunkte und
Anführungszeichen sind in WinAnsi enthalten und bleiben unangetastet
(im erzeugten Dokument nachgeprüft).
- Feld-Whitelist um `fehlerglied`, `stufen`, `systemUrteil`, `validated` und
`captivePortal` erweitert, Werkzeugname `internetkette` → „Internet-Kette".
`validated`/`captivePortal` kommen aus dem LinkInfo-Objekt und erscheinen
damit auch beim IP-Test an der Dose.
Geprüft an vier Fällen gegen die lokale Testinstanz, zwei davon sind die echten
Emulator-Messungen dieser Nacht (Gutfall und Flugmodus).
## 1.1.2 — 2026-08-16
### Geräte-Monitor bekommt einen eigenen PDF-Zweig

View file

@ -67,7 +67,7 @@ class modNetDiag extends DolibarrModules
$this->editor_url = '';
$this->editor_squarred_logo = '';
$this->version = '1.1.2';
$this->version = '1.1.3';
// Konstanten-Name in llx_const für Modul-Status
$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);

View file

@ -345,6 +345,15 @@ function netdiagKundenfelder()
'aussetzer' => array('Aussetzer', ''),
'geraete' => array('Je Gerät', ''),
'ausfaelle' => array('Einzelne Ausfälle', ''),
// Internet-Kette (Anschluss → Gateway → Namensauflösung → HTTPS).
// 'fehlerglied' ist die eigentliche Aussage des Werkzeugs — ohne diesen
// Eintrag stünde im Kundendokument nur die Ampel.
'fehlerglied' => array('Fehlerglied', ''),
'stufen' => array('Geprüfte Stufen', ''),
'systemUrteil' => array('Urteil des Betriebssystems', ''),
// Kommen aus dem LinkInfo-Objekt und damit auch im IP-Test (Dose) vor
'validated' => array('Internetverbindung bestätigt', ''),
'captivePortal' => array('Anmeldeseite erkannt', ''),
// Netz/DHCP
'server' => array('DHCP-Server', ''),
'lease' => array('Lease-Dauer', 's'),
@ -560,6 +569,7 @@ function netdiagToolName($tool)
'traceroute' => 'Traceroute',
'stresstest' => 'Dauer-/Stresstest',
'iperf' => 'Durchsatz-Test',
'internetkette' => 'Internet-Kette',
'wifikanal' => 'WLAN-Kanalanalyse',
'monitor' => 'Geräte-Monitor',
'iptest' => 'IP-Test (Dose)',

View file

@ -191,7 +191,7 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs)
$params = netdiagFormatParams($m->params);
if ($params !== '') {
$pdf->SetFont('', 'I', 7);
$pdf->MultiCell(180, 4, $params, 1, 'L');
$pdf->MultiCell(180, 4, netdiagPdfText($params), 1, 'L');
$pdf->SetFont('', '', 8);
}
if ($m->tool === 'stresstest') {
@ -200,6 +200,8 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs)
netdiagPdfWifiKanal($pdf, $m->result);
} elseif ($m->tool === 'monitor') {
netdiagPdfMonitor($pdf, $m->result);
} elseif ($m->tool === 'internetkette') {
netdiagPdfKette($pdf, $m->result);
} else {
$pdf->MultiCell(180, 5, netdiagPdfFlattenResult($m->result), 1, 'L');
}
@ -222,6 +224,48 @@ function netdiagGeneratePdf($db, $protocol, $outputlangs)
return 1;
}
/**
* Zeichen ersetzen, die der PDF-Standardzeichensatz nicht darstellen kann.
*
* `pdf_getPDFFont()` liefert in dieser Dolibarr-Installation Helvetica einen
* Core-Font mit WinAnsi-Kodierung, kein Unicode. Alles außerhalb dieses Vorrats
* druckt TCPDF als ?", ohne zu warnen (bekannt aus KB #1025, dort mit dem
* Ankreuzkästchen ).
*
* Aufgefallen am Pfeil in der Internet-Kette: aus
* „Namensauflösung: www.google.de 142.250.185.67" wurde im Kundendokument
* „www.google.de ? 142.250.185.67" — der Pfeil ist dort kein Schmuck, er
* trennt Frage und Antwort.
*
* Bewusst eine Ersetzung statt einer Unicode-Schrift: `dejavusans` würde jedes
* Zeichen können, aber Schriftbild und Dateigröße aller Protokolle ändern.
* Gedankenstrich, Mittelpunkt, Auslassungspunkte und Anführungszeichen sind in
* WinAnsi enthalten und bleiben unangetastet (im Dokument nachgeprüft).
*
* @param string $s Text aus der App oder aus Altdaten
* @return string druckbarer Text
*/
function netdiagPdfText($s)
{
return strtr((string) $s, array(
'→' => '->',
'←' => '<-',
'↔' => '<->',
'⇒' => '=>',
'✓' => 'ja',
'✔' => 'ja',
'✗' => 'nein',
'✘' => 'nein',
'≤' => '<=',
'≥' => '>=',
'≠' => '!=',
'☐' => '[ ]',
'☑' => '[x]',
'⚠' => '!',
'≈' => 'ca. ',
));
}
/**
* Mess-Ergebnis (JSON) als einzeiligen Text für PDF-Ausgabe.
*
@ -235,7 +279,7 @@ function netdiagPdfFlattenResult($json)
}
$data = json_decode($json, true);
if (!is_array($data)) {
return netdiagKundentext((string) $json);
return netdiagPdfText(netdiagKundentext((string) $json));
}
$parts = array();
foreach ($data as $key => $val) {
@ -244,7 +288,7 @@ function netdiagPdfFlattenResult($json)
$parts[] = $feld;
}
}
return empty($parts) ? '-' : implode(' | ', $parts);
return empty($parts) ? '-' : netdiagPdfText(implode(' | ', $parts));
}
/**
@ -369,7 +413,69 @@ function netdiagPdfStressTest($pdf, $json)
$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');
$pdf->Cell(180, 5, dol_trunc(netdiagPdfText($line), 120), 1, 1, 'L');
}
}
/**
* Internet-Kette ins PDF schreiben: Fehlerglied zuerst, dann die Stufen.
*
* Ohne eigenen Zweig stünden die vier Stufen als `|`-Kette in einer Zelle und
* das Fehlerglied die eigentliche Aussage mittendrin. Beim Kunden wird
* genau diese eine Zeile gelesen: „woran liegt es".
*
* @param TCPDF $pdf PDF-Objekt (Cursor steht direkt hinter der Titelzeile)
* @param string $json result-JSON der Messung
* @return void
*/
function netdiagPdfKette($pdf, $json)
{
$data = json_decode((string) $json, true);
if (!is_array($data)) {
$pdf->MultiCell(180, 5, netdiagPdfFlattenResult($json), 1, 'L');
return;
}
// Fehlerglied als eigene, hervorgehobene Zeile
$glied = isset($data['fehlerglied']) ? trim((string) $data['fehlerglied']) : '';
if ($glied !== '') {
$pdf->SetFont('', 'B', 8);
$pdf->Cell(180, 5, $glied === 'keines'
? 'Kein Glied der Kette hat versagt.'
: 'Fehlerglied: '.$glied, 1, 1, 'L');
$pdf->SetFont('', '', 8);
}
$stufen = (isset($data['stufen']) && is_array($data['stufen'])) ? $data['stufen'] : array();
if (!empty($stufen)) {
netdiagPdfEnsureSpace($pdf, 10);
$pdf->SetFont('', 'B', 8);
$pdf->Cell(180, 5, 'Geprüfte Stufen', 1, 1, 'L');
$pdf->SetFont('', '', 8);
foreach ($stufen as $line) {
netdiagPdfEnsureSpace($pdf, 5, function ($p) {
$p->SetFont('', 'B', 8);
$p->Cell(180, 5, 'Geprüfte Stufen — Fortsetzung', 1, 1, 'L');
$p->SetFont('', '', 8);
});
// MultiCell statt Cell: die Stufenzeilen enthalten Fehlertexte des
// Systems („Unable to resolve host …"), die auch mal lang werden.
// Abschneiden würde ausgerechnet die Begründung kappen.
$pdf->MultiCell(180, 5, netdiagPdfText(netdiagKundentext((string) $line)), 1, 'L');
}
}
if (!empty($data['systemUrteil'])) {
netdiagPdfEnsureSpace($pdf, 8);
$pdf->SetFont('', 'I', 7);
$pdf->MultiCell(180, 4, netdiagPdfText(netdiagKundentext((string) $data['systemUrteil'])), 1, 'L');
$pdf->SetFont('', '', 8);
}
if (!empty($data['hinweis'])) {
netdiagPdfEnsureSpace($pdf, 8);
$pdf->SetFont('', 'I', 7);
$pdf->MultiCell(180, 4, netdiagPdfText(netdiagKundentext((string) $data['hinweis'])), 1, 'L');
$pdf->SetFont('', '', 8);
}
}
@ -401,7 +507,7 @@ function netdiagPdfMonitor($pdf, $json)
return;
}
if (!empty($data['fehler']) || !empty($data['error'])) {
$pdf->MultiCell(180, 5, netdiagKundentext((string) ($data['fehler'] ?? $data['error'])), 1, 'L');
$pdf->MultiCell(180, 5, netdiagPdfText(netdiagKundentext((string) ($data['fehler'] ?? $data['error']))), 1, 'L');
return;
}
@ -448,7 +554,7 @@ function netdiagPdfMonitor($pdf, $json)
$p->Cell(180, 5, 'Überwachte Geräte ('.count($geraete).') — Fortsetzung', 1, 1, 'L');
$p->SetFont('', '', 8);
});
$pdf->Cell(180, 5, dol_trunc((string) $line, 120), 1, 1, 'L');
$pdf->Cell(180, 5, dol_trunc(netdiagPdfText($line), 120), 1, 1, 'L');
}
}
@ -483,7 +589,7 @@ function netdiagPdfMonitor($pdf, $json)
$p->Cell(180, 5, 'Einzelne Ausfälle ('.count($ausfaelle).') — Fortsetzung', 1, 1, 'L');
$p->SetFont('', '', 8);
});
$pdf->Cell(180, 5, dol_trunc((string) $line, 120), 1, 1, 'L');
$pdf->Cell(180, 5, dol_trunc(netdiagPdfText($line), 120), 1, 1, 'L');
$i++;
}
$rest = count($ausfaelle) - $max;
@ -499,7 +605,7 @@ function netdiagPdfMonitor($pdf, $json)
if (!empty($data['hinweis'])) {
netdiagPdfEnsureSpace($pdf, 10);
$pdf->SetFont('', 'I', 7);
$pdf->MultiCell(180, 4, netdiagKundentext((string) $data['hinweis']), 1, 'L');
$pdf->MultiCell(180, 4, netdiagPdfText(netdiagKundentext((string) $data['hinweis'])), 1, 'L');
$pdf->SetFont('', '', 8);
}
}
@ -558,7 +664,7 @@ function netdiagPdfWifiKanal($pdf, $json)
if (!empty($data['warnungen']) && is_array($data['warnungen'])) {
foreach ($data['warnungen'] as $w) {
$pdf->MultiCell(180, 5, '! '.dol_trunc((string) $w, 160), 1, 'L');
$pdf->MultiCell(180, 5, '! '.dol_trunc(netdiagPdfText($w), 160), 1, 'L');
}
}