Witryna xhtmlpp.gajdaw.pl stanowi suplement do książki pt. „HTML, XHTML i CSS. Praktyczne projekty”. Wykonując witrynę wykorzystałem: PHP, Smarty, MySQL, phpMyAdmin, Propel, aplikację DBDesigner oraz edytor NotH. Przyjazne adresy URL wykonałem wykorzystując moduł mod_rewrite i pliki .htaccess. Wszystkie podstrony witryny pochodzą z bazy danych, zaś obrazy, style CSS i skrypty JavaScript są udostępniane jako statyczne pliki.
Struktura funkcjonalna witryny
Witryna xhtmlpp.gajdaw.pl zawiera:
- ogólne informacje o książce (m.in. spis treści, metrykę, podziękowania, bibliografię cytowaną w książce w postaci listy odsyłaczy),
- wszystkie rozdziały (każdy rozdział ma swoją stronę),
- wszystkie projekty (każdy projekt ma swoją stronę),
- wszystkie ćwiczenia (każde ćwiczenie ma swoją stronę).
Powyższe kategorie wyznaczają podział witryny na następujące podstrony:
- strona błędu 404,
- strona z tekstem,
- strona rozdziału,
- strona projektu,
- strona ćwiczenia.
Strona błędu 404 jest wyświetlana w przypadku podania błędnego adresu URL, np. http://xhtmlpp.gajdaw.pl/abc.
Strony z tekstem zawierają informacje dostępne w menu pionowym:
- od autora,
- podziękowania,
- metryka,
- dla kogo,
- ćwiczenia,
- projekty,
- NotH,
- moje książki,
- adresy,
- index.php?id=2&id2=2 – pełny spis treści
- index.php?id=2&id2=3 – od autora
- …
Na podstronie rozdziału (tj. gdy id=3) zmienna id2 zawiera identyfikator rozdziału, np.:
- index.php?id=3&id2=1 – rozdział 1
- index.php?id=3&id2=2 – rozdział 2
- index.php?id=3&id2=3 – rozdział 3
- …
Na podstronie projektu (tj. gdy id=4) zmienna id2 zawiera identyfikator projektu, np.:
- index.php?id=4&id2=1 – projekt 11.1
- index.php?id=4&id2=2 – projekt 11.2
- index.php?id=4&id2=3 – projekt 11.3
- …
A na podstronie ćwiczenia (tj. gdy id=5) zmienna id2 zawiera identyfikator ćwiczenia, np.:
- index.php?id=5&id2=1 – ćwiczenie 2.1
- index.php?id=5&id2=2 – ćwiczenie 3.1
- index.php?id=5&id2=3 – ćwiczenie 3.2
- …
Baza danych
Baza danych składa się z pięciu tabel. Są to:
- czesc,
- rozdzial,
- projekt,
- cwiczenie
- menu.
Tabela czesc zawiera części, na jakie jest
podzielona książka. Jest ona wykorzystana wyłącznie
podczas tworzenia spisu treści książki.
Tabela rozdzial zawiera wszystkie rozdziały
występujące w książce. Tabele projekt i cwiczenie
zawierają wszystkie projekty oraz ćwiczenia.
Tabela menu zawiera teksty dostępne
w menu pionowym oraz w menu poziomym.
Tabele czesc, rozdzial, cwiczenie i projekt
są powiązane relacjami 1:n:
- każdy rozdział należy do jednej z części,
- każdy projekt należy do jednego z rozdziałów,
- każde ćwiczenie należy do jednego z rozdziałów.
Implementacja
Krok pierwszy: utworzenie pustej bazy danych
Pracę nad implementacją witryny rozpoczynamy
od przygotowania modelu bazy danych.
W programie DBDesigner należy opracować
model. Następnie model zapisany w pliku model.xml przekształcamy
do formatu aplikacji Propel i zapisujemy
pod nazwą schema.xml. Plik schema.xml przetwarzamy
aplikacją Propel.
Wynikiem działania Propela będą:
- klasy dostępu do bazy danych (folder build/xhtmlpp),
- skrypt z konfiguracją dostępu do bazy build/conf/xhtmlpp-conf.php,
- skrypt SQL o nazwie build/sql/schema.sql tworzący tabele bazy danych.
Następnie przygotowujemy skrypt SQL nazywający się base.sql:
SET NAMES utf8 COLLATE utf8_polish_ci;
DROP DATABASE IF EXISTS xhtmlpp;
CREATE DATABASE xhtmlpp DEFAULT CHARACTER SET utf8 COLLATE utf8_polish_ci;
GRANT INSERT, DELETE, UPDATE, SELECT
ON xhtmlpp.* TO xhtmlppadm@localhost IDENTIFIED BY \'xhtmlpppass\';
FLUSH PRIVILEGES;
USE xhtmlpp;
Łącząc ten skrypt ze skryptem schema.sql
wygenerowanym przez aplikację Propel otrzymamy
skrypt SQL tworzący pustą bazę danych
o nazwie xhtmlpp i strukturze odpowiadającej
modelowi.
Całe zadanie, a więc:
- przekształcenie pliku model.xml na schema.xml,
- uruchomienie aplikacji Propel,
- utworzenie pustej bazy danych,
możemy zautomatyzować wykorzystując plik
wsadowy 1-generowanie/4-all.bat.
Wykonanie tego etapu pracy możemy
sprawdzić wykorzystując aplikację phpMyAdmin.
Krok drugi: przygotowanie danych
Drugim etapem jest przygotowanie danych.
Najpierw tworzymy pliki: czesc.txt, rozdzial.
txt, projekt.txt oraz cwiczenie.txt.
Plik czesc.txt zawiera wszystkie części na
jakie podzielono treść książki:
czesc.txt
Część pierwsza|Elementarz
Część druga|Czcionki na stronach WWW
...
Plik rozdzial.txt zawiera wszystkie rozdziały:
rozdzial.txt
Wprowadzenie|1|0
Składnia języka XHTML|2|1
Znaki diakrytyczne i oznaczanie języka .
dokumentu|3|1
...
Kolejne kolumny to:
- tytuł rozdziału,
- numer rozdziału,
- numer części, z której rozdział pochodzi.
Plik projekt.txt jest nieco bardziej złożony.
W kolejnych kolumnach zawiera on:
- numer rozdziału,
- numer projektu,
- numer pełny,
- nazwę strony głównej projektu
- tytuł projektu, np.:
projekt.txt
11|1|11-01|index.html|Zadania dla czwartoklasistów
11|2|11-02|index.html|Zadania tekstowe .
z odpowiedziami
11|3|11-03|index.html|Kolokwium z PHP
...
Plik cwiczenie.txt opisuje wszystkie ćwiczenia:
cwiczenie.txt
2|1|02-01|index.html|0
3|1|03-01|index.html|1
3|2|03-02|index.html|1
...
Kolumny tego pliku to:
- numer rozdziału,
- numer projektu,
- numer pełny,
- nazwa strony głównej ćwiczenia,
- pole logiczne informujące o tym, czy w ćwiczeniu wykorzystano jakiekolwiek dane.
Oprócz czterech wymienionych plików należy
także przygotować teksty, które mają być
wyświetlane po wybraniu opcji menu głównego.
Pliki dotyczące menu są zawarte w podfolderze
menu. Plik dane-menu.txt opisuje
wszystkie dostępne opcje:
Spis treści|spis-tresci|0|Spis treści - HTML, XHTML i CSS. Praktyczne projekty
Pełny spis treści|pelny-spis-tresci|0|Pełny spis treści - HTML, XHTML i CSS. Praktyczne projekty
Od autora|index|1|Włodzimierz Gajda: HTML, XHTML i CSS. Praktyczne projekty
...
Kolejne kolumny to:
- tytuł jaki będzie wyświetlany w menu,
- nazwa pliku tekstowego, a zarazem przyjazny URL,
- pole logiczne mówiące o tym, czy opcja jest dostępna w menu pionowym,
- tytuł jaki będzie zawarty w elemencie title.
Pliki:
- index.txt
- metryka.txt
- dla-kogo.txt
- spis-tresci.txt
- …
zawierają teksty, które będą wyświetlane na
stronie po wybraniu odpowiedniej opcji menu.
Uwaga! Cztery pliki:
- spis-tresci.txt
- pelny-spis-tresci.txt
- cwiczenia.txt
- projekty.txt
należy pozostawić puste! Ich treść będzie wygenerowana
automatycznie w kroku czwartym
na podstawie zawartości bazy danych.
Należy także przygotować:
- wszystkie projekty i ćwiczenia w formacie XHTML/CSS,
- zrzuty ekranowe (tj. pliki JPG) przedstawiające wygląd projektów i ćwiczeń w przeglądarce,
- dane wykorzystane w projektach oraz ćwiczeniach.
Materiał dołączony do artykułu zawiera,
podobnie jak materiał dostępny w internecie,
wyłącznie zrzuty ekranowe w formacie JPG.
Ćwiczenie i projekty w formacie XHTML/CSS
oraz dane są zawarte wyłącznie na płycie CD
dołączonej do książki.
Krok trzeci: wstawianie rekordów
Następnie przechodzimy do wypełnienia bazy
rekordami.
Wypełnianie tabeli czesc na podstawie pliku tekstowego czesc.txt przebiega następująco:
$plk = file(\'dane/czesc.txt\');
$plk_c = count($plk);
for ($i = 0; $i < $plk _ c; $i++) {
$el = explode(\'|\', trim($plk[$i]));
$objCzesc = new Czesc;
if (trim($el[0])) {
$objCzesc->setTytul(trim($el[0]). \': \' . trim($el[1]));
} else {
$objCzesc->setTytul(\'\');
}
$objCzesc->save();
}
W podobny sposób wypełniamy tabele rozdzial, projekt, cwiczenie oraz menu.
Jeśli tabela zawiera klucze obce, to wstawienie
rekordu należy poprzedzić ustaleniem
klucza obcego. Na przykład identyfikator rozdziału o numerze zawartym w zmiennej $rozdzial
wydrukuje następujący skrypt:
$rozdzial = 17;
$kryteria = new Criteria;
$kryteria->add(RozdzialPeer::NUMER,$rozdzial);
$rozdzialy = RozdzialPeer::doSelect($kryteria);
if (count($rozdzialy) != 1) {
die(\'Problem #1\');
}
echo $rozdzialy[0]->getRozdzialId();
Krok czwarty: automatyczne generowanie spisu treści, listy ćwiczeń i listy projektów
Po wypełnieniu bazy danych wracamy do przygotowania
czterech plików:
- spis-tresci.txt
- pelny-spis-tresci.txt
- cwiczenia.txt
- projekty.txt
Pliki te należy wygenerować na podstawie
zawartości bazy danych. Oczywiście podstrony
Spis treści, Pełny spis treści, Ćwiczenia i Projekty
mogłyby być generowane na podstawie
bazy danych przy każdej wizycie. Jednak biorąc
pod uwagę to, że treść tych plików nigdy
nie będzie ulegała zmianie, lepiej wygenerować
zawartość jeden raz i zapisać w bazie
danych w postaci statycznych danych.
Generowanie listy wszystkich ćwiczeń przebiega
następująco:
$s = new Smarty;
$c = new Criteria;
$cwiczenia = CwiczeniePeer::doSelect($c);
for ($i = 0; $i < count($cwiczenia) % 4; $i++) {
array _ push($cwiczenia, \'\');
}
$cwiczenia = array_1dim_to_2dim ($cwiczenia, 4);
$s->assign(\'cwiczenia\', $cwiczenia);
$tmp = $s->fetch(\'cwiczenia.tpl\');
file_put_contents(\'dane/cwiczenia.txt\', $tmp);
Kolejno:
- pobieramy wszystkie ćwiczenia z bazy,
- otrzymaną tabelę uzupełniamy tak, by liczba jej elementów była podzielna przez 4,
- zmieniamy tablicę jednowymiarową na dwuwymiarową o czterech kolumnach,
- przetwarzamy szablon, a otrzymaną stronę zapisujemy do pliku cwiczenia.txt.
Uruchomienie skryptu 4-pomoc/pomoc.
bat spowoduje wygenerowanie czterech plików
tekstowych i zapisanie ich w folderze
dane/. Pliki te należy przekopiować do folderu
dane/menu/ w kroku trzecim, utworzyć
nową pustą bazę danych (skrypt 1-generowanie/3-create-database.bat) i od nowa wypełnić ją danymi.
Krok piąty: zrzut bazy danych
Teraz, gdy baza danych została poprawnie wypełniona,
przygotowujemy tzw. zrzut bazy danych, tj. skrypty SQL tworzące bazę danych wypełnioną rekordami. Ułatwi to odtworzenie
bazy danych oraz utworzenie bazy na innym
serwerze MySQL (np. na serwerze dostawcy hostingu).
Skrypt base.sql jest skopiowany z kroku
pierwszego. Skrypt schema.sql jest wygenerowany
przez Propel (folder build/sql/). Natomiast
plik xhtmlpp.sql zawiera wynik operacji
Eksport, wykonanej w programie phpMyAdmin.
Właściwości eksportu zostały ustawione następująco:
- wszystkie tabele,
- bez struktury, tylko zawartość,
- pełne, ale nie rozszerzone, dodania.
Skrypt 5-zrzut-db/zrzut-db.bat automatyzuje
wykonanie powyższych trzech skryptów
SQL. Najpierw tworzy skrypt zrzut-db.sql,
a następnie wykonuje go w konsoli mysql.
W ten sposób wypełniona rekordami baza
danych może zostać utworzona pojedynczym
uruchomieniem skryptu zrzut-db.bat.
Jeśli dostawca usług hostingowych nie pozwala na korzystanie
z shella, ale umożliwia korzystanie
z phpMyAdmin, to wystarczy w aplikacji php-
MyAdmin (w panelu zapytań SQL) wykonać
skrypty schema.sql oraz xhtmlpp.sql.
Krok szósty: szablon XHTML/CSS
Wykorzystany szablon XHTML/CSS opisałem
już szczegółowo w artykule pt. \”Elementy div
i style CSS w praktyce\” (Magazyn INTERNET,
11/2005). Szablon ten w zmienionej kolorystyce
został użyty na stronach książek:
- HTML, XHTML i CSS. Praktyczne projekty (http://xhtmlpp.gajdaw.pl),
- GIMP. Praktyczne projekty (http://gpp.gajdaw.pl),
- GIMP w zastosowaniach (http://gwz.gajdaw.pl).
Krok siódmy: skrypt PHP
Skrypt odpowiada za udostępnienie na stronie
WWW zawartości bazy danych wyłącznie do
odczytu. Strony nie zawierają żadnych formularzy
umożliwiających dodawanie informacji do
bazy danych. To znacznie upraszcza implementację.
Zadanie sprowadza się bowiem do:
- inicjalizacji,
- sprawdzenia poprawności zmiennych URL,
- pobrania danych z bazy,
- przetworzenia szablonu.
Proces inicjalizacji rozpoczynamy od wyłączenia
wyświetlania błędów:
error_reporting(0);
ini_set(\'display _ errors\', 0);
//następnie dołączamy wszystkie konieczne pliki:
require_once \'Smarty.class.php;
require_once \'include/walidacja.inc.php\';
...
tworzymy połączenie z bazą danych:
Propel::init(\'xhtmlpp-conf.php\');
$con = Propel::getConnection(\'xhtmlpp\');
$sql = \'SET NAMES utf8;\';
$stmt = $con->createStatement();
$rs = $stmt->executeQuery($sql);
// tworzymy nowy obiekt Smarty:
$s = new Smarty;
pobieramy z bazy danych opcje menu i przekazujemy je do szablonu:
$c = new Criteria;
$c->add(MenuPeer::CZYLEWEMENU,1);
$opcjemenu = MenuPeer::doSelect($c);
$s->assign(\'opcjemenu\', $opcjemenu);
ustalamy stronę domyślną na stronę błędu 404:
$action = 1;
po czym, w przypadku gdy użyto adresu bez
żadnych zmiennych, przekierowujemy aplikację
na stronę główną (o adresie index.php?id=2&id2=1):
if (empty($_GET)) {
$_GET[\'id\'] = \'2\';
$_GET[\'id2\'] = \'3\';
}
Sprawdzanie poprawności zmiennych URL
jest ujęte w dużą instrukcję if stwierdzającą
obecność i poprawność zmiennej id:
if (isset($_GET[\'id\']) && str_ievpifr . ($_GET[\'id\'], 2, 5)) {
...
}
W poszczególnych przypadkach sprawdzamy
poprawność zmiennej id2. Jeśli, na przykład,
id=4, to jesteśmy na stronie projektu.
Sprawdzamy czy id2 jest poprawnym identyfikatorem
projektu i jeśli tak, tworzymy obiekt
$projekt:
...
} elseif ($_GET[\'id\'] == 4) {
if ((count($_GET) == 2) && isset($_GET[\'id2\']) && str_ievpi($_GET[\'id2\']) && ($projekt = ProjektPeer::retrieveByPK ($_GET[\'id2\']))) {
$action = 4;
}
...
Po sprawdzeniu poprawności zmiennych
URL oraz po utworzeniu potrzebnych obiektów
przechodzimy do wysłania danych do
szablonu. Etap ten składa się z dużej instrukcji
switch sterowanej rodzajem wybranej
podstrony:
switch ($action) {
case 1:
break;
case 2:
$s->assign(\'opcjamenu\', $opcjamenu);
break;
case 3:
...
break;
case 4:
$s->assign(\'projekt\', $projekt);
$s->assign(\'rozdzial\', RozdzialPeer:: .
retrieveByPK($projekt->getRozdzialId()));
break;
case 5:
$s->assign(\'cwiczenie\', $cwiczenie);
$s->assign(\'rozdzial\', RozdzialPeer:: .
retrieveByPK($cwiczenie->getRozdzialId()));
break;
}
Na zakończenie przetwarzamy szablon:
przekazujemy zmienną $action i wywołujemy
metodę display():
$s->assign(\'action\', $action);
$s->display(\'index.tpl\');
Cały skrypt liczy 170 linijek.
Krok ósmy: przyjazne adresy URL
Przyjazne adresy URL wykonujemy stosując
moduł mod_rewrite serwera Apache oraz
dwukierunkowe translacje szczegółowo opisane
przeze mnie w artykule pt. \”Przyjazne adresy URL w praktyce\” (INTERNET Maker nr 4/2006).
Najpierw automatycznie generujemy na podstawie zawartości bazy danych dwa pliki
tekstowe: .htaccess oraz output-translations.txt.
Plik .htaccess zawiera dyrektywy dla modułu mod_rewrite:
RewriteRule ^spis-tresci\.html$ index.php? id=2&id2=1
RewriteRule ^index\.html$ index.php? id=2&id2=3
...
RewriteRule ^rozdz-01\.html$ index.php? id=3&id2=1
RewriteRule ^rozdz-02\.html$ index.php? id=3&id2=2
...
RewriteRule ^proj-11-01\.html$ index.php? id=4&id2=1
RewriteRule ^proj-11-02\.html$ index.php? id=4&id2=2
...
RewriteRule ^cw-02-01\.html$ index.php? id=5&id2=1
RewriteRule ^cw-03-01\.html$ index.php? id=5&id2=2
...
Na tej podstawie serwer Apache zapytania dotyczące plików.html przekieruje na
odpowiadające im adresy index.php?id=X&id2=Y.
Plik output-translations.txt służy do przekształcenia
adresów index.php?id=X&id2=Y w adresy .html:
\"index.php?id=2&id2=1\" \"spis-tresci.html\"
\"index.php?id=2&id2=3\" \"index.html\"
...
\"index.php?id=3&id2=1\" \"rozdz-01.html\"
\"index.php?id=3&id2=2\" \"rozdz-02.html\"
...
\"index.php?id=4&id2=1\" \"proj-11-01.html\"
\"index.php?id=4&id2=2\" \"proj-11-02.html\"
...
\"index.php?id=5&id2=1\" \"cw-02-01.html\"
\"index.php?id=5&id2=2\" \"cw-03-01.html\"
...
Krok dziewiąty: skrypt PHP stosujący przyjazne URL-e
Jedyną zmianą, jaką należy wykonać w kodzie
z kroku siódmego, jest dodanie, na końcu
skryptu, translacji wykorzystującej plik output-
translations.txt. Zadanie to wykonujemy
następująco:
$s->assign(\'action\', $action);
$page = $s->fetch(\'index.tpl\');
$tmpOutputTranslations = file_get_contents (\'translations/output-translations.txt\');
$tmpOutputTranslations = uncomment_and_trim($tmpOutputTranslations);
list($w, $c, $translations) = string2VArray ($tmpOutputTranslations, \"\t\");
$page = str_replace($translations[0], $translations[1], $page);
echo $page;
Po umieszczeniu obok pliku index.php pliku .htaccess przygotowanego w kroku ósmym
aplikacja będzie stosowała wyłącznie adresy przyjazne.
Krok dziesiąty: witryna w wersji offline
Tak wykonaną witrynę możemy bez problemu
umieścić na CD w postaci wersji offline. Wystarczy
witrynę stosującą przyjazne adresy URL
skopiować programem do kopiowania całych
witryn. Ja do tego celu stosuję program wget
w połączeniu ze skryptem xhtmlpp-wget.bat:
wget http://localhost/xhtmlpp//spis-tresci.html
wget http://localhost/xhtmlpp/index.html
...
wget http://localhost/xhtmlpp/rozdz-01.html
wget http://localhost/xhtmlpp/rozdz-02.html
...
wget http://localhost/xhtmlpp/proj-11-01.html
wget http://localhost/xhtmlpp/proj-11-02.html
...
wget http://localhost/xhtmlpp/cw-02-01.html\"
wget http://localhost/xhtmlpp/cw-03-01.html\"
...
Plik xhtmlpp-wget.bat generuję w kroku
ósmym jako produkt uboczny plików .htaccess
oraz output-translations.txt.
Podsumowanie
Uruchomienie opisanej aplikacji wymaga oczywiście
instalacji odpowiedniego oprogramowania.
Koniecznymi komponentami, poza Apache/
PHP/MySQL, są:
- Smarty,
- DBDesigner,
- Propel (wraz z Phing oraz Creole),
- phpMyAdmin,
- mod_rewrite.
Podczas umieszczania witryny na serwerze
możemy napotkać trudności. Brak uprawnień
do modyfikacji ścieżek dostępu (wpis include_path w pliku php.ini) wymusza zmianę ścieżek
w aplikacji Propel na bezwzględne.