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.
-
ID
- Die automatisch erzeugte ID des Benutzers. Entsprechend vom TypINT
mit den Eigenschaften AUTO_INCREMENT, PRIMARY KEY, UNSIGNED und NOT NULL. -
Username
- Der gewählte Name vom Benutzer. Je nach Größe reicht hier einVARCHAR(30)
mit NOT NULL. -
Password
- In dieser Spalte wird der Hashwert des Passwords gespeichert. Da hier der MD5-Hashwert verwendet wird bekommt diese Spalte entsprechend den TypCHAR(32)
mit NOT NULL. -
Email
- Hier wird natürlich die Emailadresse gespeichert. Je nach gewünschter Größte reicht hierVARCHAR(100)
mit NOT NULL.
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', $UserID, strtotime("+1 month"));
setcookie('Password', $Hash, strtotime("+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', $UserID, strtotime("+1 month"));
setcookie('Password', $Hash, strtotime("+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', $UserID, strtotime("+1 month"));
setcookie('Password', $Hash, strtotime("+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,
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.