PHP 5 - Ein praktischer Einstieg
2. Auflage

PHP 5 - Ein praktischer Einstieg, 2. Auflage

Von Ulrich Günther
2. Auflage, August 2004
O'Reilly Verlag, ISBN: 3-89721-278-1
www.oreilly.de/catalog/einphp2ger/



TOC PREV NEXT INDEX

Kapitel 4

Kapitel 4

Einstieg in die Programmierung:
Einfache PHP-Skripte

Im vorletzten Kapitel ist uns PHP ja schon einmal begegnet. Deshalb wissen Sie bereits schon, dass ein PHP-Skript eine Datei ist, die auf einem Webserver abgelegt wird und von einem Webbrowser über das Internet durch einen HTTP-Request angefordert werden kann.

Sie erinnern sich vermutlich auch noch daran, dass so ein PHP-Dokument aus HTML mit eingebetteten Spezial-Tags besteht, die PHP-Code enthalten. Vor dem Versand durch den Webserver wird das Dokument vom Server nach diesen Spezial-Tags durchsucht. Der in ihnen enthaltene PHP-Code, d.h. die PHP-Programmanweisungen, wird dann auf der Stelle durch den Webserver ausgeführt. Etwaige Ausgabedaten, die sich aus den PHP-Befehlen in den Tags ergeben, werden an der entsprechenden Stelle anstatt der PHP-Tags in die Datei eingefügt.

Sobald die Datei durch den Server fertig bearbeitet worden ist, wird sie in einer HTTP-Response an den Browser geschickt. Der Browser interpretiert die Antwort entsprechend, z.B. (typischerweise) als HTML-Code einer Webseite.

Ein PHP-Skript zur Datumsausgabe

Sehen wir uns den schon besprochenen Ablauf einmal anhand eines einfachen Beispiels an, das Sie - wie alle anderen Beispiele in diesem Buch auch - online auf dem O'Reilly-Webserver (http://www.oreilly.de/catalog/einphpger/) finden können. Den Code des Beispiels finden Sie in der PHP-Datei datum.php für dieses Kapitel:

Beispiel 4-1
Der Code in datum.php 
<?php
 
    // Dieses einfache PHP-Skript gibt das aktuelle Datum und die Zeit aus.
 

 
        $datumUndZeit = getdate();
 
        $jahr = $datumUndZeit["Jahr"];   
 
        $monat = $datumUndZeit["mon"];   
 
        $tag = $datumUndZeit["mday"];    
 
        $stunden = $datumUndZeit["stunden"]; 
 
        $minuten = $datumUndZeit["Minuten"]; 
 
        $sekunden = $datumUndZeit["Sekunden"]; 
 
?>  
 
<html>
 
    <head>
 
        <title>
 
            Das heutige Datum ist der <?php echo $tag.".".$monat.". 

            anno domini ".$jahr; ?>
 
        </title>
 
    </head>
 
    <body>
 
        <h1>Die momentane Uhrzeit an diesem Webserver ist <?php echo $stunden.

        ":".$minuten.":".$sekunden; ?></h1>
 
    </body>
 
</html>
 

Wenn Sie diese Datei vom Browser aus aufrufen, z.B. indem Sie die entsprechende URL in das Adressenfeld des Browsers eingeben, wird eine Webseite angezeigt, in deren Titel das aktuelle Datum steht. Im Hauptfenster des Browsers wird neben etwas Überschriftentext die aktuelle Zeit angezeigt (siehe Abbildung 4-1).
Abbildung 4-1 Die Ausgabe von datum.php im Browserfenster

Bis die Seite angezeigt wird, passieren die folgenden Dinge: Nachdem der Browser einen HTTP-Request an den angegebenen Server geschickt und ihn um Übersendung des Dokuments datum.php gebeten hat, erkennt der Server an der Dateiendung .php, dass es sich um eine PHP-Datei handelt. Er lädt daraufhin die Datei von seiner Festplatte und lässt sie durch den PHP-Interpreter bearbeiten. Wie bereits erwähnt, werden dabei jeweils die Teile der Datei, die sich zwischen <?php- und ?>-Tags befinden, als Programmcode ausgeführt.

Der erste PHP-Programmteil, der - Zeile für Zeile - ausgeführt wird, besteht (neben den Tags und dem Kommentar) aus den folgenden Kommandos, die jeweils mit einem Semikolon enden:

$datumUndZeit = getdate();
 
$jahr = $datumUndZeit["year"];    
 
$monat = $datumUndZeit["mon"];    
 
$tag = $datumUndZeit["mday"];    
 
$stunden = $datumUndZeit["hours"];    
 
$minuten = $datumUndZeit["minutes"];    
 
$sekunden = $datumUndZeit["seconds"];    
 

Wenn Sie noch nie programmiert haben, sehen die Zeilen oben für Sie vielleicht ein bisschen wie Gleichungen aus dem Mathematikunterricht aus. Wenn Sie Ihre Mathematikstunden noch nicht ganz aus dem Oberstübchen verdrängt haben, entsinnen Sie sich vielleicht, dass es dort von Variablen nur so wimmelte und dass das, was auf der linken Seite des Gleichheitszeichens stand, gleich dem Ausdruck auf der rechten Seite des Gleichheitszeichens war. Sie entsinnen sich wahrscheinlich auch noch, dass eine Variable ein Symbol war, das beliebige Werte annehmen konnte.

Bevor Sie sich jetzt in eine Matheklausur versetzt fühlen, gibt es eine gute Nachricht: Die PHP-Zeilen sehen zwar so aus wie Gleichungen, sind es aber nicht ganz. In diesem Fall handelt es sich nämlich nur um Wertzuweisungsbefehle. Und die sind eigentlich ganz einfach.

Zunächst einmal kann PHP in der Regel die rechte Seite eines Wertzuweisungsbefehls für Sie berechnen bzw. auswerten oder konstruieren. Das Resultat dieser Berechnung, Auswertung oder Konstruktion ist dann ein Wert oder auch eine kompliziertere Datenstruktur.

Links vom Gleichheitszeichen steht normalerweise eine Variable. Eine solche Variable erkennen Sie in einem PHP-Programm daran, dass ihr Name mit einem Dollarzeichen beginnt. Der Rest des Namens ist ziemlich beliebig. Üblicherweise verwendet man für den Namen eine Bezeichnung, die auf den Inhalt der Variablen hinweist. Die ungewöhnliche Groß-/Kleinschreibung in unseren Beispielen folgt dabei einer verbreiteten Konvention (siehe Kasten »Variablen« weiter unten).

Eine Variable kann einen einzelnen Wert oder auch kompliziertere Datenstrukturen aufnehmen und speichern. In einem Wertzuweisungsbefehl wird dieser Variablen der Wert oder die Datenstruktur zugewiesen, die sich aus dem Ausdruck rechts des Gleichheitszeichens ergeben hat.

Die Variable speichert diesen Wert oder die Datenstruktur, bis ihr entweder ein anderer Wert (oder eine andere Datenstruktur) zugewiesen wird oder bis das Skript beendet wird.

In unserem konkreten Fall macht der erste Befehl Folgendes: Er ruft eine so genannte Funktion auf. Hier handelt es sich um die Funktion getdate(), eine der vielen eingebauten Funktionen von PHP. Funktionen werden wir später noch eingehend besprechen. Für den Augenblick sollten Sie sich merken, dass eine Funktion bei der Ausführung immer durch ihren Rückgabewert ersetzt wird.

getdate() gibt als Rückgabewert ein so genanntes assoziatives Array an PHP zurück. Ein solches Array ist ein Beispiel für eine etwas komplexere Datenstruktur. Wir werden sie uns gleich noch näher ansehen. In unserem Wertzuweisungsbefehl speichern wir das Array zunächst aber in der PHP-Variablen $datumUndZeit ab. Damit stellt $datumUndZeit nun selbst ein assoziatives Array dar.

Variablen

Variablen in PHP können Sie leicht erkennen oder auch selbst definieren. Eine Variable fängt immer mit einem Dollarzeichen an, danach folgt ein Buchstabe (keine Umlaute oder ß, bitte!) oder ein Unterstreichungszeichen (Underscore). Dem darf eine beliebige Kombination dieser Zeichen sowie der Ziffern 0 bis 9 folgen. Die folgenden Variablennamen sind also zulässig:
$kiwi $_kiwi $_0 $a
$kiwi2 $a_b_c $a1
Am besten fangen Sie Ihre eigenen Variablennamen mit einem Buchstaben an, zwecks Unterscheidung von Variablen, die durch PHP vordefiniert sind - diese beginnen mit einem Unterstrich nach dem Dollarzeichen.
Apropos Namensgebung bei Variablen: In den Beispieldateien in diesem Buch lernen Sie auch gleich eine weit verbreitete Konvention kennen: die so genannten Camel Caps. Code ist nämlich wesentlich lesbarer, wenn er nicht nur aus Kleinbuchstaben besteht. Bei Camel Caps werden die Anfangsbuchstaben aller im Variablennamen vorkommenden Wörter (ggf. wie hier mit Ausnahme des ersten Worts) großgeschrieben, alle anderen Buchstaben klein. Damit kriegen die Namen »Kamelhöcker« und sind im Code einfacher zu finden. Eine funktionelle Bedeutung hat das allerdings nicht - PHP ist es egal, ob Ihr Variablenname augenfreundlich ist.
Ein weiterer Vorteil: Mit einer Namensgebungskonvention dieser Art ist es meist relativ klar, wie sich die Variable schreibt. Das vermeidet Zeit raubende Fehler, die durch versehentlich ungesetzte »neue« Variablen entstehen können.
Variablen können Sie einfache Werte zuweisen, wobei es egal ist, ob Sie Zahlenwerte oder Strings (Zeichenketten) verwenden:
$summand1 = 3;
und
$summand1 = "3";
sind komplett äquivalent. Aufpassen müssen Sie lediglich, wenn Sie Variablen in mathematischen Ausdrücken verwenden, z.B. in einer Summe:
$summand1 = 3; $summand2 = "4"; $summe = $summand1 + $summand2;
funktioniert ($summe enthält dann den Wert 7), aber
$summand1 = 3; $summand2 = "vier"; $summe = $summand1 + $summand2;
geht garantiert daneben! Beim Aneinanderfügen von Strings mit dem Punkt-Operator geht es aber immer gut:
$ecken = 3;
$hutEckenAnzahl = "Mein Hut, der hat ".$ecken." Ecken!";
speichert Mein Hut, der hat 3 Ecken! in $hutEckenAnzahl ab.

Ein assoziatives Array speichert Datenwerte jeweils unter einem Schlüssel (key) ab. Mit verschiedenen Schlüsseln lassen sich so beliebig viele Werte in einem Array (und damit in einer Variablen) speichern. Bei einem solchen Schlüssel kann es sich um eine beliebige Zeichenkette (String) handeln. In unserem Fall sorgt getdate() beispielsweise dafür, dass wir unter dem Schlüssel hours die Stunden der aktuellen Zeit und unter mday den jeweiligen Tag im Monat finden (also zum Beispiel den 23.). Im Prinzip recht einfach, oder?

Wie aber kommen wir an die Werte in einem assoziativen Array heran? Das sehen wir in den nächsten Zeilen. Der Bequemlichkeit halber speichern wir die für uns interessanten Werte des Arrays dort gleich wieder in einzelnen Variablen ($jahr, $monat usw.) ab. Damit müssen wir später nicht immer auf die assoziative Array-Syntax zurückgreifen.

Die Befehle, die Array-Werte in die Einzelvariablen umkopieren, illustrieren die nötige Array-Syntax, wie z.B. im ersten Befehl:

$jahr = $datumUndZeit["year"];    
 

Hier sehen Sie, wie Sie z.B. auf den Eintrag für das Jahr in $datumUndZeit zurückgreifen können: Sie setzen den Schlüssel (in unserem Fall die Zeichenkette "year") in eckige Klammern direkt hinter den Namen des Arrays. Dieser Ausdruck wird von PHP als dasjenige Element des Arrays ausgewertet, das unter dem entsprechenden Schlüssel abgespeichert wurde.

Ausgabe als zusammengesetzter String

Bisher haben wir also ein Array erzeugt, es umkopiert und einige seiner Elemente in einzelnen Variablen abgespeichert. Das Ganze spielt sich bei der Ausführung aber komplett im Verborgenen ab. Da der erste PHP-Programmschnipsel damit keinerlei Ausgabe produziert hat, fällt er in der Ausgabe an den Browser ersatzlos weg. Im Anschluss an diesen ersten Teil enthält die PHP-Datei erst einmal etwas HTML: ein <html>-Tag, ein <head>-Tag und ein <title>-Tag sowie etwas Text. Diese werden später unverändert an den Browser weitergereicht. Dann treffen wir wieder auf ein <?php-Tag:

<?php echo $tag.".".$monat.". anno domini ".$jahr; ?>
 

Das Tag enthält nur einen einzigen PHP-Befehl, aber der hat es in sich! Der Befehl echo gibt die jeweils folgende Zeichenkette (String) an dieser Stelle an den Browser aus. Ein String kann bestehen aus

  1. einer Variablen; beispielsweise lassen sich $tag, $monat und $jahr jeweils als Strings verstehen.
  2. einer expliziten Zeichenkette, die zur Unterscheidung von anderem PHP-Code durch einzelne oder doppelte (hochgestellte) Anführungszeichen umgeben ist:
  3. ' anno domini '

    ist ein Beispiel, aber

    " anno domini "

    ist genauso erlaubt (siehe oben).
  4. dem Rückgabewert einer Funktion.
  5. einer Kombination aus Strings der drei vorhergehenden Typen, wobei zwei benachbarte Strings jeweils durch einen Punkt »zusammengeklebt« werden.

Dreimal dürfen Sie raten, mit welcher Sorte String wir es hier zu tun haben: natürlich mit einem zusammengesetzen String, der aus den Teil-Strings $tag, ".", $monat, " anno domini " und $jahr besteht.

Die Werte der Variablen sind ja noch aus dem ersten PHP-Programmschnipsel bekannt. Wenn also $tag den Wert 23 hätte, $monat den Wert 2 und $jahr den Wert 2003, würde der obige echo-Befehl an Stelle des PHP-Tags den String 23.2. anno domini 2003 an dieser Stelle in die Datei einfügen.

Wie es weitergeht, können Sie sich wahrscheinlich denken. Als Übung könnten Sie vielleicht probieren, den Code des dritten PHP-Tags in der Datei datum.php zu analysieren. Alles schon bekannt? Na sehen Sie, ist doch gar nicht so schwer!

Was der Browser sieht

Sehen wir uns noch einmal kurz an, was der Server dem Browser nun letztendlich zuschickt:

Beispiel 4-2
Eine typische HTML-Ausgabe von datum.php
<html>
 
    <head>
 
        <title>
 
            Das heutige Datum ist der 
 
            23.2. anno domini 2003
 
        </title>
 
    </head>
 
    <body>
 
        <h1>Die momentane Uhrzeit an diesem Webserver ist
 
            14:22:37
 
        </h1>
 
    </body>
 
</html>
 

Wie Sie sehen, ist nun kein PHP-Code mehr in dieser Ausgabe enthalten. Das ist auch gut so, weil der Browser kein PHP versteht. Der gesamte verbliebene Code ist HTML - für den Browser also ein Kinderspiel.

Wenn Sie das obige Beispiel in der Praxis ausprobieren, werden Sie sehr schnell einen optischen Bug finden: Wenn die Sekunden oder Minuten kleiner als 10 sind, wird jeweils nur eine Stelle ausgegeben. Am Ende des Abschnitts »Formulardatenverarbeitung durch PHP-Skripte« in diesem Kapitel unter der Überschrift »Probieren Sie's selbst!« gibt es eine kleine Übung, mit der Sie dieses Problem beheben können.

PS: Die Ausgabe von formatierten Daten und Zeiten in PHP ist übrigens auch erheblich einfacher möglich: mit der PHP-Funktion date(). So gibt Ihnen z.B. echo date("G:i"); die momentane Uhrzeit in Stunden und Minuten aus. Warum ich Ihnen das erst jetzt erzähle? Ganz einfach: Weil ich Ihnen eine schonende Einführung in Variablen, Arrays und zusammengesetzte Strings geben wollte. Außerdem sollten Sie sich frühzeitig an den Gedanken gewöhnen, dass auch bei einer so umfangreichen Sprache wie PHP die passende Funktion manchmal einfach nicht vorhanden oder aufzufinden ist. Dann ist Selberbasteln angesagt. Und genau dazu will ich Sie ja ermutigen!

Formulare für Benutzereingaben

Der Hauptgrund, aus dem Skriptsprachen wie PHP so beliebt sind, besteht darin, dass ein Webserver mit Hilfe von Skripten auf Eingaben des Browser-Benutzers reagieren kann - zum Beispiel auf eine Bestellung in einem Online-Versandgeschäft. Bevor der Webserver reagieren kann, muss der Benutzer aber erst einmal ein Formular im Browserfenster ausfüllen, dessen Daten dann vom Browser an den Server geschickt werden. Dieses Formular wird im Normalfall durch den Browser beim Webserver angefordert und von diesem geliefert. Obwohl HTML-Formulare eigentlich nicht PHP-spezifisch sind, so sind sie doch hauptsächlich für den Einsatz in Verbindung mit Skriptsprachen wie PHP gedacht.

Sie erinnern sich vielleicht an unser Projekt, das sich wie ein roter Faden durch dieses Buch zieht. Die Hauptseite dieser Website kennen Sie bereits. Wenn Sie aber dem Link »Ich will spenden« folgen, erwarten Sie, auf einer Seite zu landen, auf der Sie Ihre persönlichen Daten und die Einzelheiten zur Spende eingeben können - bei einem Formular also.

Sehen wir uns dieses Spendenformular namens spendenFormular.php in Abbildung 4-2 einmal an.
Abbildung 4-2 Das Spendenformular in unserem Browser

Hier sehen Sie eine ganze Anzahl von Formularelementen, von denen Ihnen die meisten bereits aus anderen Internetseiten bekannt vorkommen dürften: ein einzeiliges Texteingabefeld (für den Namen), ein mehrzeiliges Eingabefeld für die Adresse, eine Auswahlliste für die Höhe der Spende, ein Dateiauswahlfeld, um ein Foto des edlen Spenders beifügen zu können, drei Radio-Buttons zur Auswahl des Kreditkartentyps sowie zwei Texteingaben zur Angabe von Kreditkartennummer und -ablaufdatum. Schließlich bitten wir mit einer Checkbox um Erlaubnis, den Namen des Spenders veröffentlichen zu dürfen.

Wenn Sie das Formular in Ihren Browser laden, werden Sie feststellen, dass wir diese Genehmigung frecherweise schon einmal vorweggenommen haben - lichtscheue Spender müssen sie also quasi aktiv widerrufen. Um die Spende auch abschicken zu können, gibt es einen Submit-Button mit der Aufschrift »Spende abschicken!«.

Der Code unseres Spendenformulars

So weit, so gut. Sehen wir uns einmal an, wie die Seite mittels PHP erzeugt wird und was sich sonst noch »unter der Motorhaube« verbirgt:

Beispiel 4-3
Der Code in spendenFormular.php 
<html>
 
    <head>
 
        <title>Spendenformular f&uuml;r bedrohte V&ouml;gel</title>
 
    </head>
 
    <body>
 
        <form name="spende" action="spende.php" method="post" 
 
            enctype="multipart/form-data">
 
                <input type="hidden" name="ausgabeZeit" 
 
                value="<?php echo time();?>">
 
            <h1>Spendenformular</h1>
 
            Wir freuen uns, dass Sie spenden m&ouml;chten! Bitte
 
            geben Sie Ihren Namen, Ihre Adresse, die H&ouml;he der
 
            Spende und Ihre Kreditkartendaten ein. Dr&uuml;cken Sie
 
            dann auf "Spende abschicken!"</p>
 
            <p>
 
            <b>Name:</b> <input type="text" name="spenderName" size="80"></p>
 
            <p><b>Adresse:</b><br> 
 
            <textarea name="adresse" rows="4" cols="40" align="top"></textarea></p>
 
            <p><b>H&ouml;he der Spende:</b> 
 
            <select name="betrag">
 
                <?php 
 
                    for($i = 5; $i < 101; $i = $i + 5) {
 
                        echo "<option value=\"".$i."\">".$i."&euro;\n";
 
                    }   
 
                ?>
 
            </select>
 
            &nbsp;
 
            <b>Ihr Foto</b> (optional): <input name="spenderFoto" type="file"></p>
 
            <p><b>Kreditkartentyp:</b> 
 
                <input type="radio" name="kartenTyp" value="Visa">Visa
 
                &nbsp;
 
                <input type="radio" name="kartenTyp" value="Mastercard">Mastercard
 
            </p>
 
            <p><b>Kreditkartennummer:</b>            
 
            <input type="text" name="kreditKarte" size="20" maxlength="20"> 
 
            &nbsp;
 
            <b>Verfallsdatum der Kreditkarte:</b> 
 
            <input type="text" name="ablaufDatum" size="4" maxlength="4"></p> 
 
            <p>
 
            <b>Klicken Sie hier, wenn wir Ihren Namen 
 
            ver&ouml;ffentlichen d&uuml;rfen:</b> 
 
            <input type="checkbox" name="oeffentlich" checked></p> 
 
            <p><input type="submit" value="Spende abschicken!"></p>
 
        </form>
 
    </body>
 
</html>
 

Der Kopf der Datei birgt keine großen Überraschungen. Bis zum <body>-Tag dürfte Ihnen eigentlich alles bekannt vorkommen. Nach dem <body>-Tag fängt auch gleich unser Formular an.

Wie das Formular zum Formular wird: das <form>-Tag

Um ein Formular zu einem Formular werden zu lassen, müssen sämtliche Eingabeelemente des Formulars von einem <form>-Tag umgeben sein. Für jedes Formular darf es nur ein solches Tag geben, das auch andere Elemente der Seite (hier z.B. die Überschrift und den erklärenden Text) enthalten darf. Das muss aber nicht so sein: Wir hätten hier die Überschrift im <h1>-Tag auch genauso gut aus dem <form>-Tag herausziehen können.

Die Attribute des <form>-Tags geben an, wie das Formular heißt (eine Eigenschaft, die Sie später in Verbindung mit JavaScript in einer Erweiterung des Formulars nutzen können), an welches Skriptdokument die ausgefüllten Formulardaten geschickt werden sollen und ob dies mit einem HTTP-Request vom Typ GET oder POST geschehen soll. Weil wir hier unter Umständen auch eine Datei (das Foto des Spenders) zum Server heraufladen wollen, muss außerdem das enctype-Attribut gesetzt werden, und zwar zu multipart/form-data.

Versteckte Eingabefelder

Als nächstes Formularelement begegnet uns ein etwas seltsames Konstrukt, das wir bisher noch nicht gesehen haben:

<input type="hidden" name="ausgabeZeit" 
 
value="<?php echo time();?>">
 

Kein Grund zur Panik: Der Typ des Eingabeelements <input> ist hidden (versteckt) - das Element soll also absichtlich nicht zu sehen sein. In diesem Fall verwenden wir das Element, um mittels PHP die Formularausgabezeit in das Formular zu schreiben. Dazu nehmen wir die PHP-Funktion time(), die uns die Anzahl der Sekunden angibt, die seit dem 1. Januar 1970, 00:00:00 Uhr GMT, verstrichen sind. Als Inhalt des value-Attributs wird dieser Wert unter dem Namen ausgabezeit an den Server zurückgegeben, wenn das Formular ausgefüllt an den Server zurückgeschickt wird.

Wozu das Ganze? Versteckte Eingabefelder wie dieses werden in der Praxis recht häufig eingesetzt. Sie ermöglichen dem Server, Daten auf dem Browser zwischenzuspeichern, während das Formular vom Benutzer ausgefüllt wird, und sie beim Einreichen des Formulars wieder zurückzubekommen. Damit ist es beispielsweise möglich, komplizierte Eingabevorgänge über mehrere Formulare zu erstrecken und jedem neuen Formular die Eingabedaten der jeweils vorhergehenden Formulare in versteckter Form wieder beizufügen.

In anderen Fällen lassen sich versteckte Felder zur Identifizierung des Benutzers verwenden, was zur Authentifizierung und/oder zur Untersuchung von Benutzerverhalten verwendet werden kann. In unserem Fall könnte uns z.B. interessieren, wie lange ein Benutzer typischerweise braucht, um unser Formular auszufüllen. Vielleicht interessiert uns in diesem Zusammenhang ob Spender größerer Beträge länger brauchen, um sich von ihren Euros zu trennen. Wir könnten den Zeitstempel aber auch dazu verwenden, bei Software-Upgrades eventuell veraltete Formulare anders zu behandeln.

Die Benutzung versteckter Eingabefelder zur Benutzeridentifizierung birgt allerdings gewisse Sicherheitsrisiken - wir werden das in Kapitel 7 noch näher besprechen.

Hier dient das versteckte Element erst einmal dazu, Ihnen zu zeigen, was ein verstecktes Eingabeelement ist: im Prinzip ein Texteingabeelement, dessen Wert bereits gesetzt ist. Wenn die Daten des Elements (Name und Wert/Value) an den Server zurückgeschickt werden, sind sie dort von denen eines gewöhnlichen Texteingabeelements nicht mehr zu unterscheiden.

Texteingabefelder

Ein solches gewöhnliches Texteingabefeld ist das nächste Eingabeelement, das uns erwartet:

<input type="text" name="spenderName" size="80">
 

Da der Benutzer hier den Wert durch Tastatureingabe selbst setzen soll, ist kein value-Attribut angegeben (obwohl dies zur Angabe eines Default-Texts verwendet werden kann). Dafür haben wir die Größe des Texteingabefelds in Zeichen über das size-Attribut angegeben. Beachten Sie, dass das size-Attribut nur kontrolliert, wie groß das Eingabefeld im Browser dargestellt wird. Sie können hier auch einen Spendernamen eingeben, der über 80 Zeichen lang ist.

Für mehrzeilige Texteingaben empfiehlt sich eine <textarea>. Dieses Eingabeelement unterscheidet sich etwas von den üblichen <input>-Tags:

<textarea name="adresse" rows="4" cols="40"></textarea>
 

Wie ein <input>-Tag verfügt auch die Textarea über einen Namen, der zusammen mit dem in der Textarea eingegebenen Text an den Server übergeben wird. Die Größe wird allerdings über die Attribute rows (Zeilen) und cols (Spalten) eingestellt. Ein value-Attribut wird nicht verwendet, Default-Texte können stattdessen zwischen das öffnende <textarea>-Tag und das abschließende </textarea>-Tag geschrieben werden, z.B. so:

<textarea name="adresse" rows="4" cols="40">
 
Geben Sie hier Ihre Adresse ein    
 
</textarea>
 
<select>-Auswahllisten

Das nächste Eingabeelement des Formulars ist ein <select>-Auswahlelement. In einem statischen HTML-Formular würde es so aussehen:

<select name="betrag">
 
    <option value="5">5&euro;
 
    <option value="10">10&euro;
 
    . . .
 
    <option value="100">100&euro;
 
</select>
 

Hierbei gibt das <select>-Tag den Namen an, unter dem der ausgewählte Wert an den Server übergeben wird. Innerhalb des <select>-Tags finden wir eine Anzahl von <option>-Tags. Das value-Attribut des <option>-Tags gibt den Wert an, der an den Server übergeben wird, falls der Benutzer die entsprechende Option auswählt. Der Text, der unmittelbar auf ein <option>-Tag folgt, ist der Text, der für diese Option in der Auswahlliste angezeigt wird. Soll eine bestimmte Option vorausgewählt werden, kann dies mittels eines selected-Attributs im <option>-Tag geschehen, dem kein Wert zugewiesen wird: <option value="50" selected>.

<option>-Tags in PHP mit for-Schleifen schreiben

In diesem Fall verwenden wir PHP, um uns etwas Schreibarbeit zu ersparen:

Beispiel 4-4
Automatisierte Ausgabe einer Auswahlliste in spendenFormular.php
<select name="betrag">
 
    <?php 
 
        for($i = 5; $i < 101; $i = $i + 5) { echo "<option      
 
            value=\"".$i."\">".$i."&euro;\n";
 
        }      
 
    ?>
 
</select>
 

Die tragende Konstruktion, die uns hier das Leben leichter macht, ist eine so genannte for-Schleife. Eine for-Schleife setzen Programmierer immer dann ein, wenn ein bestimmter Vorgang mehrfach durchgeführt werden soll und die Anzahl der Durchläufe vorbestimmt ist.

In unserem Fall ist der Vorgang die Angabe von Optionen für sämtliche Euro-Beträge von 5 bis 100 Euro als <option>-Tags in Schritten von 5 Euro. Das können wir über die Durchlaufanweisungen der for-Schleife arrangieren. Diese Durchlaufanweisungen befinden sich in der runden Klammer, die unmittelbar auf den for-Befehl folgt: ($i = 5; $i < 101; $i = $i + 5). Sie bestehen aus drei Teilen, die jeweils durch ein Semikolon getrennt sind.

Der erste Teil enthält (gegebenenfalls durch Kommata getrennte) Instruktionen, die auszuführen sind, bevor die Schleife zum ersten Mal durchlaufen wird. Das heißt, der erste Teil legt die Ausgangsbedingungen fest (Initialisierung). In unserem Fall wird eine Variable $i mit dem Wert 5 geladen. $i repräsentiert die Beträge, die wir in die Optionen schreiben wollen. Programmierer nennen $i auch die Schleifenvariable der for-Schleife.

Im zweiten Teil steht eine Bedingung, die am Anfang jedes Schleifendurchlaufs erfüllt sein muss, damit die Schleife abgearbeitet wird. Um sicherzustellen, dass $i nicht größer als 100 wird, verlangen wir, dass $i kleiner als 101 sein muss, um die Schleife nochmals zu durchlaufen. Wenn die Bedingung nicht erfüllt ist, wird die Schleife übersprungen.

Der dritte Teil der Durchlaufanweisungen wird unmittelbar vor dem Ende jedes Schleifendurchlaufs und dem Testen der Bedingung im zweiten Teil ausgeführt. Er besteht aus (gegebenenfalls durch Kommata getrennten) Instruktionen, die meist der Änderung der Index-Variablen dienen. Hier ist das auch so: Der Euro-Betrag in der Index-Variablen $i muss um 5 heraufgesetzt werden. Zu diesem Zweck addieren wir 5 zum gegenwärtigen Wert von $i und speichern das Resultat zurück in die Variable $i.

Die Befehle, die bei jedem Schleifendurchlauf ausgeführt werden sollen, kann man auch im dritten Teil innerhalb der runden Klammern unterbringen. Es ist aber üblicher, sie separat in geschweifte Klammern zu schreiben, die unmittelbar auf die runden Klammern mit den Durchlaufanweisungen folgen. Hier ist nur ein einziger Befehl auszuführen, der sich allerdings über zwei Zeilen erstreckt - das ist möglich, weil PHP-Strings sich über mehr als eine Zeile ausdehnen dürfen:

echo "<option 
 
         value=\"".$i."\">".$i."&euro;\n";
 

Die meisten Teile dieses echo-Kommandos können Sie sich vermutlich erklären. Der auszugebende String ist aus vier Strings zusammengesetzt:

"<option value=\""
 
$i
 
"\">"
 
$i
 
"&euro;\n"
 

Im ersten und dritten String könnte Ihnen das Konstrukt \" unbekannt vorkommen. Dabei handelt es sich um eine Backslash-Escape-Sequenz. Eigentlich ist es ganz einfach: Weil ein doppeltes Anführungszeichen das Ende des Strings markieren würde, können wir es nicht so einfach in den String hineinschreiben. Indem wir einen Backslash (Rückwärtsstrich) voranstellen, können wir PHP mitteilen, dass dieses Anführungszeichen zum eigentlichen String gehört.

Der zweite und der vierte Strings sind jeweils Kopien der Variablen $i. Beachten Sie, dass wir in den Durchlaufanweisungen der for-Schleife zunächst einen numerischen Wert in $i gespeichert haben, ihn nun aber als String verwenden. Dieser nahtlose Übergang zwischen numerischen Werten und ihren entsprechenden Zeichenketten-Repräsentationen ist eine der großen Stärken von PHP.

Im fünften String finden sich zwei Konstrukte, die einer Erklärung bedürfen. &euro; ist einfach der HTML-Code für das Euro-Symbol. \n fügt einen Zeilenumbruch am Ende des Strings ein. Das ist zwar für die Funktionalität nicht zwingend erforderlich, macht aber die Fehlersuche im HTML-Quelltext erheblich einfacher, wenn Sie sich im Browser den »Seitenquelltext anzeigen« lassen: 20 Optionen in einer Zeile wären vermutlich etwas schwer zu lesen.

Dateieingabeelemente

Auf das <select>-Element folgt das Eingabeelement, das dem Benutzer den Upload seines Konterfeis ermöglicht. Hier gibt es eigentlich nichts Besonderes zu entdecken:

<input name="spenderFoto" type="file">
 

Halt! Eine Besonderheit gibt es doch. Wie bereits erwähnt, müssen Sie bei Formularen, die Datei-Uploads ermöglichen sollen, das enctype-Attribut im <form>-Tag auf multipart/form-data setzen. Wenn Ihr Formular dann eingereicht wird, ist der Umgang mit der Fotodatei auf der Serverseite ebenfalls etwas komplizierter als mit einfachen Eingabedaten. Aber dazu kommen wir noch ausführlich im nächsten Abschnitt dieses Kapitels.

Auswahl mit Radio-Buttons

Als Nächstes gilt es, den Typ der Kreditkarte festzulegen, über den die Spende abgerechnet werden soll. Das könnte man mit einem <select>-Tag machen, so wie beim Spendenbetrag auch. Eine Alternative zum <select>-Tag sind die hier verwendeten Radio-Buttons:

Beispiel 4-5
Radio-Buttons in spendenFormular.php
<p><b>Kreditkartentyp:</b> 
 
    <input type="radio" name="kartenTyp" value="Visa">Visa
 
    &nbsp;
 
    <input type="radio" name="kartenTyp" value="Mastercard">Mastercard
 
    &nbsp;
 
    <input type="radio" name="kartenTyp" value="American Express">American Express
 
</p>
 

Radio-Buttons sind nach den Tasten benannt, die Sie vielleicht noch von alten Radios her kennen: Wenn Sie eine Taste in einer Gruppe drücken, springt jede bereits gedrückte Taste in der Gruppe wieder in ihre Ausgangsposition zurück. Das ist bei Radio-Buttons genauso: Wird ein Button angeklickt, verschwinden die Markierungen aller anderen Buttons in derselben Gruppe.

Jeder Radio-Button in einer Gruppe ist ein eigenes Eingabeelement und hat damit sein eigenes <input>-Tag. Die Gruppierung erfolgt über das name-Attribut des Tags: Buttons mit demselben Namen gehören zur selben Gruppe. In unserem Fall haben wir also drei Buttons, die alle zu der Gruppe kartenTyp gehören. Über das value-Attribut wird festgelegt, welcher Wert mit dem ausgefüllten Formular unter dem Namen kartenTyp an den Server übergeben wird, wenn der entsprechende Button angeklickt ist. Die &nbsp;-Zeichen (Leerzeichen ohne Zeilenumbruch im Browser) sorgen dafür, dass ein gewisser Mindestabstand zwischen den Optionen eingehalten wird.

Das nächste Eingabeelement in unserem Formular, also das Feld für die Kreditkartennummer, dürfte Ihnen weitgehend bekannt vorkommen. Neu ist hier lediglich das maxlength-Attribut, das die maximale Anzahl Zeichen festlegt, die der Benutzer über den Browser in dieses Feld eingeben kann. Dies ist aber lediglich eine Anweisung an den Browser - in dem Skript, das das Formular letztendlich verarbeitet, dürfen Sie sich auf keinen Fall darauf verlassen, dass unter dem Namen kreditkarte auch tatsächlich ein String mit maximal 20 Zeichen zurückgeliefert wird. Ähnliches gilt auch für das Eingabeelement für das Ablaufdatum der Kreditkarte.

Checkboxen - Sein oder Nichtsein?

Das Kästchen zum Anklicken in der Zeile darunter, das über die Veröffentlichung des Spendernamens entscheidet, ist eine so genannte Checkbox, auch Kontrollkästchen genannt:

<input type="checkbox" name="oeffentlich" checked> 
 

Checkboxen stehen für sich und werden nicht wie Radio-Buttons gruppiert. Wie ein Radio-Button kann eine Checkbox mittels value-Attribut dem Server unter dem angegebenen Namen einen Wert übertragen, wenn sie angeklickt ist. Ist die Checkbox nicht angeklickt, wird kein Wert übergeben. Das value-Attribut kann auch weggelassen werden - der Browser überträgt dann den String on bei angeklickter Checkbox. Das checked-Attribut sorgt dafür, dass die Checkbox beim Laden des Formulars zunächst angeklickt erscheint.

Als letztes Element im Formular verbleibt der Submit-Button - die Schaltfläche, auf die der Benutzer klicken soll, um das Formular an den Server zur Bearbeitung zu schicken:

<input type="submit" value="Spende abschicken!">
 

Das value-Attribut kontrolliert in diesem Fall die Beschriftung des Buttons.

So, damit kennen Sie nun schon fast alle wichtigen Eingabeelemente, die HTML zur Verfügung stellt. In Tabelle 4-1 sind sie noch einmal zusammengefasst:

Tabelle 4-1
Wichtige HTML-Tags zur Formulareingabe 
Tag Wichtige Attribute Bedeutung Anmerkungen
<input type="text">
name, id, value, size, maxlength
Texteingabefeld, einzeilig  
<input type="hidden">
name, id, value
Verstecktes Eingabefeld Wert wird über value-Attribut vom Server oder über JavaScript vom Browser gesetzt.
<textarea>
name, id, rows, cols
Textfeld Default-Wert wird zwischen öffnendes und abschließendes Tag gesetzt.
<select>
name, id
Auswahlliste Enthält <option>-Tags mit Optionen.
<option>
value, selected
Option in Auswahlliste Angezeigter Wert wird hinter das Tag geschrieben.
<input type="file">
name, id
Dateiauswahlfeld für Uploads Default-Wert kann nicht gesetzt werden; <form>-Tag braucht enctype="multipart/form-data".
<input type="radio">
name, id, value, checked
Radio-Button Radio-Buttons gruppieren sich über das gemeinsame name-Attribut.
<input type="checkbox">
name, id, value, checked
Checkbox Bei unmarkierter Checkbox wird nichts zum Server übertragen.
<input type="submit">
value
Button zum Absenden des Formulars value-Attribut kontrolliert die Beschriftung.

Damit können wir nun Daten an unseren Server schicken. Sehen wir uns also an, wie der Server auf die Eingabedaten reagieren kann.

Probieren Sie's selbst!

Mit den oben besprochenen HTML-Formularelementen können Sie unser Formular etwas erweitern. Wie wäre es, wenn Sie noch ein Textfeld für den Namen hinzufügen, der auf der Kreditkarte angegeben ist? Außerdem könnten wir Spender zu Mehrfachspenden animieren, wenn wir eine Auswahlliste hätten, auf der der edle Spender auswählen kann, wie häufig gespendet werden soll: einmal (alle 0 Tage), wöchentlich (alle 7 Tage), monatlich (alle 30 Tage), vierteljährlich (alle 90 Tage) oder jährlich (alle 365 Tage). Die Lösung finden Sie in der Datei spendenFormularErweitert.php in den Online-Beispielen zu diesem Kapitel.

Formulardatenverarbeitung durch PHP-Skripte

Sobald der Benutzer Ihr Formular ausgefüllt und auf den Submit-Button (oder wie immer Sie ihn beschriftet haben) geklickt hat, werden die Formulardaten an das im action-Attribut des <form>-Tags angegebene Skript geschickt. Der Wert des action-Attributs wird vom Browser als URL (Webadresse) interpretiert. Falls weder Protokoll noch Server angegeben ist, so wie in unserem Fall, geht der Browser davon aus, dass HTTP zu verwenden ist und dass das Skript auf demselben Server liegt wie das Dokument, das das Formular enthält.

Fremdformulare - Vorsicht, Falle!

Durch die Verwendung des URL-Formats im action-Attribut ergibt sich, dass es auch möglich ist, die Daten an ein Skript zu schicken, das auf einem anderen Server liegt. Das kann unter Umständen ganz erwünscht sein - beispielsweise können Sie auf Ihrer Homepage ein Eingabeformular für die Suchfunktion einer bekannten Suchmaschine unterbringen, das nach dem Ausfüllen an diese geschickt wird. Als PHP-Autor (oder ASP-, JSP-, CGI-Autor usw.) ergibt sich für Sie damit allerdings auch ein Problem, das oft übersehen wird: Wer garantiert Ihnen eigentlich, dass die Daten, die Ihr Skript bekommt, auch tatsächlich von dem Formular stammen, das Sie für diesen Zweck geschrieben haben? Die Antwort: niemand!

Wieso ist das ein Problem? Nun, PHP-Programmierer für Vogelschutz haben oft zunächst einmal den kreuzbraven Konfirmanden von nebenan im Sinn, der einen Teil seines Taschengelds zur Erhaltung eines Reservats spenden will. Der kriminelle Hacker, der an die Kreditkartendaten der Spender gelangen oder gar unsere Server-Maschine als Angriffsplattform in einer verteilten Denial-of-Service-Attacke auf andere Server nutzen will, ist als Zielgruppe vermutlich gedanklich nicht präsent. Da im Internet aber beide Spezies in ausreichender Zahl vorhanden sind und sich zumindest im Prinzip beide für Ihr Skript interessieren, ist Vorsicht angebracht.

Ein Skript, das absichtlich mit falschen Daten gefüttert wird, kann unter Umständen:

Dieses Problem ist übrigens nicht PHP-spezifisch, es betrifft alle Web-Technologien, die an der Schnittstelle zwischen benutzervorgegebenen Daten und der Intimsphäre des Servers sitzen, also auch CGI, Active Server Pages, Java Server Pages, mod_perl, Servlets usw.

Entsprechend groß ist die Anzahl der Server, die durch Sicherheitslücken in Skripten »geknackt« worden sind. Viele dieser Angriffe gehen auf das Konto von CGI-Skripten mit bekannten Sicherheitsproblemen, die z.T. in den Distributionen von bekannten Webservern enthalten waren. Es bedarf keines hochbegabten Hackers, um Internetadressen und Webserver nach dem Vorhandensein dieser (oft von den eigentlichen Benutzern nicht verwendeten) Skripte abzusuchen. Ähnlich verhält es sich mit einer ganzen Reihe von Skripten, die kostenlos zum Download angeboten werden.

Wie kommt es zu solchen Sicherheitslücken, die auch »alte Hasen« oft nicht erkennen? Dafür gibt es hauptsächlich zwei Gründe: Erstens, Programmierer sind traditionell darauf aus, etwas »zum Laufen zu bekommen«. Deshalb achten sie bevorzugt darauf, dass ein Programm das macht, was es soll. Der Aspekt, dass ein Programm nicht machen darf, was es nicht machen sollte, ist meist eher zweitrangig (die wenigsten Programmierer werden dafür bezahlt, dass ihr Programm etwas nicht macht). Der zweite Grund besteht in der hohen Komplexität moderner Programmiersprachen: Manche Befehle können sich unter »unvorhergesehenen« Eingabebedingungen komplett anders verhalten, als ein Programmierer dies beabsichtigt.

Diese Warnung sollte Ihnen aber jetzt keinesfalls den Mut nehmen, selbst Hand an die PHP-Programmierung zu legen. Im Gegenteil: Nun, da Ihre Aufmerksamkeit geweckt ist, werden wir an den entsprechenden Stellen sicherheitsrelevante Fragen stellen und auf Gefahren und ihre Vermeidung aufmerksam machen. Das sollte Ihren Blick genügend schärfen, um typische Anfängerfehler vermeiden zu können und Ihre Skripte ausreichend sicher zu halten.

Apropos Anfängerfehler: Eine Sammlung häufig wiederkehrender Sicherheitsfragen rund um Server und Skripte finden Sie in der WWW-Security-FAQ von Lincoln Stein und John Stewart unter http://www.w3.org/Security/faq/www-security-faq.html.

Datenübergabe an das Skript und Überprüfung der Daten

Ihr Benutzer hat also Ihr Formular ausgefüllt und schickt es von seinem Browser an Ihren Server. Idealerweise sollten jetzt zwei Dinge passieren: Die Daten sollten in einer geeigneten Datenbank abgelegt werden, und dem Browser des Benutzers sollte eine Bestätigung der Eingabe zugesandt werden.

Egal ob Sie Formulardaten in einer Datenbank speichern wollen oder ob Sie sie nur zur Erzeugung einer Webseite benötigen: Sie müssen diese Daten in Ihrem Skript zur Verfügung haben. Wie Sie die Daten in Ihre Datenbank bekommen, besprechen wir in Kapitel 6. In diesem Abschnitt sehen wir uns zunächst an, wie Sie in Ihrem Skript auf die vom Browser gesendeten Daten zugreifen können, wie Sie diese auf Plausibilität prüfen und wie Sie dem Benutzer Rückmeldung über seine Eingaben bzw. Fehleingaben geben können. In Abbildung 4-3 sehen Sie, wie eine Rückmeldung unseres Skripts an den (fiktive) Benutzer des Spendenformulars aussieht.
Abbildung 4-3 Die von spende.php versandte Spendenbestätigung

Damit ist es jetzt höchste Zeit, uns den Skriptcode in spende.php anzusehen:

Beispiel 4-6
Der Code von spende.php 
<?php
 

 
    // Dieses Skript empfängt die Daten des Spendenformulars.
 
    // Die Daten werden überprüft und dem Benutzer zur Bestätigung angezeigt.
 

 
    $hack = false;        // Diese Boolsche Variable zeigt an,
 
                          // ob ein Benutzer inkorrekte Daten eingegeben hat
 
                          // - das könnte sogar ein Hacking-Versuch sein.
 

 
    // Daten der Bequemlichkeit wegen aus $_POST auslesen:
 

 
    $spenderName = $_POST["spenderName"];
 
    $adresse = $_POST["adresse"];
 
    $betrag = $_POST["betrag"];
 
    $kartenTyp = $_POST["kartenTyp"];
 
    $kreditKarte = $_POST["kreditKarte"];
 
    $ablaufDatum = $_POST["ablaufDatum"];
 
    $oeffentlich = $_POST["oeffentlich"];
 
    $ausgabeZeit = $_POST["ausgabeZeit"];
 

 
    // Überprüfung der Daten:
 
    // $spenderName kann ein beliebiger String sein, aber er sollte nicht leer sein!
 
    if ($spenderName == "") {
 
        $hack = true; $feld = "Name";
 
    }
 
    // $adresse kann auch ein beliebiger nicht-leerer String sein:
 
    if ($adresse == "") {
 
        $hack = true; $feld = "Adresse";
 
    }
 
    // Der Betrag muss eine ganze Zahl zwischen 5 und 100 sein:
 
    if (!preg_match("/^\d*[05]$/", $betrag)) { // nicht durch 5 teilbar?
 
        $hack = true; $feld = "Betrag";
 
    }
 
    if (($betrag < 5) || ($betrag > 100)) {
 
        $hack = true; $feld = "Betrag";
 
    }
 
    // Der Kartentyp sollte Visa oder Mastercard sein:
 
    switch ($kartenTyp) {
 
        case "Visa": break;
 
        case "Mastercard": break;
 
        default:
 
            $hack = true; $feld = "Kartentyp";
 
    }
 
    // Die Kartennummer sollte aus 15 oder 16 Ziffern bestehen,
 
    // die durch nichts, ein Leerzeichen oder einen Bindestrich getrennt sind:
 
    if (!preg_match("/^(\d[\s\-]?){15,16}$/", $kreditKarte)) {
 
        $hack = true; $feld = "Kartennummer";
 
    }
 
    // Das Ablaufdatum sollte aus vier Ziffern bestehen, wobei die ersten beiden
 
    // zwischen 01 und 12 liegen sollten, die dritte (noch) eine 0 sein sollte
 
    // und die vierte jetzt zwischen 4 und 9 liegen sollte (es sei denn, die Karte
 
    // läuft nach 2009 ab, was im Moment noch unwahrscheinlich ist):
 
    if (!preg_match("/^(\d{2})0[4-9]$/", $ablaufDatum, $match)) {
 
        $hack = true; $feld = "Kreditkarten-Ablaufdatum";
 
    }
 
    else
 
    {
 
        if (($match[1] < 1) || ($match[1] > 12)) {
 
            $hack = true; $feld = "Kreditkarten-Ablaufdatum";
 
        }
 
    }
 
    // Die Checkbox ist entweder leer, "on", oder jemand versucht zu hacken:
 
    if (($oeffentlich != "") && ($oeffentlich != "on")) {
 
        $hack = true; $feld = "&Ouml;ffentliche Spende";
 
    }
 
    // Die Formularausgabezeit muss eine ganze Zahl sein:
 
    if (!preg_match("/^\d+$/", $ausgabeZeit)) {
 
        $hack = true; $feld = "Formularausgabezeit";
 
    }
 
    // Wurde ein Foto geschickt?
 
    if ($_FILES["spenderFoto"]["size"] > 0) {
 
        $foto = true;
 
        preg_match("/(\.\w+)$/",
 
            $_FILES["spenderFoto"]["name"],$match);
 
        $typ = $match[1];
 
        // Wir erlauben nur Erweiterungen von Bilddateien, damit die Leute
 
        // stattdessen keine PHP-Skripte einschmuggeln können.
 
        if (in_array(
 
                strtolower($typ),
 
                array(".gif",".bmp",".jpg",".png",".jpeg"))) {
 
            $dateiName = uniqid("").$typ;
 
            $bildPfad =
 
                preg_replace("/\/[^\/]+$/","",
 
                             $_SERVER["SCRIPT_FILENAME"])
 
                             ."/Bilder/";
 
            copy($_FILES["spenderFoto"]["tmp_name"],
 
                $bildPfad.$dateiName);
 
        }
 
    }
 
    else
 
    {
 
        $foto = false;
 
    }
 

 
    // Bei inkorrekten Eingaben eine Fehlermeldung anzeigen:
 
    if ($hack) { ?>
 

 
        <html>
 
             <body>
 
                  <h1>Eingabefehler</h1>
 
                  Ihre Eingabe im Feld <b><?php echo $feld; ?></b>
 
                  war inkorrekt.
 
             </body>
 
        </html>
 

 
    <?php
 
        exit(); // Skript beenden!
 
    }
 

 
    // An diesem Punkt fügen wir später den Code ein, der die
 
    // überprüften Daten in die Datenbank schreibt.
 

 
    // Daten zur Bestätigung an den Benutzer ausgeben:
 
    ?>
 
    <html>
 
        <body>
 
            <h1>Liebe(r) <?php echo $spenderName; ?></h1>
 
            <p>Haben Sie vielen Dank f&uuml;r Ihre Spende &uuml;ber 
 
            <?php echo $betrag; ?>&euro;.
 
            Eine Spendenquittung schicken wir an Ihre Adresse:</p>
 
            <p><b><?php
 
                echo stripslashes(preg_replace("/\r?\n/","<br>", $adresse));
 
            ?></b></p>
 
            <p>Die Spende wird von Ihrer 
 
            <b><?php echo $kartenTyp; ?></b>-Kreditkarte mit Nummer:</p>
 
            <p><b><?php echo $kreditKarte; ?></b> 
 
            g&uuml;ltig bis <b><?php echo $ablaufDatum; ?></b>
 
            abgebucht.</p>
 
            <?php
 
            if ($foto) { ?>
 
                <p>Ihr Foto sehen Sie hier:<br>
 
                <img src="<?php echo "Bilder/".$dateiName; ?>"></p>
 
            <?php
 
            } 
 
            ?>
 
            <p>Wir nehmen zur Kenntnis, dass wir Ihren Namen 
 
            <?php
 
                if ($oeffentlich == "") {
 
                    echo "nicht";
 
                }
 
            ?>
 
            ver&ouml;ffentlichen d&uuml;rfen.</p>
 
            <p>Sie haben <?php echo (time() - $ausgabeZeit); ?>
 
            Sekunden zum Ausf&uuml;llen unseres Formulars gebraucht</p>.
 
        </body>
 
    </html>
 

Wie Sie sehen können, ist dieser Code ziemlich umfangreich, deswegen werden wir ihn uns Schritt für Schritt ansehen. Als erste Amtshandlung definieren wir eine Variable namens $hack, die den Booleschen Wahrheitswert false zugewiesen bekommt. Damit legen wir fest, dass wir zu Beginn der Skriptausführung keinen Hinweis darauf haben, dass eine (absichtliche oder unabsichtliche) Fehleingabe des Benutzers vorliegt. Sobald wir einer solchen Fehleingabe auf die Spur kommen, werden wir die Variable auf true setzen, um anzuzeigen, dass ein Problem vorliegt. Wenn die Variable nach Überprüfung aller Formulardaten immer noch auf false steht, wissen wir, dass es kein erkennbares Problem gegeben hat.

Die einfachen Formulardaten, die wir mittels eines HTTP-Requests mit der POST-Methode an das Skript geschickt haben, finden sich in einem globalen assoziativen Array namens $_POST, das PHP automatisch zur Verfügung stellt.

In PHP-Versionen vor 4.10 gibt es $_POST noch nicht. Wenn Sie eine niedrigere Version einsetzen, gibt es ein äquivalentes Array namens $HTTP_POST_VARS, das aber nur bei entsprechender (Default-)Konfiguration (track_vars on in der Konfigurationsdatei php.ini) zur Verfügung steht.

Wir kopieren die Daten zunächst in »handlichere« einfache Variablen um:

    $spenderName = $_POST["spenderName"];
 
    $adresse = $_POST["adresse"];
 
    $betrag = $_POST["betrag"];
 
    $kartenTyp = $_POST["kartenTyp"];
 
    $kreditKarte = $_POST["kreditKarte"];
 
    $ablaufDatum = $_POST["ablaufDatum"];
 
    $oeffentlich = $_POST["oeffentlich"];
 
    $ausgabeZeit = $_POST["ausgabeZeit"];
 

Die Schlüsselwerte für das assoziative Array entsprechen hier den Namen der Eingabeelemente in unserem Formular, die dort jeweils im name-Attribut des entsprechenden Eingabeelements definiert sind.

Sie werden vielleicht bemerkt haben, dass die Daten für die optionale Bilddatei hier nicht auftauchen. Dateien, die an den Server geschickt werden, werden unter PHP etwas anders behandelt, wie wir gleich sehen werden. Zunächst einmal ist es aber gute Praxis, sich die Eingabedaten genauer anzusehen. Das ermöglicht Ihnen erstens, dem Benutzer bei Fehleingaben entsprechend Bescheid geben zu können. Zweitens macht es potenziellen Hackern das Leben etwas schwerer, weil ungewöhnliche Eingaben wahrscheinlich abgelehnt werden.

Überprüfung von Namenseingaben

Die genauen Prüfungen, denen Sie die Eingabedaten unterziehen, hängen in der Regel von Ihren Erwartungen an die Daten ab. In einigen Fällen ist es recht einfach, z.B. weil es so viele unterschiedliche zulässige Eingaben gibt, dass es am besten ist, fast alles zuzulassen. Bei Namen und Adressen beispielsweise können fast alle Zeichen vorkommen. Deshalb ist es oft besser, nur darauf zu bestehen, dass die entsprechende Zeichenkette nicht leer ist, wie hier im Fall des Spendernamens:

if ($spenderName == "") {
 
    $hack = true; $feld = "Name";
 
}
 

Die gleiche Prüfung wird auch für die Adresse ausgeführt. Die hier zur Prüfung verwendeten Konstrukte sind sogenannte if-Statements. In PHP bestehen sie aus dem reservierten Wort if, einer Bedingung in runden Klammern und einer oder mehreren PHP-Anweisungen in geschweiften Klammern. Wenn die Bedingung erfüllt ist, werden die Anweisungen in den geschweiften Klammern ausgeführt, wenn nicht, übergeht PHP diese Anweisungen.

In unserem konkreten Fall bedeutet das, dass die Variable $spenderName (und damit die aus dem Eingabeelement spendername an den Server geschickten Daten) leer sein muss, damit die Bedingung erfüllt ist: $spenderName muss gleich dem leeren String sein, der durch zwei einfache ('') oder zwei doppelte ("") Anführungszeichen ausgedrückt wird. Beachten Sie auch, dass die »Gleichheit« zweier Ausdrücke in PHP durch ein doppeltes Gleichheitszeichen (==) ausgedrückt wird. Dadurch ist eine klare Unterscheidung zu Wertzuweisungsbefehlen gegeben, wie sie z.B. hier in den geschweiften Klammern verwendet werden.

Bedingungen und Vergleichsoperatoren

Jetzt ist uns schon zum zweiten Mal eine Bedingung begegnet - erinnern Sie sich noch an die for-Schleife im letzten Abschnitt, die uns die <option>-Tags für den Spendenbetrag an den Browser ausgab? Dort stand in der for-Schleife ebenfalls eine Bedingung, die darüber entschied, ob die for-Schleife ein weiteres Mal durchlaufen wurde oder nicht. Bedingungen können in PHP an allen möglichen Stellen vorkommen, nicht nur in for- oder if-Statements. Höchste Zeit also, dass wir uns ansehen, wie wir eine Bedingung formulieren. Eine Bedingung ist ein Ausdruck, der von PHP entweder als true (wahr) oder als false (falsch) ausgewertet wird. Als false betrachtet PHP außerdem leere Strings, den numerischen Wert 0 sowie leere Arrays. Alle anderen Zahlen, Zeichenketten und Arrays wertet PHP als true.
Da sowohl die Werte true als auch false in PHP-Variablen gespeichert werden können, kann eine Bedingung beispielsweise auch aus einer PHP-Variablen bestehen. In der folgenden Zeile wird z.B. immer "Hallo" ausgegeben:
$x = true; if ($x) { echo "Hallo!"; }
Ebenso können diese Werte der Rückgabewert einer Funktion sein, so dass eine Funktion auch als Bedingung herhalten kann. Ein Beispiel dafür ist preg_match() in diesem Kapitel.
Sehr oft findet man aber noch eine dritte Variante von Bedingungen: Vergleiche. In diesem Fall (wie z.B. bei der Überprüfung des Spendernamens) vergleichen wir einen Ausdruck (hier den übergebenen Spendernamen) mit einem zweiten Ausdruck (in unserem Fall dem leeren String ""). Für den Vergleich benutzen wir den Operator ==. Es gibt aber noch andere Operatoren, die zu solchen Vergleichen verwendet werden können. Einen haben Sie schon in unserem Spendenformular kennen gelernt, den »kleiner als«-Operator < nämlich.
>
Hier sind einige weitere Vergleichsoperatoren zusammengefasst:


Die obigen Operatoren lassen sich sowohl auf numerische als auch auf String-Werte anwenden. Bei einem Ungleich-Operator mag das ja noch auf Anhieb einleuchten, aber was passiert bei den anderen? Die Antwort: Hier werden die Strings in Zahlen umgewandelt, die sich aus den numerischen Codes für ihre Zeichen zusammensetzen. Hört sich kompliziert an? Okay., dann lassen wir die Theorie weg und sehen uns den praktischen Nutzen an: Mit diesen Operatoren können Sie Strings in alphabetischer Reihenfolge sortieren. Ein String $a ist »kleiner« als ein anderer String $b, wenn $a in alphabetischer Reihenfolge vor $b kommt. Dabei kommt es u.a. auch auf Großschreibung an. Die folgenden Ausdrücke sind damit in PHP erstaunlicherweise alle true: "gross" < "klein", "Gross" < "gross", "Tisch" < "Tischchen".

Eingabeüberprüfung bei ganzen Zahlen

Um den Spendenbetrag zu prüfen, müssen wir etwas weiter ausholen. Auch hier verwenden wir if-Statements, aber wir sollten zum einen sicherstellen, dass es sich bei den als String übergebenen Daten tatsächlich um eine ganze Zahl handelt, die durch 5 teilbar ist. Zum anderen müssen wir darauf achten, dass die Zahl zwischen 5 und 100 liegt. Um uns zu vergewissern, dass wir es mit einer durch 5 teilbaren ganzen Zahl zu tun haben, verwenden wir einen so genannten »regulären Ausdruck«:

Beispiel 4-7
Betragsüberprüfung mit einem regulären Ausdruck in spende.php
if (!preg_match("/^\d*[05]$/", $betrag)) {
 
    $hack = true; $feld = "Betrag";
 
}
 

Dieses kryptische Konstrukt sollten wir uns einmal näher ansehen. Die Bedingung des if-Statements lautet: !preg_match("/^\d*[05]$/", $betrag). Das Ausrufezeichen am Anfang ist eine logische Negation. Das heißt, der Gesamtausdruck ist true, wenn der auf das Ausrufezeichen folgende Ausdruck false ist und umgekehrt. preg_match() ist eine in PHP eingebaute Funktion, die Musterabgleiche über Perl-kompatible reguläre Ausdrücke ermöglicht. Die kryptische Zeichenkette, die das erste Argument der Funktion preg_match() darstellt, ist ein solcher regulärer Ausdruck.

Reguläre Ausdrücke werden zum Erkennen von Zeichenfolgemustern in Strings verwendet. Hier zum Beispiel wollen wir erkennen, ob der an $betrag übergebene String ausschließlich aus Ziffern besteht. Wie der Name schon andeutet, folgen reguläre Ausdrücke in preg_...-Funktionen der gleichen Syntax wie in der Programmiersprache Perl (die oft für CGI-Skripte verwendet wird).

Eine Besprechung der gesamten Syntax für reguläre Ausdrücke in Perl geht leider weit über den Rahmen dieses Buchs hinaus. Eine gutes, leicht verdauliches Tutorial in Sachen reguläre Ausdrücke finden Sie in O'Reillys Einführung in Perl von Randal L. Schwartz und Tom Phoenix. Darüber hinaus gibt es zahlreiche Tutorials im Internet. Wenn Sie sich ernsthaft mit Web-Programmierung beschäftigen wollen, lohnt es sich, sich an reguläre Ausdrücke zu gewöhnen und die Syntax zu lernen, da Sie damit auch ziemlich komplexe String-Operationen recht einfach durchführen können. Für unsere Zwecke wird es ausreichen, wenn wir uns jeweils die Ausdrücke ansehen, die uns in den Beispielen begegnen - damit bekommen Sie bereits einen recht guten Überblick.

Alle regulären Ausdrücke in Perl (und damit in preg_...-Funktionen) starten und enden mit einem identischen Begrenzungszeichen (Delimiter). In den meisten Fällen verwenden Perl- und PHP-Programmierer dazu einen Vorwärts-Schrägstrich (/) oder eine Raute (#). In unserem Fall wollen wir herausfinden, ob unser String ausschließlich aus Ziffern besteht. Dazu muss das Muster vom String-Anfang an passen, was wir durch ein ^-Zeichen am Anfang des Musters ausdrücken können. Eine beliebige Ziffer kann in einem regulären Ausdruck durch \d repräsentiert werden. Der Asterisk gibt an, dass das vorhergehende Zeichen in beliebig vielen Kopien hintereinander vorkommen kann, aber nicht vorkommen muss. In unserem Fall sind das keine oder mehrere Ziffern. Als nächstes verlangen wir ein Zeichen, das aus der in den eckigen Klammern angegebenen Zeichenmenge kommen muss, d.h. eine 0 oder eine 5. Dieses Zeichen soll gleichzeitig am Ende des Strings stehen. Um das einzufordern, beenden wir den regulären Ausdruck mit einem Dollarzeichen ($). Es besagt, dass sich an dieser Stelle des Musters das Ende des Strings befinden soll.

Damit haben wir sichergestellt, dass wir einen String haben, der ausschließlich aus Ziffern besteht und dessen numerischer Wert durch 5 teilbar ist. Wer garantiert uns aber, dass der Betrag, der uns hier untergeschoben wird, nicht verdächtige 100.000 Euro oder auch 0 Euro ist? Das regeln wir wiederum mit einem if-Statement:

if (($betrag < 5) || ($betrag > 100)) {
 
   $hack = true; $feld = "Betrag";
 
}
 

Hier teilt sich die Bedingung in zwei Unterbedingungen auf. Wenn eine der beiden Unterbedingungen zutrifft, sollte die Gesamtbedingung auch true sein. Das erreichen wir über eine ODER-Verknüpfung, die in PHP durch zwei senkrechte Striche (||) dargestellt wird. Wenn $hack nach diesem if-Statement immer noch false ist, entspricht der String in $betrag einer der Optionen in unserer <select>-Auswahlliste.

Überprüfung von Mehrfachoptionen

Inzwischen dürften Sie mit einfachen if-Statements schon recht vertraut sein. Den Kreditkartentyp könnten wir z.B. überprüfen, indem wir mehrere if-Statements ineinander verschachteln:

if ($kartentyp != "Visa") {
 
    if ($kartentyp != "Mastercard") {
 
        if ($kartentyp != "American Express") {
 
            $hack = true; $feld = "Kartentyp";
 
        }
 
    }
 
}
 

Bei den drei gültigen Optionen, die wir hier zur Auswahl haben, ist das noch praktikabel. Bei einer größeren Anzahl Optionen werden verschachtelte if-Statements aber etwas unübersichtlich - insbesondere dann, wenn für mehrere einzelne Optionen eine spezifische Befehlsfolge ausgeführt werden soll; hier könnte das z.B. eine kartentypspezifische Überprüfung der Kartennummer sein.

Zur Überprüfung derartiger Eingaben ziehen Programmierer deshalb oft ein switch-case-Statement vor:

Beispiel 4-8
Überprüfung von festen Eingabeoptionen mit switch-case in spende.php
switch ($kartenTyp) {
 
    case "Visa": break;
 
    case "Mastercard": break;
 
    case "American Express": break;
 
    default:
 
        $hack = true; $feld = "Kartentyp";            
 
}         
 

Was passiert hier? In einem switch-Statement haben wir einen Ausdruck (in diesem Fall die Variable $kartenTyp), der einer Fallunterscheidung unterzogen wird. Jeder spezielle Fall wird als case gekennzeichnet. Der Wert, den der Ausdruck hat, wenn dieser Fall zutreffen soll, wird nach dem Schlüsselwort case angegeben. Nach einem Doppelpunkt folgen die PHP-Anweisungen, die für diesen Fall zu bearbeiten sind. Hier sind das in allen Spezialfällen lediglich break-Befehle. Diese Befehle veranlassen PHP, gleich wieder aus dem switch-Statement herauszuspringen. Falls keiner der mit case angegebenen Spezialfälle zutrifft, kann in einem switch-Statement optional auch ein Default-Fall angegeben werden: Dann werden die PHP-Anweisungen nach dem Schlüsselwort default ausgeführt. In unserer Situation ist dieser Default-Fall der eigentlich interessante: Jemand hat uns eine faule Kreditkarte untergeschoben, indem er unser Formular umgangen hat!

Überprüfung von Kreditkartennummern und -ablaufdaten

Die Kreditkartennummer gehen wir wiederum mit einem regulären Ausdruck an. Wenn Sie sich eine Kreditkartennummer ansehen, werden Sie feststellen, dass sie bei den gebräuchlichen Visa- und Mastercard-Kreditkarten aus 16 Ziffern besteht, im Fall von American Express aus 15 Ziffern. Diese Ziffern können je nach Eingabekonvention des Benutzers durch einen Bindestrich oder ein Leerzeichen getrennt sein. In unserem regulären Ausdruck gehen wir dabei so vor:

Beispiel 4-9
Kreditkartennummernüberprüfung in spende.php
if (!preg_match("/^(\d[\s\-]?){15,16}$/", $kreditKarte)) {
 
    $hack = true; $feld = "Kartennummer";
 
}
 

Die Begrenzungszeichen und die Anfangs- und Endzeichen ^ und $ kennen Sie ja schon: Sie besagen, dass das dazwischenliegende Muster sich über den ganzen auszuwertenden String erstrecken soll. Die dazwischenliegende Syntax ist Ihnen vermutlich noch neu: (\d[\s\-]?){15,16}. Die runden Klammern fassen dabei ein Unterausdruck-Muster zusammen: (\d[\s\-]?). Ihnen folgt in den geschweiften Klammern ein »Multiplikator«, der besagt, dass das davorstehende Muster zwischen 15- und 16-mal passen muss.

Wie Sie sich vielleicht bereits denken, bezieht sich der Unterausdruck in unserem Fall auf die Ziffern mit evtl. anschließendem Trennzeichen. Dabei steht \d wieder für eine beliebige Ziffer. Darauf folgt eine »Klasse« in eckigen Klammern: [\s\-].

Eine Klasse in einem regulären Ausdruck gibt eine Anzahl von Zeichen bzw. Mustern an, die an dieser Stelle vorkommen müssen. Hier ist das entweder ein Leerzeichen1 \s oder ein Bindestrich. In der Klassen-Definition sollten wir dem Bindestrich hier aber einen Backslash (Rückwärtsstrich) voranstellen: Wie wir gleich sehen werden, haben normale Bindestriche in regulären Ausdrücken nämlich auch eine andere Bedeutung. Das Fragezeichen, das auf die eckige Klammer folgt, besagt, dass das Zeichen aus dieser »Klasse« null- oder einmal vorkommen muss.

Damit passt unser Unterausdruck in den runden Klammern also auf eine Ziffer, der evtl. ein einzelnes Leerzeichen oder ein Bindestrich folgt. Der Multiplikator, der dem Unterausdruck folgt, gibt an, wie oft nacheinander der Ausdruck mindestens und höchstens passen soll, in diesem Fall zwischen 15- und 16-mal. Hier ist »zwischen« dasselbe wie »oder«. Wenn Sie aber auch noch exotische Kreditkarten mit 17 Ziffern annehmen wollen, sollten Sie {15,17} schreiben.

Für den Fall, dass Sie sich mit 16-stelligen Kreditkarten begnügen wollen, können Sie zwar durchaus {16,16} schreiben - besser und lesbarer geht es aber einfach mit {16}.

Probieren Sie's selbst! Der hier aufgeführte reguläre Ausdruck für die Kreditkartennummer hat einen kleinen Schönheitsfehler: Er lässt Kreditkartennummern passieren, die als letztes Zeichen einen Bindestrich oder ein Leerzeichen haben. Probieren Sie es doch mal mit der Nummer 1111222233334444-. Ganz klar: Das ist keine echte Kartennummer, aber sie eckt hier nicht an. Erweitern Sie den regulären Ausdruck so, dass Kartennummern definitiv in einer Ziffer enden müssen. Die Lösung finden Sie auskommentiert in der Online-Version von spende.php.

Apropos reguläre Ausdrücke: Hier kommt noch einer, diesmal zur Überprüfung des Ablaufdatums der Kreditkarte:

Beispiel 4-10
Überprüfung des Kreditkarten-Ablaufdatums in spende.php
if (!preg_match("/^(\d{2})0[4-9]$/", $ablaufDatum, $match)) { 
 
    $hack = true; $feld = "Kreditkarten-Ablaufdatum";
 
}
 
else
 
{
 
    if (($match[1] < 1) || ($match[1] > 12)) {
 
        $hack = true; $feld = "Kreditkarten-Ablaufdatum";
 
    }
 
}
 

Hier sollten wir einen String bekommen, dessen erste zwei Ziffern den Monat angeben und zwischen 01 und 12 liegen sollen. Die dritte und vierte Ziffer sollen das Jahr angeben. Im Fall der ersten beiden (Monats-)Ziffern muss der Unterausdruck (\d{2}) passen. Die runden Klammern haben hier noch eine kleine Nebenfunktion: Der Teil-String, auf den dieser Unterausdruck passt, wird in das Array $match eingetragen, das als drittes Argument an preg_match() übergeben wird. Wir kommen gleich darauf zurück. Da im Moment wohl noch keine Kreditkarten im Umlauf sind, die nach 2009 ablaufen, können wir getrost davon ausgehen, dass die dritte Ziffer eine 0 sein muss. Die vierte Ziffer kann demnach eine Ziffer zwischen 4 und 9 sein (vorausgesetzt, Sie kaufen dieses Buch in seinem Erscheinungsjahr!). Der Bindestrich drückt dabei den Bereich aus.

Wir verwenden hier außerdem eine Erweiterung des if-Statements, die else-Klausel. Eine else-Klausel folgt immer unmittelbar auf das if-Statement. Die Anweisungen in den geschweiften Klammern nach else werden nur dann ausgeführt, wenn die Bedingung des if-Statements nicht wahr ist. In unserem Beispiel ist das der Fall, wenn $verfall aus den gewünschten vier Ziffern besteht. Wir müssen jetzt nur noch sicherstellen, dass die ersten beiden Ziffern zwischen 01 und 12 liegen.

Überprüfung von Checkboxen

Die Überprüfung der Checkbox ist relativ einfach: Die übergebenen Daten müssen entweder aus einem leeren String oder dem String "on" bestehen, sonst stimmt was nicht:

Beispiel 4-11
Überprüfung von Checkboxen in spende.php
if (($oeffentlich != "") && ($oeffentlich != "on")) {
 
    $hack = true; $feld = "&Ouml;ffentliche Spende";
 
}
 

Um auszudrücken, dass etwas »nicht gleich« ist, verwenden wir den Ungleichheitsoperator !=. In diesem Fall müssen zwei Ungleichheitsbedingungen gleichzeitig erfüllt sein, damit ein Eingabeproblem vorliegt. Wir verknüpfen sie hier mit dem UND-Operator &&.

Überprüfung des Ausgabezeitfelds

Die Überprüfung des Ausgabezeitfelds erfolgt wiederum mit einem regulären Ausdruck:

Beispiel 4-12
Überprüfung des Ausgabezeitfelds in spende.php
if (!preg_match("/^\d+$/", $ausgabeZeit)) {
 
    $hack = true; $feld = "Formularausgabezeit";
 
}
 

Damit ist die Überprüfung der »normalen« Eingabefelder abgeschlossen. Es verbleibt die Verarbeitung der optionalen Bilddatei.

Verarbeitung von heraufgeladenen Dateidaten

Hier müssen wir etwas aufpassen:

Der folgende Code ist dafür zuständig:

Beispiel 4-13
Überprüfen und Abspeichern des Spenderfotos in spende.php 
if ($_FILES["spenderFoto"]["size"] > 0) {
 
    $foto = true;
 
    preg_match("/(\.\w+)$/",
 
        $_FILES["spenderFoto"]["name"],$match);
 
    $typ = $match[1];
 
    // Wir erlauben nur Erweiterungen von Bilddateien, damit 
 
    // stattdessen keine PHP-Skripte eingeschmuggelt werden können.
 
    if (in_array(
 
            strtolower($typ),
 
            array(".gif",".bmp",".jpg",".png",".jpeg"))) {
 
        $dateiName = uniqid("").$typ;
 
        $bildPfad =
 
            preg_replace("/\/[^\/]+$/","",
 
                         $_SERVER["SCRIPT_FILENAME"])
 
                         ."/bilder/";
 
        copy($_FILES["spenderFoto"]["tmp_name"],
 
             $bildPfad.$dateiName);
 
    }
 
}
 
else
 
{
 
    $foto = false;
 
}
 

Das assoziative Array $_FILES2 ist zweidimensional, das heißt, dass der Ausdruck $_FILES["spenderFoto"] selbst ein assoziatives Array darstellt. Das Element dieses Arrays, das wir unter dem Indexwert "size" abrufen können, enthält die Größe der übergebenen Datei in Bytes. Ist die Größe null, wurde keine Datei heraufgeladen. In diesem Fall setzen wir eine Variable namens $foto auf den Booleschen Wert false. Wenn eine Datei vorhanden ist, setzen wir sie auf true. Sie wird uns später bei der Ausgabe unserer Bestätigungsseite helfen.

Die Dateinamenserweiterung finden

Bei vorhandener Datei müssen wir zunächst nach der Dateinamenserweiterung der Originaldatei auf der Browser-Maschine suchen, da wir diese wiederverwenden wollen. Schließlich müssen wir Browsern, denen wir in Zukunft dieses Bild zeigen wollen, ja mitteilen, um welche Art Datei es sich handelt. Dies ließe sich auch über $_FILES["spenderfoto"]["type"] erreichen und ein switch-Statement, das je nach Dateityp eine Fallunterscheidung vornimmt. Unsere Methode mit dem regulären Ausdruck ist hier aber etwas eleganter, zumal der eigentliche Dateityp ohnehin nur für Browser interessant sein wird.

Der reguläre Ausdruck sucht am Ende des Originalnamens nach einem Punkt, gefolgt von einem oder mehreren Wortzeichen. Wortzeichen sind Buchstaben, Ziffern und das Unterstreichungszeichen - Dateierweiterungen fallen also auf jeden Fall darunter. Der Punkt wird durch einen vorangestellten Backslash als »echter« Punkt gekennzeichnet. Wortzeichen in regulären Ausdrücken werden durch das Makro \w gekennzeichnet. Das Pluszeichen bedeutet: »Das vorangehende Zeichen oder das Makro muss mindestens einmal vorkommen.« Die Dateinamenserweiterung muss also aus mindestens einem Buchstaben bestehen. Der Teil-String, der die Erweiterung darstellt, wird durch die runden Klammern im Array $match abgespeichert und anschließend in die Bequemlichkeitsvariable $typ umkopiert.

Skript-Upload - Vorsicht, Falle!

So, jetzt müssen wir etwas aufpassen, aus Sicherheitsgründen. Wir stehen kurz davor, eine Datei zu speichern, die uns von einem beliebigen Benutzer im WWW geschickt wurde. Den Namen bzw. die URL dieser abgespeicherten Datei werden wir anschließend dem Browser in einem <img>-Tag mitteilen müssen, damit er das Bild wieder laden kann. Aber: Woher wissen wir eigentlich, dass es sich um eine Bilddatei handelt?

Im Prinzip könnte unser Benutzer uns ja auch ein PHP-Skript unterjubeln, das er unter der im <img>-Tag angegebenen URL aufrufen kann. Dabei würde dieses heraufgeladene Skript dann auch ausgeführt. Mit einem solchen Skript und diversen Funktionen (z.B. den in Kapitel 8 besprochenen externen Funktionen) darin ließe sich ziemlich viel Unheil anrichten. Ein Hacker könnte auf diesem Weg beispielsweise beliebige Kommandos auf Ihrem Webserver ausführen - von jedem Browser der Welt aus.

Keine angenehme Vorstellung? Na, dann müssen wir sicherstellen, dass die hier heraufgeladenen Dateien unseren Webserver nicht in Versuchung führen können, sie als Skripte zu betrachten. Das wiederum hängt von der Dateinamenserweiterung ab. Wenn die Erweiterung die einer Bilddatei ist, wird der Server die Datei nicht als Skript interpretieren. Also müssen wir sicherstellen, dass wir die Datei nur dann speichern, wenn $typ eine anerkannte Bilddateierweiterung darstellt.

Das machen wir in einem if-Statement. Seine Bedingung verwendet die eingebaute PHP-Funktion in_array(), um herauszufinden, ob sich die Dateiendung in $typ in einem Array mit anerkannten Endungen von Bilddateien befindet. Das Array erzeugen wir mit der Array-Konstruktionsfunktion array(). Um uns Schreibarbeit für alle möglichen gültigen Kombinationen von Groß- und Kleinschreibung zu sparen, konvertieren wir $typ für die Suche in einen String aus Kleinbuchstaben.

Wenn die Erweiterung passt, wird sie in der nächsten Zeile dazu verwendet, einen Dateinamen zu erzeugen, unter dem wir dann die Datei auf dem Server abspeichern können. Der Rumpf des Dateinamens wird dabei von PHP beigesteuert. Die Funktion uniqid("") generiert aus der Systemzeit einen einzigartigen String, der aus Kleinbuchstaben und Ziffern besteht und 13 Zeichen lang ist. Ein Beispiel ist der String 3c6d829638ae5. Dieser wird mit der Dateierweiterung zu einem Dateinamen zusammengesetzt, also z.B. 3c6d829638ae5.jpg.

Pfadfinder, aufgepasst!

Zum Speichern der Datei sollten wir auch noch einen (absoluten) Verzeichnispfad zu unserem Bildverzeichnis haben, damit eindeutig klar ist, wo die Datei letztendlich mit der copy-Anweisung in der darauf folgenden Zeile abzuspeichern ist.

Da wir die Datei gleich wieder als Bild an den Browser ausgeben wollen, macht es Sinn, sie in einem Unterverzeichnis des Skriptverzeichnisses abzulegen, z.B. in Bilder/. Dieses Verzeichnis muss natürlich durch den Webserver beschreibbar sein, das heißt, Sie müssen es eingerichtet und die entsprechenden Zugriffsrechte konfiguriert haben.

Auf einem System mit Windows könnten Sie den Pfad in spende.php so definieren:

$bildPfad = "C:/Dokumente und Einstellungen/Administrator/Eigene 
 
             Dateien/ora/phpbuch/Kapitel4/RoterFaden/bilder/";
 

Jawohl - Windows versteht auch Vorwärts-Schrägstriche anstatt der üblichen Backslashs, wenn es um das Trennen von Verzeichnisnamen in einem Verzeichnispfad geht. Wenn wir unser Skript einmal in ein anderes Verzeichnis verschieben wollen oder der Webmaster gar ohne Vorwarnung unser gesamtes Webverzeichnis verschiebt, sehen wir mit diesem Pfad leider etwas alt aus.

Eine bessere Lösung besteht darin, PHP nach dem absoluten Pfad unseres Skripts zu fragen. Wir finden ihn (inklusive Dateiname des Skripts) unter dem Index SCRIPT_FILENAME des globalen assoziativen PHP-Arrays $_SERVER. Dieses Array wird dem Skript von PHP zur Verfügung gestellt und enthält Angaben zu den Umständen des HTTP-Requests, mit dem das Skript aufgerufen wurde. Dazu gehören neben Pfad und URL u.a. auch die IP-Adresse des Clients.

Nun müssen wir nur noch dafür sorgen, dass wir den Skriptnamen selbst loswerden, dann haben wir den Pfad zu unserem Skriptverzeichnis. Das machen wir mit der eingebauten Funktion preg_replace():

$bildPfad = 
 
    preg_replace("/\/[^\/]*$/","",
 
                 $_SERVER["SCRIPT_FILENAME"])."/bilder/";
 

Diese Funktion nimmt als ersten Parameter einen regulären Ausdruck, der das zu findende Zeichenmuster festlegt. Sie kennen das ja jetzt schon von preg_match(). Der zweite Parameter ist ein String, der das gefundene Muster ersetzen soll. Der dritte Parameter ist der String, in dem die Austauschoperation stattfinden soll.

In diesem Fall suchen wir in unserem Pfad nach einem Vorwärts-Schrägstrich (\/), dem beliebig viele Zeichen folgen können, die keine Vorwärts-Schrägstriche sein dürfen. Das drücken wir durch die Klasse [^\/] aus - das ^-Zeichen bestimmt hier, dass die nachfolgenden Zeichen in der Klasse nicht auftreten dürfen. Dieses Muster muss bis ans Ende des Strings reichen. Damit erfassen wir den Dateinamen der Skriptdatei zusammen mit dem unmittelbar davorstehenden Schrägstrich der Pfadangabe, also /Spende.php. Ihn ersetzen wir durch den leeren String "", also durch nichts.

An den verbliebenen Pfad können wir jetzt unser Unterverzeichnis bilder/ anhängen, müssen aber den eben entfernten Schrägstrich wieder als »Kleber« dazwischenschieben. Das geschieht ganz einfach mit einem Punkt, den wir auch früher schon zum Zusammensetzen von Strings verwendet haben.

In der nächsten Zeile kopieren wir die Datei von ihrem temporären Speicherort an ihren permanenten Platz. An dieser Stelle ist es wichtig, dass wir unseren Dateinamen selbst bestimmt und nur selektive Endungen zugelassen haben. Warum? Nehmen wir einmal an, wir würden lediglich die skriptbezogene Dateinamenserweiterung .php verwerfen, keinen eigenen Namensrumpf erzeugen und stattdessen den vom Browser übergebenen Dateinamen übernehmen.

So wäre ein Hacker in der Lage, uns den String "../index.html" als Dateinamen unterzujubeln. Damit würde die Datei unter Windows abgespeichert, z.B. als:

C:/Dokumente und Einstellungen/Administrator/Eigene
 
Dateien/ora/phpbuch/Kapitel4/RoterFaden/bilder/../index.html
 

Das ist aber äquivalent zu

C:/Dokumente und Einstellungen/Administrator/Eigene
 
Dateien/ora/phpbuch/Kapitel4/RoterFaden/index.html
 

Jetzt hätten wir aber ein Problem - das wäre nämlich unsere eigene Indexdatei! Der Hacker könnte sie also mit dem Inhalt der heraufgeladenen Datei problemlos überschreiben. Sie sehen, der Teufel steckt hier im Detail. Selbst mit einem regulären Ausdruck wären wir nicht unbedingt sicher. Nehmen wir einmal an, der reguläre Ausdruck wäre "/(\..+)$/" anstatt "/(\.\w+)$/". Mit dem Punkt vor dem Plus würden wir nach beliebigen Zeichen, aber nicht zwingend nach Wortzeichen suchen. Ein gewiefter Hacker könnte uns daher eine neue Indexdatei mit dem Namen ./../../index.html unterschieben, was denselben Effekt wie oben hätte. Holzauge, sei wachsam!

Dadurch dass wir gezielt Wortzeichen nach dem Punkt verlangen, umgehen wir dieses Problem - Dateinamen wie der obige werden so einfach auf ein harmloses .html reduziert.

Andere mögliche Sicherheitschecks an dieser Stelle könnten eine Kontrolle der Dateigröße beinhalten - wer würde Ihnen schon Fotos von 10 MByte oder mehr schicken wollen? Idealerweise sollten Fotos fürs Web ohnehin allenfalls ein paar Dutzend KByte groß sein.

In der derzeitigen Version unseres Skripts sind die Checks der »einfachen« Eingabedaten eigentlich unnötig, da unsaubere Daten allenfalls zu einer korrumpierten Seite auf dem Browser führen können. Das wird sich aber spätestens dann ändern, wenn Sie das Skript erweitern, um die Daten in einer Datenbank abzulegen. Aus diesem Grund ist es gute Praxis, Eingabewerte immer strikt auf ihren Inhalt zu überprüfen.

Skriptverhalten bei inkorrekten Eingaben

Damit ist die Überprüfung der Benutzerdaten abgeschlossen, und wir wissen, ob sie sauber sind oder nicht - der Zeitpunkt ist gekommen, an dem wir bei Unregelmäßigkeiten eine Fehlermeldung ausgeben und abbrechen sollten:

Beispiel 4-14
Ausgabe einer Fehlermeldung bei inkorrekten Eingaben in spende.php
if ($hack) { ?>
 
    <html>
 
        <body>
 
            <h1>Eingabefehler</h1>
 
            Ihre Eingabe im Feld 
 
            <b><?php echo $feld; ?></b> 
 
               war inkorrekt. 
 
        </body>
 
    </html>
 
<?php 
 
    exit(); 
 
}
 

Diese Entscheidung treffen wir je nach Wert der Variablen $hack, und zwar mit einem if-Statement. Das Besondere an diesem if-Statement ist, dass das Statement nicht nur PHP-Befehle umfasst, sondern zum größten Teil aus HTML-Code besteht, der nur ausgegeben wird, wenn $hack true ist. Zu diesem Zweck verlassen wir PHP innerhalb der geschweiften Klammern des if-Statements durch ein abschließendes ?>-Tag und kehren nur einmal kurz zu PHP zurück, um den Inhalt der Variablen $feld auszugeben. Erst kurz vor dem Ende des if-Statements kehren wir wieder über ein <?php-Tag zu PHP zurück. Mit exit() sorgen wir dafür, dass das Skript nach Ausgabe des HTML-Codes beendet wird, damit die inkorrekten Daten keinen weiteren Sachschaden anrichten können.

Skriptverhalten bei korrekten Eingaben

Wenn unser Skript jetzt noch läuft, sollten die Formulardaten weitgehend »normal« sein. Nun wäre z.B. der Zeitpunkt gekommen, die Daten in einer Datenbank abzulegen. Wie bereits erwähnt, heben wir diesen Schritt für ein späteres Kapitel auf. Für den Moment konzentrieren wir uns auf die Ausgabe der Bestätigungsseite:

Beispiel 4-15
Ausgabe der Bestätigungsseite in spende.php 
?>
 
<html>
 
    <body>
 
        <h1>Liebe(r) <?php echo $spenderName; ?></h1>
 
        <p>Haben Sie vielen Dank f&uuml;r Ihre Spende &uuml;ber 
 
        <?php echo $betrag; ?>&euro;.
 
        Eine Spendenquittung schicken wir an Ihre Adresse:</p>
 
        <p><b><?php
 
            echo stripslashes(preg_replace("/\r?\n/","<br>", $adresse));
 
        ?></b></p>
 
        <p>Die Spende wird von Ihrer 
 
        <b><?php echo $kartenTyp; ?></b>-Kreditkarte mit Nummer:</p>
 
        <p><b><?php echo $kreditKarte; ?></b> 
 
        g&uuml;ltig bis <b><?php echo $ablaufDatum; ?></b>
 
        abgebucht.</p>
 
        <?php
 
        if ($foto) { ?>
 
            <p>Ihr Foto sehen Sie hier:<br>
 
            <img src="<?php echo "bilder/".$dateiName; ?>"></p>
 
        <?php
 
        } 
 
        ?>
 
        <p>Wir nehmen zur Kenntnis, dass wir Ihren Namen 
 
        <?php
 
            if ($oeffentlich == "") {
 
                echo "nicht";
 
            }
 
        ?>
 
        ver&ouml;ffentlichen d&uuml;rfen.</p>
 
        <p>Sie haben <?php echo (time() - $ausgabeZeit); ?>
 
        Sekunden zum Ausf&uuml;llen unseres Formulars gebraucht</p>.
 
    </body>
 
</html>
 

Mit dem ?>-Tag am Anfang dieses Abschnitts verlassen wir PHP, da die Bestätigungsseite zum überwiegenden Teil aus HTML besteht. An den entsprechenden Stellen setzen wir mit kurzen PHP-Schnipseln die entsprechenden Werte ein. Im Prinzip sollten Ihnen alle Techniken hier bekannt vorkommen, oder? Eine interessante Stelle ist das src-Attribut des img-Tags, mit dem wir die URL des Fotos festlegen. Hier müssen wir auch einen Bildpfad angeben, da die Bilddatei jetzt in einem Unterverzeichnis des Verzeichnisses liegt, in dem sich das Skript befindet. Für diesen Pfad reicht der String "bilder/", der zusammen mit dem Dateinamen als relative URL interpretiert wird.

Probieren Sie's selbst!

Erinnern Sie sich noch an unser Skript datum.php? Wie schon erwähnt, hat es einen kleinen Schönheitsfehler: Die Sekunden und Minuten werden von der Funktion im assoziativen Array als Zahlen zurückgegeben. Das heißt, in unserem derzeitigen Skript könnte es auch 12:5:5 sein, wenn es in Wirklichkeit fünf Minuten und fünf Sekunden nach zwölf ist. Mit einem geeigneten regulären Ausdruck können Sie diese Situation erkennen und korrigieren. Na? Die Lösung finden Sie in der Datei datumFormatiert.php in den Beispielen zu diesem Kapitel.

Anführungszeichen - ein kleiner Schönheitsfehler oder ein großes Problem?

Bevor wir uns kopfüber in weitere Skripte stürzen, sollten Sie sich einen kleinen Schönheitsfehler ansehen, der Ihnen vielleicht noch gar nicht aufgefallen ist. Wenn Sie Glück (oder Pech - je nachdem, wie Sie das sehen) haben, ist Ihre PHP-Installation so konfiguriert, dass Sie ihn nicht sehen. Das hat allerdings wiederum Konsequenzen, die uns in späteren Kapiteln heimsuchen könnten.

Probieren Sie einmal, als Spendernamen einen Namen mit Apostroph einzugeben, z.B. O'Reilly. Wenn Ihnen das nach der Eingabe mit Backslash als O\'Reilly angezeigt wird, ist klar, dass wir hier ein kleines Problem haben. In der PHP-Konfigurationsdatei php.ini gibt es nämlich einen Parameter namens magic_quotes_gpc, der in der Regel auf on gesetzt ist. Er sorgt dafür, dass alle Apostrophe (einzelne Anführungszeichen) und doppelte Anführungszeichen sowie Backslashs in den Zeichenketten der Elemente von $_POST mit einem vorangehenden Backslash versehen werden.

Wie bitte? Warum sollte uns PHP unsere Strings derart verunstalten? Um den Grund dafür zu verstehen, müssen wir wissen, dass PHP viel mit SQL-Datenbanken verwendet wird.

Um PHP-Strings in SQL-Datenbanken abzuspeichern, müssen wir sie in den entsprechenden SQL-Befehlen mit einfachen oder doppelten Anführungszeichen umgeben. Wenn wir das nicht tun, interpretiert SQL unseren String nicht als String, sondern als einen Befehl oder ein anderes SQL-Syntaxelement. Für SQL sieht ein String also ungefähr so aus:

"Das ist ein String"
 

So, und jetzt überlegen Sie mal, was passiert, wenn Sie hier ein doppeltes Anführungszeichen einfügen:

"Das ist leider "kein" String mehr"
 

Was ist hier passiert? Das Anführungszeichen vor "kein" beendet hier ganz einfach den String. Die nachfolgenden Zeichen würden also wieder als SQL-Syntax interpretiert, was hier sicher nicht gewollt ist. Wenn der String uns von einem Browser übergeben wird, ermöglichen wir so jedem Hacker, über eingefügte SQL-Kommandos unsere Datenbank zu kontrollieren. Dazu später noch mehr.

Wie vermeiden wir das? Ganz einfach: Wir stellen allen Anführungszeichen einen Backslash voran:

"Das ist wieder \"ein\" String"
 

magic_quotes_gpc ist dazu da, diesen Prozess bei den vom Browser geschickten Daten zu automatisieren. Das kann natürlich auch zu Problemen führen - wenn Sie unser kleines Experiment mit dem Spendernamen nachvollziehen konnten, haben Sie ein Beispiel dafür gesehen. Da sowohl in PHP als auch in HTML und JavaScript Strings wahlweise mit einfachen oder mit doppelten Anführungszeichen begrenzt werden können, besteht jede Menge Potenzial für Fehler. Es hilft also nichts - da müssen wir durch.

Wenn bei Ihnen (wie in den meisten Fällen), magic_quotes_gpc eingeschaltet ist, können Sie Strings sehr einfach zwischen andere Anführungszeichen schreiben. Wenn nicht, können Sie einem String für diesen Zweck mit der eingebauten PHP-Funktion addslashes() mit den notwendigen Backslashs versehen:

$stringMitBackslashes = addslashes($stringOhneBackslashes);
 

Ebenso können Sie unerwünschte Backslashs (wie in unserem Fall bei der Darstellung des Spendernamens) mit der Umkehrfunktion stripslashes() sehr einfach loswerden:

$stringOhneBackslashes = addslashes($stringMitBackslashes);
 

Für Sie ist es wichtig zu wissen, wie sich Ihre PHP-Installation verhält. Wenn Sie Ihre PHP-Installation nicht selbst konfiguriert haben, können Sie den Wert von magic_quotes_gpc über die Funktion get_magic_quotes_gpc() abfragen. Eine 1 als Rückgabewert bedeutet, dass magic_quotes_gpc auf ON steht.

Mit diesem Wissen sollten Sie sich bei jeder String-Ausgabe überlegen, ob in dem betroffenen String Backslashs enthalten sein könnten und ob Sie sie brauchen oder nicht. Unser Skript sollten wir bei eingeschaltetem magic_quotes_gpc wie folgt ändern:

Beispiel 4-16
Backslashs mit stripslashes() entfernen  
<html>
 
    <body>
 
        <h1>Liebe(r) <?php 
 
            echo stripslashes($spenderName); ?></h1>
 
        <p>Haben Sie vielen Dank f&uuml;r Ihre Spende &uuml;ber
 
        <?php echo $betrag ?>&euro;. 
 
        Eine Spendenquittung schicken wir an Ihre Adresse:<p>
 
        <b><?php 
 
            echo stripslashes(
 
                     preg_replace("/\r?\n/","<br>",
 
                         $adresse)); 
 
        ?></b><p>geschickt.<p>
 

Bei den weiteren Skripten in diesem Buch werden wir der Einfachheit halber davon ausgehen, dass magic_quotes_gpc eingeschaltet ist. Ist das bei Ihnen nicht der Fall, müssen Sie die Skripte evtl. entsprechend adaptieren (siehe Kasten »Magische Anführungsstriche (Magic Quotes)«).

Magische Anführungsstriche (Magic Quotes)

Wie Sie sicher schon bemerkt haben, sind Zeichenketten (Strings) eine sehr nützliche Einrichtung in einer Programmiersprache wie PHP - zumindest solange sie keine Anführungszeichen enthalten: Dass wir in Werten von HTML-Attributen Anführungszeichen durch &quot; ersetzen müssen, wussten Sie ja schon aus dem vorherigen Kapitel. PHP arbeitet aber auch oft Hand in Hand mit anderen Technologien zusammen, in denen Anführungsstriche Ärger machen können: der Datenbanksprache SQL, der Browser-Skriptsprache JavaScript und dem jeweiligen Server-Betriebssystem (z.B. Windows oder Linux). Dabei setzt PHP oft Strings zusammen, die SQL oder JavaScript dann auf dem Datenbank-Server bzw. dem Browser verwenden sollen.
Wenn ein JavaScript-String ein ungeschütztes Anführungszeichen enthält, funktioniert im ungünstigsten Fall Ihre Webseite nicht richtig. Bei SQL verhält sich das schon etwas anders: Wie wir in Kapitel 6 sehen werden, können Unbefugte mit von außen in einen SQL-Abfrage-String eingeschleusten Anführungszeichen nämlich den SQL-Code selbst verändern. Damit ist es unter Umständen möglich, geheime Daten auszulesen oder die Datenbank zu korrumpieren, zu verfälschen oder sogar zu löschen.
Um uns hier vor Anfängerfehlern zu schützen, kennt PHP zwei Konfigurationsvariablen: magic_quotes_gpc und magic_quotes_runtime, beide aus der Konfigurationsdatei php.ini. Falls magic_quotes_gpc on ist (der Default), werden die vom Browser überreichten Werte in den Arrays $_GET, $_POST und $_COOKIE auf Anführungszeichen und Backslashs überprüft, und diese werden ggf. mit einem vorangestellten Backslash versehen. Das vermeidet zerbrochene Strings sowohl in JavaScript als auch in SQL.
magic_quotes_runtime macht dasselbe bei Daten, die von Datenbanken und Betriebssystem-Befehlen zurückgelieferten werden. magic_quotes_runtime ist per Default off.
Wegen der vielen Konfigurationsmöglichkeiten verwende ich oft eine simple Konvention: Alle Strings in meinen PHP-Variablen sollen nur Backslash-geschützte Inhalte enthalten. Dazu stelle ich am Anfang meiner Skripte sicher, dass vom Browser gesendete Daten so aussehen wie unter magic_quotes_gpc on. Einige dieser Befehle werden wir erst später besprechen, aber im Moment dürfen Sie sie einfach so an den Anfang Ihrer Skripte rüberkopieren:
if (!get_magic_quotes_gpc()) { // kann im Skript nur abgefragt werden
 
    foreach ($_POST as $key => $value) {$_POST[$key] = addslashes($value);}
 
    foreach ($_GET as $key => $value) {$_GET[$key] = addslashes($value);}
 
    foreach ($_COOKIE as $key => $value) {$_COOKIE[$key] = addslashes($value);}
 
}
 
set_magic_quotes_runtime(1); // kann im Skript gesetzt werden!
 
>
Das stellt sicher, dass ich auch bei optimierungswütigen Systemadministratoren keine Hintertüren öffne.
Bei der HTML-Ausgabe muss ich die Anführungsstriche dann natürlich entsprechend loswerden, z.B. so:
echo "Verlag: ".stripslashes($_POST["VERLAG"]);
 

PHP-Comboskripte und Includes

Sie haben vermutlich schon interaktive Webseiten gesehen, bei denen Ihnen bei einer fehlenden oder fehlerhaften Eingabe das ursprüngliche Formular erneut vorgesetzt wird, wobei die Felder bereits vorausgefüllt sind. Das ist mit PHP auch nicht allzu schwierig. Zu diesem Zweck empfehlen sich zwei Strategien: Zum einen wollen wir nicht zwei Versionen des Formulars schreiben, die wir anschließend immer gemeinsam auf dem neusten Stand halten müssen. Das Ausgangsformular und das Korrekturformular sollten daher aus demselben Code erzeugt werden. Zum anderen ist es eine gute Idee, den Präsentationscode (das HTML) und den Verarbeitungscode (Überprüfung und Verarbeitung der Eingabedaten sowie Steuerung des Ablaufs) so weit wie möglich getrennt zu halten. Das macht die Fehlersuche und die Wartung der Dateien einfacher.

Die Techniken, die wir zu diesem Zweck anwenden, heißen »Comboskript« und »Includes«. Was ist ein Comboskript? Eigentlich ist die Idee recht einfach: Bisher haben wir den Browser eine Datei anfordern lassen, die das Formular enthielt, um es dann an eine andere Datei (das Skript) zu schicken.

In einem Comboskript werden die beiden Dateien vereint: Wenn wir das Skript vom Browser aus anfordern, ohne als Teil des HTTP-Requests Formulardaten zu schicken, wird das Formular zurückgeschickt. Wenn wir das Skript mit Formulardaten anfordern, überprüft es, ob die Daten in Ordnung sind. Wenn ja, verarbeitet es die Daten entsprechend und schickt uns eine Bestätigung. Wenn nicht, gibt das Skript das vorausgefüllte Formular zurück.

Um das Formular vorausfüllen zu können, müssen wir es ein bisschen erweitern. Sie finden es unter spendenFormularMitVorausfuellen.php in den Online-Beispielen

:
Beispiel 4-17
Der Code von spendenFormularMitVorausfuellen.php 
<html>
 
    <head>
 
        <title>Spenden Sie f&uuml;r bedrohte V&ouml;gel!</title>
 
    </head>
 
    <body>
 
        <form name="spende" action="spendenCombo.php" method="post" 
 
            enctype="multipart/form-data">
 
            <input type="hidden" name="ausgabeZeit" 
 
                value="<?php echo time();?>">
 
            <h1>Spendenformular</h1>
 
            <?php 
 
                if ($hack) {
 
            ?>
 
                    <p>Leider konnten wir Ihre Spende nicht bearbeiten,  
 
                    weil das Formular nicht korrekt ausgef&uuml;llt war. 
 
                    Bitte korrigieren Sie das Eingabefeld  
 
                    <b><?php echo $feld; ?></b> und &uuml;berpr&uuml;fen Sie 
 
                    nochmals Ihre anderen Eingaben, einschlie&szlig;lich der
 
                    Datei f&uuml;r Ihr Foto, falls Sie eine angegeben haben. 
 
            <?php
 
                }
 
                else
 
                {
 
            ?>          
 
                    <p>Wir freuen uns, dass Sie spenden m&ouml;chten! Bitte
 
                    geben Sie Ihren Namen, Ihre Adresse, die H&ouml;he der
 
                    Spende und Ihre Kreditkartendaten ein. Dr&uuml;cken Sie
 
                    dann auf "Spende abschicken!"
 
            <?php
 
                }
 
            ?>
 
            Klicken Sie dann auf "Spende abschicken!"</p>
 
            <p>
 
            <b>Name:</b> <input type="text" name="spenderName" size="80"
 
                            value="<?php 
 
                                             echo htmlspecialchars(
 
                                                      stripslashes($spenderName)); 

                                                          ?>"></p>
 
            <p><b>Adresse:</b><br> 
 
            <textarea name="adresse" rows="4" cols="40" align="top"><?php 
 
            echo stripslashes($adresse); ?></textarea></p>
 
            <p><b>H&ouml;he der Spende:</b> 
 
            <select name="betrag">
 
                <?php 
 
                    for($i = 5; $i < 101; $i = $i + 5) {
 
                        echo "<option value=\"".$i."\"";
 
                        if ($betrag == $i) {
 
                            echo " selected";
 
                        }
 
                        echo ">".$i."&euro;\n";
 
                    }   
 
                ?>
 
            </select>
 
            &nbsp;
 
            <b>Ihr Foto</b> (optional): <input name="spenderFoto" type="file"></p>
 
            <p>
 
            <p><b>Kreditkartentyp:</b> 
 
            <input type="radio" name="kartenTyp" value="Visa" 
 
            <?php
 
                        if ($kartenTyp == "Visa") { echo "checked"; } ?>>Visa
 
                        &nbsp;<input type="radio" name="kartenTyp" value="Mastercard"

                           <?php
 
                        if ($kartenTyp == "Mastercard") { echo "checked"; } 

                           ?>>Mastercard
 
            </p>         
 
            <p><b>Kreditkartennummer:</b> 
 
            <input type="text" name="kreditKarte" size="20" maxlength="20"
 
                                        value="<?php echo $kreditKarte; ?>"> 
 
            &nbsp;
 
            <b>Verfallsdatum der Kreditkarte:</b> 
 
            <input type="text" name="ablaufDatum" size="4" maxlength="4"
 
                                        value="<?php echo $ablaufDatum; ?>"></p> 
 
            <p>
 
            <b>Klicken Sie hier, wenn wir Ihren Namen 
 
            ver&ouml;ffentlichen d&uuml;rfen:</b> 
 
            <input type="checkbox" name="oeffentlich" <?php
 
                if ((!$hack) || ($oeffentlich == "on")) { echo "checked"; }?>> 
 
            <p>
 
            <input type="submit" value="Spende abschicken!">
 
        </form>
 
    </body>
 
</html>
 

Wie Sie vermutlich schon beim Überfliegen bemerkt haben, sind ein paar Dinge neu hinzugekommen. Zunächst einmal sorgt ein if-Statement dafür, dass ein etwas anderer Einleitungstext für das Formular ausgegeben wird, wenn die Variable $hack true ist. Dabei trifft es sich gut, dass PHP unbekannte Variablen bei Booleschen Auswertungen als false betrachtet. Deswegen brauchen wir uns beim ersten Laden des Formulars auch keine Sorgen zu machen, ob $hack einen Wert hat oder nicht.

Eingabefelder vorausfüllen

Als weitere Neuheit kommt hinzu, dass allen Texteingabefeldern über das value-Attribut ein Default-Wert zugewiesen wird. Als Wert für das value-Attribut setzen wir jeweils über ein PHP-Tag mit einem echo-Befehl den Wert der entsprechenden PHP-Variablen ein. Auch hier trifft es sich gut, dass PHP unbekannte Variablen bei der String-Auswertung als leere Strings interpretiert. Der Inhalt für die <textarea> wird einfach zwischen das <textarea>-Tag und das </textarea>-Tag geschrieben, ebenfalls mit Hilfe eines echo-Befehls.

Für den Spendenbetrag ist das etwas komplizierter. Hier müssen wir in der for-Schleife mittels eines if-Statements die »richtige« Option abpassen, der wir dann ein selected-Attribut hinzufügen. Wenn $betrag nicht gesetzt wäre, würde der Browser als Default einfach die erste Option auswählen. Wie wir gleich in unserem Comboskript sehen werden, können wir aber einen Spendenbetrag »vorschlagen«, indem wir $betrag auf einen Default-Wert setzen.

Ein kleiner »Schönheitsfehler« besteht darin, dass wir das Eingabeelement zum Heraufladen der Fotodatei nicht mit einem Dateinamen füllen können. Alle modernen Browser verbieten das, weil es eine gigantische Sicherheitslücke öffnet. Über Skriptsprachen wie JavaScript ist es nämlich möglich (und auch erwünscht), Formulare automatisch an den Webserver zu schicken.

Wenn Sie ein Input-Element vom Typ file mit einem Default-Dateinamen versehen könnten, dann könnten Sie auf diese Weise bestimmen, welche Datei auf der Maschine des Benutzers Sie gern einsehen würden. Wenn der Benutzer dann Ihre Webseite besucht, könnten Sie ihm ein solches Eingabeelement unterschieben und sich damit klammheimlich die gewünschte Datei auf Ihren Server laden. Das ist natürlich unerwünscht, deswegen müssen wir hier unsere edlen Spender bitten, die Fotodatei noch einmal auszuwählen.

Eine etwas aufwendigere Alternative zum nochmaligen Upload einer Bilddatei wäre es, bei bereits vorhandener Bilddatei dem Formular den Dateinamen dieser Datei auf dem Server in einem geeigneten versteckten Eingabeelement mitzugeben und dann diesen Namen bei der Formularverarbeitung zu verwenden. Probieren Sie's doch mal aus, nachdem Sie dieses Kapitel zu Ende gelesen haben! Welche potenziellen Sicherheitslücken tun sich hier eventuell auf?

Bei den Radio-Buttons für den Kreditkartentyp kommen Sie nur schwer darum herum, jedem einzelnen Button ein eigenes if-Statement zu verpassen, das bei Bedarf das checked-Attribut hinzufügt. Wenn Sie ein Formular mit einer sehr großen Anzahl von Radio-Buttons haben, kann das recht aufwendig werden. Eine Alternative wäre die dynamische Erzeugung der Buttons mit Werten aus einem Array. Das geht so:

<p><b>Kreditkartentyp:</b> 
 
            <?php 
 
                $kartenTypen = array("Visa","Mastercard","American Express");
 
                foreach ($kartenTypen as $einKartenTyp) {
 
                   echo "<input type=\"radio\" name=\"kartenTyp\" value=\"";
 
                   echo $einKartenTyp."\" ";
 
                   if ($kartenTyp == $einKartenTyp) { echo "checked"; }
 
                   echo ">".$einKartenTyp."&nbsp;";
 
                }
 
            ?>
 

Hier definieren Sie einfach ein Array $kartenTypen und durchlaufen es dann mit einer foreach-Schleife. In jedem Durchlauf einer foreach-Schleife wird je eines der Elemente aus dem angegebenen Array, hier $kartenTypen, in der hinter dem Spezifikator as angegebenen Variablen abgelegt - in diesem Fall in $einKartenTyp. In der Schleife selbst erzeugen wir für diesen Kartentyp dann jeweils maßgeschneiderte Buttons über echo-Befehle sowie ein if-Statement.

Bei drei Buttons ist das noch etwas aufwendig, aber ab einem halben Dutzend lohnt es sich bestimmt. Sie finden dieses Beispiel in spendenFormularDynamisch.php in den Beispielen. Die dazugehörige Comboskript-Datei, die Sie im Browser aufrufen müssen, heißt spendenComboDynamisch.php und ist bis auf den Namen des Formulars mit spendenCombo.php identisch.

Die Checkbox lässt sich über ein checked-Attribut an- und ausschalten, das wir über ein if-Statement je nach Wert der Variablen $hack und $oeffentlich setzen. Das sind auch die hauptsächlichen Änderungen an unserem Spendenformular, einmal abgesehen vom action-Attribut des <form>-Tags, das jetzt auf spendenCombo.php zeigt.

Das Comboskript spendenCombo.php

Das neue Comboskript spendenCombo.php ist natürlich von spende.php abgeleitet. Auch hier bleibt vieles beim Alten:

Beispiel 4-18
Der Code des Comboskripts spendenCombo.php 
<?php
 

 
    // Dieses Skript überprüft, ob es Daten vom Spendenformular empfangen hat
 
    // oder ob es direkt angefordert wurde. Wenn $_POST-Daten vorliegen,
 
    // werden die Daten überprüft und dem Benutzer zur Bestätigung ausgegeben.
 
    // Bei inkorrekten Eingaben wird eine vorausgefüllte Version des Spenden-
 
    // formulars zur Korrektur zurückgereicht.
 
    if (sizeof($_POST) == 0) {
 
        $betrag = 50; // vorgeschlagener Spendenbetrag
 
        include("spendenFormularMitVorausfuellen.php");
 
        exit(); // Skript abbrechen!
 
    }
 

 

 
    $hack = false;        // Diese Boolsche Variable zeigt an,
 
                          // ob ein Benutzer inkorrekte Daten eingegeben hat
 
                          // - das könnte sogar ein Hacking-Versuch sein.
 

 
    // Daten der Bequemlichkeit wegen aus $_POST auslesen.
 

 
    $spenderName = $_POST["spenderName"];
 
    $adresse = $_POST["adresse"];
 
    $betrag = $_POST["betrag"];
 
    $kartenTyp = $_POST["kartenTyp"];
 
    $kreditKarte = $_POST["kreditKarte"];
 
    $ablaufDatum = $_POST["ablaufDatum"];
 
    $oeffentlich = $_POST["oeffentlich"];
 
    $ausgabeZeit = $_POST["ausgabeZeit"];
 

 
    // Überprüfung der Daten.
 
    // $spenderName kann ein beliebiger String sein, aber er sollte nicht leer sein!
 
    if ($spenderName == "") {
 
        $hack = true; $feld = "Name";
 
    }
 
    // $adresse kann auch ein beliebiger nicht-leerer String sein.
 
    if ($adresse == "") {
 
        $hack = true; $feld = "Adresse";
 
    }
 
    // Der Betrag muss eine ganze Zahl zwischen 5 und 100 sein.
 
    if (!preg_match("/^\d*[05]$/", $betrag)) { // nicht durch 5 teilbar?
 
        $hack = true; $feld = "Betrag";
 
    }
 
    if (($betrag < 5) || ($betrag > 100)) {
 
        $hack = true; $feld = "Betrag";
 
    }
 
    // Der Kartentyp sollte Visa oder Mastercard sein.
 
    switch ($kartenTyp) {
 
        case "Visa": break;
 
        case "Mastercard": break;
 
        default:
 
            $hack = true; $feld = "card type";
 
    }
 
    // Die Kartennummer sollte aus 15 oder 16 Ziffern bestehen,
 
    // die durch nichts, ein Leerzeichen oder einen Bindestrich getrennt sind:
 
    if (!preg_match("/^(\d[\s\-]?){15,16}$/", $kreditKarte)) {
 
        $hack = true; $feld = "Kartennummer";
 
    }
 

 
    // Das Ablaufdatum sollte aus vier Ziffern bestehen, wobei die ersten beiden
 
    // zwischen 01 und 12 liegen sollten, die dritte (noch) eine 0 sein sollte
 
    // und die vierte jetzt zwischen 4 und 9 liegen sollte (es sei denn, die Karte
 
    // läuft nach 2009 ab, was im Moment noch unwahrscheinlich ist):
 
    if (!preg_match("/^(\d{2})0[3-9]$/", $ablaufDatum, $match)) {
 
        $hack = true; $feld = "Kreditkarten-Ablaufdatum";
 
    }
 
    else
 
    {
 
        if (($match[1] < 1) || ($match[1] > 12)) {
 
            $hack = true; $feld = "Kreditkarten-Ablaufdatum";
 
        }
 
    }
 
    // Die Checkbox ist entweder leer, "on", oder jemand versucht zu hacken:
 
    if (($oeffentlich != "") && ($oeffentlich != "on")) {
 
        $hack = true; $feld = "&Ouml;ffentliche Spende";
 
    }
 
    // Die Formularausgabezeit muss eine ganze Zahl sein:
 
    if (!preg_match("/^\d+$/", $ausgabeZeit)) {
 
        $hack = true; $feld = "Formularausgabezeit";
 
    }
 
    // Wurde ein Foto geschickt?
 
    if ($_FILES["spenderFoto"]["size"] > 0) {
 
        $foto = true;
 
        preg_match("/(\.\w+)$/",
 
            $_FILES["spenderFoto"]["name"],$match);
 
        $typ = $match[1];
 
        // Wir erlauben nur Erweiterungen von Bilddateien, damit die Leute
 
        // stattdessen keine PHP-Skripte einschmuggeln können.
 
        if (in_array(
 
                strtolower($typ),
 
                array(".gif",".bmp",".jpg",".png",".jpeg"))) {
 
            $dateiName = uniqid("").$typ;
 
            $bildPfad =
 
                preg_replace("/\/[^\/]+$/","",
 
                             $_SERVER["SCRIPT_FILENAME"])
 
                             ."/bilder/";
 
            copy($_FILES["spenderFoto"]["tmp_name"],
 
                $bildPfad.$dateiName);
 
        }
 
    }
 
    else
 
    {
 
        $foto = false;
 
    }
 

 
    // Bei inkorrekten Eingaben das vorausgefüllte Formular ausgeben:
 
    if ($hack) {
 
        include("spendenFormularMitVorausfuellen.php");
 
        exit(); // Skript abbrechen!
 
    }
 

 
    // An diesem Punkt fügen wir später den Code ein, der die
 
    // überprüften Daten in die Datenbank schreibt.
 

 
    // Daten zur Bestätigung an den Benutzer ausgeben:
 
    ?>
 
    <html>
 
        <body>
 
            <h1>Liebe(r) <?php echo $spenderName; ?></h1>
 
            <p>Haben Sie vielen Dank f&uuml;r Ihre Spende &uuml;ber 
 
            <?php echo $betrag; ?>&euro;.
 
            Eine Spendenquittung schicken wir an Ihre Adresse:</p>
 
            <p><b><?php
 
                echo stripslashes(preg_replace("/\r?\n/","<br>", $adresse));
 
            ?></b></p>
 
            <p>Die Spende wird von Ihrer 
 
            <b><?php echo $kartenTyp; ?></b>-Kreditkarte mit Nummer:</p>
 
            <p><b><?php echo $kreditKarte; ?></b> 
 
            g&uuml;ltig bis <b><?php echo $ablaufDatum; ?></b>
 
            abgebucht.</p>
 
            <?php
 
            if ($foto) { ?>
 
                <p>Ihr Foto sehen Sie hier:<br>
 
                <img src="<?php echo "bilder/".$dateiName; ?>"></p>
 
            <?php
 
            } 
 
            ?>
 
            <p>Wir nehmen zur Kenntnis, dass wir Ihren Namen 
 
            <?php
 
                if ($oeffentlich == "") {
 
                    echo "nicht";
 
                }
 
            ?>
 
            ver&ouml;ffentlichen d&uuml;rfen.</p>
 
            <p>Sie haben <?php echo (time() - $ausgabeZeit); ?>
 
            Sekunden zum Ausf&uuml;llen unseres Formulars gebraucht</p>.
 
        </body>
 
    </html>
 

Die größte Änderung findet sich gleich am Anfang des Skripts. Wir überprüfen mit der sizeof()-Funktion, ob $_POST auch tatsächlich Daten enthält. Wenn wir spendenCombo.php nämlich direkt oder über einen Link vom Browser aus anfordern, schicken wir dabei keine Formulardaten zum Server, und $_POST ist leer. Das heißt, seine Größe (size) ist 0. In diesem Fall möchten wir einfach nur das Spendenformular zurückgeben, zusammen mit dem empfohlenen Betrag für die Höhe der Spende:

if (sizeof($_POST) == 0) { 
 
    $betrag = 50; // vorgeschlagener Spendenbetrag
 
    include("spendenFormularMitVorausfuellen.php");
 
    exit(); // Skript beenden!
 
}
 

Das Spendenformular wird von PHP mit dem include-Statement in unser Skript eingefügt. PHP geht dabei in den HTML-Modus über, so dass die eingefügte Datei bis zum Auftauchen des ersten PHP-Tags als HTML behandelt wird. Außer dem Spendenformular müssen wir nichts an den Browser zurücksenden, deshalb beenden wir nach dem include-Statement das Skript mit exit().

Die andere Änderung an unserem Skript besteht darin, dass wir die Fehlermeldung »herausoperiert« und durch ein weiteres include-Statement ersetzt haben:

if ($hack) { 
 
    include("spendenFormularMitVorausfuellen.php");
 
    exit(); // Skript beenden!
 
}
 

Ansonsten bleibt alles beim Alten!

Probieren Sie's selbst!

Versuchen Sie, die Bestätigung in eine externe Datei auszulagern und dann mit include in das Skript einzubinden. Vielleicht möchten Sie ja auch mehrere verschiedene Includes, z.B. eine aufwendigere Bestätigungsseite für generösere Spender? Eine Musterlösung finden Sie unter spendenComboErweitert.php in den Online-Beispielen für dieses Kapitel.

1Auf \s passende Leerzeichen können auch Tabulator- oder Zeilenumbruchzeichen sein.
2Auch $_FILES ist neu seit 4.1.0 - vorher hieß das entsprechende Array $HTTP_POST_FILES.

TOC PREV NEXT INDEX

Copyright © 2004 by O'Reilly Verlag GmbH & Co.KG