quakenet:#php Tutorial

Author: Progman, zuletzt bearbeitet von progman @ 2005/09/07 21:07:50

Bitte beachten Sie, dass die Tutorialkapitel zusammenhängen. Wenn sie direkt auf ein Kapitel verlinkt wurden müssen Sie gegebenenfalls die vorherigen Kapitel auch lesen. Achten Sie beim lesen darauf, dass Sie kein Kapitel überspringen.

Verbessertes Newsscript

  1. Features und Layout vom Newsscript
  2. News hinzufügen
  3. News bearbeiten
  4. News löschen
  5. Newsscript erstellen
  6. Tabellen joinen
  7. Weitere Aufbau des Newsscripts
  8. LIMIT-Angabe im MySQL-Query
  9. Blätterfunktion
  10. Datum umformatieren
  11. Kommentarfunktion
  12. Formular der Newskommentare
  13. Formartierungscodes ersetzen

1. Features und Layout vom Newsscript

Jetzt schreiben wir ein verbessertes Newsscript mit vielen Features. Einmal soll das Newsscript eine Kommentar Funktion haben. Die News und die Newskommentare sollen, wenn es zuviele Beiträge sind, auf mehrere Seiten aufgeteilt werden, also eine Blätter-Funktion. Im Newsbeitrag kann man dann eine Liste erstellen, in der zu anderen Quellen verlinkt wird. Dies kennt man von anderen Seiten als "Related Links". Und dann sollen Autor und die Leute, die einen Newskommentar schreiben, solche Formartierungen wie [b]Fetter Text[/b] und [url]http...[/url] schreiben können. Damit die User nicht die Kommentarfunktion zum Spammen benutzen brauchen wir noch eine IP-Sperre. Und das Newsscript soll dann auch noch über den Adminbereich verwaltet werden.

In der MySQL-Datenbank brauchen wir 4 Tabellen.

news(ID,Autor,Titel,Inhalt,Datum)
news_comments(ID,NewsID,Name,Email,Inhalt,Datum)
news_links(ID,NewsID,Link,Beschreibung)
news_comments_ip(IP,Datum)
        

Dies ist das Layout der Tabellen. In den Spalten NewsID kommt dann die ID der News rein, damit man die Links und Kommentare der entsprechenden News zuordnen kann. Die SQL-Querys, die die Tabellen erstellen, sehen wie folgt aus.

CREATE TABLE news (
    ID SMALLINT AUTO_INCREMENT PRIMARY KEY,
    Autor TINYINT,
    Titel VARCHAR(100),
    Inhalt TEXT,
    Datum DATETIME
);
CREATE TABLE news_comments (
    ID SMALLINT AUTO_INCREMENT PRIMARY KEY,
    NewsID SMALLINT,
    Name VARCHAR(20),
    Email VARCHAR(100),
    Inhalt TEXT,
    Datum DATETIME
);
CREATE TABLE news_links (
    ID SMALLINT AUTO_INCREMENT PRIMARY KEY,
    NewsID SMALLINT,
    Link VARCHAR(200),
    Beschreibung VARCHAR(200)
);
CREATE TABLE news_comments_ip (
    IP VARCHAR(16),
    Datum DATETIME
);
        

2. News hinzufügen

Nachdem wir in der admin_menu.php eine neue Reihe für die News-Verwaltung hinzugefügt und das Array $admin_site erweitert haben, erstellen wir erstmal unser Grundgerüst.

<?php
    $rights
= getRights();
    if(!
in_array("News", $rights)) {
        
no_rights();
    } else {
        switch(isset(
$_GET['action'])?$_GET['action']:'') {
            case
"add":
                break;

            case
"edit":
                break;

            case
"del":
                break;

            default:
                break;
        }
    }
?>

Das Recht die News-Verwaltung zu benutzen wird anhand des Rechtes News angegeben. Das Array $rights muss man entsprechend erweitern.

Als erstes erstellen wir ein Formular, wo man die Vorschau der News sieht. Dieses wird beim ersten Mal leer sein, da wir keine Daten eingegeben haben.

Script liegt im Scriptarchiv: /tutorial/login-news/news-add-v1.php

Den Teil mit dem Link Zurück zum Adminbereich hab ich nun sehr oft hingeschrieben. Ich glaube ich schreib ne extra Funktion, die dieses p-Element mit dem Link erzeugt.

<?php
    
// functions.php
    
function back2admin()
    {
        echo
"<p>\n";
        echo
"    <a href=\"index.php?section=admin&amp;".SID."\">\n";
        echo
"        Zurück zum Adminbereich\n";
        echo
"    </a>\n";
        echo
"</p>\n";
    }
?>

So.

Wie die Scripte davor brauchen wir auch wieder diverse If-Abfragen.

Script liegt im Scriptarchiv: /tutorial/login-news/news-add-v2.php

Manche If-Abfragen sind innerhalb des else-Teils, damit sie nach der Ausführung wieder die Newsvorschau zeigen. Jetzt kommt das Script welches die Daten in der Session in die Datenbank übertragen. Die Daten werden dabei aus der Session gelesen.

Script liegt im Scriptarchiv: /tutorial/login-news/news-add-v3.php

Da wir in der Tabelle news_links die NewsID brauchen müssen wir zuerst die News hinzufügen. Danach lesen wir die NewsID dieser News aus und können dann auch im 2. Schritt die Links hinzufügen.

3. News bearbeiten

Das Formular um die News zu bearbeiten ist fast identisch. Nur wir müssen einmal ein Formular haben, welches die News auswählt und dann müssen wir kein INSERT Befehl sondern ein UPDATE Befehl verwenden. Die Links müssen wir so ähnlich updaten wie die Rechte des Benutzers in der Benutzerverwaltung.

Script liegt im Scriptarchiv: /tutorial/login-news/news-edit-v1.php

Hier wird auch die News-ID in der Session gespeichert. Damit wissen wir zu welcher News die Session-Daten gehören.

4. News löschen

Das Script, um eine News zu löschen, ist um einiges einfacher. Man braucht nur die News auswählen und muss dann 2 DELETE-Befehle ausführen.

Script liegt im Scriptarchiv: /tutorial/login-news/news-del-v1.php

Man sollte zuerst die Links der News löschen und dann die News selber. Falls ein Fehler im zweiten Query vorhanden war, so sind nur die Links gelöscht. Würde man erst die News und dann die Links löschen, so würde bei einem Fehler im 2. Query die Links auf eine nicht mehr existieren News zeigen und wir haben in unserer Datenbank 'Tote links'.

5. Newsscript erstellen

Da das Newsscript nun anders aufgebaut ist, können wir das vorherige Newsscript nicht verwenden. Wir fangen dort wieder von Null an.

<?php
    
echo "<h2>\n";
    echo
"    News der Page\n";
    echo
"</h2>\n";
?>

Der MySQL-Query für das Auslesen sieht nun etwas komplizierter aus. Das hat jetzt nix mit den Links zu tun, dafür lassen wir ein extra Query in der while-Schleife durchlaufen. Nein, es geht um die ID des Autors in der News-Tabelle. Wir haben ja nur die ID des Users gespeichert, nicht seinen Namen oder seine Emailadresse. Würden wir diese einfach ausgeben würde dann sowas stehen.

Der Autor 4 schrieb um 2002-10-13 12:43:34 folgendes
        

Mit der ID kann der Besucher der Seite nix Anfangen. Wir suchen nun ein Query, der uns einmal die Daten aus der Tabelle news und dann auch noch gleichzeitig die Daten des Autors ausliest. Dies machen wir mit einem JOIN, einem sehr komplexen Thema.

6. Tabellen joinen

Wir haben 2 Tabellen, news und users, verbunden durch die Autor ID. Bei dem SELECT-Befehl müssen wir nun 2 Tabellen angeben, getrennt durch Kommas. Das könnte so aussehen

SELECT
   spalten
FROM
   tab1,
   tab2;
        

Wenn wir eine Spalte auswählen, die in beiden Tabellen vorhanden ist, so müssen wir diese mit dem Tabellennamen eindeutig halten. Dies macht man indem man vor dem Spaltennamen den Tabellennamen schreibt, verbunden mit einem Punkt.

SELECT
    tab1.ID,
    tab2.ID,
FROM
    tab1,
    tab2;
        

Dabei heißen die Spalten der Ergebnistabelle nur so wie die Spaltennamen, also ohne die Angabe der Tabelle. Hier kann dies negativ sein, weil wir dann 2 Spalten in der Ergebnistabelle haben, die den gleichen Namen haben. Deshalb sollte man dann ein Alias festlegen. Zur besseren Übersicht sollte man immer den Tabellennamen angeben, wenn man aus mehreren Tabellen gleichzeitig ließt, selbs wenn es eindeutig wäre. Dann erkennt man sofort, von welcher Tabelle man welche Spalte ausließt.

Unser Query mit den News und Autordaten könnte so aussehen.

SELECT
    news.ID,
    news.Titel,
    news.Inhalt,
    news.Datum,
    users.Name,
    users.Email
FROM
    news,
    users
ORDER BY
    news.Datum DESC;
        

Wenn wir diesen Query nun an die Datenbank schicken und wir in beiden Tabellen viele Datensätze haben, so werden wir feststellen, das die Ausgabe nicht die ist, die wir eigentlich wollten. Hier ein Beispiel.

mysql> SELECT
    ->     ID,
    ->     Titel,
    ->     Inhalt,
    ->     Datum
    -> FROM
    ->     news
    -> ORDER BY
    ->     Datum DESC;
+----+------------+-----------------------+---------------------+
| ID | Titel      | Inhalt                | Datum               |
+----+------------+-----------------------+---------------------+
|  4 | ethrthnrtn |  erg er er er er g 5e | 2003-01-26 20:01:39 |
|  3 | erververv  | tebetbetbebbe         | 2003-01-26 20:01:16 |
|  2 | ervrev     | der text              | 2003-01-26 18:25:42 |
+----+------------+-----------------------+---------------------+
3 rows in set (0.00 sec)

mysql> SELECT
    ->     Name,
    ->     Email
    -> FROM
    ->     users;
+-------------+--------------+
| Name        | Email        |
+-------------+--------------+
| Progman     | Progman@foo  |
| tumutmuzmzu | mzumzumuzm   |
| ervberv     | wrbvwrbvwrbv |
+-------------+--------------+
3 rows in set (0.00 sec)

mysql> SELECT
    ->     news.ID,
    ->     news.Titel,
    ->     news.Inhalt,
    ->     news.Datum,
    ->     users.Name,
    ->     users.Email
    -> FROM
    ->     news,
    ->     users
    -> ORDER BY
    ->     news.Datum DESC;
+----+------------+-----------------------+---------------------+-------------+--------------+
| ID | Titel      | Inhalt                | Datum               | Name        | Email        |
+----+------------+-----------------------+---------------------+-------------+--------------+
|  4 | ethrthnrtn |  erg er er er er g 5e | 2003-01-26 20:01:39 | Progman     | Progman@foo  |
|  4 | ethrthnrtn |  erg er er er er g 5e | 2003-01-26 20:01:39 | tumutmuzmzu | mzumzumuzm   |
|  4 | ethrthnrtn |  erg er er er er g 5e | 2003-01-26 20:01:39 | ervberv     | wrbvwrbvwrbv |
|  3 | erververv  | tebetbetbebbe         | 2003-01-26 20:01:16 | Progman     | Progman@foo  |
|  3 | erververv  | tebetbetbebbe         | 2003-01-26 20:01:16 | tumutmuzmzu | mzumzumuzm   |
|  3 | erververv  | tebetbetbebbe         | 2003-01-26 20:01:16 | ervberv     | wrbvwrbvwrbv |
|  2 | ervrev     | der text              | 2003-01-26 18:25:42 | Progman     | Progman@foo  |
|  2 | ervrev     | der text              | 2003-01-26 18:25:42 | tumutmuzmzu | mzumzumuzm   |
|  2 | ervrev     | der text              | 2003-01-26 18:25:42 | ervberv     | wrbvwrbvwrbv |
+----+------------+-----------------------+---------------------+-------------+--------------+
9 rows in set (0.00 sec)

        

Beide Tabellen haben 3 Datensätze. Wenn man jetzt den 3. SELECT Befehl mit den 2 Tabellenangaben ausführt, erhält man die oben stehende Ausgabe. Wie man sieht erhalten wir 9 Zeilen in der Ergebnistabelle. Wenn wir uns die Ergebnistabelle etwas genauer angucken, werden wir feststellen, dass die Ergebnistabelle jede Kombination enthält. Zuerst wird der erste Datensatz aus der Tabelle news mit allen Datensätzen aus der Tabelle users verknüpft. Dann wird der zweite Datensatz aus der Tabelle news mit allen Datensätzen aus der Tabelle users verknüpft. Und als letztes wird der letze Datensatz mit den anderen Datensätzen verknüpft. Das sind dann 3 mal 3, also 9, Datensätze in der Ergebnistabelle. Es wird also ein so genanntes kartesisches Produkt erstellt. Ich persönlich sag dazu immer Kreuzprodukt, darunter kann man sich mehr vorstellen, obwohl das eher ein Begriff aus der Vektorrechnung der Mathematik ist. Die Anzahl der Datensätze in der Ergebniss-Tabelle ist das Produkt der Teildatensätze in den einzelnen Tabellen. Wenn z.B. 2 Tabellen jeweils 100 Datensätze haben und in einem Query beide Tabellen gleichzeitig ausgelesen werden, müllen wir den MySQL-Speicher mit einer Ergebnistabelle von 10.000 Datensätzen zu.

Dies ist nicht dass was wir wollen. Denn jetzt hat jeder Autor zur gleichen Zeit die gleichen News geschrieben. In diesem Fall geben wir eine WHERE-Bedingung an.

SELECT
    news.ID,
    news.Titel,
    news.Inhalt,
    news.Datum,
    users.Name,
    users.Email
FROM
    news,
    users
WHERE
    news.Autor = users.ID
ORDER BY
    news.Datum DESC;
        

Dieser Query selektiert nur die Kreuzprodukte, wo von dem einem Datensatz die Autor-ID mit der User-ID aus dem anderen Datensatz übereinstimmt. Beachtet, dass die WHERE-Bedingung vor der ORDER BY Angabe stehen muss. Wir erhalten mit diesem Query nur 3 Datensätze in der Ergebnistabelle, mit den Autor-Daten, die die News geschrieben haben.

mysql> SELECT
    ->     news.ID,
    ->     news.Titel,
    ->     news.Inhalt,
    ->     news.Datum,
    ->     users.Name,
    ->     users.Email
    -> FROM
    ->     news,
    ->     users
    -> WHERE
    ->     news.Autor = users.ID
    -> ORDER BY
    ->     news.Datum DESC;
+----+------------+-----------------------+---------------------+---------+-------------+
| ID | Titel      | Inhalt                | Datum               | Name    | Email       |
+----+------------+-----------------------+---------------------+---------+-------------+
|  4 | ethrthnrtn |  erg er er er er g 5e | 2003-01-26 20:01:39 | Progman | Progman@foo |
|  3 | erververv  | tebetbetbebbe         | 2003-01-26 20:01:16 | Progman | Progman@foo |
|  2 | ervrev     | der text              | 2003-01-26 18:25:42 | Progman | Progman@foo |
+----+------------+-----------------------+---------------------+---------+-------------+
3 rows in set (0.01 sec)
        

Das Thema, JOINen von Tabellen, ist sehr Komplex. Weiter Infos dazu im MySQL-Manual oder auf diversen Internetseiten. Strichwort: JOIN.

7. Weitere Aufbau des Newsscripts

Den Query speichern wir in eine Variable und führen diesen, wie üblich, mit mysql_query aus.

Script liegt im Scriptarchiv: /tutorial/login-news/news-v1.php

Für die Anzahl der Kommentare und die Links lassen wir in der Schleife noch extra MySQL-Querys laufen. Wir müssen dabei aufpassen, dass wir die Result-Variable vom vorherigen Query nicht überschreiben. Dies ist ein beliebter Fehler.

Script liegt im Scriptarchiv: /tutorial/login-news/news-v2.php

Wenn wir nun 20 News in der Datenbank haben, werden alle Newsbeiträge untereinander dargestellt. Dadurch kann die Seite sehr groß werden, und der User muss 10-20 Sekunden warten, bis die News geladen sind. Deshalb schreiben wir eine Blätterfunktion. Dies geht mit der MySQL-Angabe LIMIT.

8. LIMIT-Angabe im MySQL-Query

Mit der LIMIT-Angabe können wir aus einer Ergebnis-Tabelle nur eine bestimmte Anzahl von Datensätze auslesen. Die LIMIT-Angabe ist wie folgt aufgebaut.

in SELECT:
 LIMIT [offset,]anzahl

in UPDATE und DELETE:
 LIMIT anzahl
        

Die anzahl-Angabe muss immer angegeben werden. Wenn man ab einer bestimmten Stelle die Datensätze auslesen möchte gibt man den offset an, getrennt durch Komma. Bei eine UPDATE und DELETE Befehl ist eine Offset-Angabe nicht möglich. Die Offset-Angabe startet bei 0 für den ersten Datensatz.

SELECT .... LIMIT 5
    ließt nur 5 Datensätze aus
SELECT .... LIMIT 4,6
    ließt 6 Datensätze aus, starte aber beim Offset 4
    also beim 5. Datensatz. Dieser Befehl ließt also
    Datensatz 5 bis 10 aus der Ergebnistabelle aus
    (5,6,7,8,9,10 = 6 Datensätze).
DELETE .... LIMIT 1
    Wenn die WHERE Bedingung mehrere Datensätze erfassen
    würde, wird dabei aber nur ein Datensatz gelöscht.
UPDATE .... LIMIT 2
    Es werden nur 2 Datensätze verändert, selbst
    wenn die WHERE-Bedingung mehrere erfassen würde.
        

9. Blätterfunktion

In PHP fügen wir nun die LIMIT-Angabe in den Query ein. Die Anzahl bleibt konstant. Was wir immer ändern ist die Offset-Angabe.

<?php
    $sql
= "SELECT
                news.ID,
                news.Titel,
                news.Inhalt,
                news.Datum,
                users.Name,
                users.Email
            FROM
                news,
                users
            WHERE
                news.Autor = users.ID
            ORDER BY
                news.Datum DESC
            LIMIT
                "
.$offset.",".LIMIT_NUM.";";
?>

Die Variable $offset schwangt nun von 0 bis "Anzahl Datensätze - LIMIT_NUM". Damit stellen wir sicher das immer LIMIT_NUM-Datensätze angezeigt werden. Dieses LIMIT_NUM hab ich hier als Konstante definiert und muss entsprechend in der constant.php definiert werden. Spätestens bei der Fehlermeldung "Undefined Constant LIMIT_NUM, assumed to 'LIMIT_NUM'" muss man dies machen.

Wir realisieren die Blätterfunktion mit der GET-Variable page. Anhand dessen Inhalts ändern wir die Offset-Angabe. Da die GET-Variable nur in einem bestimmten Bereich sein darf müssen wir ein paar If-Abfragen schreiben.

<?php
    $sql
= "SELECT
                COUNT(*) as Anzahl
            FROM
                news;"
;
    
$result = mysql_query($sql) OR die(mysql_error());

    
$anzahl = mysql_result($result, 0);
    
$start  = isset($_GET['page'])?(int)$_GET['page']:1;
    
/* Die Syntax von ... = .... ? .... : ....;
     * kann man auf
     * http://www.php.net/manual/de/language.expressions.php
     * nachlesen.
     */
    
if(!defined('LIMIT_NUM')) {
        die(
"Benötigte Konstante LIMIT_NUM ist nicht definiert");
    }

    
// Anzahl der Pages berechnen.
    
$num_pages = ceil($anzahl/LIMIT_NUM);

    
// Anzahl auf min. 1 setzen
    
if(!$num_pages) {
        
$num_pages = 1;
    }

    
// Die Start-Page muss zwischen
    // 1 und $num_pages liegen
    
if($start < 1) {
        
$start = 1;
    }
    if(
$start > $num_pages) {
        
$start = $num_pages;
    }

    
// offset für den Query bestimmen
    
$offset = ($start - 1) * LIMIT_NUM;

    
// MySQL-Query senden
    
$sql = "SELECT
                news.ID,
                news.Titel,
                news.Inhalt,
                news.Datum,
                users.Name,
                users.Email
            FROM
                news,
                users
            WHERE
                news.Autor = users.ID
            ORDER BY
                news.Datum DESC
            LIMIT
                "
.$offset.",".LIMIT_NUM.";";
    
$result = mysql_query($sql) OR die(mysql_error());
    
// ...
?>

Nach den News müssen wir nun die Seitenlinks hinzufügen. Da eignet sich eine for-Schleife und diverse If-Abfragen.

<?php
        
// ...
             
echo "</li>\n";
        }
        echo
"</ul>\n";
    }

    
// Page-Links nur anzeigen, wenn es mehr als eine sind.
    
if($num_pages > 1) {
        echo
"<p>\n";
        for(
$i = 1; $i <= $num_pages; $i++) {
            if(
$i == $start) {
                
// ich bin ja schon auf der Seite. Also
                // Seitenzahl ohne Link erzeugen
                
echo $i."\n";
            } else {
                echo
"<a href=\"index.php?section=news&amp;page=".$i."\">\n";
                echo
$i."\n";
                echo
"</a>\n";
            }
        }
        echo
"</p>\n";
    }

?>

Der User kann nun von Seite zu Seite springen, falls er sich eine alte News angucken möchte.

10. Datum umformatieren

Wenn wir nun schonmal das Newsscript testen, werden wir feststellen, dass das Datum der News im Schema YYYY-MM-DD HH:MM:SS dargestellt wird. Obwohl dies der ISO-Standard ist, kann ein Besucher der Homepage dieses Datum nicht gut lesen. Deshalben formatieren wir das Datum etwas um. Dies machen wir mit der MySQL-Funktion DATE_FORMAT . Sie ist von der Funktionsweise so ähnlich aufgebaut wie die date Funktion in PHP.

mysql> SELECT
    ->     NOW(),
    ->     DATE_FORMAT(NOW(), '%d.%m.%Y %T Uhr') as Changedatum;
+---------------------+-------------------------+
| NOW()               | Changedatum             |
+---------------------+-------------------------+
| 2003-02-02 11:35:53 | 02.02.2003 11:35:53 Uhr |
+---------------------+-------------------------+
1 row in set (0.01 sec)
        

Der Alias ist dafür da, damit man in PHP bessere darauf zugreifen kann. Diesen DATE_FORMAT aufruf können wir nun in unserem Newsscript einfügen.

<?php
    $sql
= "SELECT
                news.ID,
                news.Titel,
                news.Inhalt,
                DATE_FORMAT(news.Datum, '%d.%m.%Y %T Uhr') AS Changedatum,
                users.Name,
                users.Email
            FROM
                news,
                users
            WHERE
                news.Autor = users.ID
            ORDER BY
                news.Datum DESC
            LIMIT
                "
.$offset.",".LIMIT_NUM.";";
    
$result = mysql_query($sql) OR die(mysql_error());
?>

In der While-Schleife müssen wir nun das $row['Datum'] in $row['Changedatum'] ändern. Macht nicht den Fehler und setzt den Alias auf den Spaltennamen 'Datum'. Dann erkennt MySQL diese Angabe nicht mehr als Datum sondern als ein String und sortiert entsprechend bei ORDER BY die Datensätze nach diesen Strings. Dies heißt dann, dass wie folgt sortiert wird: 1, 11, 14, 18, 2, 23, 4, 8, also total falsch.

Da man diese Formartierung auch woanders braucht, sollten wir diese global im Script speichern. Dies machen wir am besten mit einer Konstanten.

<?php
    define
('DATE_STYLE', '%d.%m.%Y %T Uhr');
?>

Falls wir die Ausgabe des Datums verändern wollen brauchen wir nur die Konstante verändern. Entsprechend müssen wir auch noch den MySQL-Query bearbeiten.

<?php
    $sql
= "SELECT
                news.ID,
                news.Titel,
                news.Inhalt,
                DATE_FORMAT(news.Datum, '"
.DATE_STYLE."') AS Changedatum,
                users.Name,
                users.Email
            FROM
                news,
                users
            WHERE
                news.Autor = users.ID
            ORDER BY
                news.Datum DESC
            LIMIT
                "
.$offset.",".LIMIT_NUM.";";
    
$result = mysql_query($sql) OR die(mysql_error());
?>

Script liegt im Scriptarchiv: /tutorial/login-news/news-v3.php

11. Kommentarfunktion

Wenn man auf den Link "Kommentare (x)" klickt sollen die Kommentare zu der News kommen. Dort wird nochmal der Newsbeitrag gezeigt und da drunter stehen dann die Newskommentare. Diese soll auch eine Blätterfunktion haben. Das Formular, um einen Kommentar hinzuzufügen, wird nur dann angezeigt, wenn die IP des Users noch nicht in der Datenbank gespeichert wurde.

Script liegt im Scriptarchiv: /tutorial/login-news/news-comment-v1.php

Dies ist erstmal nur das Script um die News anzuzeigen. Nun kommen die Newskommentare und die dazugehörige Blätterfunktion.

Script liegt im Scriptarchiv: /tutorial/login-news/news-comment-v2.php

Bei dem sprintf müssen wir die % in der Konstante DATE_STYLE ändern, da sonst die %'s als Parameterplatzhalter für sprintf gelten und PHP dort dann meckert.

Diesen Quellcode speichern wir erstmal in eine neue Datei namens news_comments.php. Diese includen wird dann aus der news.php bei einer bestimmten Bedingung. In der news.php sieht das dann so aus.

<?php
    
if(isset($_GET['site']) AND "comments" == $_GET['site']) {
        include
"news_comments.php";
    } else {
        
$sql = "SELECT
                    COUNT(*) as Anzahl
                FROM
                    news;"
;
        
$result = mysql_query($sql) OR die(mysql_error());

        
/*
         *  ...
         */
                    
echo "<a href=\"index.php?section=news&amp;page=".$i."\">\n";
                    echo
$i."\n";
                    echo
"</a>\n";
                }
            }
            echo
"</p>\n";
        }
    }   
// <- Am ende des Scripts
?>

Die Kommentare werden nur dann angezeigt, wenn die GET-Variable site den Wert comments hat.

12. Formular der Newskommentare

Damit die Leute auch ein Kommentar schreiben können brauchen wir ein Formular. Dort sind nur 3 Felder: Name, Email und Text. Dieses fügen wir unter den Blätterlinks hinzu.

Script liegt im Scriptarchiv: /tutorial/login-news/news-comment-v3.php

Nun brauchen wir diverse If-Abfragen und ein MySQL-Query der den Kommentar hinzufügt.

Script liegt im Scriptarchiv: /tutorial/login-news/news-comment-v4.php

Damit der User nicht die Newskommentarfunktion vollspammed, schreiben wir nun eine IP-Sperre. Diese ist so ähnlich aufbebaut wie das User-Online-Script.

Script liegt im Scriptarchiv: /tutorial/login-news/news-comment-v5.php

Im Moment haben wir eine riesen Sicherheitslücke. Der User kann im Textfeld alles mögliche eingeben, wie z.B. HTML-Elemente oder auch überlange Wörter. Deswegen schreiben wir eine Funktion die die Eingabe des Users verarbeitet. In dieser Funktion schreiben wir dann auch den Code für die Umwandlungen der Formartierungscode wie [url] und [b].

13. Formartierungscodes ersetzen

In diversen Foren kennt man solche Formartierungscodes wie [url] und [b] um bestimmte Textpasagen in URLs oder ähnliches umzuwandeln. Dies macht man, damit man etwas Kontrolle über die Texte hat. Die Funktion, die wir dann entwicklen, macht dann noch anderen Sachen wie lange Wörter trennen und HTML-Elemente löschen. Diese Funktion definieren wir in der functions.php.

<?php
    
function changetext($str)
    {
        return
$str;
    }
?>

Erstmal löschen wir die Leerzeichen die am Anfang und am Ende stehen.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        return
$str;
    }
?>

Dann trennen wir überlange Wörter. Dies machen wir mit einem Regex.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        return
$str;
    }
?>

Falls eine 60 Zeichen lange Nicht-Whitespace-Zeichenkette gefunden wird (\S{60}) wird diese Zeichenkette (\0) um ein Leerzeichen ( ) erweitert. Der Browser hat dann die Möglich, an dieser Stelle den Text umzubrechen.

Nun wandeln wir alle HTML-Zeichen wie < und > in Steuerzeichen wie &lt; und &gt; um.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        return
$str;
    }
?>

Damit stellen wir sicher, dass keine HTML-Elemente wie <script> geladen werden, sonder dass man dann dieses im Browser sieht. Analog dazu kann man diese HTML-Elemente auch direkt mit striptags löschen.

Nun löschen wir übermäßig viele Leerzeichen und Zeilenumbrüche.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        
$str = preg_replace('/(\s{2})\s+/', '\1', $str);
        return
$str;
    }
?>

Zwei White-Spaces sind erlaubt. Falls welche folgen werden diese gelöscht.

Nun machen wir uns an die Formartierungscodes dran. Zuerst mit etwas einfachem.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        
$str = preg_replace('/(\s{2})\s+/', '\1', $str);
        
$str = preg_replace('=\[b\](.*)\[/b\]=Uis',
                            
'<span style="font-weight:bold;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[i\](.*)\[/i\]=Uis',
                            
'<span style="font-style:italic;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[u\](.*)\[/u\]=Uis',
                            
'<span style="text-decoration:underline;">\1</span>',
                            
$str);
        return
$str;
    }
?>

Nun ersetzen wir die [url] und [url=http://...] sachen.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        
$str = preg_replace('/(\s{2})\s+/', '\1', $str);
        
$str = preg_replace('=\[b\](.*)\[/b\]=Uis',
                            
'<span style="font-weight:bold;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[i\](.*)\[/i\]=Uis',
                            
'<span style="font-style:italic;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[u\](.*)\[/u\]=Uis',
                            
'<span style="text-decoration:underline;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[url\](.*)\[/url\]=Uis',
                            
'<a href="\1">\1</a>',
                            
$str);
        
$str = preg_replace('#\[url=(.*)\](.*)\[/url\]#Uis',
                            
'<a href="\1">\2</a>',
                            
$str);
        return
$str;
    }
?>

Beim 2. Regex habe ich # als Delimiter gewählt, da ich weder das Gleichheitszeichen nocht den Backslash nehmen wollte. Hätte ich eins von denen benutzt müsste ich es im Regex selber escapen, also sowas wie \[url\= schreiben. Man sollte immer ein Delimiter wählen der nicht im Regex vorkommt. Dann sollen noch so einzelne URLs in anklickbare Links umgewandelt werden. Dabei greife ich auf ein Regex in der php-FAQ zurück: 7.12. Wie mache ich aus URIs im Text anklickbare Links? .

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        
$str = preg_replace('/(\s{2})\s+/', '\1', $str);
        
$str = preg_replace('=\[b\](.*)\[/b\]=Uis',
                            
'<span style="font-weight:bold;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[i\](.*)\[/i\]=Uis',
                            
'<span style="font-style:italic;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[u\](.*)\[/u\]=Uis',
                            
'<span style="text-decoration:underline;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[url\](.*)\[/url\]=Uis',
                            
'<a href="\1">\1</a>',
                            
$str);
        
$str = preg_replace('#\[url=(.*)\](.*)\[/url\]#Uis',
                            
'<a href="\1">\2</a>',
                            
$str);
        
$str = preg_replace('#(^|[^"=]{1})(http://|ftp://|mailto:|news:)([^\s<>]+)([\s\n<>]|$)#sm',
                            
'\1<a href="\2\3">\2\3</a>\4',
                            
$str);

        
// ...
        
return $str;
    }
?>

Dann kommen natürlich die Smilies, die in Bilder umgewandelt werden sollen.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        
$str = preg_replace('/(\s{2})\s+/', '\1', $str);
        
$str = preg_replace('=\[b\](.*)\[/b\]=Uis',
                            
'<span style="font-weight:bold;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[i\](.*)\[/i\]=Uis',
                            
'<span style="font-style:italic;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[u\](.*)\[/u\]=Uis',
                            
'<span style="text-decoration:underline;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[url\](.*)\[/url\]=Uis',
                            
'<a href="\1">\1</a>',
                            
$str);
        
$str = preg_replace('#\[url=(.*)\](.*)\[/url\]#Uis',
                            
'<a href="\1">\2</a>',
                            
$str);
        
$str = preg_replace('#(^|[^"=]{1})(http://|ftp://|mailto:|news:)([^\s<>]+)([\s\n<>]|$)#sm',
                            
'\1<a href="\2\3">\2\3</a>\4',
                            
$str);

        
$str = str_replace(':)', '<img src="smile.gif" alt=":)" />', $str);
        
$str = str_replace(';)', '<img src="zwink.gif" alt=";)" />', $str);
        
$str = str_replace(':D', '<img src="grins.gif" alt=":D" />', $str);
        
// ...
        
return $str;
    }
?>

Als letzes müssen noch die Zeilenumbrüche in HTML-Zeichenumbrüche umgewandelt werden.

<?php
    
function changetext($str)
    {
        
$str = trim($str);
        
$str = preg_replace('/\S{60}/', '\0 ', $str);
        
$str = htmlspecialchars($str);
        
$str = preg_replace('/(\s{2})\s+/', '\1', $str);
        
$str = preg_replace('=\[b\](.*)\[/b\]=Uis',
                            
'<span style="font-weight:bold;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[i\](.*)\[/i\]=Uis',
                            
'<span style="font-style:italic;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[u\](.*)\[/u\]=Uis',
                            
'<span style="text-decoration:underline;">\1</span>',
                            
$str);
        
$str = preg_replace('=\[url\](.*)\[/url\]=Uis',
                            
'<a href="\1">\1</a>',
                            
$str);
        
$str = preg_replace('#\[url=(.*)\](.*)\[/url\]#Uis',
                            
'<a href="\1">\2</a>',
                            
$str);
        
$str = preg_replace('#(^|[^"=]{1})(http://|ftp://|mailto:|news:)([^\s<>]+)([\s\n<>]|$)#sm',
                            
'\1<a href="\2\3">\2\3</a>\4',
                            
$str);

        
$str = str_replace(':)', '<img src="smile.gif" alt=":)" />', $str);
        
$str = str_replace(';)', '<img src="zwink.gif" alt=";)" />', $str);
        
$str = str_replace(':D', '<img src="grins.gif" alt=":D" />', $str);
        
// ...

        
$str = nl2br($str);

        return
$str;

    }
?>

Somit sind wir Fertig. Diese Funktion bauen wir nun überall ein.

<?php
    
// news.php

    // ...
    
echo "    <div class=\"newstext\">\n";
    echo
changetext($row['Text']);
    echo
"    </div>\n";
    
// ...
?>
<?php
    
// news_comments.php

    // ...
    
echo "    <div class=\"newstext\">\n";
    echo
changetext($NewsBeitrag['Text']);
    echo
"    </div>\n";

    
// ...

    
echo "    <div class=\"news_comment_inhalt\">\n";
    echo
changetext($row['Text']);
    echo
"    </div>\n";
    
// ...
?>

Wir ändern aber auch die admin_news.php, damit der Newsposter in der Vorschau sehen kann, wie die News dann aussieht.

<?php
    
// admin_news.php

    // ...
    // add und edit Bereich
    
echo "        <li>\n";
    echo
"            Text:\n";
    echo
"            <ul>\n";
    echo
"                <li>\n";
    echo
changetext($_SESSION['text'])."\n";
    echo
"                </li>\n";
    echo
"                <li>\n";
    echo
"                    <input type=\"submit\" name=\"submit\" value=\"Text bearbeiten\" />\n";
    echo
"                </li>\n";
    echo
"            </ul>>\n";
    echo
"        </li>\n";
    
// ...
?>

Überall werden nun die Formartierungen gelöscht. Ein [img]-Code habe ich nicht verwendet weil man damit nur scheisse machen kann: [img]grosses.gif[/img][img]grosses.gif[/img][img]...

Fragen zum aktuellen Thema

  1. Was muss man bei Sessions beachten?
  2. Wie sieht es mit der Sicherheit von Sessions aus?
  3. Was muss man bei verschachtelten MySQL-Querys beachten?
  4. Was gibt die LIMIT Angabe im Query an?
  5. Warum soll man HTML-Element durch eine Code-Elemente ersetzen?
Was muss man bei Sessions beachten?

Man muss, wie hier im Adminbereich, darauf achten das die Session-ID irgendwie an die nächste Seite übermittelt wird. Dies geht entweder mit GET, POST oder mit Cookies.

Wie sieht es mit der Sicherheit von Sessions aus?

Sessions sind vom Hause aus nicht sicher. Wenn jemand die Session-ID von jemand anders hat, so kann er überall zugreifen, wo der andere zugreifen kann. Dies kann man zumindest erschweren, wenn man in der Session die IP des Sessionstarters speichert. Durch eine Überprüfung wird dann der Fremde User rausgeschmissen.

Was muss man bei verschachtelten MySQL-Querys beachten?

Bei verschachtelten MySQL-Querys durch while-Schleife muss man aufpassen dass man nicht den Resource-Link zur Ergebnistabelle überschreibt. Denn sonst wird die äußere Schleife nicht mehr durchlaufen.

Was gibt die LIMIT Angabe im Query an?

Mit dieser LIMIT-Angabe kann man nur eine bestimmte Anzahl von Datensätzen auslesen, bearbeiten oder löschen. Bei einem SELECT-Befehl kann man auch noch ein Offset angeben. Damit wird gesagt, von wo dann n Datensätze ausgelesen werden. Wenn das Limit auf -1 steht, werden alle Datensätze erfasst, sinnvoll bei angaben wie LIMIT 10,-1, um alle Datensätze nach dem 10. zu erfassen.

Warum soll man HTML-Element durch eine Code-Elemente ersetzen?

Einmal um allen anderen zu zeigen wie l33t man ist und das man es drauf hat. Die andere Sache ist die, damit die User keine störende HTML-Elemente posten können und so vielleicht das Design zerstören oder aber auch um die User zu ärgen: <script>document.alert('Na, du Looser');</script>.

Nach oben