14.2 E-Mails empfangen 

Nachdem Sie nun gesehen haben, wie ein E-Mail-Objekt aufgebaut ist, was ein Transport-Handler ist und wie der Versand mit seiner Hilfe realisiert werden kann, folgt das Empfangen von E-Mails. Sie werden einen Einblick in die Strukturen komplexerer E-Mail-Objekte bekommen und Handler kennenlernen, die nicht das Versenden, sondern das Empfangen von E-Mails unterstützen.
14.2.1 Architektur 

Die grundlegende Architektur der Mail-Komponente haben Sie bereits in Abschnitt 14.1.1, »Architektur«, kennengelernt. In diesem Abschnitt soll dieses Wissen vertieft und um Bestandteile der Mail-Komponente erweitert werden, die bisher nicht gebraucht wurden oder von denen Sie bisher nur einen Teil der Klassen kennengelernt haben.
E-Mail-Bestandteile
ezcMail ist die speziellste Variante eines E-Mail-Bestandteils, denn diese Klasse repräsentiert den Grundstein einer jeden E-Mail. Neben den von der Basisklasse ezcMailPart geerbten und den eigenen Methoden und Attributen besitzt das E–Mail-Objekt ein Attribut $body, welches die Baumstruktur des E-Mail-Körpers enthält.
Da es sich um eine Baumstruktur handelt, stellt jede ezcMailPart-Erweiterung einen Knoten des Baums dar, und besonders die Blattknoten haben eine spezielle Bedeutung. An diesen endet die Baumstruktur; in diese Bestandteile werden die eigentlichen Inhalte generiert. Alle weiteren Knoten des Baums dienen lediglich dazu, den aus einem oder mehreren Blattknoten generierten Inhalt auszuschmücken, zusammenzufassen und zu strukturieren. Die Klasse ezcMailText, mit deren Hilfe sowohl HTML- als auch Plain-Text-Inhalte generiert werden, kennen Sie bereits, ebenso die Klasse ezcMailFilePart.
Neu an dieser Stelle ist, dass es verschiedene Erben von ezcMailFilePart gibt, die in unterschiedlicher Weise Zugriff auf einen Dateianhang ermöglichen, und ezcMailFilePart lediglich die Basisklasse darstellt. ezcMailFile ist die einfachste Erweiterung der Basisklasse und stellt eine normale Datei auf Ihrer Festplatte als E-Mail-Anhang dar. Objekte dieser Klasse werden am häufigsten verwendet, um einen Anhang zu erzeugen. Außerdem wird ein Objekt dieser Klasse erzeugt, wenn Sie eine E-Mail mit Anhang empfangen und parsen.
Zwei weitere Spezialklassen, die Sie ebenfalls zum Erzeugen von Anhängen nutzen können, sind ezcFileStream und ezcFileVirtual. Die zweite Klasse erlaubt es Ihnen, einen Dateianhang aus dem Hauptspeicher anstatt von der Festplatte zu erzeugen. Dabei werden die in einer Variablen gehaltenen Daten als Dateiinhalt des Anhangs verwendet. Informationen wie den Dateinamen und den Inhaltstyp müssen Sie selbst bestimmen. Verwenden Sie einen Stream-Bestandteil, so ließt dieser im Gegensatz zu ezcMailFile nicht eine Datei ein, sondern einen beliebigen Stream. Dabei kann es sich um eine normale Datei, die mittels fopen() geöffnet wurde, um einen beliebigen anderen PHP-Stream, zum Beispiel für eine Datei auf einem HTTP- oder FTP-Server, oder um eine eigene Implementierung der PHP-Stream-API handeln. Faktisch wird der übergebene Stream bis zum Ende gelesen und der extrahierte Inhalt als einzelner Dateianhang verwendet.
Allen ezcFilePart-Erben sind einige Eigenschaften gemein: Die Virtual Properties $fileName und $dispositionType sind immer gesetzt, wobei Erstere den Dateinamen und Letztere den Typ des Anhangs beschreibt. Valide Anhangstypen sind ezcFilePart::DISPLAY_ATTACHMENT und DISPLAY_INLINE. Sie dienen als Indikatoren für den E-Mail-Empfänger, ob ein Anhang als solcher darzustellen ist oder innerhalb der E-Mail (zum Beispiel als Bild) angezeigt werden soll.
Für den Fall, dass Anhänge in E-Mails mittels ezcMailMultipartRelated eingebettet werden, haben ezcMailFilePart-Objekte noch weitere, optional gesetzte Attribute: Die $contentId stellt einen pro E-Mail-Objekt eindeutigen Identifizierer dar, welcher innerhalb des passenden HTML-Bestandteils referenziert werden kann. Dies entspricht grob gesagt dem Einbetten von Bildern in HTML-E–Mails. Passend dazu beschreibt $contentType den Typ des Anhangs mit Hilfe einer der Konstanten ezcMailPart::CONTENT_TYPE_IMAGE, CONTENT_TYPE_AUDIO, CONTENT_TYPE_VIDEO und CONTENT_TYPE_APPLICATION. Audio-, Video- und Bildtyp sagen dem empfangenden E-Mail-Client, dass der Anhang den genannten Inhaltstyp hat und entsprechend darzustellen ist, wobei CONTENT_TYPE_APPLICATION besagt, dass es sich um Daten zu einer ganz speziellen Applikation handelt und der Client diese bestimmen muss. Ein Beispiel für letzteren Typ sind Dateien Ihrer Office-Suite, wie Tabellen oder Textdokumente. Bei diesen handelt es sich weder um Bild- noch um Audio- oder Videodaten. Der empfangende E-Mail-Client muss in diesem Fall selbst herausfinden, wie eine solche Datei zu öffnen ist.
Neben diesen Standardeigenschaften haben sowohl ezcMailVirtualFile als auch ezcMailStreamFile noch jeweils eigene Attribute: $contents enthält bei einem virtuellen Dateianhang die eigentlichen Daten, entsprechend dazu enthält $stream bei einem Objekt von ezcMailStreamFile die betroffene Stream-Ressource. ezcMailFile, die einfachste Klasse zur Repräsentation von Dateianhängen, kommt lediglich mit den Attributen der Basisklasse daher.
Sie haben gerade die Klasse ezcMailMultipartRelated zur Verknüpfung von HTML-Inhalten und darin eingebetteter Anhänge gesehen. Bei dieser Klasse handelt es sich um einen Erben von ezcMailMultipart, der Basisklasse für alle E–Mail-Bestandteile. Ihre Aufgabe ist es, andere Bestandteile zusammenzuhalten. Es handelt sich also nicht um Blattknoten des E-Mail-Baums, sondern um innere Knoten. Im Folgenden werden Sie alle Vertreter von ezcMailMultipart und deren jeweiligen Einsatzzweck genauer betrachten.
Ein weiterer Vertreter der Serie ist ezcMailMultipartMixed. Aufgabe dieser Klasse ist es, eine Reihe von hintereinander geschalteten E-Mail-Bestandteilen zu verwalten. So haben Sie die Möglichkeit, verschiedene Textbestandteile hintereinander zu schalten und somit Text, Bilder oder andere Inhalte sich abwechseln zu lassen.
Während dieser Bestandteil zu den bekannteren zählt, gehören ezcMailMultipartDigest und ezcMailMultipartReport nicht dazu. Die erste Klasse dient dazu, sogenannte Digest-E-Mails zu modellieren, welche eine Reihe von weiteren E-Mails enthalten. Häufig wird die Verpackung mehrerer E-Mails in eine einzelne genutzt, um E-Mails weiterzuleiten oder Zusammenfassungen von Mailinglisten zu erstellen. Oft haben Sie beim Abonnieren einer Mailingliste oder eines E-Mail-Verteilers die Wahl zwischen dem normalen Versand und dem Erhalt eines Digests, also einer Zusammenfassung des Mail-Verkehrs einer bestimmten Periode (zum Beispiel ein Tag). E-Mails, die mit Hilfe eines Report-Bestandteils codiert werden, sind Ihnen wahrscheinlich bekannter als Ihnen lieb ist: Mit diesem E-Mail-Bestandteil werden Status-E-Mails generiert, wie beispielsweise die Mitteilung, dass eine versendete E-Mail nicht zugestellt werden konnte.
Beide E-Mail-Bestandteile werden uns nicht weiter beschäftigen. Passend zu jedem von ihnen gibt es weitere Spezialbestandteile, die nur mit den entsprechenden Verbundbestandteilen Verwendung finden: ezcMailDeliveryStatus ist ausschließlich als Bestandteil von ezcMailMultipartReport anzutreffen, während ezcMailRfc822Digest eben genannte Digest-E-Mails für Mailinglisten darstellt. Eine Instanz der letzten Klasse dient als Bestandteil von ezcMailMultipartDigest. Zwar kann diese Digest-Klasse auch normale E-Mail-Objekte enthalten, es gilt aber als gute Praxis, diese jeweils in einen ezcMailRfc822 Digest zu verpacken.
Transporter
Auch zwei Transporter-Klassen haben Sie bereits kennengelernt: ezcMailMtaTransport zum Versand von E-Mails über die PHP-interne mail()-Funktion sowie ezcMailSmtpTransport, um eine Verbindung zu einem SMTP-Server herzustellen und darüber E-Mails zu versenden. Beide Klassen implementieren das Interface ezcMailTransport und sind somit für das Versenden von E-Mails ausgelegt. – Ganz im Gegensatz zu den Transporter-Klassen, die nun Thema sein sollen. Denn mit diesen lassen sich lediglich E-Mails empfangen, aber nicht versenden.
Transporter zum Empfangen von E-Mails teilen sich kein gemeinsames Interface, da sich die Möglichkeiten gravierend von Backend zu Backend unterscheiden. Dennoch stellen alle Empfangs-Transporter ein gemeinsames Set von Methoden per Konvention bereit, solange diese für den Transporter sinnvoll sind. Bevor wir die gemeinsamen Methoden aber vorstellen, sollen die Backend-Handler vorgestellt werden:
Eine Instanz ezcMailPop3Transport ist in der Lage, Verbindung zu einem POP3-Server aufzubauen und von diesem E-Mails zu empfangen. Analog funktioniert ezcMailImapTransport auf der Basis des IMAP-Protokolls in Version 4 anstelle von POP3. Gänzlich verschieden arbeitet ezcMailMBoxTransport, denn dieser Transporter baut keine Verbindung zu einem Server auf, sondern liest E-Mails aus dem (besonders im Unix-Umfeld bekannten) Dateien im MBox-Format.
Alle drei Klassen bieten unterschiedliche Konstruktoren, welche auf das jeweilige Backend zugeschnitten sind. So erwarten die beiden Server-Transporter jeweils den anzusprechenden Server sowie optional den Port und ein Array mit weiteren Optionen, wohingegen der MBox-Transporter nur die zu öffnende MBox-Datei erwartet. Trotz solcher Unterschiede stellt dennoch jeder Transporter die Methoden fetchAll() zum Abrufen aller E-Mails auf einen Schlag sowie fetchByMessageNr() und fetchFromOffset() bereit, wobei die letzten beiden den Zugriff auf Teilmengen von E-Mails oder einzelne Exemplare ermöglichen. Mit der Methode listMessages(), die eine Liste mit allen für den Transporter zugreifbaren E-Mails zurückgibt, hören die Gemeinsamkeiten aber auf.
Alle weiteren Methoden sind Handler-spezifisch und bedienen eine bestimmte Funktionalität des zugrunde liegenden Backends. So weisen IMAP- und POP3-Transporter jeweils eine Methode authenticate() auf, mit der Sie sich dem Server gegenüber ausweisen müssen, bevor Sie Zugriff auf dessen E-Mails erhalten. Und die IMAP-Variante stellt Methoden bereit, um mit einzelnen Postfächern zu arbeiten, was keiner der anderen Handler mangels Unterstützung im Backend ermöglicht.
Auf den folgenden Seiten werden einzelne Methoden bestimmter Handler nach und nach im Zusammenhang erläutert, sodass Sie einen guten Überblick über die Möglichkeiten jedes Handlers erhalten.
Interessant ist allerdings, wie Sie aus einem Empfangs-Transporter letztendlich ezcMail-Objekte extrahieren, mit denen Sie arbeiten können. Denn die beschriebenen fetch*()-Methoden geben jeweils ein Objekt einer Sequenz-Klasse (ezcMailParserSet) zurück, welche die E-Mails lediglich für eine ezcMailParser-Instanz verständlich aufbereitet. Bedingt durch die Verschiedenartigkeit der Backends, wird eine einheitliche Darstellungsweise der zu parsenden E-Mails benötigt, aus der mit Hilfe des E-Mail-Parsers echte ezcMail-Objekte entstehen.
Aus diesem Grund müssen Sie zunächst eine Instanz von ezcMailParser erzeugen und diesem Objekt das so erhaltene E-Mail-Parser-Set übergeben. Sie erhalten dann ein Array von ezcMail-Objekten zurück. Wie dies genau funktioniert, sehen Sie im folgenden Beispiel aus dem GP-Blog.
14.2.2 E-Mail-Empfang 

Die Implementierung der Aktionsmethode checkBlogMail() steht noch aus. Wir wollen sie nun vorstellen.
Eigentlich ist es nicht sinnvoll, checkBlogMail() als Aktion im GP-Blog zu implementieren. Denn normalerweise werden solche Arbeiten zeitgesteuert mit Hilfe eines Programms wie CRON [http://en.wikipedia.org/wiki/Cron ] ausgeführt und nicht durch ein Web-Interface ausgelöst. Da die Infrastruktur für Aktionen aber im GP-Blog bereits vorhanden ist und die Ausführung über eine URL dem Beispiel an sich keinen Abbruch tut, haben wir uns entschieden, diesen Weg zu gehen, anstatt eine neue Architekturebene für die Batch-Verarbeitung extra einzuführen.
Um die im Folgenden vorgestellte Aktion zu testen, müssen Sie die Test-URL http://gpblog/check_mail aufrufen. Der Code wird dann einen konfigurierten E–Mail-Server kontaktieren, alle E-Mails abfragen, parsen und entsprechend Einträge und Medien in der Galerie erzeugen.
Konfiguration
Als Erstes muss die in Abschnitt 14.1.2, »E-Mail-Versand«, neu erstellte Konfigurationsdatei email.ini um einige Einstellungen erweitert werden:
[server] proto=imaps host=example.com user=gpblog@example.com pass=foo$23%bar [imap] mailbox=Inbox [receive] authozized_addresses[]=toby@php.net authozized_addresses[]=kore@php.net
Listing 14.5 Erweiterung der E-Mail-Konfiguration
Es werden werden der Konfigurationsdatei drei Sektionen hinzugefügt: Die Sektion [server] enthält Einstellungen, die den E-Mail-Server betreffen, die Sektion [imap] wird speziell für den Kontakt zu IMAP-Servern benötigt und die Sektion [receive] soll ein gewisses Maß an Benutzerauthentifizierung sicherstellen.
Innerhalb der Server-Sektion bestimmen Sie das Protokoll, das zur Verbindung mit dem Server dienen soll. Möglich sind hier die Werte pop für eine unverschlüsselte POP3-Verbindung, pops für die verschlüsselte Variante sowie imap und imaps für die IMAP-Verbindungen. Im gezeigten Beispiel ist also ein IMAP-Server konfiguriert, zu dem eine SSL-gesicherte Verbindung herzustellen ist. Die Host-Einstellung speichert den Domain-Namen des zu kontaktierenden Servers, entsprechend dazu repräsentieren user und pass den Benutzernamen und das Passwort zum Log-in an diesem Server. Alternativ zum vollqualifizierten Domain-Namen können Sie natürlich auch einen einfachen Hostnamen verwenden, der in Ihrem Netzwerk aufgelöst wird, oder direkt eine IP-Adresse.
Die zweite Sektion ist spezifisch für den Kontakt zu einem IMAP-Server wichtig. Da dieses Protokoll die Bereitstellung verschiedener Postfächer in einem Account erlaubt, muss die zum Abruf zu verwendende Mailbox dem Transporter mitgeteilt werden. Standardmäßig wird hier das Postfach Inbox verwendet, welches bei den meisten IMAP-Servern neu empfangende E-Mails enthält. Ist dies bei Ihrem Server anders oder wollen Sie ein anderes Postfach als die Inbox abfragen, so können Sie diese Einstellung anpassen.
Das letzte Einstellungs-Array bietet einen gewissen Schutz vor Fremdzugriffen auf den E-Mail-Blogging-Mechanismus des GP-Blogs. Hier werden E-Mail-Adressen gespeichert, die valide Absender darstellen. Enthält das abgefragte Postfach E-Mails, die nicht von diesen Absendern stammen, so werden diese ignoriert. Natürlich stellt dies keine wirkliche Sicherheit dar, denn Absenderadressen von E-Mails lassen sich recht einfach fälschen. Allerdings ersparen wir dem GP-Blog so immerhin eine Menge Spam. In einer echten Anwendung wäre es sinnvoll, zumindest eine Passwort-basierte Authentifizierung durchzuführen, oder noch besser ein Signaturverfahren wie PGP zur Identifizierung berechtigter Nutzer zu verwenden.
Wenn Sie sich wundern, wie im PHP-Code später die Einstellungen ausgelesen werden können, sollten Sie Näheres zum Thema Konfigurationsverwaltung in Kapitel 5, »Konfiguration«, nachlesen.
Verbindung herstellen
Die Methode checkBlogMail() ist in mehrere Unteraufrufe gegliedert, die Sie im Folgenden kennenlernen werden. Problematisch an dieser Methode ist, dass es sich bei dem Ablauf eher um einen Batch-Prozess, also eine rein sequentielle Abfolge von Befehlen, handelt. Objektorientierte Konzepte lassen sich also nur schwerlich anwenden und wirken immer etwas gezwungen. Behalten Sie daher für die kommenden Abschnitte im Hinterkopf, dass es sich bei der Aktion ursprünglich um einen zeitgesteuerten Batch-Prozess handelte.
Im hier dargestellten Sourcecode der Methode checkBlogMail() wurde, wie schon in Abschnitt 14.1, »E-Mails senden«, das übliche Aktionsmethoden-»Vorspiel« ausgeblendet.
public static function checkBlogMail( ezcUrl $url,ð ezcUrlConfiguration $config ) { // ... Initialisierung... try { $srv = self::getConnection(); } catch ( Exception $e ) { gpBlogController::getInstance()->mainSignals->emit( "error", $e->getMessage() ); return; } // ...
Listing 14.6 Verbindung aufbauen
Im dargestellten Codeausschnitt sehen Sie lediglich einen einfachen Methodenaufruf, innerhalb eines try-catch-Blocks, dessen Resultat in der Variablen $srv gespeichert wird. Wie Sie erkennen können, erzeugt die Methode ein neues Transporter-Objekt.
protected static function getConnection()
{
$cfg = ezcConfigurationManager::getInstance();
$options = array();
switch( $cfg->getSetting( 'email', 'server', 'proto' ) )
{
case 'pops':
$options['ssl'] = true;
case 'pop':
$srv = new ezcMailPop3Transport(
$cfg->getSetting( 'email', 'server', 'host' ),
null,
$options
);
break;
case 'imaps':
$options['ssl'] = true;
case 'imap':
$srv = new ezcMailImapTransport(
$cfg->getSetting( 'email', 'server', 'host' ),
null,
$options
);
break;
default:
throw new RuntimeException(
'Protocol ' .
$cfg->getSetting( 'email', 'server', 'proto' ) .
'not recognized, must be pop/pops/imap/imaps.'
);
}
try
{
$srv->authenticate(
$cfg->getSetting( 'email', 'server', 'user' ),
$cfg->getSetting( 'email', 'server', 'pass' )
);
}
catch ( ezcMailTransportException $e )
{
throw new RuntimeException(
"Authetication with mail server failed."
);
}
if ( $srv instanceof ezcMailImapTransport )
{
try
{
$srv->selectMailbox(
$cfg->getSetting( 'email', 'imap', 'mailbox' )
);
}
catch ( ezcMailTransportException $e )
{
throw new RuntimeException(
"Could not select mailbox."
);
}
}
return $srv;
}Listing 14.7 Die getConnection()-Methode
Zunächst entscheidet die Methode auf Basis der Konfigurationseinstellung des Protokolls, wie ein Transport-Objekt erzeugt werden muss. Je nach Basisprotokoll wird eine Instanz der Klassen ezcMailImapTransport oder ezcMail Pop3Transport erzeugt, der je nach Vorhandensein eines abschließenden s am Protokollnamen eine Option zur SSL-Verschlüsselung übergeben wird. Wird keiner der vier möglichen Identifizierer für Übertragungsvarianten erkannt, so wirft die Methode eine Exception mit einer entsprechenden Fehlermeldung.
Der Log-in funktioniert bei Objekten beider Handler-Klassen mit Hilfe der authenticate()-Methode, die mit Daten aus der Datei email.ini gefüllt wird. Ist dieser Schritt erfolgreich verlaufen, steht das Handler-Objekt zum Empfangen von E-Mails bereit: Der Server ist kontaktiert und der gewünschte E-Mail-Account wurde nach korrekter Authentifizierung geöffnet.
Nun spielen die Besonderheiten zwischen Empfangs-Transportern eine Rolle. Denn bevor der IMAP-Handler E-Mails abarbeiten kann, muss er darüber informiert werden, welche Mailbox er dazu öffnen soll. Da der POP3-Handler einen entsprechenden Mechanismus nicht unterstützt, ist die Unterscheidung mittels instanceof-Operator notwendig, um nur auf einem IMAP-Objekt die Methode selectMailbox() aufzurufen.
Zum Schluss wird das Handler-Objekt an die aufrufende checkBlogMail()-Methode zurückgegeben, mit deren Hilfe im nächsten Abschnitt die Implementierung weiter realisiert wird.
E-Mails parsen
Die Verbindung zum Server ist erfolgreich hergestellt. Jetzt gilt es, alle E-Mails abzurufen und eventuell enthaltene Blog-Einträge beziehungsweise anhängende Bilder zu parsen und ins GP-Blog einzufügen. Erhaltene Bilder werden der Einfachheit halber und im Sinne der Wiederverwertung von Code mit der processImage()-Methode aus Kapitel 12, »Bildverarbeitung«, behandelt und landen somit automatisch in der Bildergalerie.
// ... $set = $srv->fetchAll( true ); $parser = new ezcMailParser(); $emails = $parser->parsemail( $set ); foreach ( $emails as $number => $email ) { if ( in_array( $email->from->email, $cfg->getSetting( 'email', 'receive', 'authorized_addresses' ) ) ) { $processedMail = self::processMailParts( $email->body ); // ...
Im letzten Abschnitt wurde in der Variablen $srv die Transportverbindung zu dem Mail-Server abgelegt, von dem E-Mails abzurufen sind. Da nicht geplant ist, dass E-Mails auf dem Server verbleiben und so versehentlich doppelt verarbeitet werden, wird beim Abholen mittels fetchAll() bewirkt, dass alle abgeholten E-Mails direkt gelöscht werden.
Wir verstoßen an dieser Stelle gegen die Grundsätze ordentlicher Programmierung, um das Beispiel nicht unnötig aufzublähen. – Es ist nicht sinnvoll, wie im dargestellten Fall, alle E-Mails sofort beim Abholen zu löschen. Tritt anschließend bei der Verarbeitung einer E-Mail ein Fehler auf, könnte es sein, dass nachfolgende E-Mails unbearbeitet verlorengehen. Außerdem kann es je nach E-Mail-Aufkommen passieren, dass das Abholen aller E-Mails auf einen Schlag auf lange Sicht zu Skalierungsproblemen führt. Während der E-Mail-Server darauf ausgelegt ist, eine große Anzahl an E-Mails zu speichern, zu durchsuchen und zum Download bereitzustellen, liegt die Aufgabe des Webservers darin, Webseiten und andere Inhalte schnell auszuliefern. Mit der pauschalen Übertragung einer großen Zahl von E-Mails zwischen dem E-Mail- und Webserver werden beide Einheiten längerfristig blockiert, was beide Programme behindern kann. Außerdem kann die Übertragung vieler größerer E-Mails in den Kontext des Webservers dessen Arbeitsspeicherverbrauch drastisch steigern. Im realen Fall sollten Sie also ein wenig mehr Aufwand bei der Entwicklung in Kauf nehmen, um solche Probleme später im Lebenszyklus der Anwendung zu vermeiden.
Als nächster Schritt wird das eigentliche Parsen der E-Mails vorbereitet und dazu ein neues ezcMailParser-Objekt erzeugt. Dieses Objekt kann mittels der gezeigten parsemail()-Methode das von fetchAll() zurückgegebene ezcMailParserSet verwenden, um ezcMail-Objekte zu erzeugen. Besagtes Parser-Set unterscheidet sich je nach Transporter, wie Sie bereits im vorangegangenen Abschnitt 14.2.1, »Architektur«, erfahren haben.
Das in $emails gespeicherte Array von ezcMail-Instanzen wird mit Hilfe einer foreach-Schleife durchlaufen, um jede E-Mail in ihre Bestandteile aufzuspalten und diese in die geeigneten Datenstrukturen des Galileo-Press-Blogs zu überführen.
Zunächst wird hier überprüft, ob die Absenderadresse in der Whitelist (welche in der Datei email.ini definiert wurde) enthalten ist. Ist dies nicht der Fall, wird die E-Mail ignoriert und die foreach-Schleife fährt mit der nächsten E-Mail fort. Anschließend wird die statische Methode processMailParts() aufgerufen, die den eigentlichen Parsing-Prozess kapselt. Übergeben bekommt diese Methode lediglich das $body-Attribut, also den Hauptbestandteil der E-Mail. Wie die Methode arbeitet, zeigt der folgende Abschnitt.
E-Mail-Bestandteile verarbeiten
Die Methode processMailParts() verarbeitet Teile auch komplexerer E-Mails nach einem rekursiven Ansatz. Dabei besteht der Hauptteil des Sourcecodes aus einem großen switch-Statement, um die einzelnen Bestandteil-Klassen ihrer Beschaffenheit nach zu bearbeiten. Dem Beispielcharakter gerecht werdend, betrachten wir meist nur triviale Fälle und kürzen den Parsing-Prozess ein wenig ab. Trotzdem sollte das grundlegende Prinzip der E-Mail-Verarbeitung mit eZ Components in diesem Abschnitt klar werden.
protected static function processMailParts( ezcMailPart $part )
{
$result = array(
'text' => null,
'files' => array(),
);
switch ( true )
{
case ( $part instanceof ezcMailMultipartAlternative ):
// ...
// ...
}
return $result;
}Listing 14.8 E-Mails verarbeiten
Das Endergebnis von processMailParts() wird ein Array mit zwei Schlüsseln sein: 'text' kann einen String enthalten und im Array zum Schlüssel 'files' werden beliebig viele Dateianhänge verwaltet. Beide Ergebnisse sind optional, sodass ein reiner Texteintrag ebenso wie eine E-Mail nur mit Bildanhängen oder beides in Kombination verarbeitet werden können.
Den Inhalt des folgenden switch-Statements haben wir in einzelne Listings aufgeteilt, um die Aspekte der einzelnen Bestandteil-Klassen im Zusammenhang herauszustellen. Zunächst wird eine E-Mail mit HTML- und alternativer Plain-Textdarstellung behandelt.
case ( $part instanceof ezcMailMultipartAlternative ):
$plainContent = null;
$htmlContent = null;
foreach ( $part->getParts() as $part )
{
switch ( $part->subType )
{
case 'plain':
$plainContent = $part;
break;
case 'html':
$htmlContent = $part;
break;
}
}
$result = self::mergemailPartArrays(
$result,
self::processMailParts(
$htmlContent !== null ? $htmlContent : $plainContent
)
);
break;Listing 14.9 ezcMailMultipartAlternative verarbeiten
Wie zu erwarten war, wird bei der Verarbeitung von ezcMailMultipartAlternative-Objekten zwischen $htmlContent und $plainContent unterschieden. Wir beschränken uns an dieser Stelle auf diesen Fall.
Innerhalb der Schleife wird entschieden, ob es sich um eine Instanz von ezcMailText handelt mit Plain-Text-Subtyp oder HTML als Untertyp. Ist keines von beiden der Fall, wird der Bestandteil ignoriert. Insgesamt wird so auch der Fall abgedeckt, dass mehr als zwei Möglichkeiten gegeben werden: Beispielsweise, dass ein Bild als alternativer Bestandteil angegeben wird.
Ist in der E-Mail ein HTML-Bestandteil enthalten, so wird dieser der Plain-Text-Variante vorgezogen, denn HTML ist in GP-Blog-Einträgen erlaubt. Allerdings handelt es sich bei den Bestandteilen immer noch um ezcMailPart-Objekte, die zunächst verarbeitet werden sollen. Dazu findet ein rekursiver Aufruf derselben Methode auf dem ausgewählten Kind-Objekt statt. Das von diesem Aufruf zurückgegebene Ergebnis wird mit dem eigenen Ergebnis zusammengeführt und zuletzt aus der Methode zurückgegeben.
Zwar deckt die hier gezeigte Vorgehensweise den Standardfall ab; um das lückenlose Parsen aller eingehenden E-Mails zu garantieren reicht, der Code aber nicht aus. Dazu müssten Sie die verschiedenen Eigenheiten der E-Mail-Zusammensetzung und gegebenenfalls noch weitere Varianten des Multipart-Alternative-Bestandteils beachten, was wir aus bekannten Gründen hier nicht tun.
Auf die Darstellung der Hilfsmethode mergemailPartArrays() werden wir ebenfalls verzichten, denn außer der Verschmelzung zweier Ergebnis-Arrays zu einem einzigen, tut diese Methode nichts. Einzig interessant ist, dass Texteinträge einander überschreiben, wobei Dateien rekursiv hinzugefügt werden.
case ( $part instanceof ezcMailMultipartMixed ):
foreach ( $part->getParts() as $multiPart )
{
$result = self::mergemailPartArrays(
$result,
self::processMailParts( $multiPart )
);
}
break;Listing 14.10 ezcMailMultipartMixed-Bestandteile verarbeiten
Bei der Bearbeitung von ezcMailMultipartMixed gehen wir im GP-Blog pragmatisch vor: Jeder enthaltene Bestandteil wird lediglich mit dem aktuellen Resultat verschmolzen. Dabei bleibt der Sequenz-Charakter der Bestandteils-Klasse zwar für Anhänge erhalten, eingereihte Textanteile überschreiben sich aber gegenseitig. Lediglich das letzte Textelement wird als Blog-Eintrag gespeichert.
case ( $part instanceof ezcMailMultipartRelated ):
$mainRes = self::processMailParts( $part->getMainPart() );
foreach ( $part->getRelatedParts() as $relPart )
{
$mainRes = self::mergemailPartArrays(
$mainRes, self::processMailParts( $relPart )
);
}
$result = self::mergemailPartArrays( $result, $mainRes );
break;Listing 14.11 ezcMailMultipartRelated-Bestandteile verarbeiten
Im Normalfall besteht ein Objekt dieser Klasse aus einem ezcMailText-Objekt als Hauptbestandteil und einer beliebigen Anzahl darin eingebetteter Anhänge. Daher betrachten wir hier nur genau diesen Fall. Dadurch, dass der Hauptbestandteil zuletzt zum aktuellen Resultat hinzugefügt wird, werden bereits darin enthaltene Texte überschrieben.
case ( $part instanceof ezcMailText ):
$result['text'] = $part->text;
break;
case ( $part instanceof ezcMailFile ):
$result['files'][] = $part;
break;
default:
// Silently ignore
break;Listing 14.12 Weitere E-Mail-Bestandteile verarbeiten
Verbleiben noch das Parsen von reinem Text, von Anhängen und einigen weiteren Bestandteilen, die in Abschnitt 14.2.1, »Architektur«, Erwähnung gefunden haben. Leider müssen wir Sie an dieser Stelle gleich zwei Mal enttäuschen. Denn das Anhangsobjekt wird vorerst nur übernommen und alle weiteren Typen von Anhängen ignoriert das switch-Statement. Einem übergebenen Textbestandteil wird nur sein Text entrissen und im entsprechenden Element des Ergebnis-Arrays gespeichert.
Weitere mögliche E-Mail-Bestandteile sind an dieser Stelle nicht von Belang. So wäre es zum Beispiel möglich, Digest-E-Mails zu verarbeiten, um mehrere Blog-Einträge auf einen Schlag einzufügen.
Blog-Einträge einfügen und Bilder verarbeiten
Sie haben nun gesehen wie ein einzelnes E-Mail-Objekt zu einem Array mit zwei Komponenten geparst wurde: Einem Text und einer Menge von Anhängen in Form von ezcMailFile-Objekten. Diese Objekte stellen E-Mail-Anhänge auf der Festplatte dar, womit die Mail-Komponente Ihnen Platz im Arbeitsspeicher spart, indem Anhänge direkt auf der Festplatte landen. Die temporär angelegten Dateien einer geparsten E-Mail werden beim Zerstören des zugehörigen Objekts automatisch mit bereinigt. Nun müssen die so vorbereiteten Daten entsprechend weiterverarbeitet werden:
foreach( $processedMail['files'] as $file )
{
if ( $file->contentType ===
ezcMailFile::CONTENT_TYPE_IMAGE )
{
$processedMail['text'] = self::processImage(
$file,
$imageUrl,
$processedMail['text']
);
}
}
if ( $processedMail['text'] !== null )
{
self::storeEntry(
$processedMail['text'],
$email->subject
);
}Listing 14.13 Bilder verschieben und Einträge speichern
In der hier gezeigten Schleife werden alle vorher extrahierten E-Mail-Anhänge durchlaufen und für jeden findet eine Überprüfung statt, ob es sich um ein Bild handelt. Ist dies der Fall, wird das Bild mit Hilfe der Methode processImage() aus Kapitel 12, »Bildverarbeitung«, in die Bildergalerie des GP-Blogs eingefügt. Sie erinnern sich, dass hierbei automatische Konvertierungen nach JPEG oder PNG stattgefunden haben und drei verschiedene Varianten der Bilder erzeugt wurden.
| Bloggen per E-Mail testen |
|
Das Testen der hier vorgestellten Aktion des GP-Blogs bedarf einiger Vorbereitungen neben den bereits aus Kapitel 1, »Einleitung«, bekannten allgemeinen Testvorbereitungen. Zunächst benötigen Sie einen E-Mail-Account, der nicht öffentlich bekannt sein sollte und nicht aktiv verwendet wird. Außerdem müssen E-Mails dieses Accounts entweder durch einen POP- oder IMAP-Server bereitgestellt werden. |
|
Entsprechend ändern Sie nun die spezifischen Einstellungen in der Datei email.ini im Unterverzeichnis stage12/config/. Vergessen Sie nicht, Ihre E-Mail-Adresse als Absender in die Whitelist aufzunehmen, ansonsten ignoriert das nun testbereite GP-Blog Ihre E-Mails. |
|
Senden Sie zum Test ein paar E-Mails an den vorher eingestellten Account von einer E-Mail-Adresse, die Sie als validen Absender identifiziert haben. Rufen Sie anschließend die URL http://gpblog/check_mail auf. Nun werden Ihre Test-E-Mails verarbeitet und die Textinhalte der E-Mails werden später auf der Startseite des GP-Blogs unter http://gpblog/ aufgelistet. Haben Sie Bildanhänge beigefügt, können Sie die zugehörigen Bilder unter http://gpblog/gallery abrufen. In HTML-E-Mails eingebettete Bilder werden automatisch in die Blog-Einträge gelinkt. |
Wie Sie bemerkt haben, wird nicht direkt die processImage()-Methode auf der Galerie-Aktionsklasse aufgerufen, sondern noch eine eigene Methode zwischengeschaltet. Diese Methode leitet lediglich auf die besagte Galerie-Methode weiter und ersetzt anschließend die passende Content-ID im dazugehörigen Textteil des Eintrags:
protected static function processImage( $imageFilePart,
$imageUrl, $text )
{
try
{
$dstInfo = gpBlogActionGallery::processImage(
$imageFilePart->fileName,
$imageFilePart->fileName
);
}
catch ( Exception $e )
{
return $text;
}
$imageUrl->setParam( 'name', $dstInfo['filename'] );
if ( $text !== null && $imageFilePart->contentId !== null )
{
$text = preg_replace(
'/cid:' .
ð
preg_quote( $imageFilePart->contentId ) . '/',
$imageUrl->buildUrl(),
$text
);
}
return $text;
}Listing 14.14 Eingebettete Bilder verlinken
Wie schon erwähnt, ruft die Methode ihr Pedant auf der Klasse gpBlogActionGallery auf, um die eigentlich Verarbeitung des Bildes zu bewirken. Ein Fehlschlag dieser Aktion wird ignoriert. Wurde das Bild korrekt bearbeitet, wird das im Initialisierungsteil der fetchBlogMail()-Method vorbereitete ezcUrl-Objekt verwendet, um eventuelle Links auf diesen Anhang im E-Mail-Text zu ersetzen.
Die Verlinkung eingebetteter Bilder in HTML-E-Mails erfolgt über den Protokoll-Handler cid:, gefolgt von der Content-ID des Anhangs. So geartete Strings werden mittels eines regulären Ausdrucks im Text komplett durch die URL zum betreffenden Bild ersetzt.
Unabhängig davon, ob der Text verarbeitet wurde, wird der übergebene Text schließlich aus der Methode zurückgegeben. Der zweite Methodenaufruf zur letztendlichen Bearbeitung des aus einer E-Mail extrahierten Textes führt zur Speicherung durch die PersistentObject-Komponente, welche Sie bereits aus Kapitel 7, »ORM mit PersistentObject«, kennen.
Lediglich der Inhalt des <body />-Tags wird hier auch noch aus dem HTML-Text der E-Mail extrahiert, denn alles außerhalb des HTML-Körpers wird bei der Darstellung vom GP-Blog selbst generiert.
E-Mails löschen
In den letzten beiden Abschnitten haben wir Sie durch die Methode checkBlogMail() geführt. Sie haben gesehen, wie dort E-Mails von einem vorbereiteten Transporter-Objekt empfangen und anschließend mittels ezcMailParser in ezcMail-Instanzen umgewandelt werden. Sie haben außerdem gesehen, wie die einzelnen E-Mail-Bestandteile praktisch verarbeitet werden können.
Vielleicht erinnern Sie sich noch an den Anfang der Methode, bei dessen Besprechung wir darauf hingewiesen haben, dass es nicht empfehlenswert ist, alle E–Mails direkt beim Empfang bereits auf dem Server löschen zu lassen. Diese Aussage ist zwar grundsätzlich korrekt, allerdings ist es nicht ganz zutreffend, dass die empfangenen E-Mails »automatisch« gelöscht werden. Zumindest im IMAP-Transporter ist dafür abschließend der folgende Aufruf nötig:
// ...
if ( $srv instanceof ezcMailImapTransport )
{
$srv->expunge();
}
unset( $srv );
}Listing 14.15 Aufräumen
Auf IMAP-Servern werden die zu löschenden E-Mails grundsätzlich zunächst als solche markiert, anstatt sie direkt zu entfernen. Aus diesem Grund müssen Sie diesen so markierten »Müll« erst mittels expunge() wegräumen lassen. Als allerletzte Aktion dieses Codeschnipsels sehen Sie, dass der Empfangs-Transporter zerstört wird, um die Verbindung zum Mail-Server zu trennen.




Ihre Meinung






