Subversion Repositories Applications.framework

Rev

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