{progz}

Cikklista lapozással PHP-ban és MySQL-ben

Az oldal cikklistája eddig egy hosszú, kézzel szerkesztett HTML fájl volt. Most átkerült MySQL-be, és kapott lapozást is. Leírom, hogyan működik, mert a lapozás az a feladat, amit mindenki megír egyszer, és elsőre szinte mindenki hibásan.

A tábla

CREATE TABLE cikkek (
  id       INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  cim      VARCHAR(200) NOT NULL,
  szerzo   VARCHAR(50)  NOT NULL,
  datum    DATE         NOT NULL,
  bevezeto TEXT         NOT NULL,
  KEY (datum)
);

A lista

<?php
$perOldal = 10;

// 1. Oldalszám: ami a GET-ből jön, azt nem hisszük el
$oldal = isset($_GET['oldal']) ? intval($_GET['oldal']) : 1;
if ($oldal < 1) {
    $oldal = 1;
}
$kezdet = ($oldal - 1) * $perOldal;

$db = mysql_connect('localhost', 'progz', 'jelszo') or die('Adatbázis hiba');
mysql_select_db('progz', $db);

// 2. Egy lekérdezés a listára és a teljes darabszámra
$sql = "SELECT SQL_CALC_FOUND_ROWS id, cim, szerzo, datum, bevezeto
        FROM cikkek ORDER BY datum DESC
        LIMIT $kezdet, $perOldal";
$eredmeny = mysql_query($sql, $db);

$osszes = mysql_result(mysql_query('SELECT FOUND_ROWS()', $db), 0);
$oldalakSzama = max(1, ceil($osszes / $perOldal));

// 3. Kiírás: minden szöveg htmlspecialchars-on át
while ($sor = mysql_fetch_assoc($eredmeny)) {
    echo '<h3><a href="cikk.php?id=' . $sor['id'] . '">'
       . htmlspecialchars($sor['cim']) . '</a></h3>';
    echo '<p>' . $sor['datum'] . ' | ' . htmlspecialchars($sor['szerzo']) . '</p>';
    echo '<p>' . htmlspecialchars($sor['bevezeto']) . '</p>';
}

// 4. Lapozósor
if ($oldal > 1) {
    echo '<a href="?oldal=' . ($oldal - 1) . '">&lt;&lt;&lt; Előző</a> ';
}
echo $oldal . '. oldal / ' . $oldalakSzama;
if ($oldal < $oldalakSzama) {
    echo ' <a href="?oldal=' . ($oldal + 1) . '">Következő &gt;&gt;&gt;</a>';
}
?>

Miért pont így?

intval. Az intval('3; DROP TABLE cikkek') eredménye 3. Bármi kerül a címsorba, a lekérdezésbe csak egy szám jut. Mivel a $kezdet is ebből a számból jön, a LIMIT-be sem kerülhet szöveg.

SQL_CALC_FOUND_ROWS. A lapozósorhoz tudni kell, összesen hány cikk van. A naiv megoldás egy külön SELECT COUNT(*). A MySQL 4.0 óta viszont a SQL_CALC_FOUND_ROWS kulcsszóval a szerver a LIMIT ellenére is megszámolja a találatokat, és ezt a FOUND_ROWS() adja vissza.

htmlspecialchars. Egy < jel a cikk címében tönkreteszi az oldalt, egy <script> pedig ennél sokkal rosszabbat is tehet. A szabály egyszerű: ami adatbázisból jön, az kiírás előtt átmegy a htmlspecialchars-on.

Még két apróság

Ha a register_globals be van kapcsolva a tárhelyen, a $oldal változót kívülről is be lehetne állítani. A PHP 4.2 óta ez alapból ki van kapcsolva, de az olcsó tárhelyek egy részén még mindig be van. Ezért kezdi a fenti kód minden változó értékadását nulláról.

A jelszó pedig ne a listázó fájlban legyen, hanem egy külön config.php-ban, lehetőleg a webgyökéren kívül. Így ha egyszer rosszul áll be a szerver, és a PHP forráskód szövegként jelenik meg, a jelszó akkor sem kerül ki.