Subversion Repositories Applications.framework

Rev

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