Académique Documents
Professionnel Documents
Culture Documents
Cls en main
76
SCRIPTS
EFFICACES
VOTRE SITE
POUR
WEB
ENRICHIR
ur
o
p
s
u
Con version
les
6
5
et
p ar W i lli a m St e i nm e t z e t Br i an Wa r d
Pearson Education France a apport le plus grand soin la ralisation de ce livre afin de
vous fournir une information complte et fiable. Cependant, Pearson Education France
nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou
atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation.
Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les
descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou
professionnelle.
Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces
exemples ou programmes.
Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par
leurs propritaires respectifs.
ISBN : 978-2-7440-4030-6
Copyright 2009 Pearson Education France
Tous droits rservs
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des
modalits prvues larticle L. 122-10 dudit code.
No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage
retrieval system, without permission from the publisher.
TA B LE D E S M A T I RES
INTRODUCTION
1
TOUT CE QUE VOUS AVEZ TOUJOURS VOULU SAVOIR
SUR LES SCRIPTS PHP SANS JAMAIS OSER LE DEMANDER
2
CONFIGURATION DE PHP
23
23
24
25
25
Ta bl e d es m a t ir es III
26
27
28
29
29
29
30
30
31
31
31
32
32
33
34
38
3
SCURIT ET PHP
39
41
42
43
45
46
47
48
48
50
51
51
4
TRAITEMENT DES FORMULAIRES
53
53
54
55
55
56
57
60
61
61
65
66
67
5
TRAITEMENT DU TEXTE ET DE HTML
69
69
71
72
72
73
74
74
75
76
76
80
80
81
81
82
82
83
83
84
85
86
87
88
89
90
93
93
6
TRAITEMENT DES DATES
95
7
TRAITEMENT DES FICHIERS
107
107
109
109
109
110
111
112
112
113
114
114
118
118
119
119
8
GESTION DES UTILISATEURS ET DES SESSIONS
121
Suivi des donnes des utilisateurs avec des cookies et des sessions ...............
Les cookies .....................................................................................
Les sessions ....................................................................................
Recette 56 : Crer un message "Heureux de vous revoir NomUtilisateur !"
avec les cookies ...................................................................................
Problmes ventuels ..........................................................................
Recette 57 : Utiliser les sessions pour stocker temporairement des donnes .....
Problmes ventuels ..........................................................................
Recette 58 : Vrifier quun navigateur accepte les cookies ............................
Recette 59 : Rediriger les utilisateurs vers des pages diffrentes ....................
Recette 60 : Imposer lutilisation de pages chiffres par SSL ..........................
Recette 61 : Obtenir des informations sur le client .......................................
Recette 62 : Dlais dexpiration des sessions ..............................................
Recette 63 : Systme de connexion simple ...................................................
121
122
122
123
124
125
127
128
129
130
130
135
136
9
TRAITEMENT DU COURRIER LECTRONIQUE
139
140
140
141
143
143
144
10
TRAITEMENT DES IMAGES
149
Recette 66 : Crer une image CAPTCHA pour amliorer la scurit ............... 149
Recette 67 : Crer des vignettes ................................................................ 157
11
UTILISATION DE cURL POUR LES SERVICES WEB
Recette
Recette
Recette
Recette
Recette
Recette
68
69
70
71
72
73
:
:
:
:
:
:
163
164
166
167
169
172
174
12
MISE EN APPLICATION
179
179
181
183
184
187
188
189
191
195
197
198
199
201
205
206
209
ANNEXE
211
INDEX
213
Ta bl e d es m a tir es VII
I N T ROD U C T I ON
2 I n t r oduc t ion
1
TOUT CE QUE VOUS AVEZ TOUJOURS
VOULU SAVOIR SUR LES SCRIPTS PHP
SANS JAMAIS OSER LE DEMANDER
Quel systme de templates puis-je mettre en place pour que mes donnes
soient formates de la mme faon sur toutes les pages ?
Bien que ce livre contienne des scripts assez compliqus et que dautres vous
sembleront plus intressants, ceux qui sont prsents ici rpondent aux questions que nous rencontrons sans cesse. Ces scripts pour dbutants reprsentent
ce que tout le monde devrait savoir ou voudrait savoir. Faites lire ce chapitre un
programmeur en PHP que vous apprciez et il vous en sera reconnaissant.
NOTE Si vous navez pas peur de jouer le rle dadministrateur de votre serveur web, le Chapitre 2
vous aidera galement progresser si vous dbutez en PHP ; il vous facilitera galement la
vie si vous avez dj un peu programm avec ce langage.
4 Ch apitre 1
contentera dafficher le contenu brut de tout le fichier (mme sil est cod en
binaire).
La fonction require_once() sutilisant comme nimporte quelle autre instruction, vous pouvez donc lintgrer dans une structure de contrle :
if ($fichier_necessaire === true) {
require_once("fichier_necessaire.php");
}
Problmes ventuels
Plusieurs choses peuvent mal tourner lorsque lon inclut un fichier.
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 5
Vous utilisez des variables non vrifies comme noms de fichiers inclus.
Bien que vous puissiez crire include($fichier) pour inclure un script en
fonction du choix de lutilisateur, cette pratique permet un pirate dinclure
nimporte quel fichier du site avec un minimum deffort ou, selon la configuration de votre serveur, un fichier de son site qui sintroduira donc sur votre serveur. En fait, quelques virus PHP utilisent ce genre de faute de programmation.
En outre, ce genre de scripts sont bien plus sujets aux bogues et sont gnralement
impossible relire.
Si vous devez inclure des fichiers dont le nom provient dune variable, utilisez plutt un script comme celui de la recette n28, "Sassurer quune rponse
fait partie dun ensemble de valeurs", afin de vrifier que les noms de fichiers
sont corrects et viter ainsi quun pirate puisse lire votre fichier de mots de
passe.
6 Ch apitre 1
Ici, on a dfini deux classes de style, lig1 et lig2, pour les balises de lignes de
tableau (<tr>). Vous pouvez les intgrer dans un fichier CSS inclus par votre
document ou les placer entre les balises <style> et </style> dans la partie <head>
du document.
Dfinissons maintenant une fonction qui retourne alternativement ces
classes. Nous utiliserons une petite astuce consistant passer par rfrence une
variable entire cette fonction, qui pourra ainsi modifier cette variable qui servira
de bascule pair/impair :
function formater_ligne_tableau(&$cpteur_lig) {
// Renvoie la classe de style pour une ligne
if ($cpteur_lig & 1) {
$couleur_lig = "lig2";
} else {
$couleur_lig = "lig1";
}
$cpteur_lig++;
return $couleur_lig;
}
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 7
Le reste est assez simple ; il suffit dinitialiser la variable bascule $i, dappeler
formater_ligne_tableau($i) sur chaque ligne afin dobtenir des classes de style
alternes et dassocier chaque ligne du tableau la classe ainsi obtenue. Enfin, il
reste fermer le tableau :
$i = 0;
while($lig = mysql_fetch_array($resultat)) {
/* Affichage du rsultat */
$classe_lig = formater_ligne_tableau($i);
echo "<tr class=\"$classe_lig\"><td>$lig[nom_produit]</td></tr>";
}
echo "</table>";
Amlioration du script
Vous pouvez faire un million de choses avec les feuilles de style. Pour plus
dinformations sur les CSS, consultez lune des nombreuses ressources en ligne
consacres ce sujet ou lisez le livre dric Meyer, CSS par ric Meyer (Pearson,
2005). En utilisant une approche objet, vous pouvez aisment convertir cette
fonction en une mthode de formatage de tableau : au lieu de dclarer explicitement une variable dtat, il suffit de crer une classe avec une variable prive
charge de mmoriser cet tat. Le constructeur et le destructeur ouvrent et ferment, respectivement, les balises du tableau. Voici quoi ressemblera cette
classe :
class TableauAlterne {
function __construct() {
$this->etat = 0;
print "<table>";
}
function __destruct() {
print "</table>";
}
function affiche_ligne($ligne) {
if ($this->etat & 1) {
$couleur_lig = "lig2";
} else {
$couleur_lig = "lig1";
}
print "<tr class=\"$couleur_lig\">";
foreach ($ligne as $valeur) {
print "<td>$valeur</td>";
8 Ch apitre 1
}
print "</tr>";
$this->etat++;
}
}
Voici comment utiliser cette classe (en supposant que la requte SQL est
identique celle de lexemple prcdent) :
$montableau = new TableauAlterne;
while($ligne = mysql_fetch_row($resultat)) {
/* Affichage du rsultat. */
$montableau->affiche_ligne($ligne);
}
unset($montableau);
Au premier abord, vous pourriez penser que cest une belle amlioration car
elle est un peu plus simple utiliser il nest plus ncessaire de se proccuper de
la variable dtat, la mthode affiche_ligne() peut grer un nombre quelconque
de colonnes et vous navez plus non plus besoin dcrire le moindre code HTML
pour ouvrir et fermer le tableau. Cest, en effet, un avantage indniable si tous
vos tableaux se ressemblent, mais cette apparente simplicit se paye en termes de
souplesse et defficacit. Vous pourriez bien sr ajouter des mthodes et des attributs pour effectuer des traitements supplmentaires, comme lajout den-ttes
aux tableaux, mais demandez-vous si cela en vaut rellement la peine.
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 9
$navbar = "";
$navbar_prec = "";
$navbar_suiv = "";
if ($nbre > $articles_par_page) {
$cpteur_nav = 0;
$nb_pages = 1;
$nav_passee = false;
while ($cpteur_nav < $nbre) {
// Est-on sur la page courante?
if (($num_deb <= $cpteur_nav) && ($nav_passee!= true)) {
$navbar .= "<b><a href=\"$page_courante?debut=$cpteur_nav\">
[$nb_pages]</a></b>";
$nav_passee = true;
// Faut-il un lien "Prcdent"?
if ($num_deb!= 0) {
$num_prec = $cpteur_nav - $articles_par_page;
if ($num_prec < 1) {
$num_prec = 0;
}
$navbar_prec = "<a href=\"$page_courante?debut=$num_prec \">
<<Prcdent - </a>";
}
$num_suiv = $articles_par_page + $cpteur_nav;
// Faut-il un lien "Suivant"?
if ($num_suiv < $nbre) {
$navbar_suiv = "<a href=\" $page_courante?debut=$num_suiv\">
- Suivant>> </a><br>";
}
} else {
// Affichage normal.
$navbar .= "<a href=\"$page_courante?debut=$cpteur_nav\">
[$nb_pages]</a>";
}
$cpteur_nav += $articles_par_page;
$nb_pages++;
}
$navbar = $navbar_prec . $navbar . $navbar_suiv;
return $navbar;
}
}
?>
10 Ch apitr e 1
Supposons que vous utilisiez ce script pour traiter des informations provenant dune base de donnes. Il y a deux points respecter pour que laffichage
soit prcis et efficace :
Lorsquelle est ajoute la fin dune requte, la clause LIMIT nextrait que le
nombre indiqu de lignes de lensemble rsultat. En mettant, par exemple, LIMIT
75, 25 la fin dune requte, on ne rcupre que 25 lignes de la base partir de
la 75e ligne.
NOTE Si le numro de la ligne de dbut est suprieur au nombre de lignes disponibles, MySQL ne
renvoie aucune donne. Si, par exemple, vous utilisez une clause LIMIT 200, 25 alors que
la table ne contient que 199 lignes, vous obtiendrez un ensemble rsultat vide, pas une
erreur. Les programmes bien crits prennent en compte les ensembles vides en faisant un test
if (mysql_num_rows($resultat) > 0).
Maintenant que vous navez que les lignes que vous souhaitiez, il vous reste
une chose faire.
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 11
Lorsque vous disposez de cette information, vous pouvez greffer les trois
informations dans la fonction creer_navbar(). Celle-ci contient plusieurs
variables :
$page_courante : La page courante qui contient la barre de navigation. Dans
ce script, nous utilisons la variable spciale $_SERVER["PHP_SELF"] qui contient
toujours la page courante sans le nom dhte ni les ventuels paramtres
GET. Dans un script accessible lURL http://exemple.com/navbar.php?debut=0, par exemple, $page_courante vaudrait /navbar.php.
$num_deb : Le premier numro de ligne. Si, par exemple, lutilisateur examine les lignes 100 125, ce numro vaudra 100. Il est pass lURL via un
paramtre GET. Par dfaut, il vaut 0.
$articles_par_page : Le nombre de lignes affiches sur chaque page. Si, par
exemple, il y a 100 lignes de donnes, une valeur de 25 pour cette variable
produira quatre pages de 25 lignes chacune. Ces mmes 100 lignes avec une
valeur de 50 pour $articles_par_page produirait deux pages de 50 lignes. Si
cette variable vaut 200, un ensemble de 100 lignes naura pas de barre de
navigation puisque tout tient sur une seule page. La valeur par dfaut de
cette variable est 50.
$nbre : Le nombre total de lignes. Nous avons dj expliqu comment obtenir cette information dans "Compter le nombre total de lignes de lensemble
rsultat".
$cpteur_nav : Cette variable part de 0 et sincrmente de $articles_par_page
jusqu ce quelle devienne suprieure $nbre, auquel cas le script a atteint la
fin de lensemble rsultat et donc la fin de la barre de navigation.
$nb_pages : Le nombre de pages dans lensemble rsultat.
$nav_passee : Une variable temporaire qui passe vrai ds que $cpteur_nav
dpasse $num_deb; en dautres termes, nous sommes sur la page courante.
Cela permet de mettre en vidence le numro de la page courante lors de
laffichage de la barre de navigation.
Utilisation du script
<?php
$num_deb = intval($_GET("debut"));
$articles_par_page = 100;
$categorie_ventes = null;
if ($num_deb >= 0) {
12 Ch apitr e 1
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 13
La fonction print_r() affiche le tableau sous un format lisible, ce qui est particulirement pratique lorsque lon manipule des tableaux plusieurs dimensions
(tableaux contenant dautres tableaux). Il suffit de passer print_r() un tableau
en paramtre et lon obtient un affichage sous un format simple. Comme ce format utilise des espaces et pas de balises HTML, vous devrez probablement consulter le format source de la page pour visualiser le contenu du tableau tel quil est
produit. Lexemple prcdent affichera :
Array
(
[amuse-gueule] => fruit
[entre] => rosbif
[dessert] => Array
(
[0] => crme glace au chocolat
[1] => pudding la vanille
[2] => fraises la crme fouette
)
)
14 Ch apitr e 1
Vous pouvez stocker cette valeur peu prs nimporte o dans des cookies,
des bases de donnes, des paramtres cachs POST etc. Pour effectuer la manuvre
inverse, il suffit dutiliser la fonction deserialize() de la faon suivante :
$menu = deserialize($menu_s);
Problmes ventuels
serialize() est une fonction trs pratique, mais vous devez connatre certains points. Les tableaux srialiss sont relativement faciles lire : si vous stockez
des tableaux contenant des donnes sensibles dans des cookies ou des sessions, il
est donc prfrable de les chiffrer auparavant pour viter que vos donnes ne
soient rcupres par des utilisateurs malhonntes (voir la recette n23, "Chiffrer
les donnes avec Mcrypt").
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 15
Nabusez pas non plus de cette fonction. Si vous constatez que vous stockez
souvent des tableaux dans des bases de donnes ou que vous rcuprez frquemment des chanes de srialisation, vous devriez srement revoir la structure de
votre base de donnes ou le mcanisme de stockage.
Enfin, vitez de passer des tableaux srialiss dans des paramtres GET car la
limite de la taille des URL est assez courte.
Si ce script semble si simple, cest parce quil lest. La fonction de comparaison personnalise sappelle compare_noms() ; elle utilise une autre fonction de
comparaison pour comparer les deux lments qui lui sont passs en paramtres
($a et $b, ici).
16 Ch apitr e 1
if/then mais, ces valeurs tant des chanes, il est prfrable dutiliser la fonction
prdfinie strcasecmp() pour des raisons de performance, de lisibilit et de fiabilit.
NOTE PHP dispose dune fonction array_multisort() qui se veut tre une solution fourre-tout
pour le tri des tableaux plusieurs dimensions. Cependant, elle est vraiment trs complique ;
nous vous dconseillons de lutiliser.
Amlioration du script
La modification la plus vidente que vous pouvez apporter la fonction de
comparaison consiste ajouter des critres lorsque les deux lments sont gaux.
En reprenant notre exemple prcdent, voici comment trier dabord sur les
noms, puis sur les prix lorsque deux noms sont gaux :
function compare_noms($a, $b) {
$r = strcasecmp($a["nom"], $b["nom"]);
if ($r == 0) {
if ($a["prix"] < $b["prix"]) {
$r = -1;
} elseif ($a["prix"] > $b["prix"]) {
$r = 1;
} else {
$r = 0;
/* Pas vraiment ncessaire puisque $r vaut dj 0. */
}
}
return($r);
}
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 17
peut mettre les donnes en cache afin damliorer considrablement les accs
un site, mais il sagit dune technique avance qui dpasse le cadre de ce livre.
Installation de Smarty
Pour installer Smarty sur votre serveur, il suffit de suivre les tapes suivantes :
1. Crez un rpertoire smarty dans larborescence du serveur web, afin dy
stocker les fichiers principaux de Smarty.
2. Tlchargez la dernire version de Smarty partir de http://smarty.php.net/.
Dcompressez larchive et extrayez-la dans un rpertoire de votre ordinateur.
3. Transfrez tous les fichiers Smarty de votre ordinateur vers le rpertoire
smarty du serveur.
4. Sur le serveur, crez un autre rpertoire appel templates pour y stocker vos
templates Smarty. Crez deux sous-rpertoires sous celui-ci : html pour les
templates bruts et compile pour les templates compils par Smarty.
5. Faites en sorte que le serveur puisse crire dans le rpertoire compile. Si
vous ne savez pas comment faire, lisez la section "Permissions des fichiers",
au dbut du Chapitre 7.
6. Dans le rpertoire templates, crez (ou tlchargez) un fichier nomm
smarty_initialize.php, contenant les lignes suivantes :
<?php
define ("SMARTY_DIR", "/chemin/vers/web/racine/smarty/");
require_once (SMARTY_DIR."Smarty.class.php");
$smarty = new Smarty;
$smarty->compile_dir = "/chemin/vers/web/racine/templates/compile";
$smarty->template_dir = "/chemin/vers/web/racine/templates/html";
?>
Smarty tant orient objet, vous devez crer un objet Smarty. Cest ce que fait
$smarty = new Smarty dans ce script.
Smarty doit savoir o se trouvent les templates bruts et compils. Pour cela, il
utilise deux attributs dobjets, compile_dir et template_dir.
Maintenant que Smarty a t configur, il est temps dapprendre sen servir.
18 Ch apitr e 1
Pour bnficier de toute la puissance de Smarty, vous devez placer les donnes
produites par PHP dans un template. Pour crer une variable Smarty, utilisez des
accolades avec un dollar ($), comme dans cet exemple :
<HTML>
<head>
<title>{$var_titre}</title>
</head>
<body>
Je mappelle {$var_nom}.
</body>
</HTML>
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 19
Vous pouvez affecter ces variables avec quasiment nimporte quel type de donnes PHP ; peu importe que ce soit des donnes MySQL, du code HTML provenant dune autre source, des donnes fournies par lutilisateur, etc. Si tout se
passe bien, le code HTML ainsi produit affichera une page semblable la
Figure 1.1.
Problmes ventuels
Hormis les erreurs classiques dues des mauvais chemins, le problme le
plus frquent est celui o le serveur web ne peut pas crire dans le rpertoire compile que vous avez dfini pour stocker les templates compils de Smarty. Si le serveur na pas les droits dcriture et dexcution sur ce rpertoire, Smarty
chouera lorsquil tentera de crer les nouveaux fichiers ; la page saffichera,
mais vous verrez apparatre un affreux message davertissement la place de
votre template.
Vous devez galement vrifier que toutes les variables ont t affectes avec la
mthode $smarty->assign() ; dans le cas contraire, Smarty leur affectera par
dfaut une chane vide. Cela ne provoquera pas derreur, mais laspect de votre
page sen ressentira et certains lments pourront ne pas fonctionner comme
prvu.
Amlioration du script
Pour mieux tirer parti de la puissance de Smarty, vous devez connatre quelques
dtails supplmentaires. Vous pouvez, par exemple, inclure plusieurs templates
dans le mme script :
<?php
// Initialisation de Smarty.
require_once("smarty_initialize.php");
// Affichage du premier template Smarty
$smarty->display("entete.tpl");
20 Ch apitr e 1
Smarty est trs puissant vous pouvez, par exemple, construire des tableaux
et les formater entirement en affectant simplement un tableau PHP une
variable Smarty. Le langage dispose en outre de boucles if/then rudimentaires ;
il permet de mettre des donnes en cache et fournit des fonctions de pr et de
post-filtrage, etc. Maintenant que vous connaissez les rudiments du fonctionnement de Smarty, le mieux que vous ayez faire est de consulter sa documentation
en ligne pour en savoir plus.
Tout ce que vous avez toujours voulu savoir sur les scripts PHP sans jamais oser le demander 21
2
C ON F I GU RA T I ON D E P HP
si vous comptez y vivre longtemps, pourquoi ne pas rarranger le mobilier et supprimer un mur ou deux ?
NOTE Selon lhte qui hberge votre serveur web, vous ne pourrez peut-tre pas modifier vous-mme
les options de configuration. Cependant, les services dhbergement srieux voudront bien
effectuer ces modifications pour vous et de nombreuses formules dhbergement haut de
gamme permettent de modifier les directives laide dun fichier de configuration spcifique
chaque utilisateur.
Les options de configurations de PHP se trouvent dans un fichier nomm
php.ini, que vous pouvez consulter et modifier avec nimporte quel diteur de
texte. Ces options sont classes en sections et sont de la forme :
max_execution_time = 30; Temps maximum dexcution
max_input_time = 60
; Temps maximum danalyse dune saisie
memory_limit = 8M
; Mmoire maximum utilisable par un script
Pour configurer les options, utilisez le signe gal (=). Un point-virgule (;)
indique le dbut dun commentaire, mais il y a quelques exceptions car certaines
options peuvent contenir des points-virgules. Si vous voulez modifier dfinitivement la valeur dune option, sauvegardez dabord le fichier php.ini, modifiez
loriginal et relancez Apache. Si vous prfrez modifier les options script par
script, utilisez la fonction ini_set().
NOTE De nombreuses options de configuration ne se trouvent pas dans le fichier php.ini par
dfaut ; PHP utilise des valeurs par dfaut lorsque celles-ci ne sont pas prcises. Vous trouverez la liste de ces valeurs par dfaut lURL http://www.php.net/manual/fr/ini.php.
24 Ch apitr e 2
La fonction phpinfo() affiche tout ce que sait PHP de sa configuration, absolument tout. Non seulement elle indique ltat de chaque variable de la configuration, lemplacement du fichier php.ini et la version de PHP, mais elle prcise
galement la version du logiciel utilis par le serveur web, les extensions qui ont
t compiles avec PHP et lAPI du serveur. Elle est donc trs pratique pour examiner les options de configuration et sassurer quune fonctionnalit a t installe
et active correctement.
Pour excuter ce script, chargez la page dans votre navigateur web. Noubliez
pas de le supprimer lorsque vous avez termin.
ini_get() renvoie la valeur de loption dont on lui a pass le nom en paramtre. Cette valeur tant une valeur classique, elle peut donc tre affiche, affecte
une variable, etc. Vous devez cependant savoir deux choses.
Configuration de PHP 25
La premire est que les valeurs boolennes, comme "false", sont gnralement renvoyes sous la forme dune chane vide lorsque vous les affichez. Si
register_globals vaut "off", vous verrez donc probablement ce message :
register_globals vaut
Le second point est que les valeurs numriques sont souvent stockes sous
forme raccourcie. Si upload_max_filesize vaut 8 192 octets, par exemple, la
valeur renvoye sera 8KB. De mme, si la taille maximale dun fichier dpos est
de 2 Mo, upload_max_filesize ne vaudra pas 2 097 152 octets, mais 2MB.
Cela peut videmment poser problme si vous effectuez des oprations arithmtiques sur ces nombres. La documentation officielle de PHP propose donc la
fonction suivante pour convertir les formes abrges K et M en vritables
nombres :
function renvoie_octets($val) {
$val = trim($val);
$dernier = $val{strlen($val)-1};
switch(strtoupper($dernier)) {
case K:
return (int) $val * 1024;
break;
case M:
return (int) $val * 1048576;
break;
default:
return $val;
}
}
26 Ch apitr e 2
Pour empcher PHP de corriger automatiquement ces problmes, vous pouvez activer le suivi des erreurs, ce qui remplira votre cran de messages chaque
fois que PHP dtecte une erreur quelle que soit sa gravit. Vous pourrez alors
utiliser ces messages derreur pour corriger les potentiels trous de scurit et
dcouvrir les mauvaises variables de votre programme. Pour activer le suivi des
erreurs, placez le code suivant au dbut dun script :
<?php
error_reporting(E_ALL);
// Reste du script.
?>
Lactivation du suivi des erreurs force PHP afficher les messages derreur
lcran avant de traiter le reste du programme (ce qui rend impossible la mise en
place de cookies en cas derreur ce nest donc pas la peine dessayer de modifier
les valeurs de cookies la premire fois que lon active ce suivi).
Ce message indique que vous utilisez une variable qui na pas t dfinie
auparavant dans le script, ce qui peut arriver dans de nombreuses circonstances :
Configuration de PHP 27
Enfin, voici un message derreur classique qui apparat lorsque lon accde
un tableau :
Notice: Undefined index: i in script.php on line n
Il signifie que vous avez tent de lire $tableau[i] alors quil ny a pas dindice
i dans $tableau. Cette erreur survient souvent lorsque lon tente de rcuprer
une valeur de formulaire partir de $_POST ou $_GET, alors quaucun de ces
tableaux ne comporte de valeur de ce nom. Cela signifie gnralement que lutilisateur na pas coch la bonne case ou le bon bouton radio (et ne lui a donc pas
donn de valeur) ou que la variable na pas t passe lURL dans une requte
GET.
Sur un site en production, il est prfrable de dsactiver ce suivi des erreurs
afin que les utilisateurs nen aient pas connaissance et ne vous cassent pas les
pieds, mais galement parce quil interfre avec les cookies, ce qui peut poser
problme pour le suivi des sessions.
Vous devriez utiliser cette configuration dans un environnement de production afin que tout Internet ne puisse prendre connaissance des messages derreur
de votre script. Si vous devez connatre ces messages pour rsoudre des problmes,
utilisez plutt loption suivante pour que les messages soient inscrits dans le
journal dApache :
log_errors = On
Si vous prfrez, vous pouvez mme envoyer les messages derreur syslog
ou dans un fichier en initialisant loption error_log avec syslog ou en lui indiquant un nom de fichier.
Dans un environnement de dveloppement, les choses sont diffrentes car
vous souhaiterez, au contraire, obtenir le plus possible de messages de diagnostic.
Avec loption display_errors active, vous pouvez configurer loption error_
reporting de php.ini avec un champ de bits de votre choix (pour plus de dtails,
28 Ch apitr e 2
Problmes ventuels
Votre serveur tourne peut-tre en mode scuris, ce qui dsactive la modification
de max_execution_time.
Vrifiez galement votre code : vous avez peut-tre une boucle infinie quelque part ou vous avez imbriqu une boucle dans une autre boucle qui ne fait
rien.
Configuration de PHP 29
(pour savoir comment traiter les fichiers dposs, consultez la recette n54,
"Dposer des images dans un rpertoire").
upload_max_filesize = 500K
sous forme dun nombre suivi de M pour indiquer une taille en mga-octets,
comme 2M (2 Mo) ;
sous forme dun nombre suivi de K pour indiquer une taille en kilo-octets,
comme 8K (8 Ko).
Quel que soit le format choisi, les utilisateurs ne pourront plus, dsormais,
dposer un fichier dune taille suprieure. Par dfaut, cette limite est de 2 Mo.
30 Ch apitr e 2
protger les donnes lorsque vous dsactivez les apostrophes magiques, mais
vous devez encore prendre vos prcautions afin dviter le doublement des antislash dchappement. Cependant, faute de mieux, elles feront laffaire. Voici
comment les activer dans le fichier php.ini :
magic_quotes_gpc = 1
Problmes ventuels
Si les apostrophes magiques ne sont pas actives, vous devrez utiliser
mysql_real_escape_string() pour garantir que les donnes ont t protges.
Cependant, lutilisation de cette fonction sur des donnes de formulaires alors
que les apostrophes magiques sont galement actives, provoquera le doublement des anti-slash de protection : \"Il l\a dit\" deviendra donc \\"Il
l\\a dit\\". Il vaut donc mieux tre cohrent car, si vous ny prenez pas
garde, les anti-slash risquent de saccumuler dans les chanes des tables de
votre base de donnes1.
Si vous souhaitez que seul ce rpertoire soit autoris daccs, ajoutez une barre de fraction
la fin du chemin, /home/www/ par exemple.
Problmes ventuels
Lorsquun utilisateur dpose un fichier, celui-ci est stock dans un rpertoire
temporaire jusqu ce quil soit trait par un script. Ce rpertoire tant traditionnellement loign du reste des fichiers PHP, vous ne devez pas oublier de lajouter
la liste de open_basedir.
1. NdT : Notez quil faut toujours dsactiver les apostrophes magiques avec MySQL.
Configuration de PHP 31
32 Ch apitr e 2
GD
La bibliothque GD permet de crer des graphiques la demande ou
dobtenir des informations sur une image. Elle peut crer des fichiers aux
formats JPEG ou GIF lorsque vous avez besoin de produire des graphiques
ou de manipuler des images existantes ces formats pour produire des
vignettes.
MySQL
Une compilation totalement nue de PHP ne sait pas accder une base de
donnes. Heureusement, MySQL et PHP tant unis comme les doigts
de la main, la plupart des serveurs web qui proposent PHP disposent galement des bibliothques MySQL : cest la raison pour laquelle la plupart
des programmeurs ignorent gnralement que la fonction mysql_connect()
fait partie dune extension.
Parmi les autres extensions de PHP, citons SOAP (qui permet daccder aux
services web), PDF et Verisign Payment Pro. Il pourrait sembler intressant
dajouter toutes ses extensions PHP, mais noubliez pas que lajout dune extension peut ralentir linitialisation et ajouter des failles de scurit. En outre, les
extensions qui ne servent pas souvent ne sont pas aussi bien maintenues que
les autres et peuvent donc ne jamais tre mises jour.
Si cela ne fonctionne pas ou si vous pensez que cest un peu lent, vous pouvez
utiliser dautres mthodes.
Configuration de PHP 33
port technique dinstaller les extensions. De cette faon, ils seront capables (en thorie, du
moins) de rparer les problmes si quelque chose se passe mal au cours de linstallation.
34 Ch apitr e 2
NOTE Si votre serveur ne dispose pas dun panneau de contrle, vous pouvez gnralement demander
Configuration de PHP 35
4.
5.
6.
7.
./configure --with-apxs=/usr/local/apache/bin/apxs
--with-xml --enable-bcmath --enable-calendar --enable-ftp
--enable-magic-quotes --with-mysql --enable-discard-path
--with-pear --enable-sockets --enable-track-vars
--enable-versioning --with-zlib
36 Ch apitr e 2
Si vous ajoutez une extension comme GD, vous ne voudrez pas pour
autant perdre celles qui sont dj installes. Pour cela, copiez ces informations de configuration dans un fichier et ajoutez la fin les options --with
adquates. Si, par exemple, vous voulez ajouter Mcrypt ce serveur, il suffit dajouter loption --with-mcrypt la fin de la commande prcdente.
Pour connatre loption --with approprie, consultez la documentation
de lextension.
NOTE Si vous avez cras la structure arborescente de larchive tar et que vous avez plac la bibliothque
dans un rpertoire autre que celui par dfaut, vous devrez ajouter ce chemin avec une option
--with afin que PHP puisse la trouver. Cest ce qui sest pass avec la bibliothque apxs (Apache
Extension Tool Synopsis) dans lexemple prcdent : --with-apxs=/usr/local/apache/bin/
apxs indique PHP que apxs se trouve dans le rpertoire /usr/local/apache/bin/apxs.
8. Rcuprez et dcompactez une nouvelle distribution source de PHP puis
allez dans son rpertoire. Vous pouvez dcompacter le code source de PHP
exactement comme vous lavez fait prcdemment pour le code source de la
bibliothque. Si vous disposez dj dune arborescence source de PHP, vous
pouvez lutiliser condition de lancer dabord la commande make clear.
9. Copiez la commande configure que vous aviez stock plus haut dans un
fichier, collez-la sur la ligne de commande et pressez Entre pour lexcuter. Cela reconfigurera PHP avec la nouvelle bibliothque en conservant
les anciennes.
10. Compilez et installez PHP en lanant les commandes make puis make install. Ces commandes peuvent prendre un certain temps puisquelles
compilent et installent chaque composant de PHP.
NOTE Si vous avez modifi vos fichiers .ini comme on la expliqu plus haut, ils seront peut-tre
crass par les valeurs par dfaut lors de la recompilation de PHP. Par consquent, vrifiez
que vos rglages ont t prservs.
11. Relancez Apache en lanant la commande apachectl graceful.
12. Testez PHP. Essayez dabord avec un bon vieux script "Hello world!" afin
de vous assurer que PHP fonctionne sans erreur, puis exprimentez quelques fonctions spcifiques la bibliothque que vous venez dinstaller
pour vrifier quelle fonctionne.
Configuration de PHP 37
Problmes ventuels
Il y a tant de problmes qui peuvent survenir au cours de la compilation quil
est presque impossible de tous les numrer. Bien que de nombreuses erreurs
soient compliques et que beaucoup soient spcifiques une bibliothque, on
retrouve systmatiquement trois problmes classiques.
Le premier peut survenir si les outils de dveloppement ne sont pas installs
sur votre systme. Vous avez besoin du compilateur C et de nombreuses bibliothques de dveloppement pour pouvoir compiler un programme.
Le second est que vous devrez peut-tre configurer PHP avec une option --with
associe un chemin prcis : --with-mcrypt=/usr/lib/mcrypt, par exemple.
Le troisime problme peut provenir du fait que vous avez mal configur la
bibliothque de lextension. Comme indiqu plus haut, vous devez configurer
Mcrypt avec les options --disable-nls et --disable-posix-threads sous peine de
poser des problmes Apache. Les autres bibliothques peuvent galement
ncessiter ce type dajustement pour fonctionner correctement avec Apache et PHP.
Consultez les FAQ, les pages de manuel et les fichiers README pour plus de dtails.
3
S C U RI T E T PHP
40 Ch apitr e 3
open_basedir initialise avec le rpertoire /tmp (pour pouvoir stocker les informations de session) et le rpertoire racine du serveur web pour que les scripts
ne puissent pas accder lextrieur de cette arborescence.
expose_php off. Lorsquelle est active, cette option ajoute aux en-ttes dApache une signature PHP qui contient le numro de version de linterprteur.
Pourquoi divulguer cette information ?
allow_url_fopen off. Cette option nest pas rellement ncessaire si vous faites attention la faon dont vous accdez aux fichiers partir de vos scripts,
cest--dire si vous vrifiez toutes les donnes qui vous sont transmises.
S c u rit et P H P 41
En rgle gnrale, vous ne devez pas faire confiance un code qui veut utiliser ces fonctionnalits. Faites tout particulirement attention tout ce qui tente
dutiliser une fonction comme system() : un tel code est la plupart du temps une
source de problmes.
Ces options de configuration tant dsormais correctement initialises, tudions quelques attaques spcifiques, ainsi que les mthodes qui permettront
votre serveur de sen protger.
Si ce paramtre vient directement du formulaire, utilisez les protections proposes par la base de donnes en passant par des fonctions PHP, comme ici :
$sql = select * from infos_produits
where num_produit = " . mysql_real_escape_string($produit)
. ";
42 Ch apitr e 3
NOTE Pour protger automatiquement toutes les donnes des formulaires, vous pouvez activer les
apostrophes magiques, comme on la expliqu dans la recette n15, "Activer les apostrophes
magiques". Sachez toutefois que cette option de configuration est fortement dconseille dans
les dernires versions de PHP (http://fr3.php.net/magic_quotes) et que vous devriez privilgier
des solutions de contournement, comme nous lavons vu la recette n15.
Vous pouvez viter plusieurs problmes en limitant les privilges de lutilisateur MySQL. Tout compte MySQL peut, en effet, tre limit un certain type de
requtes sur les tables slectionnes. Vous pourriez, par exemple, crer un utilisateur nayant que le droit de consulter les lignes de ces tables. Cependant, ce
nest gure utile pour des donnes dynamiques et, en outre, cela nempchera
pas laccs des donnes confidentielles. Un utilisateur ayant accs des donnes de connexion pourrait, par exemple, injecter du code lui permettant
daccder un autre compte que celui qui est affect la session courante.
S c u rit et P H P 43
ces attaques. Bien que XSS ne soit pas li PHP, vous pouvez nettoyer les donnes utilisateurs avec PHP afin dempcher les attaques. Pour ce faire, vous devez
restreindre et filtrer les donnes qui sont soumises par les utilisateurs. Cest pour
cette raison que la plupart des sites de forums en ligne nautorisent pas les balises
HTML dans les articles et les remplacent par des balises personnalises comme
[b] et [linkto].
Examinons un script simple, qui illustre comment se prmunir contre certaines de ces attaques. Pour une solution plus complte, il est prfrable dutiliser
SafeHTML qui sera prsent plus loin dans ce chapitre.
function transforme_HTML($chaine, $longueur = null) {
// Aide empcher les attaques XSS
// Supression des espaces inutiles.
$chaine = trim($chaine);
// Empche des problmes potentiels avec le codec Unicode.
$chaine = utf8_decode($chaine);
// HTMLise les caractres spcifiques HTML.
$chaine = htmlentities($chaine, ENT_NOQUOTES);
$chaine = str_replace("#", "#", $chaine);
$chaine = str_replace("%", "%", $chaine);
$longueur = intval($longueur);
if ($longueur > 0) {
$chaine = substr($chaine, 0, $longueur);
}
return $chaine;
}
Cette fonction transforme les caractres spcifiques HTML par des littraux HTML. Un navigateur affichera donc le code HTML pass par ce script
comme du texte sans balise. Considrons, par exemple, cette chane HTML :
<STRONG>Texte en gras</STRONG>
44 Ch apitr e 3
Cest pour empcher cette astuce que transforme_HTML() ajoute des tapes
supplmentaires pour convertir les symboles # et % en leurs entits correspondantes, ce qui bloque les attaques hexadcimales. Il fait de mme pour convertir les
donnes encodes en UTF-8.
Enfin, pour empcher que quelquun essaie de surcharger une chane avec
une saisie trs longue en esprant casser quelque chose, vous pouvez ajouter un
paramtre $longueur facultatif pour couper la chane une longueur maximale
de votre choix1.
1. NdR : Mfiez-vous des scripts cls en main qui sinstallent en des millions dexemplaires sur
des serveurs web.
S c u rit et P H P 45
Lune des solutions reposant sur les listes blanches sappelle SafeHTML, un
analyseur anti-XSS crit par PixelApes.
SafeHTML est suffisamment malin pour reconnatre du code HTML correct
et peut donc chasser et supprimer toutes les balises dangereuses. Cette analyse
est ralise par un autre paquetage, HTMLSax.
Pour installer et utiliser SafeHTML, suivez les tapes suivantes :
1. Tlchargez la dernire version de SafeHTML.
2. Placez les fichiers dans un rpertoire classes sur votre serveur. Ce rpertoire contiendra tous les fichiers ncessaires au fonctionnement de SafeHTML et HTMLSax.
3. Incluez le fichier classe de SafeHTML (safehtml.php) dans votre script.
4. Crez un objet SafeHTML appel $safehtml.
5. Nettoyez vos donnes avec la mthode $safehtml->parse().
Voici un exemple complet :
<?php
/* Si vous avez stock HTMLSax3.php et safehtml.php dans le rpertoire
/classes, dfinissez XML_HTMLSAX3 comme une chane vide. */
define(XML_HTMLSAX3, );
// Inclusion du fichier classe.
require_once(classes/safehtml.php);
// Cration dun exemple de code incorrect.
$donnees = "Ce contenu lvera une alerte <script>alert(Attaque XSS)</script>";
// Cration dun objet safehtml.
$safehtml = new safehtml();
// Analyse et nettoyage des donnes.
$donnees_sures = $safehtml->parse($donnees);
// Affichage du rsultat.
echo Les donnes propres sont <br /> . $donnees_sures;
?>
Si vous voulez nettoyer dautres donnes de votre script, il nest pas ncessaire de crer un nouvel objet. Il suffit de rutiliser tout au long du script la
mthode $safehtml->parse().
Problmes ventuels
Lerreur la plus importante que vous puissiez faire est de supposer que cette
classe empche totalement les attaques XSS. SafeHTML est un script assez complexe qui vrifie quasiment tout, mais rien nest garanti. Vous devrez tout de
mme effectuer une validation adapte vos besoins des donnes qui vous sont
transmises. Cette classe, par exemple, ne teste pas la longueur dune variable
pour vrifier quelle tiendra dans le champ dune table. Elle ne vous protge pas
non plus contre les problmes de dbordement de tampon.
46 Ch apitr e 3
Les pirates XSS ont de limagination et utilisent un grand nombre dapproches pour tenter datteindre leurs objectifs. Il suffit de lire le didacticiel XSS
de RSnake (http://ha.ckers.org/xss.html) pour se rendre compte du nombre de
faons de faire passer du code travers les filtres. Le projet SafeHTML regroupe
de bons programmeurs qui prennent sur leur temps libre pour essayer dendiguer les attaques XSS et lapproche choisie est fiable, mais on ne peut pas tre
certain quun pirate ne trouvera pas une nouvelle technique pour court-circuiter
ses filtres.
NOTE La page http://namb.la/popular/tech.html donne un exemple de la puissance des attaques
XSS en montrant comment crer pas pas le ver JavaScript qui a surcharg les serveurs de
MySpace.
S c u rit et P H P 47
Chiffrement et hachage
Dun point de vue technique, ce traitement nest pas un chiffrement mais un hachage,
ce qui est diffrent pour deux raisons :
Contrairement au chiffrement, les donnes ne peuvent pas tre dchiffres.
Il est possible (mais trs peu probable) que deux chanes diffrentes produisent le
mme hachage. Il ny a, en effet, aucune garantie quun hachage soit unique : vous ne
devez donc pas utiliser un hachage pour vous en servir de cl dans une base de donnes.
function hash_ish($chaine) {
return md5($chaine);
}
La fonction md5() renvoie une chane hexadcimale de 32 caractres forme partir
de lalgorithme Message-Digest de RSA Data Security Inc. (galement connu sous le
nom de MD5). Vous pouvez insrer cette chane de 32 caractres dans une base de
donnes, la comparer dautres chanes de 32 caractres ou simplement admirer
sa perfection.
Amlioration du script
Il est virtuellement impossible de dchiffrer des donnes MD5. En tous cas,
cest trs difficile. Cependant, vous devez quand mme utiliser de bons mots de
passe car il est quand mme assez simple de crer une base de donnes de hachage
pour tout le dictionnaire. Il existe dailleurs des dictionnaires MD5 en ligne dans
lesquels vous pouvez entrer la chane 90a8db953336c8dabbcf48b1592a8c06 et
obtenir "chien".
Par consquent, bien que, techniquement, lon ne puisse pas dchiffrer les
chanes MD5, elles sont quand mme vulnrables si quelquun russit rcuprer votre base de donnes des mots de passe, vous pouvez tre sr quil utilisera
un dictionnaire MD5. Lorsque vous crez des systmes avec authentification par
mot de passe, vous avez donc intrt utiliser des mots de passe suffisamment
longs (au moins six caractres, mais huit est prfrable) et contenant la fois des
lettres et des chiffres. Vrifiez galement que ces mots de passe ne figurent pas
dans un dictionnaire.
48 Ch apitr e 3
sophistiqu de vos donnes. La bibliothque Mcrypt offre plus de 30 chiffrements possibles et permet dutiliser une phrase secrte garantissant que vous seul
(ou, ventuellement, vos utilisateurs) pourrez dchiffrer les donnes. Pour utiliser ce module, vous devez recompiler PHP pour quil le prenne en compte,
comme on la expliqu la recette n18, "Ajouter des extensions PHP".
Le script suivant contient des fonctions qui se servent de Mcrypt pour chiffrer
et dchiffrer les donnes :
<?php
$donnees = "Donnes chiffrer";
$cle = "Phrase secrte utilise par chiffrer pour chiffrer les donnes";
$code = "MCRYPT_SERPENT_256";
$mode = "MCRYPT_MODE_CBC";
function chiffrer($donnees, $cle, $code, $mode) {
// Chiffre les donnes
return (string)
base64_encode
(
mcrypt_encrypt
(
$code,
substr(md5($cle),0,mcrypt_get_key_size($code, $mode)),
$donnees,
$mode,
substr(md5($cle),0,mcrypt_get_block_size($code, $mode))
)
);
}
function dechiffre($donnees, $cle, $code, $mode) {
// Dchiffre les donnes
return (string)
mcrypt_decrypt
(
$code,
substr(md5($cle),0,mcrypt_get_key_size($code, $mode)),
base64_decode($donnees),
$mode,
substr(md5($cle),0,mcrypt_get_block_size($code, $mode))
);
}
?>
La phrase secrte, galement appele cl, pour chiffrer et dchiffrer les donnes.
S c u rit et P H P 49
Le code utilis pour chiffrer les donnes, qui indique lalgorithme de chiffrement. Ce script utilise MCRYPT_SERPENT_256, mais vous pouvez en choisir un
autre, notamment MCRYPT_TWOFISH192, MCRYPT_RC2, ainsi que MCRYPT_DES ou
MCRYPT_LOKI97.
NOTE Pour connatre les chiffrements disponibles sur votre serveur, utilisez la recette n8, "Afficher
toutes les options de configuration de PHP" et recherchez la section "Mcrypt" sur la page
produite par phpinfo(). Si Mcrypt est disponible, vous verrez deux sections : "Supported
Cipher" et "Supported Modes". Vous pouvez chiffrer vos donnes en les utilisant exactement
comme ils sont crits.
Le mode utilis pour chiffrer les donnes. Il existe plusieurs modes, dont
Electronic Codebook et Cipher Feedback. Ce script utilise MCRYPT_MODE_CBC (Cipher
Block Chaining).
Amlioration du script
En plus de tester les diffrentes mthodes de chiffrement, vous pouvez faciliter lutilisation de ce script en plaant, par exemple, la cl et le mode dans des
constantes globales que vous stockerez dans un fichier inclus (voir la recette n1,
"Inclure un fichier extrieur dans un script") : cela vous vitera de devoir les
rpter chaque fois.
50 Ch apitr e 3
Utilisation du script
La fonction cree_mdp() renvoie une chane ; tout ce qui vous reste faire
consiste lui fournir la longueur de cette chane en paramtre :
<?php
$mdp_de_quinze_caracteres = cree_mdp(15);
?>
S c u rit et P H P 51
4
TRA I T E M E N T D E S F ORM U LA I RE S
Les visiteurs de votre site peuvent crire leurs propres formulaires HTML et
lutiliser avec votre serveur ; ils peuvent galement se passer totalement dun
navigateur et utiliser des outils automatiss pour interagir avec vos scripts. Lorsque vous mettez un script disposition sur le Web, vous devez supposer que des
personnes essaieront de jouer avec les paramtres pour tenter de dcouvrir un
moyen plus simple dutiliser votre site (bien quils puissent galement essayer
quelque chose de bien moins innocent).
Pour garantir la scurit de votre serveur, vous devez donc vrifier toutes les
donnes reues par vos scripts.
Stratgies de vrification
Il y a deux approches pour vrifier les donnes des formulaires : les listes noires
et les listes blanches.
Les listes noires consistent tenter de supprimer toutes les donnes incorrectes en supposant que les donnes provenant des formulaires sont correctes
puis en liminant explicitement les mauvaises. En gnral, cette technique
nest pas efficace. Supposons, par exemple, que vous vouliez liminer tous les
"mauvais" caractres dune chane, comme les apostrophes. Vous pourriez
rechercher et remplacer ces apostrophes, mais le problme est quil y aura toujours des mauvais caractres auxquels vous navez pas pens. En gnral, les listes noires supposent que les donnes que vous recevez ne sont pas malicieuses.
En ralit, il est prfrable de considrer systmatiquement que ces donnes
sont suspectes ; vous pourrez alors les filtrer pour naccepter que celles qui sont
correctes : cest ce quon appelle les listes blanches. Si, par exemple, une chane
ne doit contenir que des caractres alphanumriques, vous pouvez la comparer
avec une expression rgulire qui correspond une chane forme uniquement
des caractres A-Za-z0-9.
Le filtrage par liste blanche peut galement forcer les donnes appartenir
un intervalle connu et modifier le type dune valeur. Voici un rsum de quelques
tactiques spcifiques :
Si la valeur doit tre une chane, testez-la avec la fonction is_string(). Pour la
convertir en chane, utilisez la fonction strval().
54 Ch apitr e 4
Tr a i t e m e n t d e s f o r m u l a i r e s 55
<?php
$valeur_post = $_POST[champ_post];
$valeur_get = $_GET[champ_get];
$une_valeur = $_REQUEST[un_champ];
?>
$_REQUEST est lunion des tableaux $_GET, $_POST et $_COOKIE. Si vous avez plusieurs paramtres de mme nom il faut savoir que, par dfaut, PHP renvoie
dabord le cookie, puis le paramtre POST et enfin le paramtre GET.
La scurit de $_REQUEST a donn lieu des dbats qui nont pas lieu dtre :
toutes ses sources venant du monde extrieur (le navigateur de lutilisateur),
vous devez de toutes faons vrifier toutes les donnes que vous comptez utiliser
dans ce tableau, exactement comme vous le feriez avec les autres tableaux prdfinis. Les seuls problmes que vous pourriez rencontrer sont des bogues
ennuyeux susceptibles dapparatre cause des cookies quil contient.
Parfois, les espaces inutiles peuvent se trouver au milieu de la chane lutilisateur a pu copier et coller un contenu de courrier lectronique, par exemple.
En ce cas, vous pouvez remplacer les suites despaces et les autres caractres
despacement par une espace unique laide de la fonction preg_replace().
Le terme reg indique que cette fonction utilise les expressions rgulires, une forme
trs puissante de recherche par motif que vous rencontrerez plusieurs fois dans
ce chapitre.
<?php
function suppr_espaces($chaine) {
$chaine = preg_replace(/\s+/, , $chaine);
$chaine = trim($chaine);
return $chaine;
}
?>
56 Ch apitr e 4
Ce script vous servira en de maintes occasions, mme en dehors de la vrification des formulaires, car il permet de nettoyer les donnes provenant dautres
sources externes.
Lorsque PHP rcupre les donnes dun formulaire comme celui-ci, il stocke
les valeurs des cases cocher dans un unique tableau que vous pouvez ensuite
parcourir de la faon suivante :
Tr a i t e m e n t d e s f o r m u l a i r e s 57
<?php
$genre_film = $_POST["genre_film"];
$nom_client = strval($_POST["nom_client"]);
if (is_array($genre_film)) {
foreach ($genre_film as $genre) {
print "$nom_client regarde des films du genre $genre.<br>";
}
}
?>
Cette technique ne fonctionne pas quavec les cases cocher ; elle est extrmement pratique chaque fois quil faut traiter un nombre quelconque de
lignes. Supposons, par exemple, un menu o nous voulons montrer tous les articles dune catgorie donne. Bien que lon ne sache pas le nombre darticles
dune catgorie, le client devrait pouvoir entrer une quantit dans un champ de
saisie pour chaque article quil veut acheter et ajouter tous les articles dun simple
clic. Ce menu ressemblerait celui de la Figure 4.1.
58 Ch apitr e 4
Pour traiter ce formulaire, vous devez examiner les deux tableaux passs
au script de traitement lun contient tous les numros de produit ($num_produit) et lautre les valeurs des champs de saisie pour les quantits respectives
($quantite). Peu importe le nombre darticles qui sont affichs sur la page :
$num_produit[123] contient le numro de produit du 123e article affich et
$quantite[123] le nombre que le client a entr dans le champ de saisie correspondant.
Le script de traitement ajoutpanier.php est le suivant :
<?php
$num_produit = $_POST["num_produit"];
$quantite = $_POST["quantite"];
if (is_array($quantite)) {
foreach ($quantite as $cle => $qte_article) {
$qte_article = intval($qte_article);
if ($qte_article > 0) {
$num = $num_produit[$cle];
print "Ajout de $qte_article articles du produit $num.<br>";
}
}
}
?>
Tr a i t e m e n t d e s f o r m u l a i r e s 59
Comme vous pouvez le constater, ce script dpend entirement de lutilisation de lindice du tableau $quantite (la variable $cle) pour le tableau $num_
produit.
Comment tre sr que la donne que vous recevrez sera vraiment visa, amex
ou mastercard ? Cest assez simple : il suffit dutiliser les valeurs possibles comme
cls dun tableau et vrifier que la valeur reue est bien une cl de ce tableau.
Voici un exemple :
<?php
$cartes_credit = array(
"amex" => true,
"visa" => true,
"mastercard" => true,
);
$type_carte = $_POST["type_carte"];
if ($cartes_credit[$type_carte]) {
print "$type_carte est une carte de crdit autorise.";
} else {
print "$type_carte nest pas une carte de crdit autorise.";
}
?>
Amlioration du script
Lavantage de cette mthode de stockage est que vous pouvez temporairement dsactiver un choix en mettant false la valeur qui correspond sa cl.
Vous pouvez aussi lgrement modifier le script pour quil fournisse la fois
les valeurs et les textes qui leur sont associs : il suffit de placer ces textes dans les
valeurs du tableau afin, par exemple, dafficher American Express quand lutilisateur
a choisi amex.
60 Ch apitr e 4
Tr a i t e m e n t d e s f o r m u l a i r e s 61
62 Ch apitr e 4
$checksum = 0;
for ($i = 0; $i < strlen($code_inverse); $i++) {
$num_courant = intval($revcode[$i]);
if ($i & 1) { /* Position impaire */
$num_courant *= 2;
}
/* Spare les chiffres et les additionne. */
$checksum += $num_courant % 10;
if ($num_courant > 9) {
$checksum += 1;
}
}
if ($checksum % 10 == 0) {
return $type_carte;
} else {
return $faux;
}
}
?>
Tr a i t e m e n t d e s f o r m u l a i r e s 63
Aprs le parcours de tous les chiffres, la somme de contrle finale doit tre
divisible par 10 ; sinon, le numro tait incorrect.
Cet algorithme peut tre cod de plusieurs faons ; nous avons privilgi ici
la compacit et la lisibilit.
Utilisation du script
Il suffit de fournir la fonction verifie_num_cc() une chane contenant un
numro de code et de tester sa valeur de retour. La seule prcaution prendre
consiste vrifier que cette chane ne contient que des chiffres ; pour cela, vous
pouvez utiliser preg_replace()avant dappeler la fonction. Voici un fragment de
code qui appelle la fonction pour tester plusieurs numros de carte :
$nums = array(
"3721 0000 0000 000",
"340000000000009",
"5500 0000 0000 0004",
"4111 1111 1111 1111",
"4222 2222 22222",
"4007000000027",
"30000000000004",
"6011000000000004",
);
foreach ($nums as $num) {
/* Supprime tous les caractres non numriques du numro de carte */
$num = preg_replace(/[^0-9]/, "", $num);
$t = verifie_num_cc($num);
if ($t) {
print "$num est correct (type: $t).\n";
} else {
print "$num est incorrect.\n";
}
}
Amlioration du script
Si vous connaissez le format de leurs numros, vous pouvez ajouter les autres
cartes de crdits connues. La page http://www.sitepoint.com/print/card-validation-class-php contient une foule dinformations utiles sur les cartes de crdit, de
mme que le site http://www.phpsources.org/scripts407-PHP.htm qui propose
un script permettant de vrifier les numros SIRET.
64 Ch apitr e 4
Janvier</option>
Fvrier</option>
Mars</option>
Avril</option>
Mai</option>
Juin</option>
Juillet</option>
Aot</option>
Septembre</option>
Octobre</option>
Novembre</option>
Dcembre</option>
<select name="annee_cc">
<?php
/* Cre les options pour les dix annes partir de lanne en cours */
$a = intval(date("Y"));
for ($i = $a; $i <= $a + 10; $i++) {
print "<option value=\"$i\">$i</option>\n";
}
?>
</select>
Tr a i t e m e n t d e s f o r m u l a i r e s 65
return true;
} else {
return false;
}
?>
Utilisation du script
if (verif_date_exp($mois_cc, $annee_cc)) {
// Accepte la carte.
} else {
// La carte nest plus valable.
}
utilisateurs ne soient activs que par des liens envoys par courrier lectronique, comme on lexplique dans la recette n65, " Vrifier les comptes utilisateurs avec le courrier lectronique ". Cest
une mesure plutt extrme ; si vous souhaitez que plus de personnes partagent leurs adresses
avec vous, indiquez-leur simplement que vous ne les spammerez pas (et respectez cette promesse).
66 Ch apitr e 4
<?php
function adresse_ok($mel) {
// Vrifie le format de ladresse mel
if (! preg_match( /^[A-Za-z0-9!#$%&\*+-/=?^_`{|}~]+@[A-Za-z0-9-]+(\.[AZa-z0-9]+)+[A-Za-z]$/, $mel)) {
return false;
} else {
return true;
}
}
?>
Ce script utilise une expression rgulire pour tester si ladresse e-mail indique utilise des caractres autoriss (lettres, points, tirets, barres de fraction,
etc.) avec un symbole @ au milieu et au moins un point avant la fin. La recette
n39, "Expressions rgulires" vous en apprendra plus sur le sujet.
Ce script montre la puissance des expressions rgulires combines aux fonctions standard sur les chanes. La cl consiste supprimer tous les caractres qui
Tr a i t e m e n t d e s f o r m u l a i r e s 67
ne sont pas des chiffres une opration faite pour preg_replace(). Lorsque vous
tes sr quil ne reste plus que des chiffres dans la chane, il suffit simplement
dexaminer sa longueur pour connatre le nombre de chiffres et le reste vient
quasiment tout seul.
68 Ch apitr e 4
5
TRA I T E ME N T D U T EX T E E T D E HT M L
Savoir comment retrouver, transformer et supprimer des mots est essentiel pour tout webmestre.
Certaines fonctions de ce chapitre sont assez simples, mais nous les mentionnerons malgr tout pour
mmoire. Les fonctions plus complexes utilisent des
expressions rgulires, une partie puissante de PHP que
chaque webmestre doit savoir manipuler. Commenons par
quelques oprations lmentaires sur les chanes.
Recette 34 : Extraire une partie dune chane
Imaginez que vous soyez la tte dune boutique en ligne vendant des cartes
de collection. Lorsquils achtent un lot de cartes, vos clients sont susceptibles
deffectuer des recherches au pluriel - Chteaux au lieu de Chteau, par exemple.
Cela posait des problmes notre systme de recherche car une requte utilisant
Chteaux ne renvoyait aucun rsultat. Pour rsoudre ce problme, jai utilis quelques fonctions de manipulation des chanes pour analyser la fin de la requte de
lutilisateur et supprimer les occurrences de la lettre x. Passons ces fonctions en
revue.
La fonction substr() permet dextraire la partie de la chane que vous utiliserez lors des comparaisons et des autres oprations. Si, par exemple, la dernire
lettre de la chane est le caractre x, vous pouvez le supprimer et ressayer si la
requte initiale na renvoy aucun rsultat
Lappel de substr() est de la forme :
substr(chaine, debut, nbre)
chaine est la chane initiale, debut est lindice de dbut de lextraction et nbre
est le nombre de caractres extraire. Le premier indice dune chane vaut 0. Le
code suivant, par exemple, affiche cde, les trois caractres partir de lindice 2 :
echo substr(abcdef, 2, 3);
NOTE Pour calculer un indice pour substr(), vous aurez peut-tre besoin de connatre la longueur
En outre, si debut est ngatif, substr() (et de nombreuses autres fonctions sur
les chanes) commencera compter partir de la fin de la chane, comme dans
cet exemple qui affiche les deux avant-derniers caractres dune chane :
echo substr(abcdef, -3, 2); // de
Lorsque vous connaissez la sous-chane qui vous intresse, vous pouvez manipuler la chane de diffrentes faons :
Raffecter une partie donne de la chane en utilisant substr() pour supprimer les caractres inutiles. $chaine = substr($chaine, 0, 10); initialise, par
exemple, $chaine avec les 10 premiers caractres de sa valeur initiale.
Supprimer les N derniers caractres en recourant conjointement aux fonctions substr() et strlen(). $chaine = substr($chaine, 0, strlen($chaine) - 3);
initialise, par exemple, $chaine avec sa valeur initiale, sauf les trois derniers.
Pour comprendre comment utiliser des sous-chanes dans votre travail quotidien, revenons lexemple des chteaux. Rappelez-vous que les utilisateurs
70 Ch apitr e 5
peuvent rechercher Chteaux, mais quun article peut tre dcrit par Chteau.
Ce fragment de code montre un moyen de grer cette situation :
$sql = "SELECT * FROM infos_produits WHERE nom_produit = $saisie"
$resultat = @mysql_query($sql, $connexion);
if (mysql_num_rows($resultat) == 0) {
// Aucune ligne dans lensemble rsultat (pas de produit trouv).
if (substr($saisie, -1) == x) {
// Le dernier caractre de la chane est un "x".
// Supprime le dernier caractre de la chane (le x).
$saisie = substr_replace($saisie, , -1);
// Cre une autre requte SQL avec la nouvelle chane.
$sql = "SELECT * FROM infos_produits WHERE nom_produit = $saisie";
$resultat = @mysql_query($sql, $connexion);
}
}
Amlioration du script
Ce fragment de code savre problmatique. Il peut y avoir un article Chteaux et un article Chteau. Tel quil est crit, le script ne renvoie un rsultat que
pour lun de ces deux cas et il est incohrent car il dpend de la prsence de la
forme plurielle. Si, par exemple, vous avez un article Chteaux et deux articles
Chteau, vous nobtiendrez que le premier article mais, si larticle au pluriel
nexiste pas, vous obtiendrez les deux autres.
Vous pouvez ajouter cette fonctionnalit en utilisant le test substr() pour
quil corrige votre requte SQL au lieu de la remplacer entirement :
$sql = "SELECT * FROM infos_produits WHERE nom_produit = $saisie";
if (substr($saisie, -1) == x) {
// Le dernier caractre de la chane est un "x".
// On ajoute une autre possibilit la clause WHERE.
$sql .= " OR nom_produit = " . substr_replace($saisie, , -1) . "";
}
$resultat = @mysql_query($sql, $connexion);
Cette approche est trs diffrente car elle nutilise quune seule requte SQL
pour tout faire. Au lieu de tenter une seconde requte lorsque la premire
Tr ait em en t d u text e et de H T M L 71
NOTE Vous remarquerez que ce script recourt une petite astuce : nous avons utilis strtolower()
Problmes ventuels
Il y a quelques petits problmes avec ucwords(). Le premier est quelle ne
capitalise pas les lettres situes aprs un caractre non alphabtique : une chane
comme m. dupont-durand deviendrait donc M. Dupont-durand et ac/dc donnerait
Ac/dc. Si cela vous pose problme, vous pouvez crer une fonction utilisant les
expressions rgulires pour dcomposer la chane en un tableau de mots, puis
appeler ucwords() pour capitaliser chaque mot et, enfin, rejoindre les mots de ce
tableau en une seule chane.
72 Ch apitr e 5
Enfin, si vous manipulez des noms, ucwords(strtolower()) supprime les capitales existantes : un nom comme McMurdo deviendra donc Mcmurdo. Si ces capitales sont importantes et que vous devez les prserver, mais que vous devez
comparer ce genre de chanes en PHP, utilisez strcasecmp(ch1, ch2), qui ne tient
pas compte de la casse lors de la comparaison de ch1 et ch2.
strrpos() trouve la position de la dernire occurrence de la sous-chane. Utilisez cette fonction avec substr() pour extraire tout ce qui se trouve aprs
dans la chane.
Ces trois fonctions renvoient False si la sous-chane nexiste pas. Une position et une chane pouvant tre values False dans une conditionnelle, il est
trs important de vrifier le type de la valeur de retour, comme dans le script
suivant :
<?php
$chaine = "Japprouve ce qua fait le snateur Foghorn dans la guerre sur
Buttermilk. Je mappelle Ferrett Steinmetz, et japprouve ce message.";
Tr ait em en t d u text e et de H T M L 73
$terme = "approuve";
// Apparat-il dans la chane?
$pos = strpos($chaine, $terme);
if ($pos === False) {
print "$terme nest pas dans la chane\n";
} else {
print "position: $pos\n";
print "dernire position: " . strval(strrpos($chaine, $terme)) . "\n";
print strstr($chaine, $terme) . "\n";
// Affiche "approuve ce qua fait le snateur..."
print substr($chaine, strrpos($chaine, $terme)) . "\n";
// Affiche "approuve ce message."
}
?>
Problmes ventuels
Trois problmes classiques peuvent survenir. Le premier est quil faut utiliser
loprateur triple gal (===) au lieu du double (==) dans une comparaison car
loprateur triple gal garantit que les valeurs et les types des termes compars
sont les mmes. Ceci est important parce strpos() peut renvoyer 0, alors que
strstr() et strrpos() peuvent renvoyer une chane vide ; or ces deux valeurs
sont considres comme False avec ==.
Le second problme est que ces fonctions sont sensibles la casse ; lappel
strstr($chaine, Approuve) dans lexemple prcdent renverrait donc False.
Les versions insensibles la casse de strpos(), strrpos() et strstr() sappellent,
respectivement stripos(), strripos() et stristr().
Enfin, noubliez pas que ces fonctions permettent de rechercher de simples
sous-chanes, pas des mots. Si vous devez effectuer un traitement plus labor,
comme rechercher des mots qui commencent par appro, comme approbation ou
approuve mais pas dsapprouve, vous avez besoin dun mcanisme plus puissant : les
expressions rgulires. Nous verrons comment les utiliser dans la recette n39,
"Expressions rgulires".
74 Ch apitr e 5
Toutes les occurrences de hamburger seront alors remplaces par carotte, toutes
celles de bacon par brocoli et toutes celles de pot de crme par crme allge.
Problmes ventuels
Le problme de str_replace() est quelle remplace tout ce qui correspond
au motif que vous lui avez indiqu, o quil se trouve dans la chane. Si vous remplacez oui par non, par exemple, vous constaterez que souill sest transform en
snonll et Louis en Lnons.
Tr ait em en t d u text e et de H T M L 75
Par consquent, bien que str_replace() soit dune aide inestimable pour
supprimer certains mots dans les petites chanes, vous devrez utiliser des expressions rgulires pour les oprations plus complexes (voir la recette n39,
"Expressions rgulires").
Si vous obtenez une erreur, cest que la bibliothque nest pas installe. Revenez la recette n18 : "Ajouter des extensions PHP" pour savoir comment corriger ce problme.
76 Ch apitr e 5
Pour utiliser cette fonction, il suffit de lui passer une chane en paramtre :
<?
// recherche vient du formulaire prcdent
$recherche = $_POST[saisie];
$suggestion_spell = suggere_orthographe($recherche);
if ($suggestion_spell) {
// pspell a trouv une suggestion.
echo "Essayez avec cette orthographe: <i>$suggestion_spell</i>.";
}
?>
Tr ait em en t d u text e et de H T M L 77
<?php
$dico = pspell_new("fr");
if (!pspell_check($dico, "lappin")) {
$suggestions = pspell_suggest($dico, "lappin");
foreach ($suggestions as $suggestion) {
echo "Vouliez-vous dire: $suggestion?<br />";
}
}
?>
Cet exemple renvoie "lapin", "alpin", "lopin", "latin" et toutes les autres
orthographes sapprochant du terme mal saisi !
Vous devez configurer un dictionnaire pour initialiser pspell. Pour ce faire, il
faut crer un descripteur vers un fichier de configuration de dictionnaire, modifier quelques options de ce descripteur, puis utiliser la configuration de dictionnaire pour crer un deuxime descripteur pour le vritable dictionnaire.
Si cela vous semble un peu compliqu, ne vous inquitez pas : le code change
rarement et vous pouvez gnralement vous contenter de le copier partir dun
autre script. Cependant, nous allons ici ltudier tape par tape. Voici le code
qui configure le dictionnaire :
$config_dico = pspell_config_create(fr);
pspell_config_ignore($config_dico, 3);
pspell_config_mode($config_dico, PSPELL_FAST);
$config_dico est le descripteur initial, qui contrle les options de votre dictionnaire. Vous devez charger toutes les options dans $config_dico, puis vous en
servir pour crer le dictionnaire. pspell_config_create() cre un dictionnaire
franais (fr). Pour utiliser la langue anglaise et prciser que vous prfrez lorthographe amricaine, passez en en premier paramtre et american en second.
pspell_config_ignore() indique votre dictionnaire quil devra ignorer tous
les mots de 3 lettres ou moins. En effet, la vrification orthographique de chaque
un ou le serait coteuse en temps de calcul.
78 Ch apitr e 5
PSPELL_SLOW permet dobtenir toutes les suggestions possibles, bien que cette
mthode demande un certain temps pour effectuer la vrification orthographique.
Nous pourrions utiliser encore dautres options de configuration (pour ajouter, par exemple, un dictionnaire personnalis, ainsi que nous le verrons plus
loin) mais, comme il sagit ici dune vrification rapide, nous nous contenterons
de crer le dictionnaire lui-mme avec cette ligne :
$dico = pspell_new_config($config_dico);
donc considr comme correct, bien que vous vouliez crire Marie a peur du noir. En outre,
pspell renvoie toujours True si la longueur du mot est infrieure celle indique par pspell_
config_ignore() : ici, un mot comme jlz sera donc considr comme correct.
Revenons au script initial. Maintenant que le dictionnaire est prt, nous dcoupons la chane qui a t passe en paramtre pour obtenir un tableau de mots :
voici ma phrase devient donc un tableau de trois lments, voici, ma et phrase.
Puis, nous vrifions lorthographe de chaque mot en utilisant le dictionnaire
de pspell. Ce dernier naimant pas les virgules, on commence par les supprimer
du mot avant de lancer la vrification. Si le mot compte plus de 3 caractres, la
vrification a lieu et en cas de faute dorthographe, nous effectuons les oprations
suivantes :
1. Nous demandons pspell de nous fournir un tableau contenant ses
suggestions de correction.
2. Nous prenons la suggestion la plus probable (le premier lment du
tableau $suggestion) et nous remplaons le mot mal orthographi par
celle-ci.
3. Nous mettons lindicateur $remplacement_suggere vrai pour qu la fin
de la boucle de traitement, lon sache que lon a trouv une faute dorthographe quelque part dans $chaine.
la fin de la boucle, sil y a eu des corrections orthographiques, nous reformons une chane partir des lments du tableau corrrig et nous renvoyons
cette chane. Sinon, on renvoie null pour indiquer que lon na pas dtect de
faute dorthographe.
Tr ait em en t d u text e et de H T M L 79
Cest le mme script que celui de la section prcdente, mais avec un ajout
essentiel : lappel pspell_config_personal() initialise un fichier dictionnaire
personnel. Si ce fichier nexiste pas dj, pspell en cre un vide pour vous.
Vous pouvez ajouter ce dictionnaire autant de mots que vous le souhaitez
en utilisant la fonction suivante :
pspell_add_to_personal($dico, mot);
Tant que vous navez pas sauvegard le dictionnaire, les mots ne lui sont ajouts que temporairement. Par consquent, aprs avoir insr les mots souhaits,
ajoutez cette ligne la fin du script :
pspell_save_wordlist($dicto);
Problmes ventuels
Noubliez pas que pspell supporte mal les caractres de ponctuation qui, normalement, ne se trouvent pas dans les mots virgules, points-virgules, deuxpoints, etc. Vous devez les supprimer des mots avant de les ajouter votre dictionnaire personnalis.
80 Ch apitr e 5
Bien quau premier abord, cela ne ressemble rien, cette expression rgulire permet de trouver un numro de tlphone dans une chane. Cest donc un
compromis : les expressions rgulires permettent deffectuer des traitements
trs puissants mais vous devez les construire caractre par caractre, comme un
immense chteau de cartes ; si vous ratez un seul caractre, tout le motif
seffondre.
Tr ait em en t d u text e et de H T M L 81
Vous remarquerez que lexpression rgulire est une chane et que les dlimiteurs (les barres de fraction) sont eux-mmes dans la chane. En outre, il est
prfrable dutiliser des apostrophes simples afin que PHP neffectue pas
dexpansion des variables et ninterprte pas les squences dchappement. En
effet, la syntaxe des expressions rgulires entre souvent en conflit avec les
squences dchappement dans les chanes entre apostrophes doubles.
NOTE Il est plus pratique dutiliser un dlimiteur diffrent, la barre droite (|) par exemple, lorsque
lexpression contient beaucoup de barres de fraction comme dans les URL ou les chemins.
Les dlimiteurs des expressions rgulires compatibles Perl existent essentiellement pour ne pas dpayser ceux qui connaissent dj Perl et pour troubler les
dbutants, mais ils autorisent des fonctionnalits supplmentaires, les modificateurs, qui sont des caractres placs aprs le dlimiteur fermant. Le plus courant
est le modificateur i, qui rend lexpression insensible la casse. Si vous voulez
rechercher fred, Fred, FRED, fRed, etc., lexpression rgulire sera donc /fred/i.
Caractres spciaux
Pour que les expressions rgulires soient plus compactes et plus pratiques,
vous pouvez utiliser un certain nombre de caractres et de squences correspondant des circonstances particulires. Le caractre le plus simple (mais extrmement utile) est le point (.). Il correspond nimporte quel caractre, sauf le
retour la ligne. Lexpression rgulire /f.ed/, par exemple, capturera fled, fred,
f!ed, etc. Pour capturer un vrai point utilisez un anti-slash (cette mthode de
dsactivation fonctionne pour tous les caractres spciaux) ; /f\.red/ ne capturera
donc que f.red.
Voici dautres caractres spciaux utiles :
Vous en trouverez bien dautres dans le manuel de PHP, mais ceux que nous
venons de citer sont essentiels.
Itrateurs de motifs
Il est temps de passer la vritable puissance des expressions rgulires
en expliquant comment rpter des motifs. Commenons par le caractre
astrisque (*) qui signifie capture zro ou un un nombre quelconque doccurrences
82 Ch apitr e 5
tres spciaux ! Pour capturer fred?, par exemple, il faut crire /fred\?/, pas /fred?/.
Groupements
Lastuce suivante consiste grouper un ensemble de caractres entre parenthses. Ce groupement permet dutiliser un itrateur sur une sous-expression au
lieu dun simple caractre. Dans lexpression /f(re)+d/, par exemple, (re)+
signifie quil faut capturer re au moins une fois ; le motif complet capture donc
fred, frered, frerered, etc.
Une autre fonctionnalit que vous trouverez souvent dans les groupements
est la barre droite (|) qui demande PHP de capturer, soit la partie gauche, soit
la partie droite de la barre. Lexpression /lait (et|ou) viande/ capturera la
fois lait et viande et lait ou viande. De mme, /fred(ing|ed)?/ capturera freding,
freded et fred.
Classes de caractres
La dernire syntaxe que nous prsenterons consiste utiliser des crochets
([]) pour crer une classe de caractres, cest--dire plusieurs caractres qui
seront considrs comme un seul lors de la capture. Un tiret permet de crer un
intervalle : [0-9], par exemple, capture nimporte quel chiffre dcimal, tandis
que [0-9a-fA-F] capture nimporte quel chiffre hexadcimal.
Si le premier caractre aprs le crochet ouvrant est un accent circonflexe (^),
il sagit dune classe de caractres inverse, qui correspondra nimporte quel
caractre qui nest pas entre les crochets. [^0-9], par exemple, capturera
nimporte quel caractre qui nest pas un chiffre dcimal.
Tr ait em en t d u text e et de H T M L 83
Cette expression permet de capturer une balise de lien en HTML. Ses diffrentes parties sont les suivantes :
1. Toutes les balises de liens commencent par <a, cest donc le premier
composant.
2. Il doit ensuite y avoir au moins une espace ; sinon, on pourrait avoir
<arbre>, ce qui naurait aucun sens. Cest la raison du \s+.
3. Maintenant, nous voulons capturer tous les caractres de la balise jusquau
lien, car il pourrait y avoir dautres attributs comme target="blank". Les
balises se terminant par >, nous utilisons [^>]* pour capturer zro ou plusieurs caractres qui ne sont pas >. Si cette tape ne semble pas vidente,
cest normal : gnralement, vous constaterez que vous en avez besoin
aprs avoir crit le reste de lexpression.
4. Les attributs pour les liens commencent par href=", cest donc ce qui vient
aprs (pour des raisons de simplicit, nous ignorons pour linstant le fait
que certains ne mettent pas de guillemet).
5. Nous sommes maintenant sur le premier caractre du lien et nous voulons
le capturer en entier ; cest la raison pour laquelle on utilise la mme
astuce que dans ltape 3 : [^"]+ capture tous les caractres du lien (qui en
comporte au moins un).
6. Pour capturer le guillemet qui ferme le lien, nous ajoutons un guillemet ("). Ceci pourrait sembler inutile puisque nous avons dj captur
le lien, mais il est gnralement prfrable de continuer un peu le
motif pour ne pas capturer accidentellement une valeur entirement
fausse.
7. Les noms des balises et des attributs HTML tant insensibles la casse,
nous ajoutons le modificateur i aprs le dlimiteur.
Comme vous pouvez le constater, la recherche par motif avec les expressions
rgulires est assez simple. Il est maintenant temps de voir comment les utiliser
avec PHP.
84 Ch apitr e 5
<?php
$s = blah<a href="a.php">blah</a><a href="b.php">blah</a>;
if (preg_match(/<a\s+[^>]*href="[^"]+"/i, $s, $corresp)) {
print "correspondance: $corresp[0]";
}
?>
Le groupement est signal par les parenthses autour de [^"]+. Si une correspondance est trouve, $corresp[0] sera toujours la totalit de la chane capture,
mais $corresp[1] contiendra aussi ce qui a t captur par le premier groupe
(a.php, ici). Sil y avait eu des groupes supplmentaires dans lexpression, ils
auraient t dsigns par $corresp[2], $corresp[3], etc.
ce stade, il est peut tre sage de se rappeler de la fonction print_r(), qui
affiche le contenu complet dun tableau. Elle peut tre vraiment utile afin de
savoir comment fonctionne le tableau qui contient les correspondances, notamment si vous souhaitez galement connatre lindice de ces correspondances car
le format de ce tableau change lorsque vous avez besoin de cette information. En
effet, vous devez alors ajouter le paramtre PREG_OFFSET_CAPTURE lappel de la
fonction :
preg_match(/<a\s+[^>]*href="([^"]+)"/i, $s, $corresp, PREG_OFFSET_CAPTURE);
Sil y a une correspondance, $corresp[0] et $corresp[1] contiennent toujours des informations sur la capture complte et celle du premier groupe mais
ce sont maintenant des tableaux et non plus des chanes. Le premier lment de
Tr ait em en t d u text e et de H T M L 85
chaque tableau est la chane capture et le second est lindice de dbut de la capture.
Voici le rsultat lgrement compact de print_r($corresp) pour notre exemple :
Array (
[0] => Array
[0]
[1]
)
[1] => Array
[0]
[1]
)
)
(
=> <a href="a.php"
=> 4
(
=> a.php
=> 13
Tout fonctionne bien, mais est perfectible. Supposons que nous voulions utiliser une partie de la chane initiale au lieu de tout remplacer par deb, comme
prcdemment, on veut conserver les e de la chane. Ici, fred deviendra deb,
freed deviendra deeb, etc. Pour ce faire, il suffit de grouper la partie concerne
86 Ch apitr e 5
dans lexpression rgulire, puis dutiliser une rfrence arrire dans la chane de
remplacement. Voici comment faire :
print preg_replace(/fr(e+)d/, d$1b, $s);
Grce aux rfrences arrires, cette transformation peut se faire en une seule
tape :
$table = preg_replace(/<td>([^<]*),\s*([^<]*)<\/td>/,
<td>$1</td> . "\n" . <td>$2</td>,
$table);
Tout cela mrite quelques explications. Jai spar les paramtres sur trois
lignes afin de rendre cet appel plus lisible. Vous noterez quil y a deux groupes et,
quavant le second, \s* capture tous les espaces en trop. Enfin, vous remarquerez
que la chane de remplacement est produite par concatnation car on a besoin
dun retour la ligne, or ce caractre doit tre dans une chane entre apostrophes
doubles pour pouvoir tre interprt.
Tr ait em en t d u text e et de H T M L 87
Vous pouvez alors annoncer votre chef que la transformation vous prendra
un temps fou et vous avez maintenant du temps pour vous amuser avec votre
console de jeu.
Tous les liens sont maintenant dans $corresp[1] (rappelez-vous que $corresp[0]
contient tout ce qui a t captur). Initialisons quelques tableaux qui serviront
plus tard stocker et classer les liens :
$tous_les_liens = array();
$liens_js = array();
$liens_complets = array();
$liens_locaux = array();
Nous pouvons maintenant parcourir tous les liens pour effectuer le classement. On teste dabord que le lien na pas dj t rencontr et, si ce nest pas le
cas, on utilise plusieurs expressions rgulires pour dterminer sa catgorie :
88 Ch apitr e 5
<table border="0">;
"<tr><td>Nombre de liens:</td><td>";
strval(count($corresp[1])) . "</td></tr>";
"<tr><td>Liens uniques:</td><td>";
strval(count($tous_les_liens)) . "</td></tr>";
"<tr><td>Liens locaux:</td><td>";
strval(count($liens_locaux)) . "</td></tr>";
"<tr><td>Liens complets:</td><td>";
strval(count($liens_complets)) . "</td></tr>";
"<tr><td>Liens JavaScript:</td><td>";
strval(count($liens_js)) . "</td></tr>";
</table>;
Amlioration du script
Un script comme celui-ci peut stendre linfini : vous pouvez classer les
liens selon vos besoins, en suivre certains, etc. Un exercice trs utile consiste
dcomposer lURL initiale en ses diffrents composants afin de transformer les
liens locaux (relatifs) en URL compltes (absolues).
Tr ait em en t d u text e et de H T M L 89
Comme il ny a pas de balises <BR /> ou <P> pour crer des coupures de
lignes, ce texte saffichera de la faon suivante sil est simplement inject dans un
navigateur web :
Bonjour tous, Suite ma thrapie, une partie de mon cerveau a t supprime.
Je vais bien et je peux maintenant regarder les films dric et Ramzy avec mes amis.
Sincrement, Fred
Bien quil soit dangereux de recopier aveuglment tout ce qua saisi lutilisateur puisque cela vous expose des attaques XSS, ignorons ce problme
pour le moment et supposons que vous ayez une entire confiance en cet utilisateur.
Le moyen le plus simple de convertir les retours la ligne en HTML
consiste utiliser la fonction PHP nl2br(). Malheureusement, cette mthode
nest pas trs souple car elle traduit chaque retour la ligne en balise <br> : si
votre texte brut contient galement du code HTML, vous risquez donc
dobtenir un rsultat assez illisible. Considrons, par exemple, cet extrait
HTML :
<table>
<tr><td>Je suis une ligne du tableau
</td></tr>
</table>
90 Ch apitr e 5
<?
function autop($pee, $br = 1) {
// Convertit du texte brut en HTML
$pee = $pee . "\n"; // On marque la fin pour faciliter le traitement.
$pee = preg_replace(|<br />\s*<br />|, "\n\n", $pee);
$pee = preg_replace(!(<(?:table|ul|ol|li|pre|form|blockquote|
h[16])[^>]*>)!, "\n$1", $pee); // Espace un peu
$pee = preg_replace(!(</(?:table|ul|ol|li|pre|form|blockquote|
h[16])>)!, "$1\n", $pee); // Espace un peu
$pee = preg_replace("/(\r\n|\r)/", "\n", $pee); // Retours la ligne
// portables.
$pee = preg_replace("/\n\n+/", "\n\n", $pee); // Gre les doublons.
$pee = preg_replace(/\n?(.+?)(?:\n\s*\n|\z)/s, "\t<p>$1</p>\n",
$pee);
// Cre les paragraphes, dont un la fin.
$pee = preg_replace(|<p>\s*?</p>|, , $pee); // Dans certaines
// conditions, cela pourrait crer un P ne contenant que des espaces.
$pee = preg_replace("|<p>(<li.+?)</p>|", "$1", $pee); // Problme avec
// les listes imbriques.
$pee = preg_replace(|<p><blockquote([^>]*)>|i, "<blockquote$1><p>",
$pee);
$pee = str_replace(</blockquote></p>, </p></blockquote>, $pee);
$pee = preg_replace(!<p>\s*(</?(?:table|tr|td|th|div|ul|ol|
li|pre|select|form|blockquote|p|h[1-6])[^>]*>)!,
"$1", $pee);
$pee = preg_replace(!(</?(?:table|tr|td|th|div|ul|ol|li|pre|select|
form|blockquote|p|h[1-6])[^>]*>)\s*</p>!, "$1",
$pee);
// Cre ventuellement des retours la ligne
if ($br) $pee = preg_replace(|(?<!<br />)\s*\n|, "<br />\n", $pee);
$pee = preg_replace(!(</?(?:table|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|
select|form|blockquote|p|h[1-6])[^>]*>)\s*<br />!,
"$1", $pee);
$pee = preg_replace(!<br />(\s*</?(?:p|li|div|th|pre|td|ul|ol)>)!,
$1, $pee);
$pee = preg_replace(/&([^#])(?![a-z]{1,8};)/, &$1, $pee);
return $pee;
}
?>
Tr ait em en t d u text e et de H T M L 91
Elle signifie capture toute balise table, ul, ol, li, pre, form, blockquote ou h1 h6.
Mais, ici, tout ce qui intresse Matthew Mullenweg est le groupement, car il
lui permet de prciser plusieurs balises dans la mme expression rgulire.
Sans faire une analyse ligne par ligne de cette fonction, nous pouvons dcrire
le droulement de autop() :
1. Nettoyage : autop() remplace toutes les instances rptes de <br> par des
retours la ligne et corrige tous les retours la ligne pour quils fonctionnent avec Unix (ce dernier utilise un unique caractre \n, alors que
Windows utilise \r\n) ; elle rduit galement les longues chanes de
retours la ligne en deux retours la ligne.
2. Remplacement : autop() recherche tous les retours la ligne suivis de
texte, lui-mme suivi de deux retours la ligne et remplace les retours la
ligne par des balises de paragraphes, comme dans <p>truc</p> .
3. Suppression des balises : autop() recherche les balises de blocs HTML
(comme les listes numrotes et les tableaux) qui seraient perturbes par
<p> ou <br> et les supprime.
92 Ch apitr e 5
Tr ait em en t d u text e et de H T M L 93
Pour indiquer les balises que vous voulez malgr tout autoriser, passez la
fonction un paramtre supplmentaire, une chane contenant les balises autorises
(nindiquez que les balises ouvrantes) :
print strip_tags($chaine, "<i><b><p>");
94 Ch apitr e 5
6
TRA I T E M E NT D E S D A T ES
Temps
60
Une minute
3600
Une heure
28800
Huit heures
86400
Un jour
604800
Une semaine
Vous pouvez galement stocker ce rsultat de cette fonction dans une variable :
$temps = time();
De nombreuses fonctions PHP sur les dates attendent un instant en paramtre et la plupart utilisent linstant courant si vous ne leur fournissez pas cette
information. La fonction date(), par exemple, renvoie une chane formate
selon vos envies laide de la syntaxe date("format", instant) mais, si vous ne
fournissez pas instant, elle renverra linstant courant format selon format.
96 Ch apitr e 6
Cela signifie que, dans la plupart des cas, vous navez mme pas besoin demployer
time() si vous voulez travailler sur le moment prsent.
NOTE Un instant est toujours exprim en UTC (Coordinated Universal Time, un standard inter-
national trs prcis). Cependant, les fonctions daffichage des dates et des temps utilisent la
zone horaire du serveur pour en dduire lheure locale afficher. Linstant 1136116800,
par exemple, reprsentera une heure du matin sur un serveur utilisant la zone MET (Middle European Time), mais sept heures du soir le jour prcdent sur un serveur de la zone
EST (Eastern Standard Time).
strtotime("Friday")
Vendredi minuit
strtotime("+1 week")
strtotime("-2 months")
Il y a deux mois
strtotime("October 1, 2008")
strtotime("2008-10-01")
Bien que la plupart des formats de date soient reconnus par strtotime(), certains peuvent poser problme et il nest pas vident de distinguer ceux qui fonctionnent de ceux qui ne fonctionnent pas ; strtotime("2008-10-01") se comporte
Tr ait em en t d es da tes 97
bien, par exemple, alors que strtotime("10-01-2008") produit un instant incorrect qui correspond au 28 juin 2015. Pour obtenir des rsultats cohrents, utilisez
toujours des dates au format ISO 8601 complet (voir la page http://
www.cl.cam.ac.uk/~mgk25/iso-time.html), cest--dire de la forme AAAA-MM-JJ.
Ce format est particulirement intressant parce que cest lun de ceux que
MySQL comprend, comme nous le verrons dans la section "Formats des dates
MySQL", la fin de ce chapitre.
Si strtotime() est incapable de traduire votre requte, elle renverra soit
false ( partir de PHP 5.1), soit -1 (avec les versions plus anciennes). Ce -1,
notamment, peut induire en erreur car il risque dtre interprt comme le
31 dcembre 1969, une seconde avant lepoch.
98 Ch apitr e 6
valant 12 par dfaut, juste au cas o le serveur aurait des problmes avec les zones horaires.
En effet, certains sont configurs pour utiliser UTC et, si PHP utilise la zone EST, la cration dun instant pour une date dbutant minuit peut ne pas donner cette date pendant
quatre heures.
Une astuce classique consiste utiliser les fonctions date() (voir la recette
n47 : "Formater les dates et les heures") et strtotime() pour obtenir le jour et le
mois courants utiliser avec mktime().
Cette ligne de code, par exemple, utilise date() pour obtenir le mois courant
au format 01-12, puis linsre comme valeur du paramtre mois lappel de la
fonction mktime() :
$premier_jour_du_mois = mktime(0, 0, 0, intval(date("m")), 1);
Cet extrait emploie strtotime() pour obtenir le mois suivant sous forme
dinstant, puis convertit cet instant la fois en numro de mois sur deux chiffres
et en anne sur quatre chiffres pour les utiliser avec mktime() (vous remarquerez
que lon utilise ici explicitement lanne pour prendre en compte le cas o lon
passerait de dcembre janvier, puisque le mois suivant serait alors galement
lanne suivante).
$mois_suivant = strtotime("+1 month");
$premier_jour_mois_suiv = mktime(0, 0, 0,
intval(date("m", $mois_suivant)), 1,
intval(date("y", $mois_suivant)));
Tr ait em en t d es da tes 99
Description
Exemple de rsultat
01 31
Mon Sun
1 31
l (L minuscule)
Sunday Saturday
0 (Dimanche) 6 (Samedi)
Jour
100 Ch apit re 6
Jour de lanne
0 365
January December
01 12
Jan Dec
1 12
28 31
Anne bissextile
1999 ou 2003
99 ou 03
Reprsentation minuscule de AM et PM
am ou pm
Reprsentation majuscule de AM et PM
AM ou PM
000 999
1 12
0 23
01 12
00 23
00 59
00 59
Semaine
W
Mois
Anne
Heure
Tr ait em en t d es d at es 101
Heure dt
+0200
43200 43200
Date/heure complte
C
2008-12-18T16:01:07 +02:00
Les caractres non reconnus dans la chane de format sont affichs tels
quels : si vous voulez utiliser littralement lun des caractres de format, vous
devez donc le protger par un anti-slash ("\l\e j \d\e M"). Cependant, on a tt fait
de se perdre dans les anti-slash puisquils sont galement utiliss par les squences
dchappement.
Le Tableau 6.4 prsente quelques exemples de formats de dates.
Tableau 6.4 : Exemples de chanes produites par date()
Chane de format
Exemple de rsultat
l (L minuscule)
Saturday
Oct
H:m
1:36
G:i:s A
5:26:01 PM
d-m-Y
04-10-2008
j M y
1 Jun 08
d M Y h:m:s a
102 Ch apit re 6
ventuellement un instant spcifique (sinon, cest lheure courante qui sera utilise).
En parallle, la fonction setlocale() modifie les informations de localisation
renvoyes par le serveur : en personnalisant la constante LC_TIME, on change le
format dheure. Ainsi, lexemple suivant :
setlocale(LC_TIME, fr, fr_FR, fr_FR.ISO8859-1);
echo strftime("%A %d %B %Y.");
affichera "lundi 18 aot 2008". Nous avons tout dabord modifi le format
dheure (les paramtres "fr", "fr_FR" et "fr_FR.ISO8859-1" correspondent tous
les types de valeurs attendues par les serveurs). Puis nous appelons la fonction
strftime() sur la date courante. Le formatage est spcifique cette fonction : ici,
%A est le nom complet du jour, %d sa valeur numrique, %B le nom complet du
mois et %Y lanne. Vous retrouverez la liste complte des caractres de formatage
ladresse http://tinyurl.com/5usynv.
Si vous navez pas la possibilit dexcuter setlocale() sur votre serveur, vous
pouvez vous rabattre sur une solution plus simple, qui exploite la fonction date().
En voici un exemple :
$liste_jours = array("dimanche", "lundi", "mardi", "mercredi", "jeudi",
"vendredi", "samedi");
$liste_mois = array("", "janvier", "fvrier", "mars", "avril", "mai", "juin",
"juillet", "aot", "septembre", "octobre", "novembre", "dcembre");
list($nom_jour, $jour, $mois, $annee) = explode(/, date("w/d/n/Y"));
echo $liste_jours[$nom_jour]. .$jour. .$liste_mois[$mois]. .$annee;
Ici, nous crons deux tableaux contenant le nom de tous les jours et de tous
les mois. Nous rcuprons la date du jour et nous isolons chaque information
(jour, mois, anne) dans une variable. Il ne nous reste plus qu parcourir les
deux tableaux afin dafficher une date complte, "mardi 28 juillet 2009" par
exemple.
Tr ait em en t d es d at es 103
104 Ch apit re 6
leur diffrence est un nombre de secondes). Puis, elle utilise un tableau pour
trouver lunit dans laquelle lutilisateur souhaite obtenir son rsultat. Ces units
sont exprimes en secondes. Si lunit indique est correcte, la fonction divise le
nombre total de secondes par le nombre de secondes de cette unit. Si, par
exemple, lutilisateur a choisi dobtenir un rsultat en minutes et que la diffrence entre les deux instants est de 60 secondes, la valeur renvoye sera donc 1
(60 divis par 60).
Si lutilisateur a choisi une unit non reconnue (ou a fourni des instants
incorrects), la fonction renvoie false par dfaut.
Utilisation du script
Cet exemple traduit une diffrence de sept jours dans toutes les units :
<?php
// Prend comme exemples linstant courant et sept jours plus tard
$instant1 = time();
$instant2 = strtotime(+7 days);
$unites = array("secondes", "minutes", "heures", "jours", "semaines");
foreach ($unites as $u) {
$nunites = calcule_diff_temps($instant1, $instant2, $u);
echo $nunites . " $u se sont couls entre " . date("d-m-Y", $instant1)
. et . date("d-m-Y", $instant2);
print "\n";
}
?>
Amlioration du script
Cette fonction peut renvoyer des valeurs ngatives si le deuxime instant est
avant le premier. Si, par exemple, $instant1 est le 7 juillet 2008 et $instant2 le 1
juillet 2008, la diffrence renvoye est de 6 jours. Si seule la diffrence vous
importe, remplacez la ligne $ecart_temps = $instant2 - $instant1 par ce fragment
de code :
if ($instant2 > $instant1) {
$ecart_temps = abs($instant2 - $instant1);
}
On obtiendra ainsi une valeur toujours positive ou nulle, quel que soit
lordre des dates.
Maintenant que nous avons tudi en dtail les dates et les temps de PHP,
finissons par une courte prsentation des dates et des temps en MySQL.
Tr ait em en t d es d at es 105
Bien que vous puissiez stocker les tiquettes temporelles PHP/Unix dans des
champs de type INT(10), lutilisation des formats natifs de MySQL est bien plus
pratique car vous pourrez ensuite employer ces donnes indpendamment de
PHP. Pour obtenir une tiquette temporelle PHP partir dune requte SQL,
utilisez la fonction SQL UNIX_TIMESTAMP(), comme dans cet exemple :
SELECT UNIX_TIMESTAMP(ma_date) FROM table;
7
TRA I T E M E NT D E S F I C HI E RS
considr comme faisant partie du monde (nous ne prsenterons pas les groupes
dans ce livre ; mettez les mmes permissions au groupe quau monde).
Les serveurs scuriss traitent PHP comme un utilisateur vritablement
non privilgi, qui ne peut crire nulle part sur le systme. On ne souhaite pas
que PHP possde des droits sur la machine car les utilisateurs externes
influencent au moins en partie son comportement. Si un pirate trouve un
moyen de compromettre PHP, il ne faudrait pas que cela se rpande tout le
reste du systme.
Il y a trois faons daccder un fichier et il y a donc trois types de permissions, lecture, criture et excution, qui sont indpendantes : vous pouvez, par
exemple, donner les droits de lecture et dexcution sans pour autant donner le
droit dcriture.
Le droit dcriture autorise PHP modifier le contenu du fichier, de supprimer le fichier et, dans le cas dun rpertoire, dy crer un fichier ou un sousrpertoire.
108 Ch apit re 7
5 : droits de lecture/excution
6 : droits de lecture/criture
7 : tous les droits1
Ces permissions peuvent tre modifies par un programme FTP ou en ligne
de commande.
La ligne de commande
Si vous avez accs un shell Unix sur le serveur, vous pouvez donner les
mmes permissions que ci-dessus en utilisant une mthode bien plus directe
puisquil suffit de taper la commande suivante, qui fonctionne pour tout type de
fichier et de rpertoire :
chmod 755 repertoire
Problmes ventuels
Parmi les nombreux problmes possibles, votre hbergeur peut faire tourner
PHP sous un compte spcifique, diffrent du vtre. Dans ce cas, vous devrez peuttre donner le droit dcriture tout le monde, ce qui risque dtre interdit par
votre hbergeur. Il faudra alors vous contenter des accs en lecture, ce qui ne
vous empchera pas de faire beaucoup de choses.
1. NdR : Vous tes susceptible de rencontrer une autre notation, compose des caractres "rwx".
Retenez que "r" signifie "read" et vaut 4 (droit de lecture), "w" signifie "write" et vaut 2 (droit
dcriture) et enfin "x" signifie "eXecute" et vaut 1 (droit dexcution). On retrouve les valeurs
prcdentes : les droits de lecture/excution valent bien 5 (4+1), les droits de lecture/criture
6 (4+2) et lensemble des droits correspond 7 (4+2+1). Vous trouverez souvent la notation "rwx" travers des clients FTP.
Noubliez pas que donner le droit dcriture sur un rpertoire vous expose
potentiellement un certain nombre de problmes de scurit. Vous ne devriez
jamais permettre PHP dexcuter des fichiers dans un rpertoire o il a le droit
dcrire en fait, vous devriez interdire ce rpertoire au serveur web. Si vous ne
faites pas attention la faon dont vos scripts nomment et accdent aux fichiers,
vous allez au devant de gros ennuis.
110 Ch apit re 7
Amlioration du script
De nombreux scripts traitent les fichiers ligne par ligne au lieu de les stocker
dans une seule variable norme. Cela arrive assez souvent, notamment lorsque
lon examine les listes dlments produits par dautres programmes (la recette
n55 : "Lire un fichier CSV", montre un exemple o lon a besoin de lire ligne
par ligne le contenu dun fichier). Pour cela, il suffit de modifier le code lintrieur de la boucle, comme dans cet exemple qui affiche toutes les lignes qui
contiennent chapitre.
while (! feof($fd)) {
$donnees_fichier = fgets($fd, 5000);
if (strstr($donnees_fichier, chapitre)!== FALSE) {
print $donnees_fichier;
}
}
Cependant, si vous utilisez votre propre serveur, vous devez tre trs prudent
lorsque vous autorisez fopen() accder des fichiers situs lextrieur de votre
site : certains vers PHP ont utilis cette fonctionnalit leur profit. Vous devez
notamment faire trs attention aux noms de fichiers ; assurez-vous que lutilisateur nait pas son mot dire sur les noms des fichiers ouverts par fopen() ! Pour
dsactiver cette fonctionnalit, initialisez loption allow_url_fopen false,
comme on la vu la section "Options de configuration et le fichier php.ini". Pour
disposer de fonctionnalits plus puissantes, utilisez plutt cURL pour accder
aux sites web, comme expliqu au Chapitre 11.
Problmes ventuels
Lerreur la plus classique est due au fait que PHP na pas les permissions de
lire le fichier que vous tentez douvrir. Certains fichiers ne devraient pas pouvoir
tre lus par PHP (ceux qui contiennent des mots de passe, par exemple) et vous
pouvez recevoir un message derreur si vous tentez douvrir lun deux. En ce cas,
vrifiez les permissions comme on la expliqu dans la section "Permissions des
fichiers".
Cela nous conduit un problme plus important : nautorisez jamais, en
aucun cas, un utilisateur ouvrir un fichier avant de vrifier quil a de bonnes raisons de le faire. Noubliez pas que vous ne pouvez pas avoir confiance dans ce
quun utilisateur vous envoie. Si les noms de fichiers reposent de trop prs sur les
donnes fournies par lutilisateur, celui-ci peut trs bien parvenir accder
nimporte quel fichier de votre site. Vous devez donc appliquer des rgles qui restreignent les accs aux rpertoires des fichiers sur votre serveur.
Vous devriez galement vrifier les donnes contenues dans les fichiers que
dposent les utilisateurs. La recette n54, "Dposer des images dans un rpertoire", montre comment vrifier le type, lemplacement et la taille dun fichier
dpos et bien dautres choses encore.
112 Ch apit re 7
if (!$fd) {
echo "Erreur! Impossible douvrir/crer le fichier.";
die;
}
fwrite($fd, $donnees_fichier);
fclose($fd);
?>
Le sparateur entre les deux lignes est un retour la ligne. Sur Unix et Mac,
il sagit dun simple caractre reprsent en PHP par \n entre des apostrophes
doubles.
Cependant, avec Windows, ce retour la ligne est reprsent par la squence
\r\n ("retour chariot, puis nouvelle ligne").
Par consquent, vous devez faire un peu attention si vous vous souciez de la
portabilit des fichiers textes ; sinon, un fichier Unix apparatra sur Windows
comme une seule longue ligne et, inversement, Unix verra un retour chariot la
fin de chaque ligne dun fichier Windows.
Si vous le souhaitez, vous pouvez explicitement dcider que le retour la
ligne sera \r\n mais, pour des raisons de portabilit, ajoutez plutt un t la fin
du mode fourni fopen(). Avec le mode wt, les caractres \n seront automatiquement traduits en squences \r\n sous Windows.
Vous devez, bien sr, avoir les permissions adquates pour supprimer un
fichier. Cependant, le plus grand danger, ici, est que vous pouvez supprimer un
fichier par inadvertance : la suppression dun fichier sous Unix est dfinitive car
il ny a ni poubelle ni commande undelete. La seule faon de rcuprer des donnes
consiste utiliser les sauvegardes de ladministrateur.
Si vous comptez autoriser les utilisateurs supprimer des fichiers, vous devez
donc tre trs prudent.
114 Ch apit re 7
<td>
<select name="emplacement">
<option value="articles" selected>Images darticles</option>
<option value="bannieres">Bannires/Pubs</option>
</select>
</td>
</tr>
<tr>
<td valign="top"><strong>Nom du fichier (Facultatif):</strong></td>
<td><input name="nouv_nom" type="text" size="64" maxlength="64"></td>
</tr>
<tr>
<td colspan="2">
<div align="center"><input type="submit" value="Dposer"></div>
</td>
</tr>
</form>
</table>
La variable $racine doit contenir le rpertoire sous lequel vous voulez placer
les images, tandis $racine_url contient le nom qui sera utilis pour les visualiser
partir dun navigateur. Les lments de $rep_cibles sont des sous-rpertoires
de $racine : ce sont les seuls emplacements o les utilisateurs seront autoriss
dposer leurs images.
Aprs cette configuration, vrifions dabord que le script fonctionne tout
dpend de la disponibilit de la fonction getimagesize() :
if (!function_exists(getimagesize)) {
die("La fonction getimagesize() est requise.");
}
Nous devons dterminer le nom que nous voulons donner au fichier sur le
serveur. Ce faisant, nous voulons nous assurer que le nom ne comporte pas de
caractres bizarres et quil sera impossible de quitter le rpertoire cible en utilisant des barres de fraction (/). Une expression rgulire permet de remplacer en
une seule tape tous les caractres non admis. Aprs ce traitement, $nouv_nom
contient le nouveau nom du fichier dpos :
/* Supprime les caractres non admis dans le nom cible */
if ($nouv_nom) {
$nouv_nom = preg_replace(/[^A-Za-z0-9_.-]/, , $nouv_nom);
} else {
$nouv_nom = preg_replace(/[^A-Za-z0-9_.-]/, , $nom_fichier);
}
Voici une vrification qui sassure que lon a bien indiqu le nom du fichier
dposer :
if (!$fichier) {
/* Aucun fichier */
die("Aucun fichier dposer.");
}
Il est temps maintenant de valider les donnes elles-mmes. Pour cela, on initialise les types de fichiers autoriss, puis on appelle getimagesize() pour obtenir
116 Ch apit re 7
les dimensions de limage dpose et son type ($attr sera utilise plus tard dans
le script).
/* Vrification du type du fichier. */
$types_fichiers = array(
"image/jpeg" => "jpg",
"image/pjpeg" => "jpg",
"image/gif" => "gif",
"image/png" => "png",
);
$largeur = null;
$hauteur = null;
/* Extrait le type MIME et la taille de limage. */
$infos_image = getimagesize($fichier);
$type_fichier = $infos_image["mime"];
list($largeur, $hauteur, $t, $attr) = $infos_image;
partir des paramtres que lon vient dextraire, nous nous assurons que
limage est dans un format autoris, nous en dduisons le suffixe du nom du
fichier et nous vrifions quil nest pas trop gros :
/* Vrification du type. */
if (!$types_fichiers[$type_fichier]) {
die("Limage doit tre au format JPEG, GIF ou PNG.");
} else {
$suffixe_fichier = $types_fichiers[$type_fichier];
}
/* Vrification de la taille. */
if ($largeur > $largeur_max || $hauteur > $hauteur_max) {
die("$largeur x $hauteur excde $largeur_max x $hauteur_max.");
}
Maintenant que nous avons le nom final, nous pouvons vrifier quil nexiste
pas dj un fichier de ce nom si lon na pas activ lcrasement des fichiers :
if ((!$ecrase_images) && file_exists($nouveau_chemin)) {
die("Le fichier existe dj; il ne sera pas cras.");
}
On peut alors copier le fichier vers sa destination finale et sassurer que cette
copie a bien fonctionn :
/* Copie le fichier vers son emplacement final. */
if (!copy($fichier, $nouv_chemin)) {
die("chec de la copie.");
}
Si lon est arriv ici, cest que le dpt sest bien pass et il nous reste simplement produire un peu de HTML pour fournir lutilisateur un lien vers ce
fichier :
$url_image = "$racine_url/$nouv_nom";
/* Affiche ltat. */
print "HTML pour limage:</strong><br>
<textarea cols=\"80\" rows=\"4\">";
print "<img src=\"$url_image\" $attr alt=\"$nom_fichier\"
border=\"0\"/>";
print "</textarea><br>";
print <a href="depot.html">Dposer une autre image?</a>;?>
Utilisation du script
Ce script exige que PHP ait la permission dcrire dans tous les rpertoires
cibles de $rep_cibles. En outre, il a besoin du module GD, une extension PHP
qui permet danalyser et de crer des fichiers images. La plupart des serveurs
linstallent par dfaut ; si ce nest pas le cas du vtre, reportez-vous la recette
n28, "Ajouter des extensions PHP".
Il vous reste seulement faire pointer votre navigateur vers depot.html et PHP
fera le reste.
Problmes ventuels
Mis part les classiques problmes de permissions, le plus gros problme est
la scurit. Tel quil est crit, tout idiot qui a accs lURL du script peut dposer
autant dimages quil le souhaite et ventuellement craser les vtres. Si le script
nest pas derrire un pare-feu, vous pouvez lui ajouter une fonction de connexion
118 Ch apit re 7
pour empcher les accs non autoriss, comme on lexplique dans la recette
n63, "Systme de connexion simple".
Amlioration du script
Vous pouvez imposer une limite la taille dun fichier en utilisant la variable
$_FILES[fichier][size]. Il suffit alors dcrire un test comme celui-ci :
if ($_FILES[fichier][size] > $taille_max) {
$erreur_fatale = "La taille de ce fichier dpasse $taille_max octets.";
}
NOTE tudiez galement la recette n13, "Empcher les utilisateurs de dposer de gros fichiers", car
elle explique comment imposer une limite globale sur la taille des fichiers dposs.
Bien que vous pourriez penser quil ne sagit que de lire les lignes et de les
diviser en utilisant une virgule comme dlimiteur, vous devez galement vous
occuper des apostrophes, des anti-slash et dautres dtails mineurs de ce format.
Cependant, la plupart des langages disposent de fonctionnalits permettant de
grer ceci et PHP ny fait pas exception. Grce la fonction prdfinie fgetcsv(),
le traitement des fichiers CSV est trs simple. fgetcsv() fonctionne exactement
comme fgets(), mis part quelle renvoie un tableau contenant les valeurs de la
ligne courante au lieu de renvoyer une chane. Voici un script trs simple qui
traite un fichier CSV dpos via le champ fic_csv dun formulaire :
<table>
<tr>
<th>Champ 1</th>
<th>Champ 2</th>
<th>Champ 3</th>
</tr>
<?php
$fn = $_FILES["fic_csv"]["tmp_name"];
$fd = fopen($fn, "r");
while (!feof($fd)) {
$champs = fgetcsv($fd);
print "<tr>";
print "<td>$champs[0]</td><td>$champs[1]</td><td>$champs[2]</td>";
print "</tr>";
}
fclose($fd);
?>
</table>
Comme vous pouvez le constater, ce script affiche les trois premires colonnes
du fichier CSV sous la forme dun tableau HTML.
Parfois, le fichier utilise des tabulations la place des virgules pour dlimiter
les champs. Pour lire ce format, il suffit de remplacer lappel prcdent
fgetcsv() par une ligne comme celle-ci :
$champs = fgetcsv($fd, 0, "\t");
8
GE S T I ON D E S U T I LI S A T E U RS
E T D E S S ES S I ONS
coul entre ses visites, ses prfrences, etc. En programmation web, on utilise
pour cela deux techniques connues sous les noms de cookies et de sessions.
Les cookies
Les cookies sont des fragments de donnes stocks sur la machine de lutilisateur. Lorsque celui-ci accde plusieurs pages de votre site, son navigateur renvoie tous les cookies valides votre serveur lors de ces accs. Le stockage de
donnes sur lordinateur dun utilisateur sans son consentement tant un risque
potentiel pour sa scurit, vous navez pas de contrle direct sur la faon dont ce
stockage est effectu. Si vous respectez certaines rgles que le navigateur (et lutilisateur) ont mises en place, le navigateur acceptera vos cookies et les renverra
dans les circonstances appropries. Un cookie qui a t accept est dit configur.
Avantages
Les cookies fonctionnent bien avec les serveurs distribus dont la charge est
quilibre (cas des sites fort trafic) puisque toutes les donnes sont sur la
machine de lutilisateur.
Inconvnients
La taille des cookies est limite (il est gnralement prfrable de ne pas
dpasser 512 Ko).
Les sessions
Les sessions sont formes dun identifiant de session unique et dun mcanisme de stockage spcial sur votre serveur. Les donnes tant sur le serveur, lutilisateur na aucun moyen de les manipuler.
Avantages
Bien que la plupart des sessions fonctionnent en plaant leur identifiant dans
un cookie, PHP et les autres systmes de programmation web savent crent
des sessions sans utiliser de cookies. Cette mthode fonctionne donc quasiment
toujours.
122 Ch apit re 8
Inconvnients
Avec une installation de PHP de base, les sessions sont spcifiques un serveur.
Si vous avez un serveur pour les ventes et un autre pour le contenu, le premier
ne verra aucune donne de session du deuxime. Vous pouvez cependant
adapter le systme de stockage des sessions pour rsoudre ce problme.
Lidentifiant de session tant une donne lue et envoye par lutilisateur, des
espions peuvent accder aux sessions si vous ne prenez pas quelques prcautions.
En termes abstraits, vous pouvez considrer les sessions comme des cookies
amliors. Bien que certaines donnes (comme lidentifiant de session) soient
encore stockes sur la machine de lutilisateur, les vritables donnes sont toujours sur le serveur. Il existe de nombreuses implmentations possibles des sessions,
mais lide de base est toujours la mme.
Noubliez pas, cependant, que si vous ne voulez pas que les utilisateurs puissent lire ou modifier certaines donnes, vous devez les placer dans une session,
pas dans un cookie. Vous devez vrifier les cookies exactement de la mme faon
que les donnes provenant des formulaires car elles viennent du client et sont
donc facile falsifier.
123
}
if (isset($_COOKIE["nom_utilisateur_stocke"])) {
$utilisateur = $_COOKIE["nom_utilisateur_stocke"];
print "Heureux de vous revoir <b>$utilisateur</b>!";
} else {
?>
<form method="post">
Nom Utilisateur: <input type="text" name="nom_utilisateur" />
</form>
<?php
}?>
Problmes ventuels
Plusieurs problmes peuvent survenir lors de la mise en place et la rcupration
des cookies :
Vous avez envoy des donnes au navigateur de lutilisateur avant dappeler
setcookie().
Les informations sur les demandes de cookie se trouvent dans len-tte
HTTP dune rponse du serveur. Vous ne pouvez donc pas envoyer au
124 Ch apit re 8
client des donnes faisant partie du document avant dappeler setcookie() ; en dautres termes, vous ne devez pas afficher quoi que ce soit, ni
appeler une opration qui provoquerait un affichage. Si les avertissements
sont activs, PHP vous indiquera cette erreur.
Ce problme est souvent d une gestion laxiste des espaces dans vos
fichiers PHP. Si des lignes blanches ou des espaces prcdent la balise <?
qui dbute la section de code PHP, ce problme surviendra forcment.
Cette remarque sapplique galement aux fichiers inclus avant lappel
setcookie() ; ces fichiers ne doivent pas non plus avoir despace aprs la
balise fermante ?>.
Le navigateur de lutilisateur a rejet le cookie.
Si le navigateur naccepte pas le cookie, vous naurez aucun retour. Pour
vrifier la prsence dun cookie, appelez la fonction isset() pour le
rechercher dans le tableau $_COOKIE. La cause la plus classique de rejet
dun cookie est un domaine incorrect (les navigateurs nacceptent gnralement pas les cookies pour les domaines qui ne correspondent pas celui
du serveur qui fait la demande).
Quelquun a mis un mauvais paramtre.
Les cookies, comme tout ce quenvoie un client, peuvent aisment tre
fabriqus de toute pice. Ne leur faites pas confiance et vrifiez leurs
valeurs comme celles de nimporte quelle donne provenant dun formulaire.
Vous tentez de stocker un tableau dans une variable cookie.
Ce nest pas possible, mais vous pouvez stocker un tableau srialis (voir la
recette n5 : "Transformer un tableau en variable scalaire qui pourra tre
restaure ultrieurement").
125
Pour les nouveaux visiteurs, ces deux variables ne seront pas initialises mais,
sil reviennent ce formulaire partir de nimporte o, cette partie du script capturera les anciennes valeurs. Pour afficher le formulaire, on utilise ces anciennes
valeurs comme valeurs par dfaut :
print
print
print
print
print
print
print
?>
126 Ch apit re 8
La partie suivante consiste tester si lutilisateur a cliqu sur le bouton Rinitialisation du formulaire. Si cest le cas, on utilise la fonction unset() pour supprimer les valeurs de session :
if ($_REQUEST["raz"]) {
unset($_SESSION["nom"]);
unset($_SESSION["couleur"]);
}
Nous savons maintenant que le tableau $_SESSION contient toutes les valeurs
correctes ; nous pouvons alors les utiliser pour afficher les informations destination
de lutilisateur :
$nom = $_SESSION["nom"];
$couleur = $_SESSION["couleur"];
if ($nom) {
print "Vous vous appelez <b>$nom</b>.<br />";
}
if ($couleur) {
print "Votre couleur prfre est le <b>$couleur</b>.<br />";
}
Enfin, on autorise lutilisateur revenir en arrire pour modifier ou supprimer les valeurs. Tout ayant dj t fait dans les deux scripts prcdents, il suffit
de placer des liens vers les bons endroits :
print <a href="formulaire_session.php">Modifier les dtails</a>;
print | <a href="vue_session.php?clear=1">Rinitialiser</a>;
?>
Bien que ce soit un exemple trs simple, il permet de montrer que les sessions nous facilitent beaucoup la vie lorsque lon a besoin de crer des formulaires
en plusieurs parties ou de suivre la trace dautres informations.
Problmes ventuels
Tout comme pour les cookies, vous devez appeler session_start() au
dbut du script, avant denvoyer la moindre donne, afin que le serveur puisse
configurer lidentifiant de session sur le client. Dans le cas contraire, le client
nacceptera pas cet identifiant et le serveur ne pourra pas associer le client
une session.
127
Si ce cookie nexiste pas, cela peut tre d deux raisons : la premire est
que le navigateur naccepte peut-tre pas les cookies. Cependant, nous ne pouvons pas en tre sr avant de recharger la page et nous devons savoir que le navigateur a recharg la page. Pour cela, au deuxime accs on initialise un
paramtre GET appel testing. Si ce paramtre existe mais pas le cookie, on sait
que le navigateur nenvoie pas de cookie :
} else {
if (isset($_REQUEST["testing"])) {
print "Cookies dsactivs.";
Si ni le cookie ni le paramtre testing nexistent, cest parce que cest la premire fois que le navigateur accde la page ; nous initialisons alors le cookie
puis nous rechargeons la page en initialisant testing pour signaler quil sagit du
second accs. Le fonctionnement de len-tte Location sera dcrit dans la section
suivante.
} else {
setcookie("test", "1", 0, "/");
header("Location: $_SERVER[PHP_SELF]?testing=1");
}
}
?>
128 Ch apit re 8
La fonction header() permet denvoyer des en-ttes HTTP bruts au navigateur de lutilisateur. Il faut donc lutiliser avant denvoyer quoi que ce soit au navigateur, comme pour les cookies.
Cette mthode a cependant deux inconvnients. Le premier est quelle est
instantane et que le script intermdiaire napparatra pas dans lhistorique du
navigateur. Le second est quelle repose sur HTTP et ne ncessite donc pas un
navigateur pour tre traite ; les aspirateurs web comme wget sauront donc la
grer.
Si vous voulez montrer une page intermdiaire lutilisateur avec un certain
dlai, vous devez donc utiliser une autre mthode, qui utilise la balise HTML
<meta>.
Cette mthode est assez simple : pour envoyer lutilisateur sur une autre page
aprs avoir affich la page courante pendant cinq secondes, par exemple, il suffit
de placer cette ligne dans len-tte de la page HTML :
<meta http-equiv="Refresh" content="5;URL= nouvelle_page.php" />
129
Tous les navigateurs savent reconnatre ces balises, ce qui nest pas le cas de
tous les aspirateurs web. En outre, cette page intermdiaire apparatra dans lhistorique du navigateur de lutilisateur.
130 Ch apit re 8
La fonction suivante extrait une adresse IP. Sa premire partie est relativement simple puisquelle examine ce que le serveur pense savoir du client :
function get_ip() {
/* Recherche dabord une adresse IP dans les donnes du serveur */
if (!empty($_SERVER["REMOTE_ADDR"])) {
$ip_client = $_SERVER["REMOTE_ADDR"];
}
Selon votre intrt pour cette information, ce code peut suffire. Toutefois, si
vous voulez savoir si un utilisateur est pass par un serveur mandataire (proxy), le
code suivant permettra de rechercher ce proxy et de trouver ladresse IP sousjacente du client :
/* Recherche du serveur mandataire. */
if ($_SERVER["HTTP_CLIENT_IP"]) {
$ip_proxy = $_SERVER["HTTP_CLIENT_IP"];
} else if ($_SERVER["HTTP_X_FORWARDED_FOR"]) {
$ip_proxy = $_SERVER["HTTP_X_FORWARDED_FOR"];
}
131
PHP stocke les informations sur lagent du client dans la variable $_SERVER[HTTP_USER_AGENT]. Malheureusement, cette chane est complexe et ne respecte pas de standard bien tabli. Voici, par exemple, ce que lon obtiendrait
avec Internet Explorer sous Windows :
Mozilla/4.0 (compatible; MSIE 6.0; WindowsNT 5.1; SV1)
La raison pour laquelle les chanes des agents utilisateurs semblent tre des
mensonges honts est que la plupart des navigateurs essaient de tromper le serveur
en se faisant passer pour ce quils ne sont pas.
Ici, Internet Explorer prtend tre Mozilla (le nom de code de Netscape
Navigator) ; Opra se nomme aussi Mozilla, puis indique quil est Explorer
(MSIE 6.0) et, la fin, annonce quil sappelle Opra. Ce comportement compltement idiot est d une pratique, deux fois plus stupide, consistant optimiser
les pages pour des navigateurs particuliers.
Les seules parties de cette chane susceptibles de vous intresser sont le nom
du navigateur, sa version et le nom du systme dexploitation car elles vous donneront des informations sur vos utilisateurs. Si, par exemple, vous constatez que
vous avez une large proportion dutilisateurs Mac sur votre boutique en ligne,
vous avez tout intrt leur proposer des produits spcifiques. Voici une fonction
qui extrait ces informations sous forme lisible. Le code est assez horrible mais, ici,
nous devons combattre lhorreur par lhorreur.
Nous initialisons dabord le tableau rsultat et nous plaons la chane didentification du navigateur dans la variable $agent :
function trouve_navigateur() {
// Dtermine le SE, la version et le type du navigateur du client.
$infos_navigateur = array(
"nom" => "Unknown",
"version" => "Unknown",
"SE" => "Unknown",
);
132 Ch apit re 8
Trouvons maintenant le systme dexploitation de lutilisateur. Cest relativement simple puisque ces chanes sont uniques :
// Trouve le systme dexploitation.
if (preg_match(/win/i, $agent)) {
$infos_navigateur["SE"] = "Windows";
} else if (preg_match(/mac/i, $agent)) {
$infos_navigateur["SE"] = "Macintosh";
} else if (preg_match(/linux/i, $agent)) {
$infos_navigateur["SE"] = "Linux";
}
La partie pineuse pour extraire les dtails du navigateur peut alors commencer. Lordre dans lequel les navigateurs sont tests est important, car certaines chanes prtendent tre plusieurs navigateurs diffrents. Le plus gros
menteur tant Opra, on doit commencer par lui. Avec ce dernier, le pire est
quil peut indiquer sa version de deux faons, avec ou sans barre de fraction ; il
faut donc essayer les deux :
if (preg_match(/opera/i, $agent)) {
// On commence par Opera, puisquil correspond aussi IE
$infos_navigateur["nom"] = "Opera";
$agent = stristr($agent, "Opera");
if (strpos("/", $agent)) {
$agent = explode("/", $agent);
$infos_navigateur["version"] = $agent[1];
} else {
$agent = explode(" ", $agent);
$infos_navigateur["version"] = $agent[1];
}
Le suivant sur la liste des suspects est Internet Explorer, parce quil prtend
sappeler Mozilla. Lobtention de sa version est plus simple puisquil suffit de
supprimer le point-virgule final :
} else if (preg_match(/msie/i, $agent)) {
$infos_navigateur["nom"] = "Internet Explorer";
$agent = stristr($agent, "msie");
$agent = explode(" ", $agent);
$infos_navigateur["nom"]= str_replace(";", "", $agent[1]);
133
Pour linstant, part lui, aucun autre navigateur ne prtend sappeler Firefox, mais Firefox est lui-mme une version de Mozilla, cest donc le moment
de tester ce navigateur. Vous remarquerez que lextraction de la version est
bien plus simple :
} else if (preg_match(/firefox/i, $agent)) {
$infos_navigateur["nom"]= "Firefox";
$agent = stristr($agent, "Firefox");
$agent = explode("/", $agent);
$infos_navigateur["nom"] = $agent[1];
Comme on la indiqu plus haut, cette fonction est assez horrible car elle se
contente dexaminer au hasard la chane de lagent jusqu trouver une information semblant intelligible. Vous devrez stocker le rsultat de cette fonction pour
134 Ch apit re 8
pouvoir lexploiter ensuite. Si vous avez accs aux fichiers journaux de votre serveur, il est sans doute prfrable dutiliser un analyseur comme awstats (http://
awstats.sourceforge.net/), qui peut extraire un grand nombre dinformations,
dont les adresses IP et les navigateurs utiliss par vos visiteurs.
135
Comme pour le suivi des utilisateurs, noubliez pas que session_start() doit
tre appele au dbut du script, avant la production de tout en-tte. Ces fonctions ignorent les erreurs de session_start() car elles pourraient provenir
dappels prcdents.
136 Ch apit re 8
Si lon est ici, nous savons que lon nest pas en train de se connecter ou de
se dconnecter ; la seule chose faire consiste donc vrifier si lutilisateur est
dj connect. Noubliez pas que cette information se trouve dans la variable
$_SESSION["auth"] et quil faut donc la vrifier. Si lutilisateur nest pas connect,
on lui donne la possibilit de le faire en affichant un formulaire de connexion,
puis on sort. On pourrait galement rediriger lutilisateur vers une page de
connexion spciale, mais il est important de toujours sortir aprs cette redirection
ou laffichage du formulaire car, aprs cette tape, PHP ne doit plus excuter la
moindre tche accessible un utilisateur authentifi !
$auth_ok = $_SESSION["auth"];
if ($auth_ok!= "termine") {
?><html><head></head><body>
<form method="post">
Entrez un mot de passe: <input type="password" name="mdp"/>
</form>
</body</html><?php
exit(0);
}
?>
137
Ne perdez pas de vue quil sagit dun systme dauthentification trs simple.
Vous pouvez lamliorer en ajoutant le code dexpiration des sessions de la section prcdente mais, si vous avez besoin de fonctionnalits supplmentaires, il
est srement prfrable de partir dun systme dauthentification parfaitement
test. Vous trouverez plusieurs systmes gratuits sur lInternet qui mritent dtre
essays.
9
TRA I T E M E N T D U C OU RRI E R
LE C T RONI QU E
Si ce script affiche Courrier envoy, vrifiez la bote de rception du destinataire afin de vous assurer que tout a bien fonctionn. Le sujet devrait tre Test
courrier PHP et le corps du message a marche !
La fonction mail(), comme tous les autres systmes qui expdient du courrier lectronique, peut poser des problmes au systme de dlivrance du courrier : il sera donc peut-tre ncessaire de modifier la configuration de votre
serveur de courrier pour que cela fonctionne. Ceci dit, il y a tellement de spam
dans les courriers actuels quun en-tte de courrier mal form ou anormal peut
provoquer le rejet de votre courrier par un serveur distant sans mme quil vous
prvienne. En outre, lenvoi de pices attaches ou lajout de texte HTML
demande beaucoup plus de travail. La section suivante montre comment rsoudre
ces problmes avec PHPMailer.
Installation de PHPMailer
Linstallation de PHPMailer seffectue en suivant ces tapes :
1. Tlchargez les fichiers de PHPMailer partir de http://phpmailer.sourceforge.net/.
2. Crez un rpertoire phpmailer sur votre serveur afin dy stocker les fichiers
de PHPMailer.
3. Extrayez les fichiers de PHPMailer dans le rpertoire phpmailer.
4. Choisissez votre mthode de transport du courrier. PHPMailer propose
trois mthodes diffrentes : mail, sendmail et smtp. mail est la mthode par
dfaut ; elle utilise la fonction mail() de PHP, dcrite dans la section prcdente. Cest la plus simple configurer si le courrier du serveur web est
correctement paramtr. Si cette mthode ne fonctionne pas, il vous reste
deux possibilits :
a. Vous pouvez indiquer PHPMailer un serveur SMTP auquel il pourra
sadresser. SMTP signifie Simple Mail Transfer Protocol, cest le protocole
de transport du courrier le plus utilis. Pour que PHPMailer puisse utiliser SMTP, vous devez connatre le nom dhte dun serveur SMTP. Si
celui-ci exige une authentification (ce qui est souvent le cas), vous
devrez fournir un nom dutilisateur et un mot de passe pour ce serveur.
Votre FAI vous fournira tous les dtails ncessaires sur la configuration
de son serveur SMTP.
140 Ch apit re 9
var $Host = ""; Le serveur SMTP utilis avec la mthode smtp. Vrifiez
les informations que vous a communiques votre FAI. Vous pouvez indiquer plusieurs serveurs SMTP en les sparant par des points-virgules au
cas o le premier serveur serait hors service ou rejetterait vos courriers.
var $SMTPAuth = false; Si votre serveur SMTP exige une authentification pour envoyer du courrier, mettez cette variable true. En ce cas,
vous devrez galement initialiser les deux variables suivantes.
var $Username = ""; Le nom dutilisateur sur le serveur SMTP
(uniquement lorsquon utilise lauthentification SMTP).
var $Password = ""; Le mot de passe associ $Username, si ncessaire.
var $Helo = "";
exemple.
Aprs avoir modifi les variables de configuration ncessaires, vous tes prt
tudier un script simple permettant denvoyer un message de test.
Utilisation du script
Assurez-vous que le fichier class.phpmailer.php se trouve dans un des chemins
o PHP recherche les fichiers inclus, puis essayez ce script :
<?php
include_once("class.phpmailer.php");
$mail = new PHPMailer;
$mail->ClearAddresses();
$mail->AddAddress(toto@adresse.com, toto);
$mail->From = toi@exemple.com;
$mail->FromName = Ton nom;
Tr ait em en t d u c o ur rier l ec tr on iq u e
141
Vide la liste courante des destinataires. La mthode AddAddress() ne supprimant pas les adresses prcdentes, vous risquez denvoyer plusieurs fois le mme
message au mme destinataire si vous envoyez les courriers dans une boucle avec
le mme objet PHPMailer. Il est donc conseill de prendre lhabitude de vider la
liste des destinataires avant de traiter un message ou aprs avoir appel la
mthode Send().
$mail->isHTML = true|false
Si cet attribut vaut true, PHPMailer utilise HTML au lieu du texte pur. Avec
HTML, vous pouvez intgrer des belles images, mais tous les clients de courrier
nacceptent pas le HTML. En cas de problme, vrifiez la valeur de lattribut
suivant.
$mail->AltBody = texte
Si lattribut isHTML vaut true, vous pouvez configurer lattribut AltBody avec
une version texte du corps du message.
142 Ch apit re 9
Enfin, si vous avez stock un fichier attach dans une variable PHP, vous pouvez utiliser la mthode AddStringAttachment()pour lattacher. Elle fonctionne
exactement comme AddAttachment(), mais elle se sert dune chane PHP ou
dune variable la place du paramtre chemin.
Problmes ventuels
Si vous utilisez la fonction mail() pour envoyer le courrier, vous devez vrifier
que PHP puisse le faire. Dans le cas contraire, utilisez plutt smtp ou sendmail.
Si vous envyez un courrier avec SMTP, vous pouvez recevoir ce message
derreur :
SMTP Error: The following recipients failed [email@exemple.com]
Tr ait em en t d u c o ur rier l ec tr on iq u e
143
une table attentes_inscription forme de deux colonnes : un nom dutilisateur (login) et une cl. Pour la crer, vous pouvez utiliser la requte SQL
suivante :
144 Ch apit re 9
Il reste simplement envoyer le courrier lutilisateur. L encore, il est probable que vous personnalisiez cette partie.
Tr ait em en t d u c o ur rier l ec tr on iq u e
145
include_once("phpmailer/class.phpmailer.php");
$mail = new PHPMailer;
$mail->ClearAddresses();
$mail->AddAddress($email, $login);
$mail->From = generateur@exemple.com;
$mail->FromName = Gnrateur de compte;
$mail->Subject = Vrification du compte;
$mail->Body = "Pour activer votre compte, cliquez sur lURL suivante:
$url
";
if ($mail->Send()) {
print "Message de vrification envoy.";
} else {
print $mail->ErrorInfo;
return false;
}
return true;
}
Pour utiliser cette fonction, appelez-la de la faon suivante (bd est un descripteur de base de donnes MySQL qui a t ouvert au pralable). Elle renverra true
si le compte a t plac dans la table attentes_inscription et si PHPMailer a pu
envoyer le message dactivation :
verification(login, adresse_mel, bd)
Cette vrification ayant t faite, la partie suivante est une fonction qui active un
compte lorsque lutilisateur clique sur le lien. La premire chose faire consiste
nettoyer la cl qui nous a t envoye au cas o lutilisateur lait abm ou quun
pirate essaie de pntrer sur le systme. La fonction prcdente qui a produit la cl
nutilisant que des minuscules, nous supprimons tout ce qui ne correspond pas :
function activer_compte($cle, $bd) {
/* Active un compte partir dune cl. */
/* Nettoie la cl si ncessaire. */
$cle = preg_replace("/[^a-z]/", "", $cle);
On examine ensuite la validit de la cl. Si elle nest mme pas dans la table
attentes_inscription, il ny a rien faire et on renvoie false pour lindiquer :
$requete = "SELECT login FROM attentes_inscription WHERE cle = $cle";
$c = mysql_query($requete, $bd);
if (mysql_num_rows($c)!= 1) {
return false;
}
146 Ch apit re 9
Si lon est arriv ici, nous savons que la cl est dans la table : il reste donc
supprimer la ligne correspondante pour activer le compte. Vous remarquerez
que lon na mme pas besoin de savoir quel est le nom du compte.
$requete = "DELETE FROM attentes_inscription WHERE cle = $cle";
mysql_query($requete, $bd);
if (mysql_error($bd)) {
return false;
}
return true;
}
Pour adapter ce systme votre site, vous pouvez lui ajouter un certain nombre de choses : ajouter un champ date la table pour supprimer les comptes inactifs qui nont jamais t vrifis, par exemple. Il peut y avoir beaucoup dautres
raisons de dsactiver un compte ; en ce cas, vous aurez besoin de stocker dans la
table la raison de cette dsactivation. Vous pouvez mme inclure la cl dactivation dans la table principale des comptes. Le mcanisme dactivation dcrit ici est
spcifiquement conu pour tre greff moindre frais sur dautres systmes de
connexion.
Tr ait em en t d u c o ur rier l ec tr on iq u e
147
10
TRA I T E M E N T D E S I M A GE S
la protger. Sinon, votre site risquerait dtre totalement submerg par ces derniers.
Un moyen de contourner ce problme consiste produire dynamiquement une
image contenant un texte que lutilisateur devra reproduire avant de continuer.
Si le texte saisi par le visiteur correspond au texte de limage, cest quil sagit
vraisemblablement bien dune personne et non dun robot.
Ce type de test sappelle CAPTCHA (Completely Automated Public Turing Test
to Tell Computers and Humans Apart, ce qui peut se traduire par "Test de Tring
automatique pour distinguer les ordinateurs des humains"). Cette mthode
prsente pourtant deux inconvnients. Le premier concerne videmment les
malvoyants qui ne peuvent pas lire le texte de limage CAPTCHA. De nos
jours, les visiteurs malvoyants peuvent utiliser Internet grce des logiciels
vocaux qui lisent les pages, mais ces logiciels ne savent pas interprter les images dpourvues dattribut alt. Un CAPTCHA ne tient donc pas compte de ces
utilisateurs.
En outre, un CAPTCHA ne fonctionne pas toujours. Les spammeurs sont
toujours la recherche de solutions pour contourner vos dfenses et lun des
moyens les plus ingnieux quils ont trouv consiste remplacer le lien hypertexte de votre image CAPTCHA par ladresse dun site pornographique quils
administrent. Ils nhsitent pas ajouter la mention "Entrez cette phrase et admirez de superbes cratures nues !" pour attirer certains visiteurs. Bien que vous ne
souhaitez pas que votre site web soit pirat de la sorte et quil sabaisse malgr lui
de telles pratiques, sachez que ce genre de techniques est encore relativement
rare.
Connaissant ces deux faiblesses, vous devez utiliser les CAPTCHA parcimonieusement et les comparer avec dautres mthodes comme le scanner
Akismet1.
Le CAPTCHA que nous prsentons ici est assez simple (la Figure 10.1 en
montre une copie dcran) mais il est efficace. Pour lutiliser, vous devez avoir
install la bibliothque graphique GD avec la reconnaissance de Freetype.
Comme on la expliqu la recette n8, "Afficher toutes les options de configuration de PHP", utilisez phpinfo() pour savoir si GD est install sur votre serveur : si vous voyez une section GD avec des informations sur Freetype tout va
bien. Sinon, recompilez PHP comme indiqu la recette n18, "Ajouter des
extensions PHP".
Vous aurez galement besoin dune ou plusieurs polices dans le mme
rpertoire que le script. Il est prfrable dutiliser des polices qui sont peu
prs lisibles.
1. NdR : Vous avez galement la possibilit, limage du projet ReCaptcha invent par Luis von
Ahn (le crateur du concept dorigine), denchaner deux CAPTCHA daffile afin de compliquer la reconnaissance des caractres par des robots. Mais vos visiteurs devront redoubler
deffort pour valider leur saisie !
150 Ch apit re 10
Tr ait em en t d es im a ge s 151
session_start();
/* Cration de la phrase secrte. */
$mdp = "";
$i = 0;
while ($i < $lg_phrase) {
$mdp .= chr(rand(97, 122));
$i++;
}
Nous placerons la phrase secrte dans une variable de session : elle napparatra donc jamais sur les pages, ce qui vite de devoir lencoder.
/* Stockage de la phrase secrte. */
$_SESSION["tt_pass"] = $mdp;
Avant de dessiner quoi que ce soit, nous voulons nous assurer que nous disposons de certaines polices. Ce code cre donc une liste des polices du rpertoire
courant. Si vous voulez acclrer cette tape, vous pouvez initialiser le tableau
$polices avec un ensemble de noms de fichiers de polices TTF :
/* Rcupre la liste des polices disponibles. */
$polices = array();
if ($desc = opendir($chemin_polices)) {
while (false!== ($fichier = readdir($desc))) {
/* Recherche des polices TTF. */
if (substr(strtolower($fichier), -4, 4) == .ttf) {
$polices[] = $chemin_polices . / . $fichier;
}
}
}
if (count($polices) < 1) {
die("Aucune police na t trouve!");
}
Le client doit savoir le type dimage que vous envoyez. Ce script crant une
image JPEG, cest ce type MIME quil enverra. En outre, il envoie galement plusieurs en-ttes pour dsactiver le cache et empcher ainsi le navigateur de mettre
limage en cache :
/* En-tte de limage */
header("Content-Type: image/jpeg");
/* Dsactivation du cache. */
header("Expires: Mon, 01 Jul 1998 05:00:00 GMT");
152 Ch apit re 10
Nous commencerons par tracer un rectangle qui recouvre tout le fond. Pour
cela, il faut dabord allouer une couleur dans limage avec imagecolorallocate()
dont le second, troisime et quatrime paramtres sont, respectivement, les
valeurs rouge, verte et bleue. Ces valeurs sont des entiers compris entre 0 et 255,
0 tant la couleur la plus sombre et 255 la plus claire. Linstruction suivante cre
une couleur pastel alatoire :
/* Remplit le fond avec un pastel alatoire */
$fond = imagecolorallocate($img, rand(210,255), rand(210,255),
rand(210,255));
Tr ait em en t d es im a ge s 153
$points_poly = array(
$gauche, 0, /* Coin suprieur
$droite, 0, /* Coin suprieur
rand($droite-25, $droite+25),
rand($gauche-15, $gauche+15),
gauche */
droit */
$hauteur, /* Coin infrieur droit */
$hauteur);/* Coin infrieur gauche */
154 Ch apit re 10
gauche */
droit */
/* Coin infrieur droit */
/* Coin infrieur gauche */
Tr ait em en t d es im a ge s 155
Ces lettres seront trs difficiles lire sans une certaine mise en valeur. Vous
pouvez crer une ombre colore en divisant les valeurs de couleurs initiales
par 3 :
/* Choix dune couleur pour la lettre. */
$r = rand(100, 255); $g = rand(100, 255); $b = rand(100, 255);
/* Cration de la lettre et de lombre colore */
$couleur = imagecolorallocate($img, $r, $g, $b);
$ombre = imagecolorallocate($img, $r/3, $g/3, $b/3);
Lorsque cette boucle sest termine, vous tes prt envoyer limage au
client en appelant la fonction imagejpeg(), puis on libre la mmoire quelle
occupait avec imagedestroy() :
imagejpeg($img); /* Envoie limage. */
imagedestroy($img); /* Libre la mmoire de limage. */
?>
156 Ch apit re 10
<?php
session_start();
/* Recherche un mot de passe soumis. */
if ($_REQUEST["tt_pass"]) {
if ($_REQUEST["tt_pass"] == $_SESSION["tt_pass"]) {
echo "Phrase secrte correcte.";
} else {
echo "Phrase secrte incorrecte.";
}
exit(0);
}
/* Par dfaut, on envoie le formulaire. */
print <form action=" . $_SERVER[PHP_SELF] . " method="post">;
?>
Saisissez les caractres suivants pour continuer:<br />
(si vous narrivez pas les lire, ractualisez la page)<br />
<img src="image_captcha.php"><br /><br />
Lettres: <input name="tt_pass" type="text" size="10" maxlength="10">
<input type="submit">
</form>
La bibliothque GD.
Tr ait em en t d es im a ge s 157
158 Ch apit re 10
limage initiale nest peut-tre pas carre : si lon tentait dimposer de telles
dimensions une image rectangulaire, elle apparatrait dforme . Pour rsoudre ce problme, on recherche donc le plus long ct de limage initiale et on
utilise un rapport dchelle gal au rapport entre la longueur du plus grand ct
et celle du ct de la vignette :
/* Cration dune vignette. */
if ($largeur > $hauteur) {
$ratio = $largeur_vignette / $largeur;
} else {
$ratio = $hauteur_vignette / $hauteur;
}
ATTENTION La raison pour laquelle cette fonction utilise des dimensions de vignettes carres est que le
raisonnement prcdent ne marche pas pour des rectangles gnriques. Si vous essayez de
transformer une image de 200 par 400 pixels en une vignette de 100 par 400, $ratio vaudra 1 et vous obtiendrez une vignette de 200 par 400 (voir le code qui suit). Cette taille est
donc suprieure la taille maximale dune vignette. Corriger ce problme nest pas compliqu,
mais nous le laissons en exercice au lecteur.
On utilise le ratio dchelle pour trouver les dimensions exactes de la
vignette en pixels, puis on cre un nouveau descripteur pour une image avec
cette nouvelle taille :
$nouv_largeur = round($largeur * $ratio);
$nouv_hauteur = round($hauteur * $ratio);
$image_dest = ImageCreateTrueColor($nouv_largeur, $nouv_hauteur);
On peut alors crer la nouvelle image avec cette nouvelle taille grce la
fonction imagecopyresampled() :
imagecopyresampled($image_dest, $image_src, 0, 0, 0, 0,
$nouv_largeur, $nouv_hauteur, $largeur, $hauteur);
Tr ait em en t d es im a ge s 159
} else {
/* Limage est dj suffisamment petite On la renvoie simplement. */
$image_dest = $image_src;
}
On sait maintenant que lon peut afficher $image_dest ; il reste lcrire dans
le fichier pass comme deuxime paramtre et librer la mmoire quelle
occupe :
imagejpeg($image_dest, $nom_vignette);
imagedestroy($image_dest);
}
?>
160 Ch apit re 10
de couleurs limit et, lorsque vous modifiez la taille dune image, les couleurs ont
tendance changer galement.
De toutes faons, vitez les astuces de manipulation des images en PHP : les
scripts sont gnralement utiliss la demande et vous avez donc peu de temps
pour effectuer des oprations amusantes mais coteuses en temps dexcution.
Non seulement vos utilisateurs simpatienteraient, mais le serveur web ne laisserait
pas non plus votre script sexcuter assez longtemps.
Tr ait em en t d es im a ge s 161
11
UT I LI S A T I ON D E cU RL
POUR LE S S E R V I C E S W E B
Internet regorge dinformations trs intressantes : UPS peut vous dire le prix exact du transport
dun paquet de 3 kg de Bton Rouge en Louisiane
vers Toulouse en France. Authorize.net peut vous
dire sil reste suffisamment dargent sur le compte dun
client pour quil puisse acheter un livre qui cote 50
sur votre site. Cependant, vous devez savoir comment
demander ces informations.
Pour les trouver, vous devriez toujours penser la bibliothque cURL de PHP
pour grer la connexion entre votre serveur web et les autres. Le principe
consiste considrer que vos scripts sont des clients, un peu comme des navigateurs web. Vous pouvez demander cURL tout ce qui va de la rcupration du
code HTML dune page web laccs un service web reposant sur XML. Il y a
trois faons daccder aux donnes :
On tlcharge la page web du site pour dcortiquer son code HTML, comme
on la expliqu au Chapitre 5, la recette n41, "Extraire des donnes des
pages".
On poste des paramtres de requtes et on fait le tri dans le rsultat.
On utilise lAPI dun service web pour accder aux donnes et on analyse le
rsultat avec un parseur XML. Vous rencontrerez diffrents protocoles
comme SOAP (Simple Object Access Protocol) ou REST (REpresentational State
Transfer ; cest gnralement une autre faon dappeler lenvoi de paramtres
POST ou GET). Nattendez pas trop de cohrence : mme si deux sites fournissent le mme type de donnes, il est fort probable que leurs formats de
sortie et leurs mthodes daccs soient diffrents.
Selon ce que vous comptez faire, vous aurez galement besoin dune partie
des bibliothques suivantes :
1. cURL (la bibliothque et lextension PHP). Voyez la recette n28, "Ajouter
des extensions PHP", si cette extension nest pas dj installe.
2. OpenSSL pour accder aux sites scuriss.
3. XML pour analyser les donnes provenant des services web. Les extensions XML (telles libXML) sont installes par dfaut sur la plupart des
serveurs PHP.
Par dfaut, cURL active certaines fonctionnalits qui, soit sont inutiles, soit
gnent un traitement de page classique. Lune delles consiste inclure len-tte
HTTP dans le rsultat ; vous pouvez la dsactiver de la faon suivante :
curl_setopt($c, CURLOPT_HEADER, false);
De mme, cURL affiche automatiquement la page accde au lieu de la renvoyer sous forme de chane. Comme vous avez besoin dune chane pour analyser
le contenu de la page, utilisez loption CURLOPT_RETURNTRANSFER pour indiquer
que vous voulez obtenir le rsultat sous cette forme :
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
164 Ch apit re 11
Avec cet appel, cURL accde la page et renvoie les donnes quelle contient ;
celles-ci sont alors affectes la variable $donnees_page. Enfin, il ne reste plus
qu fermer la connexion avec curl_close() pour librer la ressource :
curl_close($c);
Cest un bon dbut car nous pouvons maintenant accder des donnes web
via la mthode GET. Par contre, pour soumettre des donnes avec la mthode
POST, nous devons configurer des options supplmentaires avec curl_setopt().
Nous allons donc crire une fonction recup_page() permettant daccder aux
pages avec la mthode GET ou POST.
Cette fonction attend deux paramtres : lURL cible et un tableau facultatif de paramtres POST (les cls seront les noms des paramtres et seront
associes leurs valeurs). Si ce second paramtre nest pas fourni lappel, la
fonction utilise la mthode GET. La premire partie de son code consiste
construire une chane de requte partir du tableau des paramtres, de la
forme param1=valeur1¶m2=valeur2[...], exactement comme pour une
requte GET sauf quelle ne commence pas par un point dinterrogation. La
fonction prdfinie http_build_query() permettant de crer ce type de
chane partir dun tableau, il ne nous reste plus qu vrifier le paramtre
tableau :
function recup_page($url, $params_post = null) {
/* Connexion un site avec POST ou GET et rcupration des donnes */
$chaine_requete = null;
if (!is_null($params_post)) {
if (!is_array($params_post)) {
die("Les paramtres POST ne sont pas sous forme de tableau.");
}
/* Construction de la chane de requte. */
$chaine_requete = http_build_query($params_post);
}
165
Voici comme utiliser cette fonction pour faire une recherche sur Yahoo! :
print recup_page("http://search.yahoo.com/search",
array("p" => "vache"));
Cette fonction convient la plupart des besoins classiques des accs clients.
Nous allons maintenant montrer comment satisfaire les exigences de certains
sites et services.
Ici, c est un descripteur de connexion cURL et pot_cookies est un fichier accessible en criture.
Le plus grand inconvnient de cette approche est quelle ne fonctionne pas
avec les connexions en parallle ; si plusieurs processus utilisent le mme pot
cookies, ils auront la mme session et se marcheront dessus les uns et les autres,
voire pire. Vous pouvez contourner ce dfaut en insrant le PID du processus
166 Ch apit re 11
PHP (avec getmypid() ) dans le nom du fichier du pot cookies. Cependant, vous
devez alors vous assurer de supprimer le fichier en question lorsque vous avez termin, ou vous finirez par avoir un grand nombre de pots cookies qui risquent
de perturber les processus suivants.
167
NOTE Au lieu de passer par ces deux tapes, vous pouvez aussi crer lobjet partir dun fichier en
appelant simplexml_load_file().
Si aucun message derreur nest produit, le document XML se trouve maintenant dans lobjet $donnees. Pour examiner le premier pch du nud <peches>,
on utilise une combinaison de syntaxe oriente objet et daccs aux tableaux :
print $donnees>peches>peche[0];
Le plus grand intrt de la syntaxe daccs aux tableaux est quelle permet de
parcourir tous les nuds. Voici, par exemple, comment afficher tous les pchs
de notre document :
foreach ($donnees>peches>peche as $peche) {
print $peche . ": " . $peche["type"];
print "<br />";
}
Comme vous lavez srement remarqu, il est possible de confondre les attributs et les nuds fils avec cette syntaxe mais, en gnral, ce nest pas trop grave.
Vous pouvez mme utiliser print_r() pour tout afficher, auquel cas vous obtiendrez
le rsultat suivant :
SimpleXMLElement Object
(
[peches] => SimpleXMLElement Object
(
[peche] => Array
(
[0] => gourmandise
[1] => mauvais jeu de mots
[2] => flatulence
)
)
)
168 Ch apit re 11
tion, bien que vous pouvez la laissez telle quelle si vous ne comptez pas utiliser cette application en production ni trop souvent. Utilisez ventuellement les paramtres "state" et
"zip" si vous recherchez une adresse amricaine. Vous avez galement la possibilit dutiliser le paramtre "country" afin de prciser quil sagit dune adresse en France, mais la
requte fonctionne en ltat dans notre exemple prcdent il nexiste pas dhomonyme.
Comme vous le verrez par la suite, le document XML renvoy se complte automatiquement
et mentionne "France" dans la balise <state> et "FR" dans <country>. Vous avez donc
parfaitement le droit dajouter "country" => "FR" dans la requte prcdente, afin dliminer le moindre doute !
Aprs lexcution de ce code, $page contient le document XML suivant :
<?xml version="1.0"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://
api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">
<Result precision="address">
<Latitude>48.873032</Latitude>
169
<Longitude>2.360605</Longitude>
<Address>47bis, rue des Vinaigriers</Address>
<City>75010 Paris</City>
<State>France</State>
<Zip/>
<Country>FR</Country>
</Result>
</ResultSet>
Si vous ne vouliez connatre que ces deux informations, vous avez termin
lutilisation du service web. Faisons maintenant quelque chose dun peu plus
amusant. Supposons dabord que le script sappelle demo_carte.php et quil est
excut la rception de ce formulaire :
<form action="demo_carte.php">
Rue: <input type="text" name="rue" /><br />
Ville: <input type="text" name="ville" /><br />
tat/Pays: <input type="text" name="etat" /><br />
<input type="submit" /><br>
</form>
170 Ch apit re 11
Puis, crez les points sur la carte Google correspondant aux deux emplacements extraits prcdemment et centrez la carte sur le premier (ceci avant toute
opration) :
var latlon1 = new GLatLng(<?php print "$lat1, $lon1";?>);
var latlon2 = new GLatLng(<?php print "$lat2, $lon2";?>);
map.setCenter(latlon1);
171
Puis recentrez la carte et zoomez pour que la ligne occupe toute la vue :
var limites = line.getBounds();
niveau = map.getBoundsZoomLevel(limites);
map.setCenter(limites.getCenter(), niveau);
172 Ch apit re 11
En fait, quand tout fonctionne correctement, cest aussi simple que cela et le
script de cette section vous mettra le pied ltrier.
PHP 5 contient un ensemble de classes SOAP prdfinies, dont SoapClient
que nous prsenterons bientt. Cependant, ces classes ne sont gnralement pas
construites par dfaut : il faut utiliser loption --enable--soap lors de la configuration et de la compilation de PHP (voyez le Chapitre 2 pour plus de prcisions
sur cette tape).
Notre exemple repose sur le service web "Associates" offert par Amazon pour
vous aider vendre des articles sur son site. Il faut seulement demander un identifiant et vous avez ensuite un accs gratuit au service ( raison dune requte par
seconde). On commence par crer une instance de SoapClient en indiquant
lemplacement du document WSDL :
<?php
$client = new SoapClient("http://webservices.amazon.com/AWSECommerceService/
AWSECommerceService.wsdl");
Il faut ensuite configurer un objet pour la requte que lon veut envoyer. Ici,
on paramtre un objet $recherche contenant des nuds pour lidentifiant
daccs, la catgorie et les mots-cls dune recherche :
$recherche>AWSAccessKeyId = "votre_cle";
$recherche>Request>SearchIndex = "Music";
$recherche>Request>Keywords = "James Blunt";
Si tout sest bien pass, $r contiendra le rsultat XML que vous pourrez consulter
comme une instance SimpleXML :
foreach ($r>Items>Item as $elt) {
$attributs = $elt>ItemAttributes;
if (is_array($attributs>Artist)) {
$artiste = implode($attributs>Artist, ", ");
} else {
$artiste = $attributs>Artist;
}
print "Artiste: $artiste; titre: $attributs>Title<br />";
}
?>
173
Cest donc trs simple si vous savez comment tout cela fonctionne. Malheureusement, la plupart du temps ce ne sera pas le cas. Les fichiers WSDL sont souvent fournis sans aucune documentation et, en supposant que vous lisez du
WDSL, vous pourrez retrouver toutes les mthodes disponibles et les paramtres
quelles attendent, mais vous ne saurez pas quoi servent ces paramtres. En
outre, mme si vous savez quoi ressembleront les rponses aux mthodes, vous
ne saurez mme pas ce que sont censes faire ces mthodes exactement. Cela est
d en partie au fait quil est difficile de tirer parti dune documentation
lorsquon ne connat pas le langage de programmation ou limplmentation
SOAP utilise. De plus, personne ne veut les documenter.
Un autre problme srieux concerne les performances. Dans lexemple prcdent, PHP doit trouver le fichier WSDL, analyser son contenu XML puis crer
une toute nouvelle classe et une instance partir de ce contenu. Tout ceci est trs
inefficace, surtout si lon considre que lon a simplement besoin denvoyer quelques petits paramtres un service. PHP met en cache les fichiers WSDL quil
rencontre (dautres langages peuvent exiger que vous produisiez un code compil partir de WSDL, ce qui nest pas beaucoup plus efficace). En consquence,
si vous comptez lancer immdiatement un certain nombre de requtes pour des
services web reposant sur WSDL, vous aurez besoin soit dun matriel puissant,
soit dun moyen de contourner une bonne partie de ce processus en construisant
manuellement les requtes SOAP.
Pour toutes ces raisons, les services web SOAP ne sont pas aussi rpandus
quon pourrait le penser. Celui dAmazon.com, par exemple, na t ajout que
rcemment et cohabite avec un service REST traditionnel. Amazon.com documente les paramtres REST mais vous renvoie la lecture du fichier WSDL pour
savoir comment faire en SOAP. Une autre socit du nom de Google a dcid
dabandonner SOAP pour son service de recherche web. Cependant, SOAP nest
pas mort : vous le rencontrerez srement lorsque vous aurez faire des services
utilisant larchitecture .NET de Microsoft.
174 Ch apit re 11
<ID>12</ID>
<Prix>19.99</Prix>
</Article>
<Article>
<Nom>Pantoufles</Nom>
<ID>17</ID>
<Prix>9.99</Prix>
</Article>
</Articles>
catgories possibles car vous pourriez les mettre en cache (et vous devriez le faire si vous les
utilisez souvent).
Rcuprons maintenant les informations sur les produits partir de la base
de donnes :
175
Les choses vont maintenant commencer devenir intressantes. Pour initialiser le document XML, on cre une instance de SimpleXMLElement partir dun
document existant : libre vous de le personnaliser et de le rendre ventuellement plus complexe. Notre structure XML de base tant trs simple, nous utilisons une chane en ligne :
$doc = new SimpleXMLElement(<?xml version="1.0" encoding="utf8"?>
<Articles></Articles>
);
Le document est prt tre rempli et les donnes se trouvent dans $resultat : il reste donc parcourir les lignes. La premire opration consiste ajouter
un nouveau nud fils Article au nud Articles avec la mthode $doc>addChild() :
while ($produit = mysql_fetch_array($resultat)) {
$fils = $doc>addChild("Article");
Aprs avoir trait tous les articles, il reste produire le document XML en
appelant la mthode $doc>asXML():
print $doc>asXML();
En fait, vous navez pas tout fait encore termin car il faut vous occuper de
la fonction affiche_erreur() que nous avons mentionne plus haut. Comme
nous supposons que le message derreur est trs simple, nous navons pas nous
occuper des objets :
176 Ch apit re 11
function affiche_erreur($message) {
$message = htmlentities($message);
print "<?xml version=\"1.0\" encoding=\"utf8\"?>
<Erreur>$message</Erreur>
";
}
?>
Nous en avons termin avec les services web. Il y a bien sr des milliers de
faons de les compliquer en utilisant SOAP et WSDL notamment mais, en
dfinitive, tout cela revient rassembler des donnes et les placer dans les
bonnes cases.
177
12
MI S E E N A PPLI CA T I ON
est quils sont trs simples mettre en uvre. Celui que nous prsentons ici
devant permettre deffectuer des sondages multiples, les questions et les rponses
ne sont pas codes en dur.
Pour un sondage en ligne classique, vous navez normalement pas trop vous
soucier de tout ce qui concerne les votes car cela a rarement de limportance.
Pour empcher que lon puisse voter plusieurs fois, nous utiliserons des cookies.
Bien que ce systme puisse facilement tre contourn, les enjeux sont gnralement si peu importants que personne ne sen soucie et, si vous avez besoin dun
systme plus fiable, vous pouvez utiliser un systme dauthentification pour enregistrer les votes.
Ce systme de sondage comporte quatre scripts :
form_vote.php : Affichage dun bulletin de vote pour lutilisateur.
traitement_vote.php : Traitement dun vote.
affichage_vote.php : Affichage des rsultats du sondage.
config_vote.php : Connexion la base de donnes.
Il utilise trois tables. La table des sondages contient les questions :
CREATE TABLE sondages (
ID INT NOT NULL AUTO_INCREMENT ,
question MEDIUMTEXT NOT NULL ,
PRIMARY KEY ( ID )
) TYPE = MYISAM;
La table des rponses contient les rponses aux questions de la table des
sondages. Le champ ID permet de faire des jointures avec cette dernire :
CREATE TABLE reponses (
ID_reponse INT NOT NULL AUTO_INCREMENT ,
ID INT NOT NULL ,
reponse MEDIUMTEXT NOT NULL ,
PRIMARY KEY ( ID_reponse )
) TYPE = MYISAM;
Les champs ID et ID_reponse de la table des votes sont des rpliques des
champs correspondants dans les tables des sondages et des rponses :
CREATE TABLE votes (
ID INT NOT NULL ,
ID_reponse INT NOT NULL ,
INDEX ( ID )
) TYPE = MYISAM;
180 Ch apit re 12
INTO
INTO
INTO
INTO
sondages
reponses
reponses
reponses
M is e en a pp l ic at i on 181
$sondage = $_GET[sondage];
if (!is_numeric($sondage)) {
die("Sondage incorrect");
}
Nous pouvons vrifier en une seule requte que lidentifiant du sondage est
correct et rechercher les choix afficher. En effet, si aucun sondage ne correspond cet identifiant, la requte ne renverra aucune ligne.
/* Recherche du sondage dans la base de donnes. */
$sql = "SELECT S.question, R.reponse, R.ID_reponse
FROM sondages S, reponses R
WHERE S.ID = $sondage
AND R.ID = S.ID";
$resultat = mysql_query($sql, $bd) or
die ("Erreur mysql: " . mysql_error());
if (mysql_num_rows($resultat) == 0) {
die(Sondage inconnu.);
}
Si lon est arriv ici, cest que lutilisateur na pas encore vot et il faut donc
parcourir la liste des choix pour construire le formulaire. Cette boucle place une
suite de boutons radios dans la variable $liste_questions :
/* Formulaire de vote */
$liste_questions = "";
while($ligne = mysql_fetch_array($resultat)) {
$question = $row[question];
$liste_questions .= <li><input name="reponse" type="radio" value=" .
$ligne[ID_reponse] . "> . $ligne[reponse] .
</li>;
}
182 Ch apit re 12
Nous pouvons vrifier que les identifiants du sondage et de la rponse existent en recherchant leurs lignes dans la base de donnes. Si cest le cas, une
jointure entre la table des sondages et des rponses sur ces champs doit donner exactement une ligne ; on teste donc si cette requte a renvoy quelque
chose :
M is e en a pp l ic at i on 183
Si nous sommes arrivs ici, nous pouvons vrifier que lutilisateur na pas dj
vot et, si cest le cas, insrer une ligne de vote dans la table des votes :
/* Vrifie la prsence dun vote prcdent. */
if (!$_COOKIE["${sondage}_vote"]) {
/* On ajoute le vote dans la table. */
$sql = "INSERT INTO votes ( ID_reponse , ID)
VALUES ($reponse, $sondage);";
$resultat = @mysql_query($sql, $bd) or
die ("Ajout impossible: " . mysql_error());
184 Ch apit re 12
Lorsque lon vrifie quun identifiant de sondage existe, nous pouvons aussi
rechercher en mme temps la question de ce sondage car on aura ventuellement besoin de lafficher :
/* Recherche de la question. */
$sql = "SELECT question
FROM sondages
WHERE ID = $sondage";
$resultat = @mysql_query($sql, $bd) or
die ("Erreur MySQL: " . mysql_error());
if (mysql_num_rows($resultat)!= 1) {
die(Sondage inexistant.);
}
$ligne = mysql_fetch_array($resultat);
$question = $ligne["question"];
Trouvons le nombre total de votes car nous en aurons besoin plus tard pour
donner les pourcentages des diffrents votes :
M is e en a pp l ic at i on 185
Il est temps de passer la grosse requte qui rcupre les nombres de chaque
vote. Cest lendroit idal pour utiliser la clause LEFT JOIN avec un groupement
SQL pour classer tous les votes. Bien que cette requte soit un peu plus complique que toutes celles que nous avons dj rencontres dans ce livre, il est facile
de la dcortiquer pour mieux la comprendre :
$req = "SELECT R.reponse, R.ID_reponse, count(V.ID_reponse) as nb_votes
FROM reponses R
LEFT JOIN votes V
ON V.ID = R.ID
AND V.ID_reponse = R.ID_reponse
WHERE R.ID = $sondage
GROUP BY R.reponse
ORDER BY nb_votes DESC , R.reponse ASC
";
$resultat = @mysql_query($req, $bd) or
die ("Erreur MySQL: " . mysql_error());
Avec les rsultats de cette requte sous la main, nous prparons len-tte
HTML et la premire partie de la page :
print
print
print
print
print
"<html><head><title>Sondage: $question</title></head><body>";
<ul style="list-style-type: none; font-size: 12px;">;
<li style="font-weight: bold; padding-bottom: 10px;">;
"Sondage numro $sondage: $question";
</li>;
Puis, nous parcourons chaque choix et nous affichons le rsultat pour chacun
deux :
while ($ligne = mysql_fetch_array($resultat)) {
if ($nb_votes_total!= 0) {
$pct = sprintf("%.2f", 100.0 * $ligne["nb_votes"] / $nb_votes_total);
} else {
$pct = "0";
186 Ch apit re 12
}
$largeur_bote = strval(1 + intval($pct)) . "px";
print <li style="clear: left;">;
print "$ligne[reponse]";
print "</li>";
print <li style="clear: left; padding-bottom: 7px;">;
print <div style="width: . $largeur_bote . ; height: 15px; .
; background: black; margin-right: 5px; float: left;"> .
"</div>$pct%";
print </li>;
}
Enfin, nous terminons le code HTML avec le nombre total de votes et les
balises fermantes :
print
print
print
print
print
?>
Amlioration du script
Vous pouvez adapter ce systme vos besoins de diffrentes faons. Vous pouvez dabord ajouter une interface graphique pour ladministration du sondage
do vous pourrez non seulement crer de nouveaux sondages mais galement
activer ou dsactiver des sondages existants.
Vous pouvez rendre le sondage intgrable ; au lieu quil apparaisse comme
un script sur sa propre page, vous pouvez le transformer en un ensemble de fonctions. Lorsque vous affichez une page, vous pouvez alors placer le bulletin de vote
avec une balise <div>. Laspect le plus intressant dun sondage intgr est que
vous pouvez utilisez AJAX avec les rsultats. Lorsque lutilisateur clique sur le
bouton Votez !, le navigateur peut lancer un code JavaScript qui prend en compte
le vote et remplace le bulletin par le rsultat du sondage.
Enfin, vous pourriez rflchir dautres moyens de vous assurer que les utilisateurs ne votent pas deux fois. Pour ce faire, vous devez coupler la table des
votes avec un systme dauthentification. Ajoutez un champ index contenant les
identifiants de connexion et vrifiez dans cette table si lutilisateur a dj vot au
lieu dutiliser un cookie.
M is e en a pp l ic at i on 187
Chaque ligne de cette table dcrit une carte. Le champ contenu est du
HTML : il peut sagir dune balise img, dun fichier Flash intgr ou mme de
texte brut. Le champ apercu permet de prvisualiser la carte dans la galerie ; il
nest pas obligatoire. Voici un exemple de ligne de cette table :
INSERT INTO cartes
(description, contenu, categorie, largeur, hauteur, apercu)
VALUES
("Carte danniversaire 1", "<b>Joyeux anniversaire!</b> (1)",
"Anniversaire", 600, 300, NULL );
188 Ch apit re 12
M is e en a pp l ic at i on 189
Nous sommes prts afficher toutes les cartes comme des lignes du tableau.
Cest une boucle relativement simple qui ne ncessite pas de formatage car nous
lavons dj prcis dans len-tte du tableau. Vous remarquerez quon utilise un
lien vers le script suivant, envoi_carte.php.
while($ligne = mysql_fetch_array($resultat)) {
$lien = "envoi_carte.php?ID=$ligne[ID]";
print <tr>;
print "<td>$ligne[categorie]</td>";
print "<td><a href=\"$lien\">$ligne[description]</a></td>";
if ($ligne["apercu"]) {
print "<td><a href=\"$lien\">
<img src=\"$ligne[apercu]\" /></a></td>";
} else {
print "<td>(pas daperu)</td>";
}
print "</tr>";
}
190 Ch apit re 12
M is e en a pp l ic at i on 191
192 Ch apit re 12
M is e en a pp l ic at i on 193
194 Ch apit re 12
Nous avons besoin de connatre le contenu de la carte et les dtails de ce message particulier. Pour cela, il suffit de joindre les tables cartes et cartes_envoyees
sur leur champ ID et de rechercher le jeton.
$sql = "SELECT E.nom_exp, E.mel_exp, E.message,
E.nom_dest, E.mel_dest, E.reception,
C.contenu, C.largeur, C.hauteur
M is e en a pp l ic at i on 195
196 Ch apit re 12
Amlioration du script
Ce systme peut tre amlior de plusieurs faons. Un des ajouts les plus
importants consiste installer un CAPTCHA ou un systme similaire pour compliquer lenvoi de cartes de spam (voir la recette n66, "Crer une image CAPTCHA
pour amliorer la scurit").
M is e en a pp l ic at i on 197
La raison pour laquelle le champ date_comment est de type timestamp est que
nous navons pas lintention de permettre la modification des commentaires :
une fois post, il ne changera jamais et il ny a donc pas besoin de configurer
manuellement sa date.
Comme pour les systmes prcdents, nous utiliserons un fichier de configuration config_blog.php pour mettre en place la connexion MySQL et crer un objet
Smarty. Avec cette configuration par dfaut, les templates Smarty se trouveront
dans le rpertoire templates :
198 Ch apit re 12
<?php
session_start();
$connexion = @mysql_connect("localhost", "login", "secret") or
die("chec de la connexion.");
$bd = @mysql_select_db("nom_base", $connexion) or die(mysql_error());
require_once ("smarty/Smarty.class.php");
$smarty = new Smarty();
?>
Crations de billets
Avant de faire quoi que ce soit dautre, il faut pouvoir ajouter du contenu.
Nous allons donc commencer par lditeur de billet. Avec ce script, nous pourrons rellement nous rendre compte que lutilisation de Smarty permet de sparer les templates HTML de PHP et que cela produit un code bien plus propre.
Commenons par le template templates/edition_blog.tpl :
<html><head>
<title>{$titre}</title>
{literal}
<style>
h1 {
font-family: sans-serif;
font-size: 20px;
}
table.champs_saisie {
font-family: sans-serif;
font-size: 12px;
}
table.champs_saisie td {
vertical-align: top;
}
</style>
{/literal}
M is e en a pp l ic at i on 199
</head>
<body>
<h1>Nouveau billet</h1>
<form method="post" action="editer_blog.php">
<table class="champs_saisie">
<tr> <td>Titre:</td><td><input name="titre" type="text" /></td> </tr>
<tr> <td>Contenu:</td>
<td><textarea name="contenu" rows="15" cols="40"></textarea></td>
</tr>
<tr> <td>Catgorie:</td><td><input name="categorie" type="text" /> </tr>
<tr> <td /><td><input name="submit" type="submit" value="Poster" /></td> </tr>
</table>
</form>
</body>
</html>
Ce template nest rien de plus quun formulaire avec des champs titre, contenu
et categorie envoys editer_blog.php via la mthode POST. La Figure 12.6 montre
ce formulaire affich dans un navigateur.
200 Ch apit re 12
<?php
require_once("config_blog.php");
if ($_REQUEST["submit"]) {
$contenu = mysql_escape_string(strip_tags($_REQUEST["contenu"],
"<a><i><b><img>"));
$annonce = substr(strip_tags($contenu), 0, 80);
$titre = mysql_escape_string(strip_tags($_REQUEST["titre"]));
$categorie = mysql_escape_string(strip_tags($_REQUEST["categorie"]));
$q = "INSERT INTO billets_blog
(titre, contenu, categorie, annonce, date_billet)
VALUES ($titre, $contenu, $categorie, $annonce, now())";
mysql_query($q, $connexion) or die(mysql_error());
$id = mysql_insert_id($connexion);
header("Location: affiche_blog.php?ID=$id");
M is e en a pp l ic at i on 201
<html><head>
<title>{$titre}</title>
{literal}
<style>
h1 {
font-family: sans-serif;
font-size: 20px;
}
h4 {
font-family: sans-serif;
font-size: 12px;
}
.contenu {
font-family: sans-serif;
font-size: 12px;
}
</style>
<script>
function affiche_form_comment() {
o = document.getElementById("form_comment");
if (o) { o.style.display = ""; }
o = document.getElementById("lien_comment");
if (o) { o.style.display = "none"; }
}
</script>
{/literal}
</head>
Passons maintenant la section pour le contenu du billet. Comme sa structure est statique, elle est relativement simple. Notez le lien vers index_blog.php, qui
sera notre dernier script :
<body>
<span class="contenu">
<a href="index_blog.php">Mon blog</a>
<h1>{$titre}</h1>
{$date}
<p>
{$contenu}
</p>
Catgorie: {$categorie}<br />
<br />
202 Ch apit re 12
Pour les commentaires sur les billets, nous utiliserons la fonctionnalit {section} de Smarty car elle nous permet dafficher un nombre quelconque de commentaires en parcourant un tableau. Ici, la variable $commentaires est un tableau
de commentaires, chacun tant lui-mme un tableau ayant pour cl nom, date et
comment pour accder aux diffrents contenus des commentaires. Smarty parcourt les commentaires et affiche le contenu de cette section pour chacun deux,
ce qui permet une reprsentation trs compacte de ce traitement.
Si vous tes perdu, regardez plus bas comment affiche_blog.php affecte la variable
$commentaires.
<h4>Commentaires</h4>
{section name=i loop=$commentaires}
<b>{$commentaires[i].nom}</b> ({$commentaires[i].date})<br />
{$commentaires[i].comment}
<br /><br />
{/section}
M is e en a pp l ic at i on 203
Maintenant que nous nous sommes occup du HTML, affiche_blog.php est trs
simple crire. Nous suivons le scnario classique consistant vrifier le paramtre
dentre ID et rechercher le billet dans la base de donnes :
<?php
require_once("config_blog.php");
$ID = intval($_REQUEST[ID]);
$requete = "SELECT titre, categorie, contenu,
UNIX_TIMESTAMP(date_billet) AS date_billet
FROM billets_blog
WHERE ID = $ID";
$resultat = @mysql_query($requete, $connexion) or die(mysql_error());
if (mysql_num_rows($resultat) == 0) {
die(Identifiant incorrect.);
}
Si nous sommes arrivs jusquici, cest que lidentifiant du billet est correct et
que lon peut donc affecter ses donnes lobjet Smarty :
$ligne = mysql_fetch_array($resultat);
$smarty->assign("titre", $ligne["titre"]);
$smarty->assign("contenu", $ligne["contenu"]);
$smarty->assign("categorie", $row["categorie"]);
$smarty->assign("date", date("j M Y, G:m", $ligne["date_billet"]));
$smarty->assign("id", $ID);
204 Ch apit re 12
On retrouve les commentaires avec une requte SQL trs simple (il ny a pas
besoin de jointure) :
/* Recherche des commentaires. */
$requete = "SELECT comment, nom,
UNIX_TIMESTAMP(date_comment) AS date_comment
FROM commentaires_blog
WHERE ID = $ID
ORDER BY date_comment ASC";
$resultat = @mysql_query($requete, $connexion) or die (mysql_error());
Ajout de commentaires
Le script qui ajoute des commentaires aux billets, commenter_blog.php, est le
seul du systme qui nutilise pas de template car il naffiche rien. Dans la section
prcdente, nous avons vu quil prenait trois paramtres : ID, commentaire et nom
contenant, respectivement, lidentifiant du billet, le contenu du commentaire et
le nom de celui qui a post le commentaire. La premire tape, comme dhabitude, consiste vrifier que lidentifiant est correct et correspond un billet
existant :
M is e en a pp l ic at i on 205
<?php
require_once("config_blog.php");
$ID = intval($_REQUEST["ID"]);
/* Recherche le billet pour vrifier quil existe bien. */
$requete = "SELECT titre FROM billets_blog WHERE ID = $ID";
$resultat = mysql_query($requete, $connexion) or die(mysql_error());
if (mysql_num_rows($resultat)!= 1) {
header("Location: index_blog.php");
exit;
}
Dsormais, la seule chose qui manque notre systme de blog est une page
dindex.
206 Ch apit re 12
<html><head>
<title>{$titre}</title>
{literal}
<style>
table.billets {
font-size: 12px;
}
table.billets td {
padding-bottom: 7px;
}
.entete {
font-size: 14px;
font-weight: bold;
}
</style>
{/literal}
</head>
<body>
<a href="index_blog.php">Mon blog</a>
Lindex du blog pourra afficher les billets classs par catgorie si besoin est.
La partie suivante affiche la catgorie courante, sil y en a une :
{if $categorie}
<br />
Catgorie: {$categorie}
{/if}
M is e en a pp l ic at i on 207
</td></tr>
{/section}
</table>
</body>
</html>
208 Ch apit re 12
Amlioration du script
Les blogs ont t conus pour tre bricols. Vous pouvez donc ajouter un
grand nombre de fonctionnalits qui utiliseront vos connaissances en PHP ou
qui les tendront. Voici quelques ides simples :
Archivage : Lorsque vous commencerez avoir un certain nombre de billets,
vous devrez diviser lindex en plusieurs pages. La recette n3, "Crer des liens
Prcdent/Suivant" au Chapitre 1 pourra vous y aider.
Flux RSS : Appliquez votre connaissance des services web (recette n73,
"Construire un service web ") afin de syndiquer ce blog.
Authentification et administration : Tel quil est conu, nimporte qui peut
ajouter des billets au blog. Vous pouvez empcher cela en demandant une
authentification avant dajouter un billet. En outre, vous pouvez grer simultanment plusieurs blogs ou plusieurs auteurs, en ajoutant des noms dutilisateurs dans les tables SQL du systme.
M is e en a pp l ic at i on 209
210 Ch apit re 12
Annexe
Plusieurs scripts de ce livre utilisent une table infos_
produits contenant les dtails dun inventaire dune
hypothtique boutique. Voici la dfinition de cette table :
CREATE TABLE infos_produits (
nom_produit varchar(50) default NULL,
num_produit int(10) default NULL,
categorie enum(chaussures, gants, chapeaux) default NULL,
prix double default NULL,
PRIMARY KEY (num_produit)
);
Aprs avoir cr cette table, vous voudrez lui ajouter quelques donnes :
INSERT
INSERT
INSERT
INSERT
INSERT
INTO
INTO
INTO
INTO
INTO
infos_produits
infos_produits
infos_produits
infos_produits
infos_produits
VALUES
VALUES
VALUES
VALUES
VALUES
(Bottes Western,12,chaussures,19.99);
(Pantoufles,17,chaussures,9.99);
(Bottes de Snowboard,15,chaussures,89.99);
(Tongs,19,chaussures,2.99);
(Casquette de Baseball,20,chapeaux,12.79);
Bien que le champ num_produit soit un peu arbitraire, il doit tre unique
pour chaque ligne de la table.
I ND E X
A
Accs aux fichiers 31
AddAddress(), mthode de
PHPMailer 142
AddAttachment(), mthode de
PHPMailer 143
AddStringAttachment (), mthode de
PHPMailer 143
Afficher un tableau 14
AltBody, attribut de PHPMailer 142
Apostrophes magiques 30
Applications exemples 179
array_multisort(), fonction 17
assign(), mthode Smarty 19
Attaques
par injection SQL
protection 30
XSS 43
autop(), fonction 90
B
Balises HTML, supprimer 93
base64decode(), fonction 50
base64encode(), fonction 50
Blog, crer 198
Boutons de validation 61
C
Carte de crdit
connexion SSL 130
expiration 65
validit 62
deserialize() 15
error_reporting() 29
fclose() 111
feof() 110
fgetcsv() 119
fgets() 110
file_exists() 113
fopen() 110
function_exists() 34
GD (graphiques) 153
getimagesize() 115
header() 129, 130
htmlentities() 45
http_build_query() 165
include 6
include_once() 5
ini_get() 25
ini_set() 24
is_array() 54
is_null() 54
is_numeric() 54
isset() 54
is_string() 54
mail() 139
mcrypt() 49
md5() 48, 50
mktime() 66, 99
mysql_insert_id() 201
mysql_query() 43
mysql_real_escape_string() 30
nl2br() 90
phpinfo() 25, 33, 36
preg_match() 85
preg_match_all() 86
preg_replace() 56, 64, 86
print_r() 14, 85
pspell_config_create() 78
pspell_config_ignore () 78
pspell_config_mode() 78
pspell_config_personal() 80
pspell_save_wordlist() 80
rand() 51
require_once 4
serialize() 15
session_start() 126
setcookie() 124
srand() 51
strcasecmp() 17, 73
strip_tags () 93
strlen() 70
strpos() 73
str_replace() 73, 74
strstr() 73
strtolower() 72
strtotime() 97
strtoupper() 72
strval() 54
substr() 70
time() 96
trim() 56
ucwords() 72
unlink() 113
unset() 127
usort() 16
fopen(), fonction 110
Formulaire 53
rcuprer les donnes 55
scurit 39
function_exists(), fonction 34
G
GD
extension 33
fonctions
imagecolorallocate () 153
imagecopyresampled() 159
imagecreatefrom() 158
imagecreatetruecolor() 153
imagedestroy() 156, 159
imagefilledpolygon() 153
imagefilledrectangle() 153
imagejpeg() 160
imagepng() 160
imagesx() 158
imagesy() 158
imagettftext() 156
Golocalisation 169
getimagesize(), fonction 115
getmypid(), fonction 167
I n d ex 215
H
Hachage de mot de passe 47
header(), fonction 129, 130
htmlentities(), fonction 45
protection contre les attaques 45
HTMLSax, analyseur syntaxique 46
http_build_query(), fonction 165
I
imagecolorallocate(), fonction GD
153
imagecopyresampled(), fonction GD
159
imagecreatefrom(), fonction GD 158
imagecreatetruecolor(), fonction GD
153
imagedestroy(), fonction GD 156, 159
imagefilledpolygon(), fonction GD
153
imagefilledrectangle(), fonction GD
153
imagejpeg(), fonction GD 160
imagepng(), fonction GD 160
Images 149
CAPTCHA pour la scurit 149
crer des vignettes 157
dposer dans un rpertoire 114
GD 33
imagesx(), fonction GD 158
imagesy(), fonction GD 158
imagettftext(), fonction GD 156
include, fonction 6
include_once(), fonction 5
Inclure des fichiers 4
ini_get(), fonction 25
ini_set(), fonction 24
intval(), fonction 54
is_array(), fonction 54
isHTML, attribut de PHPMailer 142
is_null(), fonction 54
216 I n dex
is_numeric(), fonction 54
isset(), fonction 54, 125
is_string(), fonction 54
L
Liens
automatiques, crer 93
Prcdent/Suivant 9
LIMIT, clause SQL 11
Listes
blanches 54
noires 54
log_errors, option de configuration 28
M
magic_quotes_gpc, option de
configuration 31
mail(), fonction 139
max_execution_time, option de
configuration 29
Mcrypt 48
mcrypt(), fonction 49
Mcrypt, extension 32
mcrypt_get_block_size(), fonction 50
mcrypt_get_key_size(), fonction 50
md5(), fonction 48, 50
mktime(), fonction 66, 99
Modles,Voir Templates
Mot de passe alatoire 51
MySQL
extension 33
format des dates 106
mysql_insert_id(), fonction 201
mysql_query(), fonction 43
mysql_real_escape_string(), fonction
30, 42
N
nl2br(), fonction 90
Numro de tlphone, vrifier 67
O
open_basedir
option de configuration 31
variable 5
Orthographe, corriger 76
P
php.ini, fichier 24
phpinfo(), fonction 24, 25, 33, 36
PHPMailer 140, 193, 196
Postfix, programme 141
preg_match(), fonction 81, 85
preg_match_all(), fonction 86
preg_replace(), fonction 56, 64 68, 86
print_r(), fonction 14, 85, 168
pspell 76
pspell_check(), fonction 79
pspell_config_create(), fonction 78
pspell_config_ignore(), fonction 78
pspell_config_mode(), fonction 78
pspell_config_personal(), fonction 80
pspell_save_wordlist(), fonction 80
pspell_suggest(), fonction 79
R
rand(), fonction 51
Rediriger vers une page web 129
Rfrence arrire 87
register_globals, option de
configuration 30, 40
require_once, fonction 4
REST, protocole 164, 174
construire un service web 174
Risques de scurit
attaques XSS 43
formulaires 39
utilisateurs 40
variables globales automatiques 40
S
SafeHTML 46
Screen scraper 88
strip_tags(), fonction 93
strlen(), fonction 70
strpos(), fonction 73
str_replace(), fonction 73, 74
strrpos(), fonction 73
strstr(), fonction 73
strtolower(), fonction 72
strtotime(), fonction 97
strtoupper(), fonction 72
strval(), fonction 54
substr(), fonction 50, 70, 73
substr_replace(), fonction 70
T
Table infos_produits 211
Tableaux
afficher 14
couleur des lignes 7
srialisation 15
tri 16
Templates 17
Temps d'excution d'un script 29
Texte, convertir en HTML 90
time(), fonction 96
TIME, type MySQL 106
218 I n dex
Niveau : Intermdiaire
Catgorie : Dveloppement Web
ISBN : 978-2-7440-4030-6