Quakenet/#php Tutorial

Hinweis: Wenn sie diese Seite von einer externen URL aufgerufen haben achten sie darauf das alle Kapitel aufeinander aufbauen. Stellen sie daher sicher dass sie alle vorherigen Kapitel gelesen haben, da sie sonst relevante Informationen übersehen.

Loginskript

  1. Loginskript
  2. Tabelle in MySQL
  3. Bereiche anlegen
  4. Neuen Benutzer registieren
  5. Ins System einloggen
  6. Aus dem System ausloggen
  7. Eigenes Profil bearbeiten

1. Loginskript

Die meisten Internetplatformen besitzen eine Möglichkeit sich zu registieren und einzuloggen. Damit man über mehrere Tage eingeloggt bleibt wird üblicherweise in einem Cookie der Loginstatus gespeichert, damit jeder sich nicht bei jedem Besuch neu einloggen muss. Unser Loginskript wird daher auch Cookies verwenden.

2. Tabelle in MySQL

Alle Benutzer müssen natürlich im System gespeichert sein. Dafür legen wir in MySQL eine Tabelle User an, die alle Daten vom Benutzer enthällt. In unserem Fall sind es folgende Daten.

Daraus ergibt sich dann der folgende SQL-Query.

CREATE TABLE User (
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Username VARCHAR(30) NOT NULL,
    Password CHAR(32) NOT NULL,
    Email VARCHAR(100) NOT NULL 
);

Diesen fügen wir dann mit phpMyAdmin in unsere Datenbank ein.

3. Bereiche anlegen

Für unsere Grundfunktionen eines Logins brauchen vier 4 Bereiche. Zuerst müssen wir ein Teilbereich schreiben in dem sich neue Benutzer registierieren können. Dann brauchen wir ein Bereich für den eigentlichen Login, ein weiteren für die Bearbeitung des eigenen Profils und zuletzt noch ein Bereich um uns auszuloggen. Daher legen wir in unserem $dateien-Array vier neue Einträge für register, login, profile und logout mit den entsprechenden PHP-Skripten register.php, login.php, profile.php und logout.php an.

4. Neuen Benutzer registieren

Zuerst müssen wir dem Benutzer ein Formular anzeigen welches den Benutzer nach den nötigen Daten wie Username und Password fragt. Daher zeigen wir als erstes das Formular an.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    
// formular verarbeiten
}
return 
$ret;
?>

Es ist nicht schlimm dass wir die Variable $ret verwenden, sie wird eh später durch die Anweisung $ret = include ....; überschrieben. Die register.tpl sieht dabei wie folgt aus.

<form action="index.php?section=register" method="post">
    <fieldset>
        <legend>Registieren</legend>
        <label>Username: <input type="text" name="Username" /></label>
        <label>Password: <input type="password" name="Password[]" /></label>
        <label>Bestätigung: <input type="password" name="Password[]" /></label>
        <label>Email: <input type="text" name="Email" /></label>
        <label>{FRAGE}: <input type="text" name="Antwort" /></label>
        <input type="submit" name="formaction" value="Registieren" />
    </fieldset>
</form>

Hier sieht man wieder die Eingabe nach einer Antwort zu einer Frage. Dies dient wie vorher dazu Spambots aus dem System zu halten, mit all den vorher besprochenden Einschränkungen. Außerdem werden die Passwörter in ein Array gespeichert auf die wir dann später mit den Index 0 und 1 zugreifen können.

Nun prüfen wir in unserem PHP-Skript ob alle Formularelemente vorhanden sind.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
}
return 
$ret;
?>

Hier sieht man dass eine Konstante INVALID_FORM verwendet wurde. Diese Konstante ist nicht vordefiniert, daher müssen wir sie definieren. Dies machen wir in einer constants.php die wie auch in der index.php laden.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'constants.php';
include 
'functions.php';
include 
'classes.php';
include 
'variables.php';
// [...]
?>

In der neuen Datei definieren wir dann eine passende Fehlermeldung.

<?php
define
('INVALID_FORM''Benutzen sie nur Formulare von der Homepage.');
?>

Nun prüfen wir die Password-Felder.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
}
return 
$ret;
?>

Nun prüfen wir ob wirklich was eingegeben wurde.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '' OR
            (
$Email trim($_POST['Email'])) == '' OR
            (
$Antwort trim($_POST['Antwort'])) == '') {
        return 
EMPTY_FORM;
    }
}
return 
$ret;
?>

Hier verwenden wir wieder eine neue Konstante, die wir entsprechend in der constants.php definieren.

<?php
define
('INVALID_FORM''Benutzen sie nur Formulare von der Homepage.');
define('EMPTY_FORM''Bitte füllen sie das Formular vollständig aus.');
?>

Nun prüfen wir den Wert unserer pseudo-CAPTCHA-Abfrage.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '' OR
            (
$Email trim($_POST['Email'])) == '' OR
            (
$Antwort trim($_POST['Antwort'])) == '') {
        return 
EMPTY_FORM;
    }
    if (
'' != $Antwort) { // entsprechend anpassen
        
return 'Bitte geben sie die richtige Antwort an.';
    }
}
return 
$ret;
?>

Nun prüfen wir ob der Benutzername normal aussieht und noch nicht verwendet wurde. Normal heißt für uns ein Username zwischen 3 und 30 Zeichen ohne Leerzeichen. Dafür verwenden wir einmal ein Regex und prüfen dann in der Datenbank ob es schon ein solchen Username gibt.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '' OR
            (
$Email trim($_POST['Email'])) == '' OR
            (
$Antwort trim($_POST['Antwort'])) == '') {
        return 
EMPTY_FORM;
    }
    if (
'' != $Antwort) { // entsprechend anpassen
        
return 'Bitte geben sie die richtige Antwort an.';
    }
    if (!
preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
}
return 
$ret;
?>

Da wir nun mit MySQL sprechen müssen brauchen wir ein MySQLi-Objekt. Dazu müssen wir die index.php Datei bearbeiten.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'constants.php';
include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$db = @new MySQLi(/*host*//*username*//*password*//*database*/);
$ret 1// speichert den rückgabewert von include, standardwert 1
if (mysqli_connect_errno()) {
    
$ret 'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error();
} else {
    
// Laden der Include-Datei
    // [...]
}
// Laden des HTML-Kopfs
// [...]
?>

Wir benutzen die $ret-Variable um die Fehlermeldung für ein Verbindungsfehler anzuzeigen. Da wir nun unser MySQLi-Objekt haben können wir mit der Datenbank arbeiten. Dabei lesen wir alle Datensätze aus der User-Tabelle aus die den gleichen Namen haben wie die Eingabe des Usernames. Dabei kann es nur 0 oder einen Datensatz geben, den wir dann mit dem Attribut num_rows auslesen.

<?php
    
// [...]
    
if (!preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ?
            LIMIT
                1'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('s'$Username);
    
$stmt->execute();
    
$stmt->store_result();
    if (
$stmt->num_rows) {
        return 
'Der Username wird bereits verwendet.';
    }
    
$stmt->close();
    
// [...]
?>

Die Methode store_result() (mysqli_stmt_store_result) wird verwendet damit die Daten aus der Datenbank gelesen werden, insbesondere wird dann auch das num_rows-Feld gesetzt. Nun wissen wir dass der Username gültig ist, also können wir unseren Benutzer hinzufügen.

<?php
    
// [...]
    
if ($stmt->num_rows) {
        return 
'Der Username wird bereits verwendet.';
    }
    
$stmt->close();
    
$sql 'INSERT INTO
                User(Username, Email)
            VALUES
                (?, ?)'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('ss'$Username$Email);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
// [...]
?>

Mit der insert_id-Eigenschaft können wir die ID abfragen die für diesen Datensatz erzeugt wurde. Diese verwenden wir für den Salt der Hashfunktion.

<?php
    
// [...]
    
if (!$stmt->execute()) {
        return 
$stmt->error;
    }
    
$UserID $stmt->insert_id;
    
$sql 'UPDATE
                User
            SET
                Password = ?
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Password);
    
$stmt->bind_param('si'$Hash$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
// [...]
?>

Nun wurde der Benutzer mit dem Passwort gespeichert. Nun geben wir noch eine entsprechende Meldung beim Benutzer aus.

<?php
    
// [...]
    
if (!$stmt->execute()) {
        return 
$stmt->error;
    }
    return 
showInfo('Der Benutzer wurde hinzugefügt. Sie können sich nun anmelden.');
    
// [...]
?>

Bei der Funktion showInfo() handelt es sich um eine benutzerdefinierte Funktion die wir noch schreiben müssen. Sie soll für uns ein Array generieren welches dann später ein Template mit einer Infobox anzeigt. Dazu schreiben wir erstmal die templates/info.tpl-Datei.

<?php
/* Daten
 * - msg: Die Nachricht die angezeigt werden soll.
 */
?><p class="info">
    <?php echo htmlspecialchars($data['msg']); ?>
</p>

Da wir jetzt wissen wie die Template-Datei aussieht wissen wir auch wie die Funktion aussehen muss.

<?php
/**
 * Erzeugt ein Array für das Infomessage-Template.
 *
 * Diese Funktion erzeugt eine Array für unsere Templateengine die dann
 * die Infomessage-Template-Datei "info.tpl" läd. Der Parameter gibt
 * dabei die Nachricht an die angezeigt werden soll.
 *
 * @param msg Die Nachricht die angezeigt werden soll.
 * @return Das Array für unsere Templateengine.
 */
function showInfo($msg) {
    
$ret = array();
    
$ret['filename'] = 'info.tpl';
    
$ret['data'] = array();
    
$ret['data']['msg'] = $msg;
    return 
$ret;
}
?>

Da sich nun jeder registieren kann kommt nun der Programmteil zum einloggen.

5. Ins System einloggen

Da unser Login Cookies verwenden soll müssen wir uns überlegen was wir beim Client speichern. Die einfachste Möglichkeit wäre es den Benutzernamen und das Password zu speichern. Jedoch mag es vielleicht der Benutzer nicht wenn bei jedem Seitenaufruf die lesbaren Logindaten übermittelt werden. Daher werden wir die UserID und den Hashwert vom Passwort speichern. Die können zwar auch abgefangen werden aber dadurch kriegt keiner das eigentliche Password vom Benutzer raus.

Für den Bereich login brauchen wir wieder 2 Dateien. Einmal die templates/login.tpl welches das Loginformular anzeigt und entsprechend die includes/login.php welches die Formulardaten verarbeitet.

<form action="index.php?section=login" method="post">
    <fieldset>
        <legend>Einloggen</legend>
        <label>Benutzername: <input type="text" name="Username" /></label>
        <label>Password: <input type="password" name="Password" /></label>
        <input type="submit" name="formaction" value="Einloggen" />
    </fieldset>
</form>

In unserer includes/login.php verarbeiten wir die Logindaten. Zuerst bauen wir unser Grundgerüst.

<?php
$ret 
= array();
$ret['filename'] = 'login.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if ((
'' == $Username trim($_POST['Username'])) OR
            (
'' == $Password trim($_POST['Password']))) {
        return 
EMPTY_FORM;
    }
}
return 
$ret;
?>

Nun prüfen wir mit den Username ob ein Benutzer vorhanden ist.

<?php
    
// [...]
    
if (('' == $Username trim($_POST['Username'])) OR
            (
'' == $Password trim($_POST['Password']))) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('s'$Username);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($UserID);
    if (!
$stmt->fetch()) {
        return 
'Es wurde kein Benutzer mit den angegebenen Namen gefunden.';
    }
    
$stmt->close();
    
// [...]
?>

Die bind_result()-Methode ist das selbe wie bind_param(), nur in der anderen Richtung. Wenn die fetch()-Methode aufgerufen wird so werden die Daten vom nächsten Datensatz aus dem Resultset in die gebundenen Variablen gespeichert. Hier ist dies nur die ID des Benutzers. Wenn kein Datensatz mehr vorhanden ist liefert fetch() den Wert NULL der äquivalent zu false umgewandelt wird. Dies nutzen wir aus indem wir die Methode in einer if-Abfrage aufrufen und dann entsprechend eine Fehlermeldung zurückliefern wenn kein Datensatz vorhanden ist.

Nun haben wir die ID des Benutzers und prüfen nun ob das Passwort stimmt.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Es wurde kein Benutzer mit den angegebenen Namen gefunden.';
    }
    
$stmt->close();
    
$sql 'SELECT
                Password
            FROM
                User
            WHERE
                ID = ? AND
                Password = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Password);
    
$stmt->bind_param('is'$UserID$Hash);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($Hash);
    if (!
$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
// [...]
?>

Nun wissen wir dass die Logindaten stimmen und können nun die Cookies senden.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
setcookie('UserID'$UserIDstrtotime("+1 month"));
    
setcookie('Password'$Hashstrtotime("+1 month"));
    
// [...]
?>

Cookies alleine sorgen nicht dafür dass man eingeloggt ist, wir müssen sie auch abfragen. Dazu definieren wir uns eine Funktion getUserID(). Sie prüft die Cookie-Werte und liefert entweder false wenn der Benutzer nicht eingeloggt ist oder die Benutzer-ID wenn die Logindaten gültig sind. Diese Funktion soll also zwei Dinge leisten, einmal gucken ob man eingeloggt ist und dann gucken mit welcher ID der Benutzer eingeloggt ist.

<?php
/**
 * Liefert die Benutzer-ID zurück.
 *
 * Diese Funktion liefert die Benutzer-ID des angemeldeten Benutzers zurück.
 * Falls kein Benutzer angemeldet ist liefert diese Funktion den Wert false
 * zurück. Der Parameter gibt dabei das MySQLi-Objekt an in der nach
 * dem Login geprüft werden soll. Es werden dabei die Cookies "UserID" und
 * "Password" ausgelesen. Bei einem MySQL-Fehler wird ein String mit
 * der Fehlermeldung zurückgeliefert.
 *
 * @param db Das MySQLi-Objekt auf dem gearbeitet wird.
 * @return false wenn der Benutzer nicht eingeloggt ist, die ID des Benutzers
 *         wenn er eingeloggt ist oder ein string wenn eine Fehlermeldung
 *         auftrat.
 */
function getUserID($db) {
    if (!
is_object($db)) {
        return 
false;
    }
    if (!(
$db instanceof MySQLi)) {
        return 
false;
    }
    if (!isset(
$_COOKIE['UserID'], $_COOKIE['Password'])) {
        return 
false;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                ID = ? AND
                Password = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('is'$_COOKIE['UserID'], $_COOKIE['Password']);
    if (!
$stmt->execute()) {
        
$str $stmt->error;
        
$stmt->close();
        return 
$str;
    }
    
$stmt->bind_result($UserID);
    if (!
$stmt->fetch()) {
        
$stmt->close();
        return 
false;
    }
    
$stmt->close();
    return 
$UserID;
}
?>

Zuerst sind einige Abfragen ob auch alle Werte da sind. Dann wird ein SELECT-Query gesendet und geprüft ob die Logindaten stimmen. Wenn sie stimmen wir die ID des Benutzers zurückgeliefert, wenn nicht wird false zurückgeliefert.

Problematisch bei dieser Funktion sind MySQL-Fehler die auftreten können. Idealerweise würde man hier vielleicht anstatt ein return lieber eine Exception werfen. Dies haben wir jedoch noch nicht gelernt, daher bleibt uns nur eine return-Anweisung.

Wie man sieht wird hier auf die entsprechenden Cookies zugegriffen. Beim Einloggen sind die Cookies jedoch (noch) nicht gesetzt. Man ist zwar eingeloggt aber dies sieht man erst beim nächsten Aufruf wenn dann die Cookies gesendet werden. Hier kann man ein wenig Tricksen wenn man beim einloggen für den aktuellen Request die Cookies von Hand setzt.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
setcookie('UserID'$UserIDstrtotime("+1 month"));
    
setcookie('Password'$Hashstrtotime("+1 month"));
    
$_COOKIE['UserID'] = $UserID// fake-cookie setzen
    
$_COOKIE['Password'] = $Hash// fake-cookie setzen
    // [...]
?>

Somit liefert schon im aktuellen Request die getUserID()-Funktion die ID des Benutzers zurück.

Um MySQL-Fehler innerhalb der getUserID-Funktion richtig zu behandeln prüfen wir sehr früh ob die Funktion ein MySQL-Fehler hat, indem wir in der index.php-Datei den Rückgabewert prüfen.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'constants.php';
include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$db = @new MySQLi(/*host*//*username*//*password*//*database*/);
$ret 1// speichert den rückgabewert von include, standardwert 1
if (mysqli_connect_errno()) {
    
$ret 'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error();
} else if (
is_string($error getUserID($db))) { // String, also ein MySQL Fehler
    
$ret $error// die Fehlermeldung in $ret speichern, damit sie angezeigt wird.
} else {
    
// Laden der Include-Datei
    // [...]
}
// Laden des HTML-Kopfs
// [...]
?>

So werden MySQL-Fehlermeldungen angezeigt und wenn es keine gab (is_string liefert false) kann normal weitergearbeitet werden.

Nun da wir eine funktionierende getUserID()-Funktion haben können wir diese in unseren beiden Bereichen verwenden. Es ergibt keinen Sinn ein neuen Benutzer zu registieren wenn man eingeloggt ist oder sich einzuloggen wenn man bereits eingeloggt ist. Daher fügen wir in der register.php oben folgenden Code ein.

<?php
if (getUserID($db)) {
    return 
'Sie sind eingeloggt und können sich nicht registieren.';
}
$ret = array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
// [...]
?>

Und in der login.php ein entsprechender Code.

<?php
if (getUserID($db)) {
    return 
'Sie sind bereits eingeloggt.';
}
$ret = array();
$ret['filename'] = 'login.tpl';
$ret['data'] = array();
// [...]
?>

Die Funktion getUserID() kann nun auch verwendet werden um in der templates/menu.tpl-Datei die Links zum Anmelden und Registieren nur dann anzuzeigen wenn man auch wirklich nicht angemeldet ist. Zum Schluss liefern wir in der includes/login.tpl noch eine Infomessage zurück dassder Benutzer nun eingeloggt ist.

<?php
    
// [...]
    
setcookie('UserID'$UserIDstrtotime("+1 month"));
    
setcookie('Password'$Hashstrtotime("+1 month"));
    
$_COOKIE['UserID'] = $UserID// fake-cookie setzen
    
$_COOKIE['Password'] = $Hash// fake-cookie setzen
    
return showInfo('Sie sind nun eingeloggt.');
}
return 
$ret;
?>

6. Aus dem System ausloggen

Um sich aus dem System auszuloggen braucht man nur die Cookies zu löschen. Dafür schreiben wie entsprechend die templates/logout.tpl- und includes/logout.php-Dateien.

<form action="index.php?section=logout" method="post">
    <fieldset>
        <legend>Ausloggen</legend>
        <p class="info">Um sich auszuloggen klicken sie unten auf den Button.</p>
        <input type="submit" name="formaction" value="Ausloggen" />
    </fieldset>
</form>

Für die includes/logout.php schreiben wir wieder unser Grundgerüst.

<?php
if (!getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'logout.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['formaction'])) {
        return 
INVALID_FORM;
    }
}
return 
$ret;
?>

Wie man sieht verwenden wir hier eine neue Konstante NOT_LOGGED_IN, die wir entsprechend in der constants.php definieren müssen.

<?php
// [...]
define('NOT_LOGGED_IN''Sie müssen eingeloggt sein um diese Funktion nutzen zu können.');
?>

Nun löschen wir die Cookies.

<?php
if (!getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'logout.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    
setcookie('UserID'''strtotime('-1 day'));
    
setcookie('Password'''strtotime('-1 day'));
    unset(
$_COOKIE['UserID']);
    unset(
$_COOKIE['Password']);
    return 
showInfo('Sie sind nun ausgeloggt.');
}
return 
$ret;
?>

Und somit sind wir fertig mit der Logout-Funktion.

7. Eigenes Profil bearbeiten

Früher oder später möchte man sein Nicknamen, sein Password oder seine Emailadresse ändern. Deshalb brauchen wir eine Möglichkeit das Profil zu bearbeiten. Dafür benötigen wir die Dateien templates/profile.tpl und includes/profile.php.

Für die includes/profile.php-Datei legen wir wieder ein Grundgerüst an.

<?php
if (!$UserID getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'profile.tpl';
$ret['data'] = array();

return 
$ret;
?>

Es gibt drei Möglichkeiten wie dieser Bereich aufgerufen wird. Erste Möglichkeit ist der einfache Aufruf mittels eines Links. Als zweite Möglichkeit wird die Seite aufgerufen wenn der Benutzername oder die Emailadresse geändert wird. Und als dritte Möglichkeit wird die Seite aufgerufen wenn das Password geändert wird. Für alle diese Möglichkeiten verwenden wir die $_POST['formaction']-Variable, die wir mit einem switch abfragen.

<?php
if (!$UserID getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'profile.tpl';
$ret['data'] = array();

switch (@
$_POST['formaction']) {
default:
    break;
}

return 
$ret;
?>

Mit dem @-Zeichen unterdrücken wir zwar die Fehlermeldung falls das Arrayelement nicht existiert, jedoch definieren wir ein default-Teil der standardmäßig aufgerufen wird. In diesem Bereich laden wir die Profildaten Username und Email aus der Datenbank aus und zeigen sie im Template an.

<?php
// [...]
default:
    
$sql 'SELECT
                Username,
                Email
            FROM
                User
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($Username$Email);
    
$stmt->fetch();
    
$stmt->close();
    break;
// [...]
?>

Diese Daten speichern wir nun in unser return-Array ab um später im Template darauf zugreifen zu können.

<?php
// [...]
    
$stmt->bind_result($Username$Email);
    
$stmt->fetch();
    
$stmt->close();
    
$ret['data']['Username'] = $Username;
    
$ret['data']['Email'] = $Email;
    break;
// [...]
?>

Somit ist der default-Teil fertig. Wir hätten bei bind_result die Daten aus der Datenbank direkt in die Felder $ret['data']['Username'] und $ret['data']['Email'] speichern können, jedoch sollten wir erstmal lernen wie es überhaupt geht bevor wir elegante Kurzschreibweisen verwenden.

In der templates/profile.tpl geben wir nun diese Daten aus.

<?php
 
/*
  * Daten:
  * - Username: Der Benutzername
  * - Email: Die Emailadresse
  */
?><form action="index.php?section=profile" method="post">
    <fieldset>
        <legend>Profil bearbeiten</legend>
        <label>Benutzername: <input type="text" name="Username" value="<?php echo htmlspecialchars($data['Username']); ?>" /></label>
        <label>Email: <input type="text" name="Email" value="<?php echo htmlspecialchars($data['Email']); ?>" /></label>
        <input type="submit" name="formaction" value="Profil speichern" />
    </fieldset>
</form>
<form action="index.php?section=profile" method="post">
    <fieldset>
        <legend>Password ändern</legend>
        <label>Altes Password: <input type="password" name="Old" /></label>
        <label>Neues Password: <input type="password" name="Password[]" /></label>
        <label>Bestätigen: <input type="password" name="Password[]" /></label>
        <input type="submit" name="formaction" value="Password ändern" />
    </fieldset>
</form>

Hier sieht man das zwei Formulare definiert wurden, beide mit einem unterschiedlichen Submit-Button. Das erste Formular dient dazu nicht sensible Daten wie Benutzername und Email zu verändern. Dieses benötigt auch keine Password-Eingabe. Das zweite Formular ändert jedoch sensible Daten, in diesem Fall das Password. Daher wird auch nach dem alten Password verlangt. Dies ist eine Sicherheitsmaßnahme damit nicht z.B. der Freund kurz an den Rechner geht mit dem wissen das sein Freund eingeloggt ist und einfach so das Password ändert. Dies ist auch immer die Gradwanderung zwischen Sicherheit und Komfort. Das muss jeder selbst entscheiden wie sicher bzw. wie benutzerfreundlich jemand etwas programmieren will. Hier könnte man z.B. auch das Ändern des Benutzernamens mit einer Passwordabfrage schützen da der Benutzername gleichzeitig der Login ist. Um hier den Unterschied zu zeigen kann man Benutzername und Email ohne Password ändern, das neue Password jedoch nur mit dem alten Password.

In der includes/profile.php können wir nun auf die entsprechenden Submit-Buttons reagieren. Zuerst schreiben wir den Code für die Bearbeitung des Benutzernamens und der Emailadresse. Auch hier kommen wieder unsere Grundabfragen, die so ähnlich sind wie die aus der includes/register.php-Datei.

<?php
// [...]
case 'Profil speichern':
    if (!isset(
$_POST['Username'], $_POST['Email'])) {
        return 
INVALID_FORM;
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Email trim($_POST['Email'])) == '') {
        return 
EMPTY_FORM;
    }
    if (!
preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
    break;
// [...]
?>

Nun prüfen wir ob bereits ein Benutzer den ggf. neuen Benutzernamen verwendet.

<?php
    
// [...]
    
if (!preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ? AND
                ID != ?'
;
    
// das "ID != ?" dient dazu das wir nicht unseren eigenen Benutzernamen
    // finden sondern nur die der anderen.
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('si'$Username$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($OtherID);
    
$stmt->fetch();
    
$stmt->close();
    if (
$OtherID) {
        return 
'Es gibt bereits ein Benutzer der diesen Namen verwendet.';
    }
    
// [...]
?>

Da der Benutzername und die Email in Ordnung ist speichern wir diese nun ab.

<?php
    
// [...]
    
if ($OtherID) {
        return 
'Es gibt bereits ein Benutzer der diesen Namen verwendet.';
    }
    
$sql 'UPDATE
                User
            SET
                Username = ?,
                Email = ?
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('ssi'$Username$Email$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    return 
showInfo('Der Benutzername und die Email wurde aktuallisiert.');
    break;
    
// [...]
?>

Auch wenn die break;-Anweisung nie erreicht wird dient sie der Übersicht vom switch().

Beim Password ändern prüfen wir zuerst das alte Password und speichern dann das neue. Dabei können wir auch wieder den Code aus der includes/register.php verwenden. Wir beginnen mit dem Grundgerüst.

<?php
// [...]
case 'Password ändern':
    if (!isset(
$_POST['Old'], $_POST['Password'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Old trim($_POST['Old'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '') {
        return 
EMPTY_FORM;
    }
    break;
// [...]
?>

Nun prüfen wir ob das Password stimmt.

<?php
    
// [...]
    
if (($Old trim($_POST['Old'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '') {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                ID = ? AND
                Password = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Old);
    
$stmt->bind_param('is'$UserID$Hash);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    if (!
$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
// [...]
?>

Nun da das alte Password stimmt, das neue Password auch gültig ist speichern wir nun das neue Password.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
$sql 'UPDATE
                User
            SET
                Password = ?
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Password);
    
$stmt->bind_param('si'$Hash$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
// [...]
?>

Bevor wir eine Nachricht ausgeben lassen löschen wir die Cookies da sie eh ungültig sind.

<?php
    
// [...]
    
if (!$stmt->execute()) {
        return 
$stmt->error;
    }
    
setcookie('UserID'''strtotime('-1 day'));
    
setcookie('Password'''strtotime('-1 day'));
    unset(
$_COOKIE['Username']);
    unset(
$_COOKIE['Password']);
    return 
showInfo('Das Password wurde geändert. Sie wurden automatisch ausgeloggt und müssen '.
                    
'sich ggf. neu einloggen.');
    break;
    
// [...]
?>

Nun kann auch jeder sein Password ändern.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Zurück zu Weiter zu
Copyright © bei den OPs von #php.de/QuakeNet Valid XHTML 1.0 Strict Valid CSS!