mardi, mars 19, 2024
Nom d'utilisateur : Mot de passe :
[NEWS]
Envoyé par unreal
Quand vous développez un site Web dynamique, il convient qu'il puisse s'afficher dans plusieurs langues, sans modification de code. Le but étant clairement de pouvoir livrer ensuite des "packs de langues" contenant entièrement les changements d'une langue à une autre. Cela peut paraître compliqué, mais ce n'est pas vraiment le cas, à condition toutefois de prévoir la localisation dès le début du projet.

La localisation d'un projet PHP s'organise à deux niveaux :



1.0 Localisation du texte

Remarque importante : quelle que soit la méthode de localisation choisie, aucun texte non-localisé ne doit figurer en dur dans le code PHP. L'idée est donc de stocker le texte "statique" dans des fichiers à part afin de l'insérer dans la page au moment de l'affichage.
Pour cela, il existe plusieurs méthodes, dont le deux suivantes :

1.1 Gettext

Gettext est un logiciel GNU utilisé sous Unix pour afficher du texte localisé. Gettext a plusieurs avantages et inconvénients, mais on retient surtout qu'il est rapide, standardisé et qu'il supporte les pluriels. Côté inconvénients, il est nécessaire que gettext soit installé sur le serveur, que PHP ait été compilé avec support Gettext, et que les locales à utiliser (en_US, fr_FR...) soient aussi présents sur le serveur. Donc le bon fonctionnement de Gettext est très dépendant de la configuration du serveur, ce qui n'est pas vraiment idéal. De plus, le support Gettext sous Windows n'est pas vraiment très au point, et pour finir, il faut utiliser un logiciel fourni avec Gettext pour compiler les fichiers de localisation.

Si vous avez besoin d'utiliser Gettext, voici comment faire.

1.1.1 Les fichiers Gettext

Les traductions sont contenues dans des fichiers .po (je vous invite à appeler vos fichiers messages.po). Par exemple :

messages.po
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2004-12-24 16:33+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

msgid "Access denied."
msgstr "Accès refusé."

msgid "Wrong password."
msgstr "Mot de passe incorrect."


msgid contient la phrase à traduire et msgstr la traduction. On peut alors compiler les fichiers messages.mo grâce à la commande msgfmt :

$ msgfmt messages.po -o messages.mo


L'arborescence de vos fichiers doit être semblable à ceci :

www_root
    '- locale
        '- fr
        '    '- LC_MESSAGES
        '        '- messages.mo
        '        '- messages.po
        '- en
            '- LC_MESSAGES
                '- ...


Passons maintenant à la partie PHP.

1.1.2 Utilisation dans PHP

Je vous invite à créer une fonction pour simplifier l'utilisation de Gettext :

inc/inc_functions.php
function SetupLang($lang) {
    setlocale(LC_ALL, $lang);

    $domain = 'messages';
    $localedir = dirname($_SERVER['SCRIPT_FILENAME']) . '/locale';
    bindtextdomain($domain, $localedir);
    textdomain($domain);
}


Puis dans index.php :

index.php
<?php
[...]
require_once ('inc/inc_functions.php');
[...]
SetupLang('fr_FR'); // On active la version française.
[...]
?>


Enfin, pour traduire un texte, il suffit d'appeler la fonction gettext() avec un msgid en paramètre :

echo (gettext('Access denied.'));


Comme vous pouvez le voir, l'implémentation d'une localisation Gettext n'est pas si complexe ; l'inconvénient majeur reste que le serveur Web doit être configuré pour supporter Gettext, ce qui n'est pas obligatoirement le cas.
Quand gettext ne convient pas, je vous conseille la méthode qui suit.

1.2 Array de langue

Le principe est très simple : on crée un fichier qui définit un array (table) contenant les mots à traduire. Après, il suffit d'inclure ce fichier dans chaque page et d'accéder à l'array pour traduire. L'inconvénient de cette méthode est qu'elle réclame plus de mémoire et temps CPU que la méthode précédente, sachant que plus l'array est grand et plus ce sera lent. Son avantage est bien sur que c'est vraiment très simple à mettre en place, indépendamment de la configuration du serveur.

1.2.1 Le fichier

locale/fr_FR/lang.php
<?php
$lang = array (

'access_denied' => 'Accès refusé.',
'wrong_passwd' => 'Mot de passe incorrect.'

)
?>


Note : je vous conseille d'utiliser les noms de locales standards, en_US, fr_FR, de_DE...

1.2.2 Utilisation dans PHP

Il est préférable d'utiliser require_once() au lieu d'include().

index.php
$SetDisplayLang = 'fr_FR';
require_once ('locale/' . $SetDisplayLang . '/lang.php');
?>


On peut alors accéder aux phrases grâce aux index :

echo ($lang['wrong_passwd');


2.0 Localisation du reste

Le PHP est capable d'afficher certaines informations nativement en version localisée ; le passage en version localisée se faisant en appelant la fonction setlocale() :

$SetDisplayLang = 'fr_FR';
setlocale(LC_ALL, $SetDisplayLang.'.ISO8859-1');


Ensuite, certaines informations seront automatiquement affichées en version localisée (les fractions, par exemple), pour le reste il vous faudra utiliser des fonctions PHP supportant la localisation.

2.1 Les fonctions

Je vous invite à consulter la documentation des fonctions suivantes (liste clairement pas exhaustive) avant de regarder les exemples :



2.1 Exemples

Les fractions seront automatiquement affichées en version localisée :

setlocale(LC_ALL, 'fr_FR.ISO8859-1');
echo (1/2); // Affiche 0,5

setlocale(LC_ALL, 'en_US.ISO8859-1');
echo (1/2); // Affiche 0.5


Et pour finir, un exemple qui montre plus ou moins tout ce qu'on pourrait être amené à faire en localisation PHP. happy.gif

Historique

1.0 - 08/08/2005
1.01 - 09/08/2005
1.02 - 12/01/2006
Sous FreeBSD, il faut préciser l'encodage quand on utilise setlocale() (par exemple setlocale(LC_ALL, 'en_US.ISO8859-1')).

Conclusion

Le PHP n'est pas vraiment le langage le plus puissant en matière de localisation, mais il offre néanmoins des options intéressantes à condition d'y penser dès le début du projet.

Posté le 08/08/05 à 19:35 - 0 Commentaires...

[NEWS]
Envoyé par unreal
Il arrive souvent à ce qu'on souhaite proposer des fichiers à télécharger sur un site. Pour cela, il existe plusieurs solutions selon le niveau de protection des fichiers dont on a besoin.

Solution simplicite

Il s'agit simplement d'une redirection dont la seule utilité c'est compter le nombre de téléchargements (pour l'afficher sur le site).

La méthode classique consiste alors à programmer un download.php qui acceptera comme paramètre l'ID du fichier ou le path vers le fichier.

Exemple d'implémentation.

download.php
<?php

$p = '';
if (isset($_GET['p']))
$p = $_GET['p'];

if ($p) {
// On n'autorise pas les chemins '..'
if (preg_match('/\.\./', $p)) {
Header ('Location: ./');
exit();
}

// Code pour mettre a jour
// les stats de telechargements

Header ('Location: ./files'.$p);
} else {
Header ('Location: ./');
}
?>


Même si la détection du '..' n'est pas vraiment nécessaire dans ce cas précis (vu que la redirection ne donnera aucunement accès à des fichiers en dehors du www_root du serveur Web), cela reste une bonne pratique. Et c'est une bonne idée d'adopter les bonnes pratiques dès le début. happy.gif

L'inconvénient de cette méthode c'est qu'il est facile de connaître l'adresse réelle du fichier, donc il est possible de contourner download.php. Si le but de download.php est seulement de générer des statistiques de téléchargements, ceci n'est guère un problème. Par contre, pour sécuriser les fichiers, il va falloir faire mieux.


Solution sécurisée

Avec cette méthode, il n'est plus question de placer les fichiers à télécharger dans le www_root et de réaliser une redirection, mais d'envoyer les données à travers download.php.

L'exemple qui suit montre une implémentation (parmi d'autres).

download.php
<?php
$UploadDir = '/path/to/download/folder'; // Pas de / final !

$p = '';
if (isset($_GET['p']))
$p = $_GET['p'];

if ($p) {
// On n'autorise pas les chemins '..'
if (preg_match('/\.\./', $p)) {
Header ('Location: ./');
exit();
}

// Code pour mettre a jour
// les stats de telechargements

// Code pour gerer les droits d'acces

$path = $UploadDir . $p;

if (!is_file($path))
exit();

@ob_end_clean();
@ini_set('zlib.output_compression', 'Off');

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: public');
header('Content-Type: application/force-download');
header('Content-Type: application/octet-stream');
header('Content-Type: application/download');
header('Content-Disposition: attachment; filename="' . basename($path) . '";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($path));

$handle = fopen($path, 'rb');

do {
$data = fread($handle, 8192);
if (strlen($data) == 0) {
break;
}
echo($data);
} while (true);

fclose($handle);
exit();

} else {
Header ('Location: ./');
}
?>


Quelques remarques :
  • La détection '..' est particulièrement importante ici, sans quoi il serait possible d'accéder à tous les fichiers du disque.
  • Si après vérification l'utilisateur n'a pas les droits nécessaires, il suffit de quitter (exit();) ou de renvoyer sur une autre page (Header ('Location: page.php');).
  • Cette implémentation charge le fichier par bloc de 8ko -- ceci est important car il n'est pas concevable de charger entièrement le fichier à télécharger en mémoire.


Voilà c'est fini, à vous de jouer maintenant. happy.gif

Posté le 12/07/05 à 19:47 - 2 Commentaires...

[NEWS]
Envoyé par unreal
Le PHP n'a pas une très bonne réputation en matière de sécurité. Cette mauvaise réputation est souvent le résultat de négligence de la part des développeurs qui ne prennent pas systématiquement les mesures nécessaires pour renforcer la sécurité des sites. Alors voici un petit guide qui rappelle les règles de base.


Surveiller vos variables !

Une des bêtes noires des programmeurs vient du fait que le PHP est un langage non-typé, ce qui veut dire qu'il n'est pas possible de définir le type (int, string...) des variables avant utilisation. Ainsi, une requête SQL comme celle qui suit est très dangereuse :

<?php
$id = $_GET['post_id']; // N'est pas forcément un (int)

// Sans être sûr que $id un (int) on s'expose à de gros soucis !
$sql = 'SELECT c1, c2, c3 FROM t1 WHERE id = ' . $id;
?>


La solution est d'utiliser des casts. Par exemple :

$id = (int)$_GET['post_id']; // $id est forcément un (int)


L'autre bête noire est l'oubli fréquent des addslashes dans les requêtes SQL.

Par exemple :

<?php
$Nom_Utilisateur = $_POST['nom'];
$sql = "SELECT c1, c2, c3 FROM t1 WHERE UserName = '$Nom_Utilisateur'";
?>


Ceci est aussi très dangereux car si jamais '$Nom_Utilisateur' contient un apostrophe, non seulement la requête va échouer, mais en plus il est possible de réaliser une injection SQL, qui pourrait donner au malfaiteur accès complet à la base. Je conseille, donc, de placer les addslashes directement au niveau des requêtes pour éviter de se retrouver avec des \' sur la page Web elle même :

$sql = "SELECT c1, c2, c3 FROM t1 WHERE UserName = '" . addslashes($Nom_Utilisateur) . "'";



Sécurité HTML

Ce n'est pas vraiment une très bonne idée d'autoriser les visiteurs d'un site à saisir directement du HTML car filtrer convenablement ce dernier n'est pas chose facile.
Sans filtrage, le visiteur pourrait insérer du code javascript pour essayer de récupérer des informations sensibles (session PHP) ou pour installer des spywares.
Hélas, filtrer du HTML requière des bonnes connaissances en HTML. Supposons par exemple qu'on autorise la balise <div> pour les blocs de quote (<div class="quote">...</div>), qu'est ce qui empêcherait un visiteur à saisir <div style="...">...</div> ?
Du coup, il faudrait détecter qu'il s'agit bien d'une classe de div autorisée, mais le visiteur pourrait essayer de contourner en mettant <div class="quote" style="font: normal 200pt;">...</div> (notez que ceci est un exemple de HTML non valide, mais qui fonctionne sur certains browsers).
Il faut aussi penser à corriger les erreurs HTML (volontaires ou non) comme les balises laissées ouvertes ou encore les erreurs de syntaxe (comme dans l'exemple précédent).

Toute cette explication pour dire que j'encourage vivement à exploiter un système à base de BBCode qui permet bien plus de contrôle et de sécurité.

Pour info, la classe BBCode qui anime SlashOrg.Net est disponible à cette adresse.


Sécurisation des informations sensibles

Cela peut paraître incroyable, mais il existe encore aujourd'hui des sites qui ne sécurisent pas les informations sensibles des utilisateurs (comme le mot de passe) ; en clair, un site où on vous propose d'envoyer un mot de passe oublié par mail est un site qui ne sécurise pas les mots de passe. La conséquence directe est que n'importe quelle personne ayant accès à la base de données (légitimement ou en exploitant des failles de codage ou de sécurité) peut lire en clair les mots de passe. Ceci est évidemment une mauvaise chose.

Alors qu'est ce qu'on entend par sécurisation ? La méthode la plus simple consiste à hasher les mots de passe avant de les enregistrer dans la base. Le hashage fait intervenir une fonction de codage à sens unique : il est donc impossible (en principe) de retrouver un mot de passe à partir de son hash. En réalité, la seule manière de retrouver le mot de passe est d'essayer toutes les combinaisons, qui peut prendre un temps considérable du moment où on utilise des mots complexes.

L'algo de hashage le plus courant en PHP s'appelle le MD5 (128 bits), mais sachez qu'il en existe d'autres (SHA1, par exemple, offre un codage 160 bits).

Fonctionnement : dans un premier temps, quand l'utilisateur s'inscrit, on stocke son mot de passe sous forme de hash dans la base.

$password = md5($_POST['password']);
$sql = "INSERT INTO users VALUES (..., '$password', ...)";


Ensuite, quand l'utilisateur se logge, on hash à nouveau son mot de passe et on compare à la valeur contenue dans la base :

<?php
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
$sql = "SELECT username FROM users WHERE username = '$username' AND password = '$password'";
[...] // On recupère le nombre d'entrées dans $no

if ($no == 1) {
$login_ok = true;
} else {
$login_ok = false;
}
?>


Pour exploiter le hashage SHA1, il suffit de remplacer md5() par sha1().


Les sessions

Quand un utilisateur se connecte, il faut pouvoir le "suivre" sur le site pour lui proposer les options adéquates. Pour cela, il convient d'employer les sessions PHP qui permettent de s'assurer que l'utilisateur est bien celui qu'il prétend être. Concrètement, le PHP va envoyer un cookie de session au browser client et stocker une copie de cette session sur le disque du serveur. Ainsi, si jamais l'utilisateur modifie son cookie, la session sera automatiquement détruite. Cette méthode offre donc un excellent niveau de sécurité (tant que le pirate n'arrive pas à intercepter le cookie en sniffant le réseau, par exemple).

Exemple d'utilisation :

<?php
session_start(); // Initialise la session
$_SESSION['nom_de_ma_session'] = 'blablabla'; // Ecriture dans la session
[...]
$ses = $_SESSION['nom_de_ma_session']; // Lecture de la session
[...]
session_unset(); // Libération de toutes les variables de session
session_destroy(); // Destruction de la session
?>


Dans tous les cas, il ne faut jamais stocker des informations sensibles (user ID, nom d'utilisateur, niveau d'utilisateur) dans un cookie non sécurisé, parce que dans ce cas, il est très facile à un utilisateur de modifier le cookie pour se faire passer pour quelqu'un d'autre (un administrateur).


Fin

Comme toujours, n'hésitez pas à faire part de vos remarques. happy.gif

Posté le 10/06/05 à 04:49 - 5 Commentaires...

[NEWS]
Envoyé par unreal
Objet PHP, pour quoi faire ?

Commençons par une question (celle que vous vous posez peut-être) :

Quel intérêt de programmer en objet dans un langage Web comme le PHP ?


L'intérêt principal est de pouvoir ré-utiliser facilement le même code dans d'autres projets. En effet, l'utilisation de classes permet de développer des blocs, dont le fonctionnement interne importe peu mais dont les entrées/sorties sont parfaitement décrites. Un peu à la manière des fonctions classiques, avec l'organisation en plus : on range les éléments (variables et fonctions) de même type ensemble. Ceci évite d'avoir des 'fonctions.php' contenant 500 fonctions complètement en vrac, organisation qui devient vite ingérable.


Syntaxe d'une classe php

class ma_classe {
    // On déclare les variables
    var $var1;
    var $array1 = array();
    [...]

    // Méthodes

    // Constructeur : il porte le même nom que la classe
    function ma_classe() {
        [...]
    }

    // Fonction membre
    function func1($var2) {
        [...]
    }

    [...]
}


Comme dans toute programmation objet, le constructeur sera appelé lors de la création d'un nouvel objet :

$mon_objet = new ma_classe();


A partir de là, il est possible d'accéder aux variables et fonctions membres :

$mon_objet->var1 = 'Salut !';
$mon_objet->func1($mon_objet->var1);



Exemple de code

La classe suivante permet de calculer le temps de génération d'un script PHP.

class debug {
    var $starttime;

    function start_timer() {
        $mtime = microtime();
        $mtime = explode(' ', $mtime);
        $this->starttime = $mtime[1] + $mtime[0];
    }

    function stop_timer() {
        $mtime = microtime();
        $mtime = explode(' ', $mtime);
        $mtime = $mtime[1] + $mtime[0];
        return (round(($mtime - $this->starttime), 4));
    }
}


Remarque : il faut employer $this-> pour faire appel à une variable d'une classe depuis l'intérieur de l'objet, par exemple $this->starttime.

L'utilisation de la classe se fait de la manière suivante :

index.php
<?php
$debug = new debug;
$debug->start_timer();
[...]
echo ('La page a été générée en ' . $debug->stop_timer() . ' secondes');
?>



Conclusion

Sachez qu'il est possible de faire des classes dérivées ainsi que des fonctions virtuelles, mais cela ne sera pas traité dans ce guide. wink.gif

Pour la documentation officielle, c'est par ici.

Posté le 10/06/05 à 03:35 - 0 Commentaires...