Excel Dateien mit PHPExcel erstellen

Letzte Woche habe ich hier bereits darüber geschrieben, wie man mit dem MS-Excel Stream Handler Excel Dateien erstellen kann, nun habe ich hier noch eine andere, viel umfangreichere Lösung.
Die Lösung nennt sich PHPExcel und kommt von CodePlex. Die "Installation" ist sehr simpel, ZIP Datei herunterladen und die PHP-Dateien auf dem Server speichern. Im Script, das die Excel Datei schreiben soll dann die Dateien PHPExcel.php und PHPExcel/IOFactory.php einbinden.
<?
require_once("includes/PHPExcel.php");
require_once(
"includes/PHPExcel/IOFactory.php");
?>

Die ganze Klasse ist viel zum Umfangreich um hier komplet beschrieben zu werden, ich beschränke mich lediglich auf das schreiben eines Excel Files ohne Formatierung. Wer etwas Farbe ins speil bringen will, oder Formeln, Rahmen usw. benutzen will führt sich am besten die sehr ausführliche Dokumentation zu gemüte.

Wenn die beiden Dateien eingebunden sind, muss man als erstes eine Instanz der Klasse erstellen und kann danach gewisse Dokumenteigenschaften wie den Ersteller Titel usw. schreiben.
<?
// neue instanz erstellen
$xls=new PHPExcel();

// dokumenteigenschaften schreiben
$xls->getProperties()->setCreator("David Lienhard")
                     ->
setLastModifiedBy("David Lienhard")
                     ->
setTitle("Test")
                     ->
setSubject("Test");
?>


Beim nächsten Schritt definiert man, in welches Worksheet man schreiben will. Ich habe bei diesem Beispiel nur ein Sheet und so kann ich auch nur das anwählen.
<?
// das erste worksheet anwaehlen
$sheet=$xls->setActiveSheetIndex(0);
?>


Nun kann man die gewünschten Daten in die Datei schreiben. Die Methode erwartet zwei Parameter. Zum einen das Feld, in das geschrieben werden soll und der Wert, der geschrieben werden soll. Das Feld muss im Format A1 angegeben werden, also genau wie im Excel.
<?
// den wert test in das Feld A1 schreiben
$sheet->setCellValue("A1","test");
?>


Nun kann man noch den Titel des aktuellen Worksheets definieren. Wird nichs angegebn wird einfach der Filename benutzt.
<?
// den namen vom Worksheet 1 definieren
$xls->getActiveSheet()->setTitle("Titel");
?>


Zum Schluss müssen die Daten noch in eine Datei geschrieben werden. Dazu erstellt man erstmal eine neue Instanz der IOFactory und gibt dort an, welchen Dateityp die Datei haben soll. Also Excel 2007, Excel 5 usw. Ich habe aus Kompatibilitätsgründen Excel 5 gewählt.
<?
// eine writer instanz erstellen
// das dateiformat ist hier excel5
// alt, aber von allen excel versionen lesbar
$writer=PHPExcel_IOFactory::createWriter($xls,"Excel5");

// die daten speichern
$writer->save("filename.xls");
?>


Und so sieht das ganze Script aus:
<?
// neue instanz erstellen
$xls=new PHPExcel();

// dokumenteigenschaften schreiben
$xls->getProperties()->setCreator("David Lienhard")
                     ->
setLastModifiedBy("David Lienhard")
                     ->
setTitle("Test")
                     ->
setSubject("Test");

// das erste worksheet anwaehlen
$sheet=$xls->setActiveSheetIndex(0);

// den wert test in das Feld A1 schreiben
$sheet->setCellValue("A1","test");

// den namen vom Worksheet 1 definieren
$xls->getActiveSheet()->setTitle("Titel");

// eine writer instanz erstellen
// das dateiformat ist hier excel5
// alt, aber von allen excel versionen lesbar
$writer=PHPExcel_IOFactory::createWriter($xls,"Excel5");

// die daten speichern
$writer->save("filename.xls");
?>

Excel Dateien mit PHP und MS-Excel Stream Handler erstellen

Im Geschäft musste ich eine kleine Web-App erstellen, die Excel-Files ausliest und in einem anderen Format wieder neue Excel-Files schreibt. Wie man Excel-Files ausliest habe ja ja bereits beschrieben, darum hier eine kleines Howto, wie man Excel-Dateien schreibt.

Von PHP selber gibt es dazu keine Funktionen, dafür aber verschiedene Klassen und Funktionen dritter. Eine recht simple Möglichkeit ist der MS-Excel Stream Handler, den man nach einer Registration gratis herunterladen kann.

Die Funktion wird über ein File eingebunden und registriert sich dann gleich selber einen neuen Filestream mit dem Namen xlsfile. Die Vorgehensweise ist recht simpel: Man erstellt sich einen zweidimensionalen Array und schreibt diesen serialisiert über den Stream in die Datei. Etwas unschön ist in meiner Meinung, dass der Array-Index gleich als erste Zeile benutzt wird.

Aber hier ein kleines Beispiel:
<?
// einen array erstellen
$data=array();

// zweite zeile definieren
// der array-index (ueberschrift1-5) wird als erste zeile verwendet
$data[]=array(
    
"ueberschrift1" =>  "zeile 2 - spalte 1",
    
"ueberschrift2" =>  "zeile 2 - spalte 2",
    
"ueberschrift3" =>  "zeile 2 - spalte 3",
    
"ueberschrift4" =>  "zeile 2 - spalte 4",
    
"ueberschrift5" =>  "zeile 2 - spalte 5");

// dritte zeile definieren
$data[]=array(
    
"ueberschrift1" =>  "zeile 3 - spalte 1",
    
"ueberschrift2" =>  "zeile 3 - spalte 2",
    
"ueberschrift3" =>  "zeile 3 - spalte 3",
    
"ueberschrift4" =>  "zeile 3 - spalte 4",
    
"ueberschrift5" =>  "zeile 3 - spalte 5");

// dateiname definieren
$filename="excel.xls";

// stream oeffnen
$fp=fOpen("xlsfile:/".$filename,"wb");

// check ob datei erstellt werden konnte (schreibrechte, korrekter pfad)
if(!is_resource($fp))
{
    die(
"konnte datei nicht erstellen");
}

// den serialisierten array schreiben
fWrite($fp,serialize($data));

// datei schliessen
fClose($fp);
?>

Das ergibt dann das folgende Erbebnis:
http://images.t-error.ch/blog/1256/excel.jpg


Der erstellen eines Excel-Files geht so sehr einfach und schnell von der Hand, dafür hat man aber keine weiteren Konfigurationsmöglichkeiten. Man kann keine weiteren Tabellenblätter hinzufügen und die erste Zeile ist immer der Arrayindex.
Ich werde auf meiner Blog demnächst eine andere Möglichkeit beschrieben, die etwas aufwändiger ist, aber auch mehr Konfigurationsmöglichkeiten bietet.

RTF-Dateien mit PHP erstellen

Letzthin musste ich im Geschäft mit PHP einige Wordfiles erstellen. Da sich die Formatierung der Wordfiles auf fetten Text beschränkte machte ich es mir einfach und erstelle RTF-Files mit der Endung .doc. RTF-Dateien sind Plaintext und so recht einfach zu erstellen und wenn die Dateiendung .doc ist, werden sie automatisch mit dem Word geöffnet. So habe ich erstmal im Word eine Datei mit fettem und normalem Text erstellt und die als RTF abgespeichert. So kann man diese Datei gleich als Vorlage benutzen.

Ich habe nun den Inhalt der Datei in einer Variable gespeichert, um so die RTF-Datei schnell erstellen zu können. Das sieht dann so aus (man beachte die doppelten Backslashes)
<?
// template erstellen
$template="{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang2055{\\fonttbl{\\f0\\froman\\fprq2\\fcharset0 Times New Roman;}{\\f1\\fnil\\fcharset0 Calibri;}}
{\\colortbl ;\\red0\\green0\\blue0;}
{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\f0\\fs24
\\b {fett}
\\par
\\b0 {normal}
\\par\\cf0\\lang7\\f1\\fs22\\par
}"
;
?>


In diesem Template kann man nun die beiden Variabeln {fett} und {normal} durch den gewünschten Text ersetzen. Allerdings kann man den Text nicht einfach so einfügen, denn Sonderzeichen und Zeilenumbrüche müssen erst noch richtig codiert werden. Das geht so:
<?
// sonderzeichen und zeilenumbrueche ersetzen
$replace['from'][]="&auml;";
$replace['from'][]="&ouml;";
$replace['from'][]="&uuml;";
$replace['from'][]="&Auml;";
$replace['from'][]="&Ouml;";
$replace['from'][]="&Uuml;";
$replace['from'][]="\r";

$replace['to'][]="\'e4";
$replace['to'][]="\'f6";
$replace['to'][]="\'fc";
$replace['to'][]="\'c4";
$replace['to'][]="\'d6";
$replace['to'][]="\'dc";
$replace['to'][]="\n\\par\n";

$text=str_replace($replace['from'],$replace['to'],$text);
?>


Alles in allem kann das nun so aussehen:
<?
// template erstellen
$template="{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang2055{\\fonttbl{\\f0\\froman\\fprq2\\fcharset0 Times New Roman;}{\\f1\\fnil\\fcharset0 Calibri;}}
{\\colortbl ;\\red0\\green0\\blue0;}
{\\*\\generator Msftedit 5.41.21.2509;}\\viewkind4\\uc1\\pard\\cf1\\f0\\fs24
\\b {fett}
\\par
\\b0 {normal}
\\par\\cf0\\lang7\\f1\\fs22\\par
}"
;

// fetter Text
$fett="dies ist ein fetter Text";

// normaler Text
$normal="Dies ist ein normaler Text

mit Umlauten und Zeilenumbr&uuml;chen"
;

// sonderzeichen und zeilenumbrueche ersetzen
$replace['from'][]="&auml;";
$replace['from'][]="&ouml;";
$replace['from'][]="&uuml;";
$replace['from'][]="&Auml;";
$replace['from'][]="&Ouml;";
$replace['from'][]="&Uuml;";
$replace['from'][]="\r";

$replace['to'][]="\'e4";
$replace['to'][]="\'f6";
$replace['to'][]="\'fc";
$replace['to'][]="\'c4";
$replace['to'][]="\'d6";
$replace['to'][]="\'dc";
$replace['to'][]="\n\\par\n";

$fett=str_replace($replace['from'],$replace['to'],$fett);
$normal=str_replace($replace['from'],$replace['to'],$normal);

// Template bearbeiten
$template=str_replace("{fett}",$fett,$template);
$template=str_replace("{normal}",$normal,$template);

// daten schreiben
$fp=fopen("file.doc","w");
if(!
$fp)
{
    die(
"cannot write file");
}
fPuts($fp,$template);
fClose($fp);
?>




Simpler Kalender mit PHP

Hier ein kleines How-To, wie man mit PHP einen Simplen Kalender macht. In diesem Beispiel ohne grosse Einstellmöglichkeiten, aber das kann man auch im Nachhinein noch ganz gut ergänzen.
Der Kalender wird hier in einer HTML-Tabelle dargestellt, was im Normalfall die beste Lösung ist.

Ich möchte hier nicht allzuviele Worte verlieren, da ich denke, dass der Quelltext mit den Kommentaren verständlich genug sein sollte, aber den Ablauf möchte ich noch kurz erläutern:
Als erstes wir berchnet welches Wochentag der erste Tag im Monat hat und so werden wenn nötig die ersten leeren Zellen geschrieben. Wenn der erste Tag also ein Freitag ist werden 4 Zellen (Mo - Do) geschrieben. Nun werden die Tage des Monats geschrieben und immer nach sieben Tagen wird eine neue Zelle begonnen. Schlussendlich werden noch die restlichen Zellen bis zum Schluss des Monats geschrieben.

Das sieht dann so aus:
<?
echo "
<table border=\"1\" cellspacing=\"0\">
    <tr>
        <th>Mo</th>
        <th>Di</th>
        <th>Mi</th>
        <th>Do</th>
        <th>Fr</th>
        <th>Sa</th>
        <th>So</th>
    </tr>
    <tr>"
;
    
    
// monat bestimmen
$month=date("m");

// jahr bestimmen
$year=date("y");

// timestamp des ersten tages im monat
$ts=mkTime(0,0,0,$month,1,$year);

// anzahl der tage im monat
$days_of_month=date("t",$ts);

// erster tag im monat
// mo=0, di=1, ..., so=6
$first_day=date("w",$ts);
if(
$first_day==0)
{
    
$first_day=7;
}
$first_day--;

// der aktuelle wochentag
// mo=0, di=1, ..., so=6
$weekday=0;

// tage bis zum ersten tag auffuellen
for($i=0;$i<$first_day;$i++)
{
    
$weekday++;

    echo 
"
        <td>&amp;nbsp;</td>"
;
}

// wochentage schreiben
for($i=0;$i<$days_of_month;$i++)
{
    
// neue zeile nach sonntag
    
if($weekday==7)
    {
        echo 
"\n    </tr><tr>";
        
$weekday=0;
    }
    
$weekday++;

    
// aktueller tag hervorheben
    
if(intVal($i+1)==intVal(date("d")) AND intVal($month)==intVal(date("m")) AND intVal($year)==intVal(date("y")))
    {
        echo 
"
        <td style=\"background-color:#f0f0f0;\">"
.($i+1)."</td>";
    }
    else
    {
        echo 
"
        <td>"
.($i+1)."</td>";
    }
}


// tage bis zum letzen tag auffuellen
for($i=$weekday;$i<7;$i++)
{
    echo 
"
        <td>&amp;nbsp;</td>"
;

    
$weekday++;
}

// tabelle abschliessen
echo "
    </tr>
</table>"
;
?>

Ordner mit PHP löschen oder leeren

Mit der PHP-Funktion rmDir() kann man recht einfach einen Order löschen, vorausgesetzt dieser ist leer. Mit der folgenden Funktion kann man ganz einfach einen Ordner mit allen Unterordner und Dateien darin löschen oder den Ordner leeren.
<?
/*
$dir = den zu loeschenden ordner
$empty = definiert, ob der ordner geleert oder geloescht wird
    TRUE = leeren
    FALSE = loeschen

removeDir("leeren",TRUE);
removeDir("loeschen",FALSE);
*/
function removeDir($dir,$empty=FALSE)
{
    
// Handle vom Verzeichnis erstellen
    
if(!$fp=@openDir($dir))
    {
        return;
    }
    
    
// Inhalt auslesen
    
while($obj=readDir($fp))
    {
        
// . und .. ignorieren
        
if($obj=="." OR $obj=="..")
        {
            continue;
        }

        if(
is_dir($dir."/".$obj))
        {
            
// removeDir() aufrufen,
            // wenn das objekt ein ordner ist
            
removeDir($dir."/".$obj,FALSE);
        }
        else
        {
            
// datei loeschen
            
unLink($dir."/".$obj);
        }
    }

    
// hnadle schliessen
    
closeDir($fp);

    
// ordner loeschen, wenn empty = FALSE
    
if(!$empty)
    {
        
rmDir($dir);
    }
}
?>

Apache und PHP Versions-Header entfernen

Apache und PHP sind unter Ubuntu standardmässig etwas gespächig einstellt und teilen dem Browser so immer die Apache und PHP Version mit. Auch die Serversignatur (bei Fehlermeldungen) zeigt die Versionsnummern an. Dies ist aufgrund der Sicherheit nicht empfehlenswert, denn was der Hacker nicht weiss, mach ihn nicht heiss. Die HTTP-Header sehen Standardmässig so aus:
HTTP/1.1 200 OK
Date: Mon, 04 May 2009 11:23:04 GMT
Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.5 with Suhosin-Patch
X-Powered-By: PHP/5.2.4-2ubuntu5.5

Content-Length: 4045
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/html


In der Datei apache2.conf kann man mit den Optionen ServerTokens und ServerSignature die HTTP-Headers und die Signatur etwas kürzen:
ServerTokens Prod
ServerSignature Off

Die HTTP-Headers vom Apache sehen dann nur noch so aus:
HTTP/1.1 200 OK
Date: Mon, 04 May 2009 11:43:17 GMT
Server: Apache
X-Powered-By: PHP/5.2.4-2ubuntu5.5

Content-Length: 4045
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/html


Nun muss nur noch das X-Powered-By von PHP weg und dann ist sind die Headers sauber. Dazu muss man in der Datei php5.ini die Option expose_php anpassen:
expose_php = Off
Und nun sind in den Headers keine Versionsnummern mehr drin.
HTTP/1.1 200 OK
Date: Mon, 04 May 2009 11:45:13 GMT
Server: Apache
Content-Length: 4045
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/html

Caching von AJAX-Requests verhindern

Bei meiner Ajax Klasse hatte ich ein kleines Problem, dass der Internet-Explorer gewisse Ajax-Requests zwischengespeichert wurden. Dies ist ja oft nicht was man will, den schliesslich soll immer das neuste Resultat zurückgeliefert werden.
Zum einen kann man bei der Serverseitigen Datei, die mit dem Request aufgerufen wird mit den Headers eine Cache-Control bewirken. Leider hat das bei mir nicht immer geholfen. Funktionieren würde es bei PHP so:
<?
header
("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
?>


Die Lösung die bei mir funktioniert hatte war die folgende: Wenn bei der Url die angefragt wird zusätzlich noch ein Parameter mit einer zufälligen Zahl angehängt wird meint der Browser, dass er die Adresse zum ersten mal aufruft und nimmt so keine Daten aus dem Cache. Das sieht dann so aus:
ajax=new XMLHttp;
var url="rpc.php?param=test&rand="+Math.random();
ajax.start(url,"elem");

Umlaute in UTF8 umwandeln

Bei einem Projekt im Geschäft muss ich die Dateinamen aus einem Ordner in der Datenbank speichern. Dabei hatte ich das Problem, dass Umlaute nicht richtig dargestellt werden. So kamen ü's zum Bsp so raus:
Sehenswürdigkeiten.jpg

Das ganze lässte sich mit der Funktion utf8_decode ganz einfach beheben:
<? $filename=utf8_decode($filename); ?>


So kommt sieht der Filename dann so aus:
Sehenswürdigkeiten.jpg

Artikel im RSS Feeder verzögern

Welcher Blogger kennt das Problem nicht: man scheibt einen neuen Artikel, liest ihn vor dem veröffentlichen nochmals genau durch beseitigt alle Fehler, und stellt den Artikel danach ins Internet. Auf dem Blog noch kurzen eine Kontrolle, ob alles passt und Prompt findet man noch einen Fehler. Soweit so schlecht, den Fehler noch schnell beseitigen bevor es jemand sieht, das Internet ist ja Realtime. Fast; auf der Seite wird der Fehler nicht mehr angezeigt, dafür haben sich die Feedreader schon die alte und fehlerhafte Version gespeichert.
Dieses Problem kann man ganz einfach beheben. Entweder man machte keine Fehler mehr, oder man zeigt die neuen Artikel erst eine gewisse Zeit (zum Bsp 5 Min) später im RSS-Feed an. Dadurch hat man Zeit um die Fehler zu beheben, bevor die Feedreader überhaupt merken, dass es etwas neues gibt. In meinem Fall war das eine kleine Anpassung in der mySQL Abfrage.

Vorher:
<? mysql_query("... articleDate<".time()." ..."); ?>

Nachher:
<? mysql_query("... articleDate<".(time()-(5*60))." ..."); ?>


Nun hoffe, ich dass die Artikel in den Feedreader (und auch im Blog) etwas weniger Fehler haben.

Moblogscript in PHP

Eigentlich wollte ich ja schon seit längerem etwas über mein Moblog-Script schreiben. Eigentlich seit dem Wunsch von Znuk, aber leider ist es bisher immer beim "wollen" geblieben. Da ich nun mein Script etwas verbessert habe, greife ich die Gelegenheit gleich beim Schopf und erkläre die Funktionsweise kurz.

Funktionsweise

In der Theorie sieht das ganze sehr simpel aus. Ich mache auf meinem Handy (Samsung SGH-600E) ein Foto, und sende es per Mail an eine (geheime) Mailadresse. Auf dem Webserver wird nun alle 15 Minuten ein PHP-Script aufgerufen, welches überprüft ob in der Mailbox neue Mails sind, diese dann verarbeitet und schlussendlich einen neuen Beitrag auf meiner Homepage erstellt.

Spammails

Ein Problem an der Sache sind die Spammails. Das Problem kann man aber einfach umgehen, indem man eine Mailadresse nimmt, die nicht so schnell erraten werden kann, diese nirgens publiziert und im PHP-Script noch eine Passwortabfrage einbaut. Ich bin hier auf Nummer sicher gegangen und habe alle beiden Methoden eingesetzt, den schliesslich möchte ich keine Spammails auf meiner Homepage.

Mailaufbau

Damit das ganze mit dem Passwortschutz und der zuordnung zum richtigen Thema auch funktioniert, muss das E-Mail natürlich auch immer gleich aufgebaut werden. Bei mir sieht das nun folgendermassen aus:
passwort
Thema
Ort
Text für die Homepage
Auf der ersten Zeile steht das Passwort, welches auch als erstes geprüft wird, darauf gehe ich aber spätzer genauer ein. Darauf folgt das Thema, in welches der Aktrikel gehört. Zum Bsp. "Bike", "Diverses" usw. Da ich Moblogs ja immer von unterwegs versende ist es interessant von wo das Mail versendet wurde. Mein Handy hat noch keinen GPS Empfänger und so kann ich auch keine Koordinaten angeben, also muss ich mich auf den Ortsnamen beschränken. Aber das reicht im Normalfall bestens aus. Zum Schluss folgt der Text, welcher auf der Homepage stehen soll.
Das Problem, welches ich meistens habe, ist das Tippen auf der kleinen "Tastatur" auf dem Handy, aber für kurze Nachrichten reicht es meist und wirklich wichtig ist ja das Bild.

Das Script

Nun zum wirklich spannenden Teil dieses Beitrags. Das Script ist in PHP geschrieben und wird alle 15 Minuten aufgerufen. Wenn ein neues Mail vorhanden ist wird das Passwort geprüft und der Artikel erstellt, sofern das Passwort natürlich korrekt ist.
Die Verbindung zur Datenbank und das schreiben der Daten in die DB habe ich hier ausgelassen, da dies ja je nach Blogsoftware bzw. Tabellenstruktur variert. Das Script gibt einfach Variabeln mit dem Inhalt zurück und diese können dann nach belieben in die DB geschrieben werden.

Voraussetzungen

Damit das Script richtig funktioniert, bzw die Mails auch lesen und bearbeiten kann müssen die Imap-Funktionen von PHP Installiert sein. Sonst wird es zu kompliziert.

Ablauf

Damit man einen groben Überblick über das Script bekommt fasse ich hier den Ablauf des Scripts etwas zusammen.
  1. Überprüfen ob neue Mails vorhanden sind
  2. Passwort des Mails überprüfen
  3. Headerdaten auslesen (Betreff, Datum & Zeit)
  4. Text auslesen
  5. Attachments auslesen
  6. Text aufteilen (Thema, Ort und Text)
  7. Bilder speichern
  8. (Daten in die Datenbank schreiben)
  9. E-Mail löschen
  10. Verbindungen trennen

Code

Zuerst werden einige Konstanten definiert, die Hostname vom Mailserver, Benutzername, Passwort usw. beinhalten.
<?
// Konfiguration
define("MAIL_HOST","{mail.server.ch:143}");                // Hostname und Port vom Mailserver
define("MAIL_USER","moblog@adresse.ch");                   // Der Benutzername
define("MAIL_PASS","passwort");                            // Passwort vom Mailaccount

define("PASS","passwort");                                 // Das Passwort das im Mail stehen soll 
define("IMAGES_PATH","images/");                           // Pfad zum Bilderordner
?>



Damit die allfälligen Fehler oder Mitteilungen vom Script auch sinnvoll Protokolliert werden, habe ich eine kleine Funktion geschrieben, welche das aktuelle Datum sowie eine Fehlermeldung zurückgibt. Wenn das Script mit einem Cronjob aufgerufen wird, wird diese nachricht automatisch ins Logfile geschrieben.
<?
// kleine funktion die eine fehlermeldung mit dem aktuellen datum ausgibt
function _log($text)
{
    echo 
date("r")."    ".$text."\n";
}
?>


Nun kann auch schon die Verbindung zum Mailserver hergestellt werden. Die angaben zum Hostnamen usw, werden aus den oben definierten Konstanten gelesen. Wenn die Verbindung zum Mailserver nicht hergestellt werden kann wird eine Fehlermeldung ausgegeben und das Script abgebrochen.
<?
// verbindung zum mailserver herstellen
if(!$mail=imap_open(MAIL_HOST,MAIL_USER,MAIL_PASS))
{
    
_log("Keine Verbindung zum Mail-Server");
    exit;
}
?>


Wenn die Verbindung zum Server steht kann überprüft werden, ob neue, ungelesen Mails auf der Server sind. Ist dies nicht der Fall wird das Script abgebrochen.
<?
// ueberpruefen, ob mails vorhanden sind
$headerstrings=imap_headers($mail);
$mails=count($headerstrings);                              // Die Anzahl der neuen Mails

if($mails==0)
{
    echo 
"keine mails";
    exit;
}
_log($mails." neue E-Mails auf dem Server.");              // Nachricht ausgeben
?>


Ist bekannt ob und wieviele neue Mails vorhanden sind, gehts ans verarbeiten der Mails. Mit einer Schleife wird jedes Mail durchgegangen und überprüft.
Aus Übersichtlichkeitsgründen habe ich hier den Code in zwei Funktionen ausgelagert. Zum einen gibt es die Funktion _getData(). Diese Funktion list die Daten aus dem Mail aus und gibt sie schön sortiert in einem Array zurück. Dieser kann so aussehen:
Array
(
    [header] => Array
        (
            [date] => datum als timestamp
            [email] => mailadresse des absenders
            [name] => name des absenders
            [subject] => betreff
        )
    [text] => Inhalt des Mails (mit Passwort, Thema, Ort und Text)
    [attachment] => Array
        (
            [0] => Array
                (
                    [filename] => Dateiname des ersten Anhangs
                    [content] => Inhalt des ersten Anhangs
                )
            [1] => Array
                (
                    [filename] => Dateiname des zweiten Anhangs
                    [content] => Inhalt des zweiten Anhangs
                )
        )
)


Die zweite Funktion heisst _getMetaData() und sie unterteilt den Inhalt der Nachricht, überprüft das Passwort und gibt die verschiedenen Angaben (Thema, Ort und Text) geordnet zurück.

Nun aber erstmal der Code, der die Funktionen aufruft:
<?
// mail fuer mail durchgehen, daten auslesen, passwort check machen, daten in die db schreiben und das mail loeschen
foreach($headerstrings as $headerstring)
{
    
preg_match("/[0-9]/",$headerstring,$number);           // Numer des E-Mails auslesen

    
$mailData=_getData($headerstring,$number);             // Inhalt aus dem Mail auslesen

    
$meta=_getMetaData($mailData['text']);                 // Inhalt aufteilen

    
if($meta===FALSE)                                      // wenn $meta FALSE ist, stimmt das Passwort nicht
    
{
        
log("falsches pw");
        continue;
    }

    
$mailData['text']=$meta[0];                            // Text des E-Mails
    
$mailData['meta']=$meta[1];                            // Meta Daten des E-Mails (Thema und Ort)

    
foreach($mailData['attachment'] AS $attachment)        // alle attachments durchgehen
    
{
        
$filename=$attachment['filename'];                 // dateiname auslesen

        
$fp=fOpen(IMAGES_PATH.$filename,"w+");             // das attachment abspeichern
        
fWrite($fp,$attachment['content']);
        
fClose($fp);
    }

    
imap_delete($mail,$number[0]);                         // Mail l&ouml;schen
}
?>


Nun zu den Funktionen. Als erstes _getData(), die den Inhalt der Mails ausliest und zurückgibt. Hierzu muss ich vieleicht noch sagen, dass jedes Gerät die Mails etwas anders gestaltet und es so zu Problemen bei dieser Funktion kommen könnte. Mit meinem Handy funktioniert es wunderbar, aber bei anderen Geräten muss man vieleicht die eine oder andere Anpassung vornehmen.
<?
// liest die daten aus einem mail aus, verarbeitet sie und gibt sie als array zur&uuml;ck
function _getData($headerstring,$number)
{
    global 
$mail;
    
$mailData=array();                                     // Array, in den schlussendlich die
                                                           // Daten geschrieben werden

    
$header=imap_fetchheader($mail,$number[0]);            // mailheader auslesen

    
preg_match("/Date: (.*)?[\+|-]/",$header,$date);       // datum des mails auselesen
    
$date=htmlEntities($date[1]);
    
$mailData['header']['date']=strToTime($date);

    
$headerinfo=imap_headerinfo($mail,$number[0],256,256); // liest die headerinformationen aus

    
$mailData['header']['email']=$headerinfo->from[0]->mailbox."@".$headerinfo->from[0]->host// mailadresse des absenders auslesen

    
$decode=imap_mime_header_decode($headerinfo->from[0]->personal); // name des absenders auslesen
    
$mailData['header']['name']=$decode[0]->text;

    
$decode=imap_mime_header_decode($headerinfo->fetchsubject); // betreff auslesen
    
$mailData['header']['subject']=$decode[0]->text;


    
// inhalt der mailnachricht auslesen
    
$struct=imap_fetchstructure($mail,$number[0]);
    
    
// mails mit attachments werden immer als multipart, also mail mit verschieden teile versendet
    // sollte eine mail ohne attachment und mit nur als plaintext versendet worden sein, wird nur der text ausgelesen 
    
if(!empty($struct->parts))
    {
        
// durch alle teile gehen
        
for($i=0;$i<count($struct->parts);$i++)
        {
            
$part=$struct->parts[$i];

            
$msg=imap_fetchbody($mail,$number[0],$i+1);    // inhalt des teils

            // wenn der teil ein attachment, bild und jpg ist.
            
if ($part->disposition==ATTACHMENT OR $part->type==TYPEIMAGE OR ($part->type==TYPEAPPLICATION AND $part->subtype!="SMIL"))
            {
                if(
$part->subtype=="JPEG" OR $part->subtype=="JPG" OR $part->subtype=="OCTET-STREAM")
                {
                    
$attachment=array();

                    
// daten in einen array schreiben.
                    
$attachment['filename']=$part->dparameters[0];
                    
$attachment['filename']=$attachment['filename']->value;
                    
$attachment['content']=base64_decode($msg);

                    
$mailData['attachment'][]=$attachment;
                    unSet(
$attachment);
                }
            }

            
// wenn der teil nur text ist
            
elseif(($part->type==TYPETEXT OR $part->type==TYPEMULTIPART OR $part->disposition==INLINE))
            {
                
$mailData['text']=base64_decode($msg);
            }
        }
    }
    else
    {
        
// wenn das mail als plaintext versendet wurde
        
$mailData['text']=imap_body($mail,$number[0]);
    }

    return 
$mailData;                                      // daten zur&uuml;ckgeben
}
?>


Und nun noch zur zweiten Funktion, welche den Text der Nachricht verarbeitet und das Passwort überprüft.
<?
function _getMetaData($text)
{
    
$text=explode("\n",$text);                             // den text aufteilen

    
if(trim($text[0])!=PASS)                               // ueberprufen, ob die erste zeile dem passwort entspricht
    
{
        return 
FALSE;
    }
    unSet(
$text[0]);

    
$metaData['topic']=trim($text[1]);                     // die zweite zeile als thema (topic) abspeichern
    
unSet($text[1]);

    
$metaData['location']=trim($text[2]);                  // die dritte zeile als ort (location) abspeichern
    
unSet($text[2]);

    
$text=implode("\n",$text);                             // den rest zusammenfuegen und als text speichern

    
return array($text,$metaData);                         // den array zurueckgeben
}
?>


Zum Schluss müssen die gelöschten Mails mit imap_expunge() noch definitiv gelöscht und die Verbindung zum Server getrennt werden.
<? 
// mailbox leeren und verbindung zum server trennen
imap_expunge($mail);
imap_close($mail);
?>


Das ist auch schon der ganze Zauber. Wie schon gesagt müssen die Daten noch in die DB geschrieben werden und idealerweise generiert man für die Bilder auch noch ein Thumbnail, aber das sollte ja kein grosses Problem darstellen.
Wer sich das Script nicht aus den einzelnen Teilen zusammensetzen will, kann es sich hier herunterladen.