Subversion Repositories Applications.framework

Rev

Rev 285 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

<?php
// declare(encoding='UTF-8');
/**
 * Classe Url, gérant le découpage des paramètres, leurs modification etc...
 * Traduction et conversion d'une classe (NET_Url2) issue de Pear
 *
 * @category    Php 5.2
 * @package     Framework
 * @author              Christian SCHMIDT <schmidt@php.net> (Auteur classe originale)
 * @author              Aurélien PERONNET <aurelien@tela-botanica.org>
 * @author              Jean-Pascal MILCENT <jpm@tela-botanica.org>
 * @copyright   Copyright (c) 2010, Tela Botanica (accueil@tela-botanica.org)
 * @license     http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
 * @license     http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
 * @version     SVN: $Id: Url.php 286 2011-01-05 11:37:57Z jpm $
 * @link                /doc/framework/
*/
class Url {

        /**
         * Répresenter les tableaux dans les requêtes en utilisant la notation php []. Par défaut à true.
         */
        const OPTION_UTILISER_CROCHETS = 'utiliser_crochets';

        /**
         * URL-encoder les clés des variables dans les requêtes. Par défaut à true.
         */
        const OPTION_ENCODER_CLES = 'encoder_cles';

        /**
         * Séparateurs de variables lors du parsing de la requête. Chaque caractère
         * est considéré comme un séparateur. Par défaut, spécifié par le paramêtre
         * arg_separator.input dans php.ini (par défaut "&").
         */
        const OPTION_SEPARATEUR_ENTREE = 'separateur_entree';

        /**
         * Séparateur de variables lors de la génération de la requête. Par défaut, spécifié
         * par le paramètre arg_separator.output dans php.ini (par défaut "&").
         */
        const OPTION_SEPARATEUR_SORTIE = 'separateur_sortie';

        /**
         * Options par défaut correspondant au comportement de php
         * vis à vis de $_GET
         */
        private $options = array(
                self::OPTION_UTILISER_CROCHETS => true,
                self::OPTION_ENCODER_CLES => true,
                self::OPTION_SEPARATEUR_ENTREE => '&',
                self::OPTION_SEPARATEUR_SORTIE => '&');

        /**
         * @var  string|bool
         */
        private $schema = false;

        /**
         * @var  string|bool
         */
        private $infoUtilisateur = false;

        /**
         * @var  string|bool
         */
        private $hote = false;

        /**
         * @var  int|bool
         */
        private $port = false;

        /**
         * @var  string
         */
        private $chemin = '';

        /**
         * @var  string|bool
         */
        private $requete = false;

        /**
         * @var  string|bool
         */
        private $fragment = false;

        /** Tableau des noms des paramètres à définir dans le fichier de config car obligatoirement nécessaire à cette classe.*/
        private $parametres_obligatoires = array('url_arg_separateur_entree', 'url_arg_separateur_sortie');
        
        /**
         * @param string $url    une URL relative ou absolue
         * @param array  $options
         */
        public function __construct($url, $options = null) {
                Config::verifierPresenceParametres($this->parametres_obligatoires);
                
                $this->setOption(self::OPTION_SEPARATEUR_ENTREE, Config::get('url_arg_separateur_entree'));
                $this->setOption(self::OPTION_SEPARATEUR_SORTIE, Config::get('url_arg_separateur_sortie'));
                if (is_array($options)) {
                        foreach ($options as $nomOption => $valeur) {
                                $this->setOption($nomOption);
                        }
                }

                if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) {
                        $this->schema = $reg[1];
                        $url = substr($url, strlen($reg[0]));
                }

                if (preg_match('@^//([^/#?]+)@', $url, $reg)) {
                        $this->setAutorite($reg[1]);
                        $url = substr($url, strlen($reg[0]));
                }

                $i = strcspn($url, '?#');
                $this->chemin = substr($url, 0, $i);
                $url = substr($url, $i);

                if (preg_match('@^\?([^#]*)@', $url, $reg)) {
                        $this->requete = $reg[1];
                        $url = substr($url, strlen($reg[0]));
                }

                if ($url) {
                        $this->fragment = substr($url, 1);
                }
        }
        
        /**
         * Renvoie la valeur de l'option specifiée.
         *
         * @param string $nomOption Nom de l'option demandée
         *
         * @return  mixed
         */
        public function getOption($nomOption) {
                return isset($this->options[$nomOption]) ? $this->options[$nomOption] : false;
        }

        /**
         * Met à jour la valeur de l'option spécifiée.
         *
         * @param string $nomOption une des constantes commençant par self::OPTION_
         * @param mixed  $valeur          valeur de l'option
         *
         * @return void
         * @see  self::OPTION_STRICTE
         * @see  self::OPTION_UTILISER_CROCHETS
         * @see  self::OPTION_ENCODER_CLES
         */
        public function setOption($nomOption, $valeur) {
                if (!array_key_exists($nomOption, $this->options)) {
                        return false;
                }
                $this->options[$nomOption] = $valeur;
        }

        /**
         * Renvoie la partie autorité, i.e. [ infoUtilisateur "@" ] hote [ ":" port ], ou
         * false si celle-ci est absente.
         *
         * @return string|bool
         */
        private function getAutorite() {
                if (!$this->hote) {
                        return false;
                }

                $autorite = '';

                if ($this->infoUtilisateur !== false) {
                        $autorite .= $this->infoUtilisateur . '@';
                }

                $autorite .= $this->hote;

                if ($this->port !== false) {
                        $autorite .= ':' . $this->port;
                }

                return $autorite;
        }

        /**
         * @param string|false $autorite
         *
         * @return void
         */
        private function setAutorite($autorite) {
                $this->user = false;
                $this->pass = false;
                $this->hote = false;
                $this->port = false;
                if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $autorite, $reg)) {
                        if ($reg[1]) {
                                $this->infoUtilisateur = $reg[2];
                        }

                        $this->hote = $reg[3];
                        if (isset($reg[5])) {
                                $this->port = intval($reg[5]);
                        }
                }
        }

        /**
         * Renvoie vrai ou faux suivant que l'instance en cours représente une URL relative ou absolue.
         *
         * @return  bool
         */
        private function etreAbsolue() {
                return (bool) $this->schema;
        }
        
        /**
         * La suppression des segments à points est décrite dans la RFC 3986, section 5.2.4, e.g.
         * "/foo/../bar/baz" => "/bar/baz"
         *
         * @param string $chemin un chemin
         *
         * @return string un chemin
         */
        private static function supprimerSegmentsAPoints($chemin) {
                $sortie = '';

                // Assurons nous de ne pas nous retrouver piégés dans une boucle infinie due à un bug de cette méthode
                $j = 0;
                while ($chemin && $j++ < 100) {
                        if (substr($chemin, 0, 2) == './') {// Étape A
                                $chemin = substr($chemin, 2);
                        } else if (substr($chemin, 0, 3) == '../') {
                                $chemin = substr($chemin, 3);
                        } else if (substr($chemin, 0, 3) == '/./' || $chemin == '/.') {// Étape B
                                $chemin = '/' . substr($chemin, 3);
                        } else if (substr($chemin, 0, 4) == '/../' || $chemin == '/..') {// Étape C
                                $chemin = '/' . substr($chemin, 4);
                                $i = strrpos($sortie, '/');
                                $sortie = $i === false ? '' : substr($sortie, 0, $i);
                        } else if ($chemin == '.' || $chemin == '..') {// Étape D
                                $chemin = '';
                        } else {// Étape E
                                $i = strpos($chemin, '/');
                                if ($i === 0) {
                                        $i = strpos($chemin, '/', 1);
                                }
                                if ($i === false) {
                                        $i = strlen($chemin);
                                }
                                $sortie .= substr($chemin, 0, $i);
                                $chemin = substr($chemin, $i);
                        }
                }

                return $sortie;
        }
        
        /**
         * (Re-)Création de la partie requête de l'URL à partir des données du tableau (passé en paramètre).
         * 
         * @param array (nom => valeur) tableau de clés & valeurs pour la partie requête de l'url.
         * @return void (Re-)Création de la partie requête.
         */
        public function setRequete(Array $parametres) {
                if (!$parametres) {
                        $this->requete = false;
                } else {
                        foreach ($parametres as $nom => $valeur) {
                                if ($this->getOption(self::OPTION_ENCODER_CLES)) {
                                        $nom = rawurlencode($nom);
                                }

                                if (is_array($valeur)) {
                                        foreach ($valeur as $k => $v) {
                                                if ($this->getOption(self::OPTION_UTILISER_CROCHETS)) {
                                                        $parties[] = sprintf('%s[%s]=%s', $nom, $k, $v);
                                                } else {
                                                        $parties[] = $nom.'='.$v;
                                                }
                                        }
                                } else if (!is_null($valeur)) {
                                        $parties[] = $nom . '=' . $valeur;
                                } else {
                                        $parties[] = $nom;
                                }
                        }
                        $this->requete = implode($this->getOption(self::OPTION_SEPARATEUR_SORTIE), $parties);
                }
        }
        
        /**
         * (Re-)Création de la partie requête de l'URL à partir de la fusion du tableau (passé en paramètre) et 
         * les valeurs présentes dans $_GET.
         * 
         * @param array (nom => valeur) tableau de clés & valeurs pour la partie requête de l'url.
         * @return void (Re-)Création de la partie requête.
         */
        public function fusionnerRequete(Array $parametres) {
                if ($parametres) {
                        $requete = $parametres + $_GET;
                        $this->setRequete($requete);
                }
        }

        /**
         * Normalise les données de l'instance d'Url faisant appel à cette méthode.
         *
         * @return  void l'instance d'Url courrante est normalisée.
         */
        public function normaliser() {
                // Voir RFC 3886, section 6

                // les cchémas sont insesibles à la casse
                if ($this->schema) {
                        $this->schema = strtolower($this->schema);
                }

                // les noms d'hotes sont insensibles à la casse
                if ($this->hote) {
                        $this->hote = strtolower($this->hote);
                }

                // Supprimer le numéro de port par défaut pour les schemas connus (RFC 3986, section 6.2.3)
                if ($this->port && $this->schema && $this->port == getservbyname($this->schema, 'tcp')) {
                        $this->port = false;
                }

                // normalisation dans le cas d'un encodage avec %XX pourcentage (RFC 3986, section 6.2.2.1)
                foreach (array('infoUtilisateur', 'hote', 'chemin') as $partie) {
                        if ($this->$partie) {
                                $this->$partie  = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $this->$partie);
                        }
                }

                // normalisation des segments du chemin (RFC 3986, section 6.2.2.3)
                $this->chemin = self::supprimerSegmentsAPoints($this->chemin);

                // normalisation basée sur le schéma (RFC 3986, section 6.2.3)
                if ($this->hote && !$this->chemin) {
                        $this->chemin = '/';
                }
        }

        /**
         * Renvoie une instance d'objet Url representant l'URL canonique du script PHP en cours d'éxécution.
         *
         * @return Url retourne un objet Url ou null en cas d'erreur.
         */
        public static function getCanonique() {
                $url = null;
                if (!isset($_SERVER['REQUEST_METHOD'])) {
                        trigger_error("Le script n'a pas été appellé à travers un serveur web", E_USER_WARNING);
                } else {
                        // À partir d'une URL relative
                        $url = new self($_SERVER['PHP_SELF']);
                        $url->schema = isset($_SERVER['HTTPS']) ? 'https' : 'http';
                        $url->hote = $_SERVER['SERVER_NAME'];
                        $port = intval($_SERVER['SERVER_PORT']);
                        if ($url->schema == 'http' && $port != 80 || $url->schema == 'https' && $port != 443) {
                                $url->port = $port;
                        }
                }
                return $url;
        }

        /**
         * Renvoie une instance d'objet Url representant l'URL utilisée pour récupérer la requête en cours.
         *
         * @return Url retourne un objet Url ou null en cas d'erreur.
         */
        public static function getDemande() {
                $url = null;
                if (!isset($_SERVER['REQUEST_METHOD'])) {
                        trigger_error("Le script n'a pas été appellé à travers un serveur web", E_USER_WARNING);
                } else {
                        // On part d'une URL relative
                        $url = new self($_SERVER['REQUEST_URI']);
                        $url->schema = isset($_SERVER['HTTPS']) ? 'https' : 'http';
                        // On met à jour les valeurs de l'hôte et si possible du port
                        $url->setAutorite($_SERVER['HTTP_hote']);
                }
                return $url;
        }

        
        /**
         * Renvoie un représentation sous forme de chaine de l'URL.
         *
         * @return  string l'url
         */
        public function getURL() {
                // Voir RFC 3986, section 5.3
                $url = '';
                
                if ($this->schema !== false) {
                        $url .= $this->schema . ':';
                }

                $autorite = $this->getAutorite();
                if ($autorite !== false) {
                        $url .= '//' . $autorite;
                }
                $url .= $this->chemin;

                if ($this->requete !== false) {
                        $url .= '?' . $this->requete;
                }

                if ($this->fragment !== false) {
                        $url .= '#' . $this->fragment;
                }

                return $url;
        }
}
?>