Rev 120 | Rev 149 | 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 Php5* @package Framework// Auteur principal* @author Christian Schmidt <schmidt@php.net>// Autre auteurs* @author Aurélien PERONNET <aurelien@tela-botanica.org>* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>* @copyright 2009 Tela-Botanica* @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 144 2010-03-02 13:01:18Z jpm $* @link /doc/framework/**/class Url{/*** Parsing strict dans resoudre() (voir RFC 3986, section 5.2.2). Par défaut* à true.*/const OPTION_STRICTE = 'strict';/*** Répresenter les tableaux dans les requêtes en utilisant la notation php []. Par défaut à true.*/const OPTION_UTILISER_CROCHETS = 'use_brackets';/*** URL-encoder les clés des variables dans les requêtes. Par défaut à true.*/const OPTION_ENCODER_CLES = 'encode_keys';/*** 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 = 'input_separator';/*** 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 = 'output_separator';/*** Options par défaut correspondant au comportement de php* vis à vis de $_GET*/private $options = array(self::OPTION_STRICTE => true,self::OPTION_UTILISER_CROCHETS => true,self::OPTION_ENCODER_CLES => true,self::OPTION_SEPARATEUR_ENTREE => 'x&',self::OPTION_SEPARATEUR_SORTIE => 'x&');/*** @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;/*** @param string $url une URL relative ou absolue* @param array $options*/public function __construct($url, $options = null) {$this->setOption(self::OPTION_SEPARATEUR_ENTREE,ini_get('arg_separator.input'));$this->setOption(self::OPTION_SEPARATEUR_SORTIE,ini_get('arg_separator.output'));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);}}/*** Retourne le schéma, c.a.d. "http" ou "urn", ou false si aucun schéma n'est* spécifié, i.e. l'url est une url relative** @return string|bool*/public function getSchema() {return $this->schema;}/*** @param string|bool $schema** @return void* @see getSchema()*/public function setSchema($schema) {$this->schema = $schema;}/*** renvoie la partie user de la partie infoUtilisateur (partie précédant le premier* ":"), ou false si aucune partie infoUtilisateur n'est définie.** @return string|bool*/public function getUtilisateur() {return $this->infoUtilisateur !== false ? preg_replace('@:.*$@', '', $this->infoUtilisateur) : false;}/*** renvoie la partie mot de passe de la partie infoUtilisateur (partie après le premier* ":"), , ou false si aucune partie infoUtilisateur n'est définie (i.e. l'URL ne contient* pas de "@" en face du nom d'hôte) ou si la partie infoUtilisateur ne contient pas de ":".** @return string|bool*/public function getMotDePasse() {return $this->infoUtilisateur !== false ? substr(strstr($this->infoUtilisateur, ':'), 1) : false;}/*** Renvoie la partie userinfio, ou false si celle-ci n'existe pas, i.e. si la partie* autorité ne contient pas de "@"** @return string|bool*/public function getInfoUtilisateur() {return $this->infoUtilisateur;}/*** Setteur pour la partie infoUtilisateur. Si deux argument sont passé, ils sont combinés* dans la partie infoUtilisateur de cette manière username ":" password.** @param string|bool $infoUtilisateur infoUtilisateur ou username* @param string|bool $motDePasse** @return void*/public function setInfoUtilisateur($infoUtilisateur, $motDePasse = false) {$this->infoUtilisateur = $infoUtilisateur;if ($motDePasse !== false) {$this->infoUtilisateur .= ':' . $motDePasse;}}/*** Renvoie la partie hôte, ou false s'il n'y a pas de partie autorité, c.a.d.* l'URL est relative.** @return string|bool*/public function getHote() {return $this->hote;}/*** @param string|bool $hote** @return void*/public function setHote($hote) {$this->hote = $hote;}/*** Renvoie le numéro de port, ou false si aucun numéro de port n'est spécifié,* i.e. le port par défaut doit utilisé.** @return int|bool*/public function getPort() {return $this->port;}/*** @param int|bool $port** @return void*/public function setPort($port) {$this->port = intval($port);}/*** Renvoie la partie autorité, i.e. [ infoUtilisateur "@" ] hote [ ":" port ], ou* false si celle-ci est absente.** @return string|bool*/public 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*/public 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 la partie chemin (chemin) (éventuellement vide).** @return string*/public function getChemin() {return $this->chemin;}/*** @param string $chemin** @return void*/public function setChemin($chemin) {$this->chemin = $chemin;}/*** renvoie la chaine de requête (requete string) (sans le premier "?"), ou false si "?"* n'est pas présent dans l'url.** @return string|bool* @see self::getVariablesRequete()*/public function getRequete() {return $this->requete;}/*** @param string|bool $requete** @return void* @see self::setVariablesRequete()*/public function setRequete($requete) {$this->requete = $requete;}/*** Renvoie le nom du fragment, ou false si "#" n'est pas present dans l'URL.** @return string|bool*/public function getFragment() {return $this->fragment;}/*** @param string|bool $fragment** @return void*/public function setFragment($fragment) {$this->fragment = $fragment;}/*** Renvoie la requete string sous forme d'un tableau de variables telles qu'elles apparaitraient* dans le $_GET d'un script PHP** @return array*/public function getVariablesRequete() {$pattern = '/[' .preg_quote($this->getOption(self::OPTION_SEPARATEUR_ENTREE), '/') .']/';$parties = preg_split($pattern, $this->requete, -1, PREG_SPLIT_NO_EMPTY);$retour = array();foreach ($parties as $partie) {if (strpos($partie, '=') !== false) {list($cle, $valeur) = explode('=', $partie, 2);} else {$cle = $partie;$valeur = null;}if ($this->getOption(self::OPTION_ENCODER_CLES)) {$cle = rawurldecode($cle);}$valeur = rawurldecode($valeur);if ($this->getOption(self::OPTION_UTILISER_CROCHETS) &&preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $cle, $matches)) {$cle = $matches[1];$idx = $matches[2];// On s'assure que c'est bien un tableauif (empty($retour[$cle]) || !is_array($retour[$cle])) {$retour[$cle] = array();}// Ajout des donnéesif ($idx === '') {$retour[$cle][] = $valeur;} else {$retour[$cle][$idx] = $valeur;}} elseif (!$this->getOption(self::OPTION_UTILISER_CROCHETS)&& !empty($retour[$cle])) {$retour[$cle] = (array) $retour[$cle];$retour[$cle][] = $valeur;} else {$retour[$cle] = $valeur;}}return $retour;}/*** @param array $tableau (nom => valeur) tableau** @return void*/public function setVariablesRequete(array $tableau) {if (!$tableau) {$this->requete = false;} else {foreach ($tableau as $nom => $valeur) {if ($this->getOption(self::OPTION_ENCODER_CLES)) {$nom = rawurlencode($nom);}if (is_array($valeur)) {foreach ($valeur as $k => $v) {$parties[] = $this->getOption(self::OPTION_UTILISER_CROCHETS)? sprintf('%s[%s]=%s', $nom, $k, $v): ($nom . '=' . $v);}} elseif (!is_null($valeur)) {$parties[] = $nom . '=' . $valeur;} else {$parties[] = $nom;}}$this->requete = implode($this->getOption(self::OPTION_SEPARATEUR_SORTIE),$parties);}}/*** @param string $nom* @param mixed $valeur** @return array*/public function setVariableRequete($nom, $valeur) {$tableau = $this->getVariablesRequete();$tableau[$nom] = $valeur;$this->setVariablesRequete($tableau);}/*** @param string $nom** @return void*/public function unsetVariableRequete($nom) {$tableau = $this->getVariablesRequete();unset($tableau[$nom]);$this->setVariablesRequete($tableau);}/*** Renvoie un représentation sous forme de chaine de l'URL** @return string*/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;}/*** Renvoie une représentation de cette URL sous forme de chaine normalisée. Utile pour la* comparaison d'URLs** @return string*/public function getURLNormalisee() {$url = clone $this;$url->normaliser();return $url->getUrl();}/*** Renvoie une instance normalisée de Url** @return Url*/public function normaliser() {// See RFC 3886, section 6// les cchémas sont insesibles à la casseif ($this->schema) {$this->schema = strtolower($this->schema);}// les noms d'hotes sont insensibles à la casseif ($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 vrai ou faux suivant que l'instance en cours représente une URL relative ou absolue.** @return bool*/public function etreAbsolue() {return (bool) $this->schema;}/*** Renvoie une instance de Url représentant une URL absolue relative à* cette URL.** @param Url|string $reference URL relative** @return Url*/public function resoudre($reference) {if (is_string($reference)) {$reference = new self($reference);}if (!$this->etreAbsolue()) {throw new Exception('L\'URL de base doit être absolue !');}// Un parseur non strict peut choisir d'ignorer un schema dans la référence// si celui ci est identique au schéma de base de l'URI.if (!$this->getOption(self::OPTION_STRICTE) && $reference->schema == $this->schema) {$reference->schema = false;}$cible = new self('');if ($reference->schema !== false) {$cible->schema = $reference->schema;$cible->setAutorite($reference->getAutorite());$cible->chemin = self::supprimerSegmentsAPoints($reference->chemin);$cible->requete = $reference->requete;} else {$autorite = $reference->getAutorite();if ($autorite !== false) {$cible->setAutorite($autorite);$cible->chemin = self::supprimerSegmentsAPoints($reference->chemin);$cible->requete = $reference->requete;} else {if ($reference->chemin == '') {$cible->chemin = $this->chemin;if ($reference->requete !== false) {$cible->requete = $reference->requete;} else {$cible->requete = $this->requete;}} else {if (substr($reference->chemin, 0, 1) == '/') {$cible->chemin = self::supprimerSegmentsAPoints($reference->chemin);} else {// Concaténation chemins (RFC 3986, section 5.2.3)if ($this->hote !== false && $this->chemin == '') {$cible->chemin = '/' . $this->chemin;} else {$i = strrpos($this->chemin, '/');if ($i !== false) {$cible->chemin = substr($this->chemin, 0, $i + 1);}$cible->chemin .= $reference->chemin;}$cible->chemin = self::supprimerSegmentsAPoints($cible->chemin);}$cible->requete = $reference->requete;}$cible->setAutorite($this->getAutorite());}$cible->schema = $this->schema;}$cible->fragment = $reference->fragment;return $cible;}/*** 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 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) {// Étape Aif (substr($chemin, 0, 2) == './') {$chemin = substr($chemin, 2);} elseif (substr($chemin, 0, 3) == '../') {$chemin = substr($chemin, 3);// Étape B} elseif (substr($chemin, 0, 3) == '/./' || $chemin == '/.') {$chemin = '/' . substr($chemin, 3);// Étape C} elseif (substr($chemin, 0, 4) == '/../' || $chemin == '/..') {$chemin = '/' . substr($chemin, 4);$i = strrpos($sortie, '/');$sortie = $i === false ? '' : substr($sortie, 0, $i);// Étape D} elseif ($chemin == '.' || $chemin == '..') {$chemin = '';// Étape E} else {$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;}/*** Renvoie une instance de Url representant l'URL canonique du script PHP* en cours d'éxécution** @return string*/public static function getCanonique() {if (!isset($_SERVER['REQUEST_METHOD'])) {// ALERT - pas d'URL en coursthrow new Exception('Le script n\'a pas été appellé à travers un serveur web');}// on part 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 l'URL utilisée pour récupérer la requête en cours** @return string*/public static function getURLDemande() {return self::getDemande()->getUrl();}/*** Renvoie une instance de Url representant l'URL utilisée pour* récupérer la requête en cours** @return Url*/public static function getDemande() {if (!isset($_SERVER['REQUEST_METHOD'])) {// ALERTE - pas d'URL en coursthrow new Exception('Le script n\'a pas été appellé à travers un serveur web');}// 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'hote et si possible du port$url->setAutorite($_SERVER['HTTP_hote']);return $url;}/*** 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*/function setOption($nomOption, $valeur) {if (!array_key_exists($nomOption, $this->options)) {return false;}$this->options[$nomOption] = $valeur;}/*** Renvoie la valeur de l'option specifiée.** @param string $nomOption Nom de l'option demandée** @return mixed*/function getOption($nomOption) {return isset($this->options[$nomOption])? $this->options[$nomOption] : false;}public function __toString() {return $this->getURL();}}