Subversion Repositories Applications.framework

Compare Revisions

Ignore whitespace Rev 226 → Rev 227

/trunk/framework/Url.php
1,30 → 1,23
<?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
* 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
// Auteur principal
* @author Christian Schmidt <schmidt@php.net>
* @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$
* @link /doc/framework/
*
* @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$
* @link /doc/framework/
*/
class Url
{
/**
* Parsing strict dans resoudre() (voir RFC 3986, section 5.2.2). Par défaut
* à true.
*/
const OPTION_STRICTE = 'strict';
class Url {
 
/**
* Répresenter les tableaux dans les requêtes en utilisant la notation php []. Par défaut à true.
54,7 → 47,6
* 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&',
133,119 → 125,33
$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
* Met à jour la valeur de l'option spécifiée.
*
* @return string|bool
*/
public function getSchema() {
return $this->schema;
}
 
/**
* @param string|bool $schema
* @param string $nomOption une des constantes commençant par self::OPTION_
* @param mixed $valeur valeur de l'option
*
* @return void
* @see getSchema()
* @see self::OPTION_STRICTE
* @see self::OPTION_UTILISER_CROCHETS
* @see self::OPTION_ENCODER_CLES
*/
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;
public function setOption($nomOption, $valeur) {
if (!array_key_exists($nomOption, $this->options)) {
return false;
}
$this->options[$nomOption] = $valeur;
}
 
/**
* 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() {
private function getAutorite() {
if (!$this->hote) {
return false;
}
270,7 → 176,7
*
* @return void
*/
public function setAutorite($autorite) {
private function setAutorite($autorite) {
$this->user = false;
$this->pass = false;
$this->hote = false;
288,128 → 194,67
}
 
/**
* Renvoie la partie chemin (chemin) (éventuellement vide).
* Renvoie vrai ou faux suivant que l'instance en cours représente une URL relative ou absolue.
*
* @return string
* @return bool
*/
public function getChemin() {
return $this->chemin;
private function etreAbsolue() {
return (bool) $this->schema;
}
 
/**
* @param string $chemin
* La suppression des segments à points est décrite dans la RFC 3986, section 5.2.4, e.g.
* "/foo/../bar/baz" => "/bar/baz"
*
* @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.
* @param string $chemin un chemin
*
* @return string|bool
* @see self::getVariablesRequete()
* @return string un chemin
*/
public function getRequete() {
return $this->requete;
}
private static function supprimerSegmentsAPoints($chemin) {
$sortie = '';
 
/**
* @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 tableau
if (empty($retour[$cle]) || !is_array($retour[$cle])) {
$retour[$cle] = array();
// 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);
}
 
// Ajout des données
if ($idx === '') {
$retour[$cle][] = $valeur;
} else {
$retour[$cle][$idx] = $valeur;
if ($i === false) {
$i = strlen($chemin);
}
} elseif (!$this->getOption(self::OPTION_UTILISER_CROCHETS)
&& !empty($retour[$cle])
) {
$retour[$cle] = (array) $retour[$cle];
$retour[$cle][] = $valeur;
} else {
$retour[$cle] = $valeur;
$sortie .= substr($chemin, 0, $i);
$chemin = substr($chemin, $i);
}
}
 
return $retour;
return $sortie;
}
 
/**
* @param array $tableau (nom => valeur) tableau
*
* @return void
* (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 setVariablesRequete(array $tableau) {
if (!$tableau) {
public function setRequete(Array $parametres) {
if (!$parametres) {
$this->requete = false;
} else {
foreach ($tableau as $nom => $valeur) {
foreach ($parametres as $nom => $valeur) {
if ($this->getOption(self::OPTION_ENCODER_CLES)) {
$nom = rawurlencode($nom);
}
416,106 → 261,43
 
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);
if ($this->getOption(self::OPTION_UTILISER_CROCHETS)) {
$parties[] = sprintf('%s[%s]=%s', $nom, $k, $v);
} else {
$parties[] = $nom.'='.$v;
}
}
} elseif (!is_null($valeur)) {
} else if (!is_null($valeur)) {
$parties[] = $nom . '=' . $valeur;
} else {
$parties[] = $nom;
}
}
$this->requete = implode($this->getOption(self::OPTION_SEPARATEUR_SORTIE),
$parties);
$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);
}
/**
* @param array $noms tableau des noms de variable à supprimer de l'url.
*
* @return void
* (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 unsetVariablesRequete($noms) {
$tableau = $this->getVariablesRequete();
foreach ($noms as $nom) {
unset($tableau[$nom]);
public function fusionnerRequete(Array $parametres) {
if ($parametres) {
$requete = $parametres + $_GET;
$this->setRequete($requete);
}
$this->setVariablesRequete($tableau);
}
 
/**
* Renvoie un représentation sous forme de chaine de l'URL
* Normalise les données de l'instance d'Url faisant appel à cette méthode.
*
* @return string
* @return void l'instance d'Url courrante est normalisée.
*/
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
// Voir RFC 3886, section 6
 
// les cchémas sont insesibles à la casse
if ($this->schema) {
528,10 → 310,7
}
 
// 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')) {
 
if ($this->port && $this->schema && $this->port == getservbyname($this->schema, 'tcp')) {
$this->port = false;
}
 
552,222 → 331,75
}
 
/**
* Renvoie vrai ou faux suivant que l'instance en cours représente une URL relative ou absolue.
* Renvoie une instance d'objet Url representant l'URL canonique du script PHP en cours d'éxécution.
*
* @return bool
* @return Url retourne un objet Url ou null en cas d'erreur.
*/
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;
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 {
$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());
// À 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;
}
$cible->schema = $this->schema;
}
 
$cible->fragment = $reference->fragment;
 
return $cible;
return $url;
}
 
/**
* La suppression des segments à points est décrite dans la RFC 3986, section 5.2.4, e.g.
* "/foo/../bar/baz" => "/bar/baz"
* Renvoie une instance d'objet Url representant l'URL utilisée pour récupérer la requête en cours.
*
* @param string $chemin un chemin
*
* @return string un chemin
* @return Url retourne un objet Url ou null en cas d'erreur.
*/
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 A
if (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);
}
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 $sortie;
return $url;
}
 
/**
* Renvoie une instance de Url representant l'URL canonique du script PHP
* en cours d'éxécution
* Renvoie un représentation sous forme de chaine de l'URL.
*
* @return string
* @return string l'url
*/
public static function getCanonique() {
if (!isset($_SERVER['REQUEST_METHOD'])) {
// ALERT - pas d'URL en cours
throw new Exception('Le script n\'a pas été appellé à travers un serveur web');
public function getURL() {
// Voir RFC 3986, section 5.3
$url = "";
if ($this->schema !== false) {
$url .= $this->schema . ':';
}
 
// 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) {
$autorite = $this->getAutorite();
if ($autorite !== false) {
$url .= '//' . $autorite;
}
$url .= $this->chemin;
 
$url->port = $port;
if ($this->requete !== false) {
$url .= '?' . $this->requete;
}
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 cours
throw new Exception('Le script n\'a pas été appellé à travers un serveur web');
if ($this->fragment !== false) {
$url .= '#' . $this->fragment;
}
 
// 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();
}
}
?>