Subversion Repositories Applications.framework

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
229 jpm 1
<?php
2
/**
3
 * Classe principale gérant les services.
4
 * Paramètres liés dans config.ini :
5
 *  - serveur.baseURL
6
 *
7
 * Encodage en entrée : utf8
8
 * Encodage en sortie : utf8
9
 *
10
 * @category	Php 5.2
11
 * @package		JRest
12
 * @author		Jean-Pascal MILCENT <jpm@tela-botanica.org>
13
 * @license		GPL v3 <http://www.gnu.org/licenses/gpl.txt>
14
 * @license		CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
15
 * @copyright	2010 Tela-Botanica
16
 * @version		$Id$
17
 */
18
// TODO : gerer les retours : dans ce controleur : code retour et envoi ...
19
class Serveur {
20
 
21
	/** Nom de la méthode appelée dans un service pour éxécuter une requête de type GET. */
22
	const METHODE_GET = 'getElement';
23
 
24
	/** Nom de la méthode appelée dans un service pour éxécuter une requête de type POST. */
25
	const METHODE_POST = 'getElement';
26
 
27
	/** Nom de la méthode appelée dans un service pour éxécuter une requête de type DELETE. */
28
	const METHODE_DELETE = 'getElement';
29
 
30
	/** Nom de la méthode appelée dans un service pour éxécuter une requête de type PUT. */
31
	const METHODE_PUT = 'getElement';
32
 
33
	/** Les paramètres de configuration dynamiques internes au serveur. */
34
	private static $config;
35
 
36
	/** La méthode de la requête HTTP utilisée. */
37
	private $methode = 'GET';
38
 
39
	/** Le contenu de la requête HTTP (s'il y en a). */
40
	private $requeteDonnees = null;//requestData
41
 
42
	/** Version de l'API demandée.
43
	 * Généralement deux nombres séparés par un point. Ex. : 1.0
44
	 * Ex. http://www.mondomaine.org/services/[apiVersion]/monService/
45
	 */
46
	private $apiVersion = null;
47
 
48
	/** Nom du service demandé.
49
	 * Ex. http://www.mondomaine.org/services/apiVersion/[monService]/
50
	 */
51
	private $service = null;// $ressource
52
 
53
	/** Morceaux de l'url servant à préciser la ressource concerné pour service demandé.
54
	 * Ex. http://www.mondomaine.org/services/apiVersion/monService/[maRessource/maSousResource...]
55
	 */
56
	private $ressources = array();// $uid
57
 
58
	/** Partie de l'url servant à paramétrer le service demandé.
59
	 * Ex. http://www.mondomaine.org/services/apiVersion/monService?monParametre1=maValeur1&monParametre2=maValeur2
60
	 */
61
	private $parametres = array();
62
 
63
	/** Codes HTTP. */
64
	private static $http10 = array(
65
		'201' => 'Created',
66
		'204' => 'No Content',
67
		'400' => 'Bad Request',
68
		'401' => 'Unauthorized',
69
		'404' => 'Not Found',
70
		'405' => 'Method Not Allowed',
71
		'406' => 'Not Acceptable',
72
		'411' => 'Length Required',
73
		'500' => 'Internal Server Error'
74
	);
75
 
76
	/**
77
	 * Analyse les données envoyées au serveur, enregistre la méthode HTTP utilisée pour appeler le serveur et parse
78
	 * l'url appelée pour trouver le service demandé.
79
	 */
80
	public function __construct() {
81
		if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD']) && isset($_SERVER['QUERY_STRING'])) {
82
			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {
83
				$this->requeteDonnees = '';
84
				$httpContent = fopen('php://input', 'r');
85
				while ($data = fread($httpContent, 1024)) {
86
					$this->requeteDonnees .= $data;
87
				}
88
				fclose($httpContent);
89
			}
90
			if (strlen($_SERVER['QUERY_STRING']) == 0) {
91
				$tailleURL = strlen($_SERVER['REQUEST_URI']);
92
			} else {
93
				$tailleURL = -(strlen($_SERVER['QUERY_STRING']) + 1);
94
			}
95
			$urlString = substr($_SERVER['REQUEST_URI'], strlen(Config::get('serveur.baseURL')), $tailleURL);
96
 
97
			$urlParts = explode('/', $urlString);
98
			if (isset($urlParts[0]) && !empty($urlParts[0])) {
99
				$this->apiVersion = $urlParts[0];
100
				self::$config['chemins']['api'] = Config::get('chemin_modules').$this->apiVersion.DS;
101
			} else {
102
				$e = "Aucune version d'API n'a été spécifié dans l'url qui doit avoir la forme suivante http://www.mondomaine.org/services/apiVersion/monService/";
103
				trigger_error($e, E_USER_ERROR);
104
			}
105
 
106
			if (isset($urlParts[1]) && !empty($urlParts[1])) {
107
				$this->service = $this->traiterNomService($urlParts[1]);
108
			} else {
109
				$e = "Aucun service n'a été spécifié dans l'url qui doit avoir la forme suivante http://www.mondomaine.org/services/apiVersion/monService/";
110
				trigger_error($e, E_USER_ERROR);
111
			}
112
 
113
			if (count($urlParts) > 2 && $urlParts[2] != '') {
114
				array_shift($urlParts);
115
				array_shift($urlParts);
116
				foreach ($urlParts as $ressource) {
117
					if ($ressource != '') {
118
						$this->ressources[] = urldecode($ressource);
119
					}
120
				}
121
			}
122
 
123
			$this->nettoyerGet();
124
			$this->parametres = $_GET;
125
 
126
			$this->methode = $_SERVER['REQUEST_METHOD'];
127
 
128
			// Enregistrement en première position des autoload de la méthode gérant les classes des services
129
			spl_autoload_register(array(get_class(), 'chargerClasse'));
130
		} else {
131
			$e = "La classe Serveur du TBFRamework nécessite, pour fonctionner, l'accès aux variables serveurs REQUEST_URI, REQUEST_METHOD et QUERY_STRING.";
132
			trigger_error($e, E_USER_ERROR);
133
		}
134
	}
135
 
136
	private function traiterNomService($serviceNom) {
137
		return str_replace(' ', '', ucwords(str_replace('-', ' ', strtolower($serviceNom))));
138
	}
139
 
140
	private function nettoyerGet() {
141
		if (isset($_GET) && count($_GET) > 0) {
142
			foreach ($_GET as $cle => $valeur) {
143
				$verifier = array('NULL', "\n", "\r", "\\", "'", '"', "\x00", "\x1a", ';');
144
				$_GET[$cle] = strip_tags(str_replace($verifier, '', $valeur));
145
			}
146
		}
147
	}
148
 
149
	/**
150
	* La méthode __autoload() charge dynamiquement les classes trouvées dans le code.
151
	* Cette fonction est appelée par php5 quand il trouve une instanciation de classe dans le code.
152
	*
153
	*@param string le nom de la classe appelée.
154
	*@return void le fichier contenant la classe doit être inclu par la fonction.
155
	*/
156
	public static function chargerClasse($classe) {
157
		if (class_exists($classe)) {
158
			return null;
159
		}
160
 
161
		$chemins = array('', self::$config['chemins']['api']);
162
		foreach ($chemins as $chemin) {
163
			$chemin = $chemin.$classe.'.php';
164
			if (file_exists($chemin)) {
165
				require_once $chemin;
166
			}
167
		}
168
	}
169
 
170
	/**
171
	 * Execute la requête.
172
	 */
173
	function executer() {
174
		switch ($this->methode) {
175
			case 'GET':
176
				$this->get();
177
				break;
178
			case 'POST':
179
				$this->post();
180
				break;
181
			case 'DELETE':
182
				$this->delete();
183
				break;
184
			case 'PUT':
185
				$this->add();
186
				break;
187
		}
188
		// 	Affichage des exceptions et erreurs générées par les services
189
		echo GestionnaireException::getExceptions();
190
	}
191
 
192
	/**
193
	 * Execute a GET request. A GET request fetches a list of resource when no resource name is given, a list of element
194
	 * when a resource name is given, or a resource element when a resource and resource unique identifier are given. It does not change the
195
	 * database contents.
196
	 */
197
	private function get() {
198
		Debug::printr($this);
199
		$Service = new $this->service(self::$config);
200
		if (method_exists($Service, self::METHODE_GET)) {
201
			$methodeGet = self::METHODE_GET;
202
			$Service->$methodeGet($this->ressources, $this->parametres);
203
		} else {
204
			$e = "La classe '{$this->service}' ne contient pas de méthode '".self::METHODE_GET."'.";
205
			trigger_error($e, E_USER_ERROR);
206
		}
207
	}
208
 
209
	private function post() {
210
	   	$pairs = array();
211
		// Récupération des paramètres passés dans le contenu de la requête HTTP (= POST)
212
	   	if ($this->requestData) {
213
			$pairs = $this->parserDonneesRequete();
214
		}
215
 
216
		// Ajout des informations concernant l'upload de fichier passées dans la variable $_FILE
217
		if(isset($_FILES)) {
218
			foreach ($_FILES as $v) {
219
				$pairs[$v['name']] = $v;
220
			}
221
 
222
			// Ne pas effacer cette ligne ! Elle est indispensable pour les services du Carnet en ligne
223
			// qui n'utilisent que le tableau pairs dans les posts
224
			$pairs = array_merge($pairs, $_POST);
225
		}
226
 
227
		// Gestion du contenu du post
228
		if(isset($_POST))
229
		{
230
			// Safari ne sait pas envoyer des DELETE avec gwt...
231
			// Nous utilisons le parametre "action" passé dans le POST qui doit contenir DELETE pour lancer la supression
232
			if ($pairs['action'] == 'DELETE') {
233
				$this->delete();
234
				return;
235
			}
236
 
237
			if (count($pairs) != 0) {
238
				if ($this->uid) { // get a resource element
239
					$resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';
240
					$resource_class = $this->ressource;
241
					if (file_exists($resource_file)) {
242
						include_once $resource_file;
243
						if (class_exists($resource_class)) {
244
							$service = new $resource_class(self::$config);
245
							if (method_exists($service,'updateElement')) { // Update element
246
								// TODO : a voir le retour ...
247
								if ($service->updateElement($this->uid, $pairs)) {
248
									$this->envoyerEnteteHttp(201);// Created
249
								}
250
							}
251
						}
252
					}
253
				} else { // get all elements of a ressource
254
					$this->add($pairs);
255
				}
256
			} else {
257
				$this->envoyerEnteteHttp(411);// Length required
258
			}
259
		}
260
	}
261
 
262
	private function delete() {
263
		$resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';
264
		$resource_class = $this->ressource;
265
		if (file_exists($resource_file)) {
266
			include_once $resource_file;
267
			if (class_exists($resource_class)) {
268
				$service = new $resource_class(self::$config);
269
				if ($this->uid) { // get a resource element
270
		 			if (method_exists($service, 'deleteElement')) { // Delete element
271
						if ($service->deleteElement($this->uid)) {
272
							$this->envoyerEnteteHttp(204);// No Content
273
						}
274
	 				}
275
				}
276
			}
277
		}
278
	}
279
 
280
	private function add($pairs = null) {
281
		if (is_null($pairs)) {
282
			$pairs = array();
283
			// Récupération des paramètres passés dans le contenu de la requête HTTP (= POST)
284
			// FIXME : vérifier que l'on récupère bien les données passées par PUT
285
		   	if ($this->requestData) {
286
				$pairs = $this->parserDonneesRequete();
287
			}
288
		}
289
 
290
		if (count($pairs) != 0) {
291
			$resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';
292
			$resource_class = $this->ressource;
293
			if (file_exists($resource_file)) {
294
				include_once $resource_file;
295
				if (class_exists($resource_class)) {
296
					$service = new $resource_class(self::$config);
297
					if (method_exists($service,'createElement')) { // Create a new element
298
						if ($service->createElement($pairs)) {
299
							$this->envoyerEnteteHttp(201);// Created
300
						}
301
					}
302
				}
303
			}
304
		} else {
305
			$this->envoyerEnteteHttp(411);// Length required
306
		}
307
	}
308
 
309
	/**
310
	 * Parse les données de la requête HTTP.
311
	 * @return str[] Array of name value pairs
312
	 */
313
	private function parserDonneesRequete() {
314
		$values = array();
315
		$pairs = explode('&', $this->requeteDonnees);
316
		foreach ($pairs as $pair) {
317
			$parts = explode('=', $pair);
318
			if (isset($parts[0]) && isset($parts[1])) {
319
				$parts[1] = rtrim(urldecode($parts[1]));
320
				$values[$parts[0]] = $parts[1];
321
			}
322
		}
323
		return $values;
324
	}
325
 
326
	/**
327
	 * Envoyer un entête HTTP.
328
	 * @param int $code entier indiquant le code de l'entête http à envoyer
329
	 */
330
	public static function envoyerEnteteHttp($code) {
331
		if (isset(self::$http10[$code])) {
332
			$txt = self::$http[$code];
333
			header("HTTP/1.0 $code $txt");
334
		}
335
	}
336
}
337
?>