Na stránce o sjednání obsahu (content negotiation) byly ukázány základní principy a byly zveřejněny odkazy na další užitečné informace. Nyní se podíváme, k čemu a jak se dá sjednání obsahu využít ve spojení s dynamickými stránkami vytvářenými v PHP. Nabízejí se nám tyto možnosti:
V tomto dokumentu si postupně popíšeme obě metody. Sice se zaměříme výhradně na PHP, ale obdobný přístup se dá využít i v jiných skriptovacích jazycích, jako jsou ASP, Perl, Python, Ruby apod.
PHP skript je obvykle směsí programových instrukcí, v nichž se
volají standardní i uživatelské funkce, a kódu HTML. Největší část je tedy
společná pro všechny jazykové verze. Proto nechceme vytvářet pro jednotlivé
jazyky samostatné soubory odlišené příponami, jako jsme to dělali v případě
statických stránek. Odlišení příponami využít chceme, aby fungovalo sjednání
obsahu s využitím MultiViews
, ale programový kód chceme sdílet.
Při kopírování ze souboru do souboru by totiž snadno vznikaly chyby a
nekonzistentnosti.
PHP skript zobrazuje texty ve stránkách WWW pomocí funkcí
echo
a printf
. Tyto funkce pracují s řetězci, které
budou mít v každé jazykové verzi jiné hodnoty. Jednojazyčná verze používá
řetězcové literály. My však použijeme proměnné nebo konstanty definované funkcí
define
. Programový kód pak bude v jednom společném souboru. Každá
jazyková verze bude pouze definovat konstanty a proměnné závislé na jazyku a
pak načte společný soubor funkcí require
. Vícejazyčný text
může vypadat takto:
Český soubor pojmenovaný file.cs.php
:
<?php define(_title_, 'Sjednání obsahu'); require 'incl.file.php'; ?>
Anglický soubor pojmenovaný file.en.php
:
<?php define(_title_, 'Content negotiation'); require 'incl.file.php'; ?>
Společný soubor pojmenovaný incl.file.php
:
<?php printf('<title>%s</title>', _title_); ?>
Na příslušnou stránku se budete odvolávat jen jako na
file
bez jakékoliv přípony. Apache pak vybere vhodnou variantu.
Nakonec ještě musíme zabránit přístupu ke společným souborům, protože ty by samostatně nefungovaly. Dosáhneme toho následující direktivou v konfiguračním souboru:
<FilesMatch "incl\."> Order allow,deny Deny from all </FilesMatch>
Všimněte si, že incl.
je na začátku jména. Pokud by
se společný soubor jmenoval file.incl.php
a přístup by byl zakázán
obdobnou direktivou FilesMatch
, pak při hledání file
by Apache zkusil též file.incl.php
a do chybového souboru by
zaznamenal, že prohlížeči byl zákazem v konfiguraci odmítnut přístup k souboru.
Nyní si probereme případ, kdy chceme do souboru vložit dvě velké části kódu HTML.
Vytvoříme samostatné soubory file1.cs.html
a file2.cs.html
pro češtinu,
obdobně file1.en.html
a file2.en.html
pro angličtinu. Když na příslušná
místa společného souboru napíšeme virtual('file1');
a virtual('file2');
,
Apache s použitím sjednání obsahu vloží správné soubory. Bohužel to nefunguje vždy. Uvědomme si, že
při hledání URL s názvem file
nemusela být nalezena správná jazyková varianta a
uživatel si zvolil jazykovou verzi explicitně. Stejně zareaguje Apache na požadavek
virtual('file1');
a nabídne nepoužitelné odkazy. Soubor pro daný jazyk by tedy měl
definovat řetězcovou konstantu _ext_
obsahující potřebnou příponu a kód HTML by se pak
načítal funkcí virtual('file1' . _ext_);
.
Sjednání obsahu v PHP skriptech je využito v programu MemoDisx. Můžete si vyzkoušet demonstrační verzi. Můžete si též stáhnout distribuci programu MemoDisx a podívat se do zdrojových kódů, jak je to uděláno. MemoDisx používá rámy a otvírá se hlavní stránkou s tímto obsahem:
<!--#include virtual="html-head.html" --> <!--#if expr="$DOCUMENT_URI != /\/index$/" --> <body> <p class="error">Please add "English" to the language preferences of your browser and then <a target="_top" href="./">try again</a>, otherwise MemoDisx will not work properly.</p> <p>More details can be found in the description of <a href="http://hroch486.icpf.cas.cz/wagner/content-negotiation">Content Negotiation</a>.</p> </body> <!--#else --> <frameset cols="15%,*"> <!--#include virtual="noframes" --> <frame name="left" src="menu"> <frame name="right" src="main"> </frameset> <!--#endif --> </html>
Proměnná $DOCUMENT_URI
obsahuje požadované jméno dokumentu. Pokud nemá
příponu, znamená to, že vhodná jazyková verze byla zvolena podle preferencí nastavených v
prohlížeči. Pokud příponu má, víme, že uživatel si zvolil jazyk explicitně. Pak bychom ale museli
vše programovat složitěji, proto takovou možnost explicitně zakážeme. Ostatně, MemoDisx je
aplikace, která má být používána opakovaně. Proto můžeme po uživateli vyžadovat, aby věnoval více
času nastavení svého prohlížeče WWW.
Tento dokument není programátorský toolkit, který byste mohli vzít a přímo použít. Cílem dokumentu je vysvětlení metody. Samozřejmě si můžete vytáhnout příklady kódu, poskládat je do souborů a získat tím funkční řešení. Budete muset nahradit znakové entity odpovídajícími znaky. Abyste to nemuseli provádět ručně. můžete použít jednoduchý perlovský skript (ale ten si nejprve musíte ručně upravit:
#!perl while (<STDIN>) { s/&/&/g; s/</</g; s/>/>/g; print; }
V návodu se předpokládá, že jsou registrovány globální proměnné. Lze tedy použít
$HTTP_ACCEPT_LANGUAGE
a $REQUEST_URI
. Pokud by globální proměnné
registrovány nebyly, bylo by nutno použít $_SERVER['HTTP_ACCEPT_LANGUAGE']
a
$_SERVER['REQUEST_URI']
.
Podobně jako při použití běžné metody sjednání obsahu chceme, aby všechny odkazy
vedly na hlavní soubor filename.php
, který podle požadavků od prohlížeče WWW zvolí
správnou jazykovou verzi. Jména souborů s danými jazykovými verzemi však nebudou mít více přípon.
Jméno bude mít formát filename-XXX_YYY.EXT
, kde XXX
a YYY
určují jazyk a kódování, EXT
je přípona. V příkladech, které zde uvedeme, může přípona
být php
nebo html
. Formát hlavního souboru je velmi jednoduchý:
<?php require 'engine.php'; if ($inc) { include $fn; } else virtual($fn); exit; ?>
Vlastní činnost se provádí v souboru engine.php
. Ten vrátí jméno
souboru s příslušnou jazykovou verzí v proměnné $fn
. Je-li nastavena proměnná
$inc
, jedná se o PHP skript, který je nutno zpracovat příkazem include
. V
opačném případě použijeme funkci virtual
.
Soubor engine.php
obsahuje kód, který se přímo provádí. V něm se
používají funkce, které jsou definovány v soubory functions.php
, takže tento soubor
musíme nejprve načíst příkazem require
. Žádný z uvedených souborů nesmí být spuštěn
přímo, proto na začátek functions.php
vložíme:
$bsn = basename($REQUEST_URI); if ($bsn == 'engine.php' || $bsn == 'functions.php') die('Access not allowed!');
Programový kód v engine.php
nejprve najde seznam souborů. Jména
kontroluje s regulárním výrazem, který si sám určí.
# Find the list of the files, $re evaluated from $REQUEST_URI $rq = $REQUEST_URI; if (substr($rq, -1) == '/') $rq .= 'index.php'; $re = '/^' . substr(basename($rq), 0, -4) . '-(.*)\./'; unset($flist); $dirhandle = opendir('.'); while ($fn = readdir($dirhandle)) { if (preg_match($re, $fn, $m)) { $f = array('fn' => $fn, 'ext' => explode('_', $m[1])); $flist[] = $f; } } closedir($dirhandle); if ($flist) sort($flist);
Dále si zjistíme seznam přijatelných jazyků. Pokud prohlížeč seznam jazyků nepošle, použijeme defaultní hodnotu podobně jako Apache má direktivu LanguagePriority.
# Get the list of acceptable languages unset($acclang); if ($HTTP_ACCEPT_LANGUAGE) { $acclang = explode(',', $HTTP_ACCEPT_LANGUAGE); for ($i = 0; $i < count($acclang); $i++) { $L = explode(';', $acclang[$i]); $acclang[$i] = trim($L[0]); } } else $acclang = array('en', 'cs');
Uživatel může požadovat specifickou jazykovou variantu (např. en-zw), kterou na serveru nemáme. Pokud tedy jazykovou variantu nenajdeme, odstraníme specifikace jazykových variant a hledáme znovu.
$xlang = FindLanguage($flist, $acclang); # Remove language variants and find a file if ($xlang < 0) { for ($i = 0; $i < count($acclang); $i++) { $L = explode('-', $acclang[$i]); $acclang[$i] = trim($L[0]); } $xlang = FindLanguage($flist, $acclang); }
Funkce FindLanguage
je definována v souboru functions.php
.
Nejprve však musíme definovat seznam známých jazyků a seznam kódování (např. čeština potřebuje
ISO-8859-2).
$languages = array( 'cs' => 'CS', 'en' => 'EN', 'fr' => 'FR', 'de' => 'DE' ); $encodings = array( 'iso2' => 'iso-8859-2' ); # Function for finding a language function FindLanguage($flist, $acclang) { $lx = -1; for ($j = 0; $lx < 0 && $j < count($acclang); $j++) { for ($i = 0; $lx < 0 && $i < count($flist); $i++) { $ext = $flist[$i]['ext']; for ($k = 0; $lx < 0 && $k < count($ext); $k++) { if ($acclang[$j] == $ext[$k]) $lx = $i; } } } return $lx; }
Pokud se nepodaří najít žádnou přijatelnou variantu, vypíšeme seznam a ukončíme skript.
# If no file found, display variants and exit, otherwise fill the file name if ($xlang < 0) { ?> <html><body> <p>No acceptable variant was found. Your browser is set to require text in languages: <code><?php echo $HTTP_ACCEPT_LANGUAGE; ?></code> but only the following variants are available:</p> <ul> <?php for ($i = 0; $i < count($flist); $i++) { $ext = $flist[$i]['ext']; $f = $flist[$i]['fn']; reset($languages); while (list($k, $v) = each($languages)) { for ($j = 0; $f && $j < count($ext); $j++) { if ($ext[$j] == $k) { echo "<li><a href=\"$f\">$v</a></li>\n"; $f = false; } } } } ?> </ul> <p>You can read more about <a title="Content negotiation" target="content_neg" href="http://hroch486.icpf.cas.cz/wagner/content-negotiation.shtml.en">content negotiation</a> if you wish to know how to setup your WWW browser correctly.</p> <hr> </body></html> <?php exit; }
Na závěr naplníme proměnné a případně požádáme Apache o vyslání nějaké hlavičky.
$fn = $flist[$xlang]['fn']; EmitHeader($fn); $inc = preg_match('/\.php$/', $fn);
Funkce EmitHeader
je také definována v souboru
functions.php
. Některé jazykové varianty specifikují též požadované kódování. Pak je
nutno vyslat hlavičku s odpovídající informací. V souboru functions.php
máme informace
k příslušným příponám přiřazeny.
$headers = array( 'iso2' => 'Content-Type: text/html; charset=iso-8859-2' ); # Function for emitting a header function EmitHeader($fn) { global $headers; if (preg_match('/-([^-]+)\./', $fn, $m)) { $e = explode('_', $m[1]); while (list($k, $v) = each($e)) { if ($headers[$v]) header($headers[$v]); } }}
Dynamicky generované stránky se obvykle neukládají ve vyrovnávací paměti. Naše
stránky však jsou ve skutečnosti statické, proto můžeme Apache požádat, aby vyslal údaj o expiraci.
Máme k tomuto účelu připravenu funkci Expires
.
# Longer time units define(HOUR, 3600); define(DAY, 24 * HOUR); define(MONTH, 30 * DAY); # Expiration function Expires($after = DAY) { setlocale(LC_ALL, 'EN_US'); Header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $after) . ' GMT'); }
Funkce LangLinks
, jak název napovídá, připravuje odkazy na další
jazykové varianty.
# Links to languages function LangLinks() { global $fn, $REQUEST_URI, $languages; echo '<div id="langlinks"> '; $rq = $REQUEST_URI; if (substr($rq, -1) == '/') $rq = index.php; else $rq = basename($rq); if (!$fn) $fn = $rq; if (preg_match('/^([^-]+)-[^-]+$/', $fn, $m)) $rq = $m[1]; $re = '/^' . $rq . '-(.*)\./'; $dirhandle = opendir('.'); while ($f = readdir($dirhandle)) { if (preg_match($re, $f, $m)) { if ($f != $fn) { $ext = explode('_', $m[1]); while (list($k, $v) = each($ext)) { if ($languages[$v]) { $lang = $languages[$v]; printf('<a title="%s" href="%s">%s</a> ', $lang, $f, $lang); } } } } } closedir($dirhandle); echo "</div>\n"; }
Aby indexovací roboty nehodnotily stránku podle názvů či zkratek jazyků, jsou odkazy uvedeny na konci stránky, ale kaskádovým stylem se zobrazí na začátku. Kaskádový styl pak obsahuje:
h1 { font-size: 180%; margin-top: 2.5em; font-family: "Tms Rmn", "Times New Roman", "Times Roman", serif } #langlinks { position: absolute; top:0.5em; right:0; background-color: rgb(153,0,51); color: rgb(204,221,255); width: 100%; text-align: right; font-family: "Lucida Sans", Helvetica, Arial, sans-serif; font-weight: bold; font-size: 125%; padding-color: rgb(153,0,51); margin-color: rgb(153,0,51); }
Pokud soubory s jednotlivými jazykovými variantami zapíšeme jako PHP skripty, pak jejich struktura bude:
<?php if (!function_exists('FindLanguage')) { require 'functions.php'; EmitHeader(basename($REQUEST_URI)); } Expires(); ?> <html> <head> ... <link rel="STYLESHEET" type="text/css" href="style.css" /> </head> <body> ... <?php LangLinks(); ?> </body> </html>
Takové skripty se chovají obdobně jako standardní technika sjednání obsahu.