Subversion Repositories Applications.framework

Rev

Rev 286 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 aurelien 1
<?php
105 aurelien 2
// declare(encoding='UTF-8');
5 aurelien 3
/**
227 jpm 4
 * Classe Url, gérant le découpage des paramètres, leurs modification etc...
5
 * Traduction et conversion d'une classe (NET_Url2) issue de Pear
6
 *
7
 * @category	Php 5.2
8
 * @package	Framework
241 jpm 9
 * @author		Christian SCHMIDT <schmidt@php.net> (Auteur classe originale)
227 jpm 10
 * @author		Aurélien PERONNET <aurelien@tela-botanica.org>
11
 * @author		Jean-Pascal MILCENT <jpm@tela-botanica.org>
241 jpm 12
 * @copyright	Copyright (c) 2010, Tela Botanica (accueil@tela-botanica.org)
227 jpm 13
 * @license	http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
14
 * @license	http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
15
 * @version	SVN: $Id: Url.php 405 2012-02-22 15:45:39Z gduche $
16
 * @link		/doc/framework/
105 aurelien 17
*/
227 jpm 18
class Url {
5 aurelien 19
 
120 aurelien 20
	/**
21
	 * Répresenter les tableaux dans les requêtes en utilisant la notation php []. Par défaut à true.
22
	 */
286 jpm 23
	const OPTION_UTILISER_CROCHETS = 'utiliser_crochets';
5 aurelien 24
 
120 aurelien 25
	/**
26
	 * URL-encoder les clés des variables dans les requêtes. Par défaut à true.
27
	 */
286 jpm 28
	const OPTION_ENCODER_CLES = 'encoder_cles';
5 aurelien 29
 
120 aurelien 30
	/**
405 gduche 31
	* URL-encoder les valeurs des variables dans les requêtes. Par défaut à false.
32
	*/
33
	const OPTION_ENCODER_VALEURS = 'encoder_valeurs';
34
 
35
	/**
120 aurelien 36
	 * Séparateurs de variables lors du parsing de la requête. Chaque caractère
37
	 * est considéré comme un séparateur. Par défaut, spécifié par le paramêtre
38
	 * arg_separator.input dans php.ini (par défaut "&").
39
	 */
286 jpm 40
	const OPTION_SEPARATEUR_ENTREE = 'separateur_entree';
5 aurelien 41
 
120 aurelien 42
	/**
43
	 * Séparateur de variables lors de la génération de la requête. Par défaut, spécifié
44
	 * par le paramètre arg_separator.output dans php.ini (par défaut "&").
45
	 */
286 jpm 46
	const OPTION_SEPARATEUR_SORTIE = 'separateur_sortie';
5 aurelien 47
 
120 aurelien 48
	/**
49
	 * Options par défaut correspondant au comportement de php
50
	 * vis à vis de $_GET
51
	 */
52
	private $options = array(
144 jpm 53
		self::OPTION_UTILISER_CROCHETS => true,
54
		self::OPTION_ENCODER_CLES => true,
405 gduche 55
		self::OPTION_ENCODER_VALEURS => false,
274 jpm 56
		self::OPTION_SEPARATEUR_ENTREE => '&',
57
		self::OPTION_SEPARATEUR_SORTIE => '&');
5 aurelien 58
 
120 aurelien 59
	/**
60
	 * @var  string|bool
61
	 */
62
	private $schema = false;
5 aurelien 63
 
120 aurelien 64
	/**
65
	 * @var  string|bool
66
	 */
67
	private $infoUtilisateur = false;
5 aurelien 68
 
120 aurelien 69
	/**
70
	 * @var  string|bool
71
	 */
72
	private $hote = false;
5 aurelien 73
 
120 aurelien 74
	/**
75
	 * @var  int|bool
76
	 */
77
	private $port = false;
5 aurelien 78
 
120 aurelien 79
	/**
80
	 * @var  string
81
	 */
82
	private $chemin = '';
5 aurelien 83
 
120 aurelien 84
	/**
85
	 * @var  string|bool
86
	 */
87
	private $requete = false;
5 aurelien 88
 
120 aurelien 89
	/**
90
	 * @var  string|bool
91
	 */
92
	private $fragment = false;
5 aurelien 93
 
241 jpm 94
	/** Tableau des noms des paramètres à définir dans le fichier de config car obligatoirement nécessaire à cette classe.*/
274 jpm 95
	private $parametres_obligatoires = array('url_arg_separateur_entree', 'url_arg_separateur_sortie');
241 jpm 96
 
120 aurelien 97
	/**
98
	 * @param string $url	 une URL relative ou absolue
99
	 * @param array  $options
100
	 */
144 jpm 101
	public function __construct($url, $options = null) {
241 jpm 102
		Config::verifierPresenceParametres($this->parametres_obligatoires);
103
 
286 jpm 104
		$this->setOption(self::OPTION_SEPARATEUR_ENTREE, Config::get('url_arg_separateur_entree'));
105
		$this->setOption(self::OPTION_SEPARATEUR_SORTIE, Config::get('url_arg_separateur_sortie'));
120 aurelien 106
		if (is_array($options)) {
107
			foreach ($options as $nomOption => $valeur) {
108
				$this->setOption($nomOption);
109
			}
110
		}
5 aurelien 111
 
120 aurelien 112
		if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) {
113
			$this->schema = $reg[1];
114
			$url = substr($url, strlen($reg[0]));
115
		}
5 aurelien 116
 
120 aurelien 117
		if (preg_match('@^//([^/#?]+)@', $url, $reg)) {
118
			$this->setAutorite($reg[1]);
119
			$url = substr($url, strlen($reg[0]));
120
		}
5 aurelien 121
 
120 aurelien 122
		$i = strcspn($url, '?#');
123
		$this->chemin = substr($url, 0, $i);
124
		$url = substr($url, $i);
5 aurelien 125
 
120 aurelien 126
		if (preg_match('@^\?([^#]*)@', $url, $reg)) {
127
			$this->requete = $reg[1];
128
			$url = substr($url, strlen($reg[0]));
129
		}
5 aurelien 130
 
120 aurelien 131
		if ($url) {
132
			$this->fragment = substr($url, 1);
133
		}
134
	}
227 jpm 135
 
286 jpm 136
	/**
137
	 * Renvoie la valeur de l'option specifiée.
138
	 *
139
	 * @param string $nomOption Nom de l'option demandée
140
	 *
141
	 * @return  mixed
142
	 */
143
	public function getOption($nomOption) {
144
		return isset($this->options[$nomOption]) ? $this->options[$nomOption] : false;
145
	}
5 aurelien 146
 
120 aurelien 147
	/**
227 jpm 148
	 * Met à jour la valeur de l'option spécifiée.
120 aurelien 149
	 *
227 jpm 150
	 * @param string $nomOption une des constantes commençant par self::OPTION_
151
	 * @param mixed  $valeur	  valeur de l'option
120 aurelien 152
	 *
153
	 * @return void
227 jpm 154
	 * @see  self::OPTION_STRICTE
155
	 * @see  self::OPTION_UTILISER_CROCHETS
156
	 * @see  self::OPTION_ENCODER_CLES
120 aurelien 157
	 */
227 jpm 158
	public function setOption($nomOption, $valeur) {
159
		if (!array_key_exists($nomOption, $this->options)) {
160
			return false;
120 aurelien 161
		}
227 jpm 162
		$this->options[$nomOption] = $valeur;
120 aurelien 163
	}
5 aurelien 164
 
120 aurelien 165
	/**
166
	 * Renvoie la partie autorité, i.e. [ infoUtilisateur "@" ] hote [ ":" port ], ou
167
	 * false si celle-ci est absente.
168
	 *
169
	 * @return string|bool
170
	 */
227 jpm 171
	private function getAutorite() {
120 aurelien 172
		if (!$this->hote) {
173
			return false;
174
		}
5 aurelien 175
 
120 aurelien 176
		$autorite = '';
5 aurelien 177
 
120 aurelien 178
		if ($this->infoUtilisateur !== false) {
179
			$autorite .= $this->infoUtilisateur . '@';
180
		}
5 aurelien 181
 
120 aurelien 182
		$autorite .= $this->hote;
5 aurelien 183
 
120 aurelien 184
		if ($this->port !== false) {
185
			$autorite .= ':' . $this->port;
186
		}
5 aurelien 187
 
120 aurelien 188
		return $autorite;
189
	}
5 aurelien 190
 
120 aurelien 191
	/**
192
	 * @param string|false $autorite
193
	 *
194
	 * @return void
195
	 */
227 jpm 196
	private function setAutorite($autorite) {
120 aurelien 197
		$this->user = false;
198
		$this->pass = false;
199
		$this->hote = false;
200
		$this->port = false;
201
		if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $autorite, $reg)) {
202
			if ($reg[1]) {
203
				$this->infoUtilisateur = $reg[2];
204
			}
5 aurelien 205
 
120 aurelien 206
			$this->hote = $reg[3];
207
			if (isset($reg[5])) {
208
				$this->port = intval($reg[5]);
209
			}
210
		}
211
	}
5 aurelien 212
 
120 aurelien 213
	/**
227 jpm 214
	 * Renvoie vrai ou faux suivant que l'instance en cours représente une URL relative ou absolue.
120 aurelien 215
	 *
227 jpm 216
	 * @return  bool
120 aurelien 217
	 */
227 jpm 218
	private function etreAbsolue() {
219
		return (bool) $this->schema;
120 aurelien 220
	}
227 jpm 221
 
120 aurelien 222
	/**
227 jpm 223
	 * La suppression des segments à points est décrite dans la RFC 3986, section 5.2.4, e.g.
224
	 * "/foo/../bar/baz" => "/bar/baz"
120 aurelien 225
	 *
227 jpm 226
	 * @param string $chemin un chemin
120 aurelien 227
	 *
227 jpm 228
	 * @return string un chemin
120 aurelien 229
	 */
227 jpm 230
	private static function supprimerSegmentsAPoints($chemin) {
231
		$sortie = '';
5 aurelien 232
 
227 jpm 233
		// Assurons nous de ne pas nous retrouver piégés dans une boucle infinie due à un bug de cette méthode
234
		$j = 0;
235
		while ($chemin && $j++ < 100) {
236
			if (substr($chemin, 0, 2) == './') {// Étape A
237
				$chemin = substr($chemin, 2);
238
			} else if (substr($chemin, 0, 3) == '../') {
239
				$chemin = substr($chemin, 3);
240
			} else if (substr($chemin, 0, 3) == '/./' || $chemin == '/.') {// Étape B
241
				$chemin = '/' . substr($chemin, 3);
242
			} else if (substr($chemin, 0, 4) == '/../' || $chemin == '/..') {// Étape C
243
				$chemin = '/' . substr($chemin, 4);
244
				$i = strrpos($sortie, '/');
245
				$sortie = $i === false ? '' : substr($sortie, 0, $i);
246
			} else if ($chemin == '.' || $chemin == '..') {// Étape D
247
				$chemin = '';
248
			} else {// Étape E
249
				$i = strpos($chemin, '/');
250
				if ($i === 0) {
251
					$i = strpos($chemin, '/', 1);
120 aurelien 252
				}
227 jpm 253
				if ($i === false) {
254
					$i = strlen($chemin);
120 aurelien 255
				}
227 jpm 256
				$sortie .= substr($chemin, 0, $i);
257
				$chemin = substr($chemin, $i);
120 aurelien 258
			}
259
		}
5 aurelien 260
 
227 jpm 261
		return $sortie;
120 aurelien 262
	}
227 jpm 263
 
120 aurelien 264
	/**
227 jpm 265
	 * (Re-)Création de la partie requête de l'URL à partir des données du tableau (passé en paramètre).
266
	 *
267
	 * @param array (nom => valeur) tableau de clés & valeurs pour la partie requête de l'url.
268
	 * @return void (Re-)Création de la partie requête.
120 aurelien 269
	 */
227 jpm 270
	public function setRequete(Array $parametres) {
271
		if (!$parametres) {
120 aurelien 272
			$this->requete = false;
273
		} else {
227 jpm 274
			foreach ($parametres as $nom => $valeur) {
120 aurelien 275
				if ($this->getOption(self::OPTION_ENCODER_CLES)) {
276
					$nom = rawurlencode($nom);
277
				}
5 aurelien 278
 
405 gduche 279
				if ($this->getOption(self::OPTION_ENCODER_VALEURS)) {
280
					$valeur = rawurlencode($valeur);
281
				}
282
 
120 aurelien 283
				if (is_array($valeur)) {
284
					foreach ($valeur as $k => $v) {
227 jpm 285
						if ($this->getOption(self::OPTION_UTILISER_CROCHETS)) {
286
							$parties[] = sprintf('%s[%s]=%s', $nom, $k, $v);
287
						} else {
288
							$parties[] = $nom.'='.$v;
289
						}
120 aurelien 290
					}
227 jpm 291
				} else if (!is_null($valeur)) {
120 aurelien 292
					$parties[] = $nom . '=' . $valeur;
293
				} else {
294
					$parties[] = $nom;
295
				}
296
			}
227 jpm 297
			$this->requete = implode($this->getOption(self::OPTION_SEPARATEUR_SORTIE), $parties);
120 aurelien 298
		}
299
	}
171 jpm 300
 
301
	/**
227 jpm 302
	 * (Re-)Création de la partie requête de l'URL à partir de la fusion du tableau (passé en paramètre) et
303
	 * les valeurs présentes dans $_GET.
304
	 *
305
	 * @param array (nom => valeur) tableau de clés & valeurs pour la partie requête de l'url.
306
	 * @return void (Re-)Création de la partie requête.
171 jpm 307
	 */
227 jpm 308
	public function fusionnerRequete(Array $parametres) {
309
		if ($parametres) {
310
			$requete = $parametres + $_GET;
311
			$this->setRequete($requete);
171 jpm 312
		}
313
	}
5 aurelien 314
 
120 aurelien 315
	/**
227 jpm 316
	 * Normalise les données de l'instance d'Url faisant appel à cette méthode.
120 aurelien 317
	 *
227 jpm 318
	 * @return  void l'instance d'Url courrante est normalisée.
120 aurelien 319
	 */
144 jpm 320
	public function normaliser() {
227 jpm 321
		// Voir RFC 3886, section 6
5 aurelien 322
 
120 aurelien 323
		// les cchémas sont insesibles à la casse
324
		if ($this->schema) {
325
			$this->schema = strtolower($this->schema);
326
		}
5 aurelien 327
 
120 aurelien 328
		// les noms d'hotes sont insensibles à la casse
329
		if ($this->hote) {
330
			$this->hote = strtolower($this->hote);
331
		}
5 aurelien 332
 
120 aurelien 333
		// Supprimer le numéro de port par défaut pour les schemas connus (RFC 3986, section 6.2.3)
227 jpm 334
		if ($this->port && $this->schema && $this->port == getservbyname($this->schema, 'tcp')) {
120 aurelien 335
			$this->port = false;
336
		}
5 aurelien 337
 
120 aurelien 338
		// normalisation dans le cas d'un encodage avec %XX pourcentage (RFC 3986, section 6.2.2.1)
339
		foreach (array('infoUtilisateur', 'hote', 'chemin') as $partie) {
340
			if ($this->$partie) {
341
				$this->$partie  = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $this->$partie);
342
			}
343
		}
5 aurelien 344
 
120 aurelien 345
		// normalisation des segments du chemin (RFC 3986, section 6.2.2.3)
346
		$this->chemin = self::supprimerSegmentsAPoints($this->chemin);
5 aurelien 347
 
120 aurelien 348
		// normalisation basée sur le schéma (RFC 3986, section 6.2.3)
349
		if ($this->hote && !$this->chemin) {
350
			$this->chemin = '/';
351
		}
352
	}
5 aurelien 353
 
120 aurelien 354
	/**
227 jpm 355
	 * Renvoie une instance d'objet Url representant l'URL canonique du script PHP en cours d'éxécution.
120 aurelien 356
	 *
227 jpm 357
	 * @return Url retourne un objet Url ou null en cas d'erreur.
120 aurelien 358
	 */
227 jpm 359
	public static function getCanonique() {
360
		$url = null;
361
		if (!isset($_SERVER['REQUEST_METHOD'])) {
362
			trigger_error("Le script n'a pas été appellé à travers un serveur web", E_USER_WARNING);
120 aurelien 363
		} else {
227 jpm 364
			// À partir d'une URL relative
365
			$url = new self($_SERVER['PHP_SELF']);
366
			$url->schema = isset($_SERVER['HTTPS']) ? 'https' : 'http';
367
			$url->hote = $_SERVER['SERVER_NAME'];
368
			$port = intval($_SERVER['SERVER_PORT']);
369
			if ($url->schema == 'http' && $port != 80 || $url->schema == 'https' && $port != 443) {
370
				$url->port = $port;
120 aurelien 371
			}
372
		}
227 jpm 373
		return $url;
120 aurelien 374
	}
5 aurelien 375
 
120 aurelien 376
	/**
227 jpm 377
	 * Renvoie une instance d'objet Url representant l'URL utilisée pour récupérer la requête en cours.
120 aurelien 378
	 *
227 jpm 379
	 * @return Url retourne un objet Url ou null en cas d'erreur.
120 aurelien 380
	 */
227 jpm 381
	public static function getDemande() {
382
		$url = null;
383
		if (!isset($_SERVER['REQUEST_METHOD'])) {
384
			trigger_error("Le script n'a pas été appellé à travers un serveur web", E_USER_WARNING);
385
		} else {
386
			// On part d'une URL relative
387
			$url = new self($_SERVER['REQUEST_URI']);
388
			$url->schema = isset($_SERVER['HTTPS']) ? 'https' : 'http';
389
			// On met à jour les valeurs de l'hôte et si possible du port
390
			$url->setAutorite($_SERVER['HTTP_hote']);
120 aurelien 391
		}
227 jpm 392
		return $url;
120 aurelien 393
	}
5 aurelien 394
 
227 jpm 395
 
120 aurelien 396
	/**
227 jpm 397
	 * Renvoie un représentation sous forme de chaine de l'URL.
120 aurelien 398
	 *
227 jpm 399
	 * @return  string l'url
120 aurelien 400
	 */
227 jpm 401
	public function getURL() {
402
		// Voir RFC 3986, section 5.3
279 jpm 403
		$url = '';
227 jpm 404
 
405
		if ($this->schema !== false) {
406
			$url .= $this->schema . ':';
120 aurelien 407
		}
5 aurelien 408
 
227 jpm 409
		$autorite = $this->getAutorite();
410
		if ($autorite !== false) {
411
			$url .= '//' . $autorite;
412
		}
413
		$url .= $this->chemin;
5 aurelien 414
 
227 jpm 415
		if ($this->requete !== false) {
416
			$url .= '?' . $this->requete;
120 aurelien 417
		}
5 aurelien 418
 
227 jpm 419
		if ($this->fragment !== false) {
420
			$url .= '#' . $this->fragment;
120 aurelien 421
		}
5 aurelien 422
 
120 aurelien 423
		return $url;
424
	}
5 aurelien 425
}
227 jpm 426
?>