Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Tickets\ImapFetcher;
/**
* Deckt ab, welche Inline-Bilder einer Mail als Anhang am Ticket landen.
*
* Ein Bild, das im Text steht, ist dort sichtbar und braucht keinen Chip. Für
* jedes andere Inline-Bild ist der Anhang der einzige Weg ins Ticket – ohne ihn
* fehlt es ersatzlos, weil auch der Text es nicht trägt.
*/
return static function (): void {
/** Fetcher ohne Verbindung; geprüft werden nur die reinen Textschritte. */
$fetcher = static fn (): ImapFetcher => new ImapFetcher(['username' => 'tickets@example.com']);
/** Ruft eine private Methode auf. */
$ruf = static function (ImapFetcher $f, string $name, array $args) {
$m = new ReflectionMethod(ImapFetcher::class, $name);
$m->setAccessible(true);
return $m->invokeArgs($f, $args);
};
/** MIME-Part, wie ihn die IMAP-Erweiterung liefert. */
$part = static function (string $contentId): object {
$p = new stdClass();
$p->id = $contentId;
return $p;
};
test('Ein eingebettetes Bild wird nicht zusätzlich Anhang', function () use ($fetcher, $ruf, $part) {
$f = $fetcher();
$html = '<p>Hallo</p><img src="cid:logo1" alt="">';
$bild = ['logo1' => ['mime' => 'image/png', 'data' => 'ROHDATEN']];
$ergebnis = $ruf($f, 'inlineCidImages', [$html, $bild]);
assertTrue(str_contains($ergebnis, 'data:image/png;base64,'), 'Das Bild steht nicht im Text');
assertTrue($ruf($f, 'stehtImText', [$part('<logo1>')]), 'Das Bild gilt nicht als eingebettet');
});
test('Ein zu großes Bild wird zum Anhang', function () use ($fetcher, $ruf, $part) {
// Über der Einbettungsgrenze sammelt getInlineImages() das Bild nicht
// ein; im Text steht es damit nicht.
$f = $fetcher();
$html = '<p>Screenshot:</p><img src="cid:gross1" alt="">';
$ruf($f, 'inlineCidImages', [$html, []]);
assertFalse($ruf($f, 'stehtImText', [$part('<gross1>')]), 'Das Bild gilt fälschlich als eingebettet');
});
test('Ohne Content-ID wird das Bild zum Anhang', function () use ($fetcher, $ruf, $part) {
$f = $fetcher();
assertFalse($ruf($f, 'stehtImText', [$part('')]));
});
test('Eine im Text nicht benutzte Content-ID zählt nicht als eingebettet', function () use ($fetcher, $ruf, $part) {
// Der Body verweist nur auf das erste Bild; das zweite bleibt übrig.
$f = $fetcher();
$html = '<img src="cid:eins" alt="">';
$bilder = [
'eins' => ['mime' => 'image/png', 'data' => 'A'],
'zwei' => ['mime' => 'image/png', 'data' => 'B'],
];
$ruf($f, 'inlineCidImages', [$html, $bilder]);
assertTrue($ruf($f, 'stehtImText', [$part('<eins>')]));
assertFalse($ruf($f, 'stehtImText', [$part('<zwei>')]));
});
test('Die Merkliste gilt nur für die gerade gelesene Mail', function () use ($fetcher, $ruf, $part) {
$f = $fetcher();
$ruf($f, 'inlineCidImages', ['<img src="cid:logo1">', ['logo1' => ['mime' => 'image/png', 'data' => 'A']]]);
assertTrue($ruf($f, 'stehtImText', [$part('<logo1>')]));
$eigenschaft = new ReflectionProperty(ImapFetcher::class, 'eingebetteteCids');
$eigenschaft->setAccessible(true);
$eigenschaft->setValue($f, []);
assertFalse($ruf($f, 'stehtImText', [$part('<logo1>')]), 'Die Merkliste wirkt über die Mail hinaus');
});
test('Die Grenzen der Einbettung stehen fest', function () {
// Sie entscheiden, ab wann ein Bild als Anhang statt im Text erscheint.
$src = (string)file_get_contents(dirname(__DIR__) . '/src/Tickets/ImapFetcher.php');
assertTrue(str_contains($src, 'strlen($data) > 500 * 1024'), 'Die Größengrenze fehlt');
assertTrue(str_contains($src, 'count($out) >= 5'), 'Die Stückzahlgrenze fehlt');
assertTrue(
str_contains($src, "if (\$disposition === 'inline' && \$this->stehtImText(\$part)) {"),
'Inline-Bilder werden wieder pauschal verworfen'
);
});
};