Artykuły publikowane w internecie są często dzielone na kilka mniejszych podstron. Rozwiązanie takie wykorzystują m.in. witryny ONLamp (http://www.onlamp.com), SitePoint (http://www.sitepoint.com) czy DevShed (http://www.devshed.com). W artykule przedstawimy przykładową implementację stronicowania, w której artykuły, wraz z towarzyszącymi plikami, będą przechowywane w bazie danych.
Prezentacja artykułów w postaci serii niewielkich
podstron ma wiele zalet. Po pierwsze, liczba otwieranych przez użytkownika stron wzrasta. Ponieważ każde otwarcie strony to emisja nowych reklam, zatem stronicowanie zwiększa liczbę wyemitowanych reklam, na czym zyskują dochody serwisu.
Drugi argument dotyczy adresów URL. Serwis stosujący stronicowanie artykułów będzie posiadał znacznie więcej unikalnych adresów URL, co jest dobrze widziane przez wyszukiwarki.Jako trzeci czynniki przemawiający za stronicowaniem należy wymienić funkcjonalność serwisu. Nawigacja po monstrualnie długim tekście jest bardzo niewygodna. Jeżeli poszczególne strony artykułu zawierają ułatwienia w postaci odnośników do spisu treści, do poszczególnych stron czy do kategorii, to witryna taka jest znacznie bardziej funkcjonalna.
Sformułowanie zadania
Dane wejściowe
Dana jest pewna (dowolnie duża) liczba artykułów. Wszystkie artykuły są opracowane w identycznym formacie, w postaci statycznych stron WWW. Każdy artykuł jest zapisany w osobnym folderze. Na artykuł składają się pliki:
- art.html
- style.css
oraz foldery:
- img
- zip
- preview
W folderze img znajdują się wszystkie ilustracje dołączone do artykułu. Folder zip zawiera omówione przykłady i oprogramowanie, zaś w folderze preview umieszczone są przykłady, przeznaczone do podglądu (m.in. statyczne strony WWW oraz obrazy).
W kodzie artykułu art.html znajdują się odwołania do wszystkich plików z folderów img (w postaci znaczników img) oraz zip (w postaci znaczników a).
Całość jest przygotowana w taki sposób, że po otworzeniu dokumentu art.html ujrzysz kompletną
stronę WWW, zawierającą wszystkie obrazy z folderu img i pozwalającą na pobranie spakowanych
plików z folderu zip oraz podgląd przykładów z folderu preview.
Wynikiem przygotowywanych skryptów ma być witryna WWW, która będzie zawierała wszystkie artykuły źródłowe. Z uwagi na dużą liczbę artykułów
należy wprowadzić podział na kategorie.
Każdy z artykułów:
- ma być umieszczony w dokładnie jednej kategorii,
- ma być podzielony na kilka osobnych stron,
- ma zawierać spis treści, odwołujący się do poszczególnych stron artykułu.
Wynikowa witryna WWW
Strona główna serwisu ma prezentować – w postaci menu – dostępne kategorie. Ilustruje to rysunek 1.
Widoczne na nim menu główne zawiera opcje: STRONA GŁÓWNA, BAZY DANYCH, GIMP, HTML/XHTML/CSS oraz PHP.
Po wybraniu jednej z opcji menu głównego, ujrzysz listę artykułów wybranej kategorii.
Nieco poniżej wstępu do każdego artykułu znajduje się strzałka, która umożliwia przeczytanie całego artykułu. Po kliknięciu strzałki pojawia się strona ze spisem treści artykułu.
Każda pozycja spisu treści jest z kolei hiperłączem do konkretnej strony artykułu. Każda ze stron zawiera serię ikon o wyglądzie strzałek. Za pomocą ikon można przewijać tekst do pierwszej, poprzedniej, następnej oraz ostatniej strony. Strzałka do góry ułatwia dotarcie do spisu treści.
Format artykułów wejściowych
Artykuły poddawane stronicowaniu muszą być opracowane w spójny sposób. Wykorzystany przeze mnie format stosuje:
- element h1 do ustalenia tytułu artykułu,
- element h2 do ustalenia autora artykułu,
- elementy h3 oraz h4 do podzielenia artykułu na rozdziały i podrozdziały,
- element p o klasie date do ustalenia daty publikacji
artykułu, - element p o klasie abstract do ustalenia wstępu.
W treści dokumentu wykorzystuję elementy span o klasach filename, variable, program,
option, shortcut, math, title oraz input:
dane.txt
$x
Apache
File → Open
Ctrl+Alt+Del
x + y = 3
Janko Muzykant
cd /tmp
Wstawki kodu umieszczam w elemencie pre. Większe fragmenty, takie jak obrazy, listingi, ramki oraz tabele, umieszczam w sekcjach div, na przykład:
$str = ucfirst($str);
Listing 1. Wywołanie funkcji
ucfirst()
lub
Rys. 1.
Strona HOTELE
Foldery artykułów wejściowych
Artykuły mogą zawierać obrazy, pliki do pobrania
oraz przykłady do podglądu. Obrazy są zapisane
w folderze img. Dopuszczalnymi formatami są png oraz jpg. Nazwy plików zawierają numery, np. 1.png, 2.png, itd. Niektóre ilustracje mogą składać się z kilku plików, wówczas nazwa pliku jest dwuczłonowa: 3-1.jpg, 3-2.jpg, 3-3.jpg (ilustracja
3, człony 1, 2 oraz 3).
Każda z ilustracji jest przygotowana w dwóch wymiarach: w naturalnej wielkości oraz przeskalowana tak, by żaden z jej wymiarów nie przekraczał 400 pikseli. Obrazy duże są zapisane w plikach z przyrostkiem -duze, np. 2-duze.png czy 5-1-duze.jpg.
Zwróć uwagę, że ten sam znacznik img:
może wystąpić w każdym artykule! Będzie to stwarzało pewne problemy przy umieszczaniu obrazów
w bazie danych. Identyczny problem pojawi się także w odniesieniu do plików z folderów zip oraz preview.
W folderze zip umieszczone są pliki do pobrania.
Folder ten może zawierać dane dowolnego typu: .zip, .pdf, .txt, .exe czy .xpi.
Ostatni z folderów nazywa się preview. Może on zawierać dane dowolnego typu przeznaczone do podglądu w przeglądarce. Głównie będą to statyczne strony WWW, obrazy, pliki tekstowe oraz dokumenty PDF.
Zawartość folderów img oraz zip umieszczę w bazie danych, zaś zawartość folderu preview będzie udostępniana z odpowiedniego folderu (bez żadnej modyfikacji).
Dodatkowe pliki danych
Przygotowanie wynikowej witryny wymaga trzech dodatkowych plików:
- url.txt,
- folder.txt
- cathegory.txt.
Pliki takie należy umieścić w folderze każdego artykułu.
Adresy URL: url.txt
Zanim przystąpimy do implementacji stronicowania,
należy ustalić przestrzeń adresów URL. Jest to konieczne, z racji na to, że adresy URL będą występowały w wygenerowanych potem spisach treści artykułów.
Witryna będzie stosowała przyjazne URL-e zapisane
w pliku .htaccess. Strona główna artykułu, zawierająca spis treści, będzie adresowana pewnym
napisem xxxxx.html, zaś kolejne podstrony otrzymają przyrostek -p2, określający numer strony, np. xxxxx-p1.html, xxxxx-p2.html, itd..
Na przykład artykuł pt. \”PEAR::DB – interfejs
API dostępu do bazy danych\” będzie miał adresy:
tutorial-pear-db.html
tutorial-pear-db-p1.html
tutorial-pear-db-p2.html
...
tutorial-pear-db-p7.html
zaś artykuł pt. \”Formatowanie kodu PHP\” otrzyma adresy:
formatowanie-kodu-php.html
formatowanie-kodu-php-p1.html
formatowanie-kodu-php-p2.html
...
formatowanie-kodu-php-p7.html
Dzięki takiemu rozwiązaniu przygotowanie danych umieszczanych w bazie jest niezależne od wykorzystywanych zmiennych URL (czyli id, id2, itd. dołączanych w zapytaniach index.php?id=
4&id2=123).
Adres URL artykułu będzie pochodził z pliku url.txt. W pliku tym w pierwszej linijce wprowadzamy adres URL (bez rozszerzenia .html):
struktura-witryn-php
Unikalność nazw plików: folder.txt
Problem unikalności nazw plików graficznychoraz plików z folderu zip rozwiążemy, konwertując adresy:
dane.zip
do postaci:
dane.zip
gdzie nnnn jest dowolnym (ale unikalnym w skali wszystkich artykułów) napisem. Napis ten będzie pochodził z pliku folder.txt.
Zwróć uwagę, że wykorzystanie do tego celu identyfikatorazbazydanychbyłobykłopotliwe.W przypadku zmiany kolejności rekordów w bazie (np. przez usunięcie i ponowne dodanie), zmianie uległaby – zupełnie niepotrzebnie – treść dokumentu
(dokładniej: odwołania do zewnętrznych plików).
Kategorie: cathegory.txt
Podział artykułów na kategorie wymaga utworzenia pliku cathegory.txt. W pliku tym wprowadzamy,
w dwóch pierwszych linijkach, nazwę kategorii oraz jej adres URL, np.:
HTML/XHTML/CSS
html
W menu pojawi się pozycja HTML/XHTML/CSS. Będzie ona miała adres html.html.
Implementacja stronicowania
Artykuły przeznaczone do stronicowania i umieszczenia w serwisie kopiujemy do jednego folderu. W naszym przykładzie folder ten nazywa się 01-dane. Wewnątrz folderu 01-dane umieszczamy
artykuły, każdy z nich w osobnym folderze. Nazwy folderów zawierających artykuły rozpoczynają
się od przedrostka as-, np. as-gimp-01 czy as-html-03. Utworzenie witryny zawierającej artykuły podzielone na strony będzie przebiegało w kilku etapach.
Etap pierwszy: krojenie
Najpierw artykuły poddamy analizie w celu ustalenia poszczególnych danych. Przetwarzanie wszystkich artykułów odbywa się w pętli sterowanej nazwami folderów, wyznaczonymi funkcją glob():
$foldery = glob(\'../01-dane/as-*\');
foreach ($foldery as $folder) {
...
//analiza pojedynczego artykułu
...
}
Analiza pojedynczego artykułu rozpoczyna się od odczytania treści artykułu:
$tmp = file_get_contents($plik);
Następnie z treści artykułu wycinamy zawartość elementów body oraz h1 (tytuł artykułu). Ustalony tytuł zapisujemy do pliku title.txt, po czym usuwamy
z tekstu artykułu (włącznie ze znacznikami {html}
{/html} i {html}
{/html}):
$body = trim(htmlGetBody($tmp));
$title = trim(htmlGetTitle($body)); file_put_contents $folder.\'/title.txt\', $title);
$body = trim(htmlDeleteTitle($body));
W podobny sposób wyznaczamy autora tekstu:
$author = trim(htmlGetAuthor($body)); file_put_contents folder.\'/author.txt\', $author);
$body = trim(htmlDeleteAuthor($body));
wstęp:
$abstract = trim(htmlGetAbstract ($body)); file_put_contents($folder.\'/abstract.txt\', $abstract);
$body = trim(htmlDeleteAbstract($body));
i datę publikacji:
$data = trim(htmlGetDate($body)); file_put_contents($folder.\'/data.txt\', $data);
$contents = trim(htmlDeleteDate($body));
Po tych operacjach w zmiennej {stala}$contents{/stala} znajduje się pełny tekst artykułu, bez tytułu, autora, wstępu, daty publikacji oraz elementów DOCTYPE, html, head, title czy body.
Teraz przygotowujemy spis treści artykułu. Zadanie to wykonają, na podstawie adresu bazowego odczytanego z pliku url.txt, funkcje {stala}getTableOfContents(){/stala}, {stala}getTableOfContentsAsStringPages(){/stala}
oraz {stala}replaceChaptersPages(){/stala}.
Gotowy spis treści zapisujemy do pliku toc.txt.
$url = trim(file_get_contents($folder. \'/url.txt\'));
$tablicaTOC =getTableOfContents ($contents);
$napisTOC =getTableOfContentsAsStringPages($tablicaTOC, $url);
$newContents =replaceChaptersPages
($tablicaTOC, $contents); file_put_contents ($folder.\'/toc.txt\', $napisTOC);
W zmiennej $newContents znajduje się teraz
treść artykułu, w której rozdziały i podrozdziały zostały ponumerowane, a dodatkowo podrozdziały uzbrojono w identyfikatory id, umożliwiające stosowanie hiperłączy wewnętrznych (tj. zawierających
znak # w adresie).
Treść artykułu dzielimy na strony elementami h3. Po wywołaniu funkcji {stala}split(){/stala}:
$podziel = preg_split(\"/(.*)<\/h3>/Uis\",
$newContents
);
tablica $podziel będzie zawierała – w kolejnych elementach – poszczególne strony artykułu. Strony te wzbogacamy o tytuł (funkcja {stala}split(){/stala} usuwa całe elementy h3 podczas krojenia!), po czym zapisujemy do plików r-1.txt, r-2.txt, itd.:
$ile = count($tablicaTOC);
for ($i = 1; $i <= $ile; $i++) {
$r_tekst = trim($podziel[$i]);
$r_tytul = $tablicaTOC[$i][0];
$r_numer = $i;
$r_tekst = \"{$r_numer}. {$r_tytul}
\" . $r_tekst;
file_put_contents($folder.\'/r-\'. $i . \'.txt\', $r_tekst);
}
Kompletny skrypt ustalający dane artykułów jest przedstawiony na listingu 1.
$foldery = glob(\'../01-dane/as-*\');
foreach ($foldery as $folder) {
$tmp = file_get_contents($plik);
$body = trim(htmlGetBody($tmp));
$title = trim(htmlGetTitle($body));
file_put_contents($folder.\'/title.txt\',$title);
$body = trim(htmlDeleteTitle($body));
$author = trim(htmlGetAuthor($body));
file_put_contents($folder.\'/author.txt\',$author);
$body = trim(htmlDeleteAuthor($body));
$abstract = trim(htmlGetAbstract($body));
file_put_contents($folder.\'/abstract.txt\',$abstract);
$body = trim(htmlDeleteAbstract($body));
$data = trim(htmlGetDate($body));
file_put_contents($folder.\'/data.txt\',$data);
$contents = trim(htmlDeleteDate($body));
$url = trim(file_get_contents($folder.\'/url.txt\'));
$tablicaTOC = getTableOfContents($contents);
$napisTOC = getTableOfContentsAsStringPages($tablicaTOC, $url);
$newContents = replaceChaptersPages($tablicaTOC, $contents);
file_put_contents($folder.\'/toc.txt\',$napisTOC);
$podziel = preg_split(
\"/(.*)<\/h3>/Uis\",
$newContents
);
$ile = count($tablicaTOC);
for ($i = 1; $i <= $ile; $i++) {
$r_tekst = trim($podziel[$i]);
$r_tytul = $tablicaTOC[$i][0];
$r_numer = $i;
$r_tekst = \"{$r_numer}. {$r_tytul}
\" . $r_tekst;
file_put_contents($folder.\'/r-\'.$i.\'.txt\',$r_tekst);
}
}
Skrypt ten wykorzystuje trzy rodzaje funkcji. Funkcje rodziny {stala}htmlGetXXXX(){/stala}:
htmlGetBody()
htmlGetTitle()
htmlGetAuthor()
htmlGetAbstract()
htmlGetDate()
wyznaczają treść konkretnych elementów HTML. Na przykład wynikiem działania funkcji {stala}htmlGetBody(){/stala}
jest zawartość elementu body, zaś wynikiem działania funkcji {stala}htmlGetAbstract(){/stala}
jest zawartość elementu p klasy abstract. Funkcje te są zawarte w pliku html.inc.php. Ich działanie jest oparte o wyrażenia regularne. Treść funkcji {stala}htmlGetDate(){/stala} przedstawiono
na listingu 2.
function htmlGetDate($AStr)
{
if (preg_match(\"|(.*)
|Uis\", $AStr, $matches)) {
return trim($matches[1]);
} else {
return false;
}
}
Drugi rodzaj funkcji rozpoczyna się od przedrostka
htmlDelete. Funkcje te odpowiadają za usunięcie wybranych danych z treści dokumentu:
htmlDeleteTitle()
htmlDeleteAuthor()
htmlDeleteAbstract()
htmlDeleteDate()
Przykładowa funkcja {stala}htmlDeleteTitle(){/stala} została przedstawiona na listingu 3.
function htmlDeleteTitle($AStr)
{
return preg_replace(\"|(.*)
|Uis\",\'\',
$AStr
);
}
Wreszcie funkcje:
getTableOfContents()
getTableOfContentsAsStringPages()
replaceChaptersPages()
pochodzą z pliku toc.inc.php. Ich treść jest bardzo zbliżona do funkcji opisanych w artykule pt. \”Formatowanie artykułów – jak przygotowywać teksty do publikacji na stronach WWW\”.
W wyniku wykonania skryptu z listingu 1 każdy artykuł zostanie przeanalizowany. Wynikiem analizy
będzie ustalenie – w odniesieniu do każdego artykułu – następujących danych:
- tytułu artykułu (plik title.txt),
- autora artykułu (plik author.txt),
- wstępu artykułu (plik abstract.txt),
- daty publikacji artykułu (plik date.txt),
- spisu treści artykułu (plik toc.txt),
- rozdziałów artykułu (pliki r-1.txt, r-2.txt, r-3.txt…),
Etap drugi: tworzenie pustej bazy danych
Najpierw w programie DBDesigner4 przygotowujemy
model bazy danych widoczny na rysunku 2.
Ponieważ wszystkie artykuły wykorzystane w przykładzie są mojego autorstwa, więc zrezygnowałem
z umieszczania w bazie danych tabeli autorów.
Na etapie tworzenia bazy danych warto zwrócić uwagę na kodowanie polskich znaków. Wszystkie artykuły źródłowe stosują kodowanie utf-8. W celu utworzenia bazy danych paged_arts, w której wszystkie tabele będą stosowały utf-8 oraz polskie funkcje do porównywania napisów, należy zapytanie
CREATE DATABASE wzbogacić o klauzulę DEFAULT CHARACTER SET, jak to zostało pokazane na listingu 4.
SET NAMES utf8 COLLATE utf8_polish_
ci;
DROP DATABASE IF EXISTS paged_arts;
CREATE DATABASE paged_arts DEFAULT CHARACTER SET utf8 COLLATE utf8_polish_
ci;
USE paged_arts;
GRANT
SELECT, UPDATE, INSERT, DELETE ON paged_arts.*
TO paa@localhost IDENTIFIED BY \'artsstr\';
FLUSH PRIVILEGES;
CREATE TABLE tarticle (
...
);
CREATE TABLE tcathegory (
...
);
CREATE TABLE timg (
...
);
CREATE TABLE ttext (
...
);
CREATE TABLE tzip (
...
);
33
stronicowanie
Jeżeli teraz wydawanie zapytań SQL rozpoczniesz
od:
SET NAMES utf8 COLLATE utf8_polish_ci;
to baza danych będzie zawierała polskie znaki w kodach utf-8.
Listing 5. Zarys klasy DBA
class DBA
{
private $Fdb;
public function __construct()
{
$dsn = \'mysqli://paa:artsstr@
localhost/paged_arts\';
$this->Fdb = DB::connect($
dsn);
if (DB::isError($this->
Fdb)) {
die(__LINE__ . \': \' . $this->Fdb->getMessage());
}
$this->Fdb->setFetchMode(
DB_FETCHMODE_ASSOC);
$this->Fdb->query(\'SET NAMES
utf8 COLLATE utf8_polish_ci\');
}
function DBA_podaj_id_artykulu($
ATytul)
{
...
}
public function DBA_wstaw_artykul()
{
...
}
....
}
Etap trzeci: implementacja klasy DBA
oraz aktywnych rekordów
Cały dostęp do bazy danych realizuję za pośrednictwem klasy DBA. Klasa ta, w swoim konstruktorze, tworzy obiekt klasy PEAR::DB.
Wydając w konstruktorze zapytanie SET NAMES, wymuszam stosowanie kodowania utf-8 w odniesieniu do wszystkich kolejnych zapytań. Zapytania wykonuję za pośrednictwem metod {stala}getOne(){/stala}, {stala}getRow(){/stala}, {stala}getAll(){/stala} oraz {stala}query(){/stala} klasy PEAR::DB.
W przykładzie zaimplementowałem także klasy: Artykul, Cathegory, Image oraz Zip. Są one tzw. aktywnymi rekordami: obiektami, które potrafią synchronizować wartości swoich pól z bazą danych.
Zarys klasy Image został przedstawiony na listingu 6.
class Image implements DBObject
{
private $Fdb;
public $Fid;
public $Ftartykul_id;
public $Fimg;
public $Flarge;
public $Fformat;
public $Ffilename;
public function __construct($ADataSource)
{
}
public function set($AArray)
{
}
public function find()
{
}
public function findId($AId)
{
}
public function insert()
{
}
}
Klasa ta posiada pola identyczne z polami tabeli timg oraz metody umożliwiające wstawianie i wyszukiwanie obrazu w bazie.
Etap czwarty:
umieszczenie danych w bazie
Przystępujemy do umieszczenia artykułów w bazie danych. Podobnie jak w przypadku krojenia,
wyszukujemy wszystkie foldery zawierające artykuły, po czym każdy z nich przetwarzamy:
$f = \'../01-dane/\';
$foldery = glob($f . \'as-*\');
foreach ($foldery as $folder) {
....
//dodawanie do bazy danych
//pojedynczego artykułu
...
}
Przetwarzanie pojedynczego artykułu rozpoczynamy
od wczytania plików z danymi:
$title = trim(file_get_contents($
folder . \'title.txt\'));
$abstract = trim(file_get_contents($
folder . \'abstract.txt\'));
$d = trim(file_get_contents($
folder . \'data.txt\'));
$toc = trim(file_get_contents($
folder . \'toc.txt\'));
$url = trim(file_get_contents($
folder . \'url.txt\'));
$tmpcath = file($folder.\'cathegory.
txt\');
$cathegory = trim($tmpcath[0]);
$cathURL = trim($tmpcath[1]);
$fld =trim(file_get_contents($
folder . \'folder.txt\'));
$rozdzialy = glob($folder . \'r-*.txt\');
$pages = count($rozdzialy);
Następnie wykorzystując klasę DBA oraz aktywne rekordy, dodajemy kolejne dane do bazy. Za dodawanie kategorii odpowiada kod:
$objCathegory = new Cathegory($db);
$atr = array(
\'name\' => $cathegory,
\'url\' => $cathURL
);
$objCathegory->set($atr);
switch ($objCathegory->insert()) {
case 1:
echo \"DODANO kategoria: $cathegory
\";
break;
case 2:
echo \"******* JEST kategoria:
$cathegory
\";
break;
case 3:
echo \"******* BŁĄD WSTAWIANIA:
kategoria: $cathegory
\";
break;
case 4:
echo \"******* BŁĄD PO WSTAWIENIU:
kategoria: $cathegory
\";
break;
}
$tcathegory_id = $objCathegory->Fid;
Oprócz kategorii, do bazy danych należy dodać artykuły, obrazy, pliki zip oraz poszczególne strony. Proces ten przebiega dość podobnie, z tą różnicą, że w przypadku obrazów oraz plików zip należy najpierw wyszukać pliki.
Pliki z folderu zip wyszukujemy funkcją {stala}glob(){/stala}:
$zipy = glob($folder . \'zip/*.*\');
natomiast do ustalenia obrazów wykorzystałem funkcję {stala}files_from_folder(){/stala}. Umożliwia ona wyszukiwanie plików, których nazwa pasuje do wyrażenia regularnego, dzięki czemu pojedynczym
wywołaniem mogę odnaleźć wszystkie pliki .jpg oraz .png:
$obrazy = files_from_folder($folder.\'img/\', \'-duze\.((png)|(jpg))$\');
Zwróć uwagę na fakt, że treść artykułu musi zostać poddana transformacji. Artykuły oryginale zawierały odwołania do obrazów oraz plików zip postaci:
dane.zip
Odwołania te należy zamienić na:
dane.zip
gdzie XXXX jest wartością odczytaną z pliku folder.txt. Funkcja transformacja_rozdzialu()
odpowiedzialna za takie przekształcenie została przedstawiona na listingu 7.
function transformacja_rozdzialu($APlk, $AId)
{
$tmp = $APlk;
$tmp = ereg_replace(
\"\",
\"\",
$tmp
);
$tmp = ereg_replace(
\"\",
\"\",
$tmp
);
$tmp = ereg_replace(
\"\",
\"\",
$tmp
);
$tmp = ereg_replace(
\"\",
\"\",
$tmp
);
$tmp = ereg_replace(
\"\",
\"\",
$tmp
);
return $tmp;
}
Etap piąty: ustalenie
przestrzeni adresowej aplikacji
Aplikacja prezentująca artykuły zawarte w bazie danych wykorzystuje siedem akcji ponumerowanych
od 1 do 7. Numer akcji jest ustalany zmienną URL o nazwie id (czyli $_GET[\’id\’]). Pełne zestawienie
zmiennych URL stosowanych w aplikacji jest zawarte w tabeli 1.
Każda z akcji powoduje przejście do strony wybranego
typu. Zauważ, że – ze względu na fakt, iż obrazy i pliki zip są zapisane w bazie danych – wysyłanie
obrazów oraz plików z danymi wymaga dodatkowych akcji. Pamiętaj, że obrazy zapisane w bazie danych nie mają adresów URL, nie mogą zatem być udostępniane poprzez standardowe znaczniki postaci:
Konieczny jest skrypt PHP, który komunikuje się z bazą danych i wysyła strumień bajtów pliku JPG w odpowiedzi na żądania HTTP. Oczywiście po zastosowaniu przyjaznych URL-i adres obrazu przyjmuje postać:
jednak dostęp do niego odbywa się w sposób emulowany (takiego pliku w systemie nie ma):
- do serwera Apache dociera żądanie HTTP o plik nazwapliku.jpg,
- żądanie zostaje przekierowane pod adres {stala}index.php?id=1&id2=987{/stala},
- skrypt index.php komunikuje się z bazą danych i odczytuje obraz JPG,
- skrypt index.php wysyła strumień JPG, dołączając
stosowny nagłówek HTTP.
Etap szósty: bezpieczne adresy URL
Po przygotowaniu bazy danych oraz ustaleniu przestrzeni adresowych URL (zarówno: przyjaznych
adresów jak i zmiennych z tablicy {stala}$_GET{/stala}) przystępuję do wygenerowania plików .htaccess oraz filtr.log definiujących konwersje bezpiecznych adresów URL.
W plikach tych należy umieścić wpisy dotyczące:
- każdej strony artykułu,
- każdego obrazu,
- każdego pliku zip,
- każdej kategorii
W pliku .htaccess umieszczamy konwersje artykułu:
RewriteRule pierwsze-kroki-z-mysql.html index.php?id=4&id2=1
RewriteRule pierwsze-kroki-z-mysql-p1.html index.php?id=5&id2=1&id3=1
RewriteRule pierwsze-kroki-z-mysql-p2.html index.php?id=5&id2=1&id3=2
...
kategorii:
RewriteRule gimp.html index.php?id=3&id2=2
RewriteRule html.html index.php?id=3&id2=3
...
obrazów:
RewriteRule img/bd-01/1.png index.php?id=6&id2=1
RewriteRule img/bd-02/1.png index.php?id=6&id2=2
...
oraz plików zip:
RewriteRule zip/bd-01/01-bazaosob\.zip index.php?id=7&id2=1
RewriteRule zip/bd-01/02-hotele\.zip index.php?id=7&id2=2
...
Wpisy w pliku filtr.log różnią się kolejnością oraz obecnością cudzysłowów. Konwersja adresów dotyczących jednego z artykułów ma postać:
\"index.php?id=4&id2=1\" \"pierwsze-kroki-z-mysql.html\"
\"index.php?id=5&id2=1&id3=1\" \"pierwsze-kroki-z-mysql-p1.html\"
\"index.php?id=5&id2=1&id3=2\" \"pierwsze-kroki-z-mysql-p2.html\"
...
Zawartość plików .htaccess oraz filtr.log powstaje na podstawie bazy danych. Skrypt generuj-translacje.php ustala nazwy wszystkich plików graficznych zawartych w bazie danych:
$obrazy = $db->DBA_podaj_wszystkie_obrazy();
Nazwy te są przekazane do szablonu:
$s->assign(\'obrazy\', $obrazy);
Przetworzony szablon:
$f = $s->fetch(\'filtr.tpl\');
zapisuję do pliku:
file_put_contents(\'filtr/filtr.log\',$f);
W szablonie filtr.tpl odpowiednia pętla section produkuje wszystkie wpisy dotyczące obrazów:
{section name=i loop=$obrazy}
\"index.php?id=6&id2={$obrazy[i].timg_id}\" \"img/{$obrazy[i].folder}/{$obrazy[i].filename}\"
{/section}
W przypadku plików z folderu zip pojawia się pewna komplikacja. Otóż wpisy pliku .htaccess są wyrażeniami regularnymi. Nazwy plików zip dołączanych do artykułów mogą zawierać znaki, które są traktowane jako metaznaki wyrażeń regularnych. Taka sytuacja ma miejsce w przypadku
pliku {stala}html_validator-0.7.9-fx+mz-windows.xpi{/stala} (metaznakami wyrażeń regularnych są + oraz .). Nazwa pliku a+b.zip spowoduje komplikacje. Zapis:
RewriteRule a+b.zip index.php?a=567
jest niepoprawny. Reguła ta powinna mieć postać:
RewriteRule a\+b.zip index.php?a=567
Cytowanie meta znaków wyrażeń regularnych wykonuje funkcja {stala}quotemeta(){/stala}. Za jej pomocą nazwy wszystkich plików zip przed przekazaniem do szablonu przekształcamy, cytując meta znaki:
$zipy = $db->DBA_podaj_wszystkie_zipy();
$zipy_c = count($zipy);
for ($i = 0; $i < $zipy_c; $i++) {
$zipy[$i][\'filename\']=quotemeta($
zipy[$i][\'filename\']);
}
Etap siódmy: skrypt główny
Skrypt główny aplikacji jest zapisany w pliku index.php. Plik ten zawiera szkielet kodu aplikacji. Odbywają się w nim:
- inicjalizacja zmiennych i utworzenie obiektów,
- walidacja poprawności zmiennych z tablicy {stala}$_GET{/stala} i ustalenie wybranej akcji,
- pobranie z bazy danych informacji wymaganych przez wybraną akcję i przekazanie ich do szablonu,
- przetworzenie szablonu.
Ponieważ w akcji o numerze 6 żądanie HTTP dotyczy pliku graficznego zapisanego w bazie danych,
zatem należy wymienić nagłówek Content-type.
Wysyłanie pliku jest zrealizowane następująco:
case 6:
$obraz = $db->DBA_podaj_obraz($_GET[\'id2\']);
header(\"Content-type: image/\" . $obraz[\'format\']);
echo $obraz[\'img\'];
exit();
Nieco bardziej skomplikowane jest wysyłanie
plików z tabeli tzip. W ich przypadku należy napisać funkcję {stala}podaj_typ_pliku(){/stala}, która na podstawie rozszerzenia nazwy pliku ustali odpowiedni nagłówek Content-type:
$typ_pliku = podaj_typ_pliku($zip[\'format\']);
header($typ_pliku);
Pamiętaj, że po przetworzeniu całego szablonu instrukcją:
$strona = $s->fetch(\'index.tpl\');
w otrzymanej stronie WWW należy przekształcić adresy URL.
W tym celu odczytujemy plik filtr.log,po czym wywołujemy funkcję {stala}str_replace(){/stala},
przekazując jej jako parametry zamieniane adresy:
$tmpFiltr = file_get_contents(\'filtr.log\');
$tmpFiltr= uncomment_and_trim($tmpFiltr);
list($w, $c, $filtr)=string2VArray($tmpFiltr, \"\t\");
$strona = str_replace($filt[0],$filtr[1], $strona);
Do sprawdzenia poprawności kodu XHTML wykorzystuję wbudowane właściwości Firefoksa. Wysyłając stronę jako typ {stala}application/xhtml+xml{/stala}:
header(\'Content-Type: application/xhtml+xml; charset=utf-8\');
echo $strona;
wymuszam sprawdzanie poprawności kodu HTML przez przeglądarkę.
Etap ósmy: kopiowanie plików z folderów preview
Na zakończenie należy przekopiować pliki z folderów
preview dołączonych do poszczególnych artykułów. Idea folderu preview jest prosta: jeśli artykuł
omawia tworzenie witryny WWW, to należy czytelnikowi umożliwić podejrzenie wykonanego projektu.
Folder preview nie narzuca żadnych założeń: dane, jakie są w nim umieszczone, zostaną udostępnione
w postaci statycznych plików. W oryginalnych
artykułach odwołania do folderu preview wyglądały następująco:
index.html
Podczas wstawiania stron artykułu do bazy danych odwołania te zastąpiono przez:
index.htmlgdzie NNNN jest napisem pochodzącym z pliku foler.txt, np.:
index.html
Należy zatem utworzyć folder preview/gimp-05 oraz umieścić w nim wszystkie przykłady z folderu preview artykułu gimp-05. Zadanie to należy wykonać w odniesieniu do każdego artykułu.
Uwagi końcowe
W opisanym rozwiązaniu warto zwrócić uwagę na kilka ciekawostek. Po pierwsze, zauważ,
że spis treści artykułu jest zawarty w bazie danych w postaci statycznego dokumentu HTML. Dokument ten nie jest generowany przy każdej wizycie.
Po drugie, przyjrzyj się bliżej zawartości pliku toc.txt. Plik ten zawiera odwołania do statycznych dokumentów HTML. Poszczególne strony WWW artykułu poddanego stronicowaniu są dostępne - za pośrednictwem przyjaznych adresów URL - pod statycznymi adresami.
W ten sposób oddzielamy
przestrzeń zmiennych URL (czyli zawartość tablicy {stala}$_GET{/stala}) od zawartości dokumentów (m.in. spisów treści) przechowywanych w bazie danych. Innymi słowy: zmienne tablicy {stala}$_GET{/stala} mogą być w aplikacji dowolnie modyfikowane i zmieniane. Nie powoduje to konieczności jakichkolwiek zmian w spisach treści.
Podobnie sprawa wygląda w przypadku obrazów
oraz plików zip dołączanych do dokumentu. Pisząc artykuł, operujemy prostymi i intuicyjnymi nazwami plików:
Wstawiając dokument do bazy danych, dodajemy
jedynie folder, np. gimp-05 (nazwa folderu pochodzi z pliku folder.txt), w celu uniknięcia niejednoznaczności.
Zatem pisząc tekst artykułu, operujemy króciutką i intuicyjną nazwą pliku 1.png. Problem niejednoznaczności rozwiązujemy, dodając jeden plik folder.txt, a dane zawarte w bazie nie zawierają
odwołań do zmiennych URL z tablicy {stala}$_GET{/stala}. Prawda, że proste?