Subversion Repositories eFlore/Applications.del

Rev

Rev 1390 | Rev 1430 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1390 raphael 1
<?php
2
/**
3
 * @author		Raphaël Droz <raphael@tela-botanica.org>
4
 * @copyright	Copyright (c) 2013, Tela Botanica (accueil@tela-botanica.org)
5
 * @license	http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
6
 * @license	http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
7
 * @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Images
1422 raphael 8
 *
9
 * Backend pour PictoFlora (del.html#page_recherche_images)
10
 *
11
 *
12
 * == Notes ==
13
 *
14
 * tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages())
15
 * Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe).
16
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
17
 *
18
 * protocole: il affecte l'affichage des information, mais le JSON contient déjà
19
 * l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique.
20
 * Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné".
21
 * Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY.
22
 * (cf requestFilterParams())
23
 *
24
 * Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs
25
 * utilsées ensuite pour l'ORDER BY. La situation à base de del_image_stat
26
 * est déjà bien meilleur sans être pour autant optimale. cf commentaire de sqlAddConstraint()
27
 *
28
 *
29
 * Tags:
30
 * Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces,
31
 * implod()ed par des AND (tous les mots doivent matcher).
32
 * Et le test effectué doit matcher sur:
33
 * %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)%
34
 *
35
 * Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression),
36
 * Et le test effectué doit matcher sur:
37
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
38
 *
39
 * Par défaut les tags sont comma-separated (OU logique).
40
 * Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur
41
 * des tags séparés par des espaces recherche
42
 *
43
 * TODO:
44
 * -affiner la gestion de passage de mots-clefs dans le masque général.
45
 * - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image
46
 * plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN)
47
 * (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/)
48
 * - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission
49
 * (cf VIEW del_image)
50
 * - réorganiser les méthodes statiques parmis Observation, ListeObservations2 et ListImages2
51
 * - *peut-être*: passer requestFilterParams() en méthode de classe
52
 *
1390 raphael 53
 */
54
 
55
require_once(dirname(__FILE__) . '/../observations/ListeObservations2.php');
56
require_once(dirname(__FILE__) . '/../observations/Observation.php');
57
restore_error_handler();
58
restore_exception_handler();
59
error_reporting(E_ALL);
60
 
1422 raphael 61
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc
62
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop
63
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
64
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
65
 
1390 raphael 66
class ListeImages2 {
67
 
68
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
69
 
1422 raphael 70
	static $tri_possible = array('date_observation', 'votes', 'tags');
1390 raphael 71
 
1422 raphael 72
	// en plus de ceux dans ListeObservations2
73
	static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
74
 
75
	static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
76
								   'tri' => 'date_transmission', 'ordre' => 'desc',
77
								   // spécifiques à PictoFlora:
78
								   'format' => 'XL');
79
 
80
	static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes))
81
 
82
	static $mappings = array(
83
		'observations' => array( // v_del_image
84
			"id_observation" => 1,
85
			"date_observation" => 1,
86
			"date_transmission" => 1,
87
			"famille" => "determination.famille",
88
			"nom_sel" => "determination.ns",
89
			"nom_sel_nn" => "determination.nn",
90
			"nom_referentiel" => "determination.referentiel",
91
			"nt" => "determination.nt",
92
			"ce_zone_geo" => "id_zone_geo",
93
			"zone_geo" => 1,
94
			"lieudit" => 1,
95
			"station" => 1,
96
			"milieu" => 1,
97
			"mots_cles_texte" => "mots_cles_texte",
98
			"commentaire" => 1,
99
			"ce_utilisateur" => "auteur.id",
100
			"nom_utilisateur" => "auteur.nom",
101
			"prenom_utilisateur" => "auteur.prenom",
102
		),
103
		'images' => array( // v_del_image
104
			'id_image' => 1,
105
			// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte
106
			// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex)
107
			'i_mots_cles_texte' => 1
108
		));
109
 
110
 
1390 raphael 111
	public function __construct(Conteneur $conteneur = null) {
112
		$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
113
		$this->conteneur->chargerConfiguration('config_images.ini');
114
		$this->gestionBdd = $conteneur->getGestionBdd();
115
		$this->bdd = $this->gestionBdd->getBdd();
116
	}
117
 
118
	public function consulter($ressources, $parametres) {
1422 raphael 119
		/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas
120
		   Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
121
		   Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
122
		   Le propriétaire d'une obs et de l'image associée est *toujours* le même. */
123
		array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
124
		array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
125
		// pour les votes, les mappings de "Observation" nous suffisent
126
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
1390 raphael 127
 
1422 raphael 128
		// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
129
		// de `ce_image` dans ces tables jointes.
130
		// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
131
		$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
132
 
133
 
1390 raphael 134
		$db = $this->bdd;
135
 
1422 raphael 136
		// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
137
		// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs.
138
		// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
139
		// même si ceux-ci sont improbables (pas d'<input> pour cela).
140
		$params_ip = ListeObservations2::requestFilterParams($parametres,
141
														  array_diff(ListeObservations2::$parametres_autorises,
142
																	 array('masque.type')),
143
														  $this->conteneur);
144
 
145
		// Cette variable est utile au filtrage des tags
146
		// Elle peut-être redéfinie en interne par sqlAddMasqueConstraint() ci-dessous
147
		// lorsque celui-ci transforme le masque général en masque par champ
148
		// et ré-appelle self::requestFilterParams()
149
		$parametres['tag_explode_char'] = ','; // " " (whitespace) ou "," ou NULL
150
		// tag_explode_semantic est lui utilisé lors de l'ajout des contraintes sur tags self::sqlAddConstraint()
151
		$parametres['tag_explode_semantic'] = 'OR'; // "AND" ou "OR" (si 'tag_explode_char' NOT NULL)
1390 raphael 152
		// notre propre filtrage sur l'INPUT
1422 raphael 153
		$params_pf = self::requestFilterParams($parametres,
154
											   array_merge(ListeObservations2::$parametres_autorises,
155
														   self::$parametres_autorises));
1390 raphael 156
 
157
 
1422 raphael 158
		$params = array_merge(ListeObservations2::$default_params, // paramètre par défaut Identiplante
159
							  self::$default_params, // paramètres par défaut PictoFlora
160
							  $params_ip, // les paramètres passés, traités par Identiplante
161
							  $params_pf); // les paramètres passés, traités par PictoFlora
1390 raphael 162
 
1422 raphael 163
		// XXX: temp tweak
164
		/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'),
165
		   "%09d", $params['format']));*/
166
 
167
		// création des contraintes (génériques, de ListeObservations2)
1390 raphael 168
		ListeObservations2::sqlAddConstraint($params, $db, $req, $this->conteneur);
1422 raphael 169
		// création des contraintes spécifiques (sur les tags essentiellement)
170
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
171
		// création des contraintes spécifiques impliquées par le masque général
172
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
173
		// l'ORDER BY s'avére complexe
174
		self::sqlOrderBy($params, $db, $req);
1390 raphael 175
 
176
		// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
1422 raphael 177
		// $idobs_tab = ListeObservations2::getIdObs($params, $req, $db);
178
		$idobs_tab = self::getIdImages($params, $req, $db);
1390 raphael 179
 
1422 raphael 180
		// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats dans la table del_obs_images
181
		if(!$idobs_tab) {
182
			header('HTTP/1.0 404 Not Found');
183
			// don't die (phpunit)
184
			throw(new Exception());
1390 raphael 185
		}
186
 
187
 
1422 raphael 188
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
189
		$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
190
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
1390 raphael 191
 
1422 raphael 192
		$liaisons = self::chargerImages($db, $idobs);
193
 
194
		/*
195
		   // Q&D
196
		   $images = array();
197
		   $o = new Observation($this->conteneur);
198
		   foreach($idobs as $i) {
199
		   $images[$i] = $o->consulter(array($i), array('justthrow' => 1));
200
		   }
201
		*/
202
		list($images, $images_keyed_by_id_image) = ListeObservations2::reformateImagesDoubleIndex(
203
			$liaisons,
204
			$this->conteneur->getParametre('url_images'),
205
			$params['format']);
206
 
207
 
208
		// on charge les votes pour ces images et pour *tous* les protocoles
209
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
210
 
211
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
212
		// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
213
		// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
214
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
215
		// c'est encore possible.
216
		if($votes) Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
217
 
1390 raphael 218
		$resultat = new ResultatService();
219
		$resultat->corps = array('entete' => ListeObservations2::makeJSONHeader($total, $params, Config::get('url_service')),
220
								 'resultats' => $images);
221
		return $resultat;
1422 raphael 222
	}
1390 raphael 223
 
1422 raphael 224
	/**
225
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
226
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
227
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
228
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
229
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
230
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
231
	 */
232
	static function sqlOrderBy($p, $db, &$req) {
233
		// parmi self::$tri_possible
234
		if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
235
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'];
236
			return;
237
		}
238
 
239
		if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
240
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
241
			return;
242
		}
243
 
244
		// tri == 'date_transmission'
245
		$req['orderby'] = 'date_transmission ' . $p['ordre'];
246
		// avant cel:r1860, date_transmission pouvait être NULL
247
		// or nous voulons de la consistence (notamment pour phpunit)
248
		$req['orderby'] .= ', id_observation ' . $p['ordre'];
1390 raphael 249
	}
250
 
1422 raphael 251
	/*
252
	 * in $p: un tableau de paramètres, dont:
253
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
254
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
255
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
256
	 *
257
	 * in/ou: $req: un tableau de structure de requête MySQL
258
	 *
259
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
260
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
261
	 * Soit directement $this->consulter() si des masque.tag* sont passés
262
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
263
	 * Soit via sqlAddMasqueConstraint():
264
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
265
	 * équivalent à:
266
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
267
	 *
268
	 */
269
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
270
		// TODO implement dans ListeObservations2 ?
271
		if(!empty($p['masque.milieu'])) {
272
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
273
		}
274
 
275
 
276
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
277
		   celui-ci indique sur quels votes porte l'AVG.
278
		   (c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
279
		/* TODO: perf problème:
280
		   1) SQL_CALC_FOUND_ROWS: fixable en:
281
		   	- dissociant le comptage de la récup d'id + javascript async
282
			- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
283
				(paramètre booléen "with-total" par exemple)
284
		   2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
285
		   	JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
286
			Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
287
			jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
288
		   3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
289
		   	(cel_images/cel_obs_images/cel_obs/del_image_stat)
290
			Cependant c'est à l'optimiseur de définir son ordre préféré. */
291
		if($p['tri'] == 'votes') {
292
			// $p['protocole'] *est* défini (cf requestFilterParams())
293
			// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
294
			if($p['ordre'] == 'desc') {
295
				// pas de group by nécessaire pour cette jointure
296
				// PRIMARY KEY (`ce_image`, `ce_protocole`)
297
				$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
298
										 ' ON vdi.id_image = dis.ce_image'.
299
										 ' AND dis.ce_protocole = %d',
300
										 $p['protocole']);
301
			} else {
302
				$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
303
										 ' ON vdi.id_image = dis.ce_image'.
304
										 ' AND dis.ce_protocole = %d',
305
										 $p['protocole']);
306
				// nécessaire (dup ce_image dans del_image_stat)
307
				$req['groupby'][] = 'vdi.id_observation';
308
			}
309
		}
310
 
311
		if($p['tri'] == 'tags') {
312
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
313
									 ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
314
			// nécessaire (dup ce_image dans del_image_stat)
315
			$req['groupby'][] = 'vdi.id_observation';
316
		}
317
 
318
 
319
		// TODO: support du "ET", "OU", "(", ")"
320
		// http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html
321
		// http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/
322
		if(!empty($p['masque.tag_cel'])) {
323
			if($p['tag_explode_semantic'] == 'AND') {
324
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
325
				// et auquel cas laisser au client le choix du couteux "%" ?
326
				$tags = $p['masque.tag_cel'];
327
				array_walk($tags, create_function('&$val, $k, $db',
328
												  '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
329
 																  $db->proteger("%".$val."%"));'),
330
						   $db);
331
				$req['where'][] = implode(' AND ', $tags);
332
			}
333
			else { // OR assumed
334
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.mots_cles_texte) REGEXP %s",
335
										  $db->proteger(implode('|', $p['masque.tag_cel'])));
336
			}
337
		}
338
 
339
 
340
		// XXX: utiliser tag plutôt que tag_normalise ?
341
		if(!empty($p['masque.tag_pictoflora'])) {
342
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
343
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
344
			   $req['where'][] = 'dit.actif = 1'; */
345
 
346
			if($p['tag_explode_semantic'] == 'AND') {
347
				// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
348
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
349
				sort($p['masque.tag_pictoflora']);
350
				$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
351
										  " GROUP BY ce_image".
352
										  " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
353
										  $db->proteger(implode('.*', $p['masque.tag_pictoflora'])));
354
			}
355
			else { // OR assumed
356
				$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
357
										  " GROUP BY ce_image".
358
										  " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
359
										  $db->proteger(implode('|', $p['masque.tag_pictoflora'])));
360
			}
361
		}
362
 
363
	}
364
 
365
	static function getIdImages($p, $req, $db) {
366
		return $db->recupererTous(sprintf(
367
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
368
			' FROM v_del_image vdi'.
369
			' %s' . // LEFT JOIN if any
370
			' WHERE %s'. // where-clause ou TRUE
371
			' %s'. // group-by
372
			' ORDER BY %s'.
373
			' LIMIT %d, %d -- %s',
374
 
375
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
376
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
377
 
378
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
379
 
380
			$req['orderby'],
381
 
382
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__));
383
 
384
	}
385
 
386
	static function chargerImages($db, $idImg) {
387
		$obs_fields = Observation::sqlFieldsToAlias(self::$mappings['observations'], NULL);
388
		$image_fields = Observation::sqlFieldsToAlias(self::$mappings['images'], NULL);
389
 
390
		return $db->recupererTous(sprintf('SELECT '.
391
										  ' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
392
										  ' %1$s, %2$s FROM v_del_image '.
393
										  ' WHERE %3$s'.
394
										  ' -- %4$s',
395
										  $obs_fields, $image_fields,
396
										  sprintf('id_image IN (%s)', implode(',', $idImg)),
397
										  __FILE__ . ':' . __LINE__));
398
 
399
	}
400
 
401
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
402
	   de manière identique à la seule différence que:
403
	   1) ils sont combinés par des "OU" logiques plutôt que des "ET".
404
	   2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
405
	   	  Tous les mots-clefs doivent matcher et sont séparés par des espaces
406
		  (dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
407
	   Pour plus d'information: ListeObservations2::sqlAddMasqueConstraint() */
408
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
409
		if(!empty($p['masque'])) {
410
			$or_params = array('masque.auteur' => $p['masque'],
411
							   'masque.departement' => $p['masque'],
412
							   'masque.commune' => $p['masque'], // TODO/XXX ?
413
							   'masque.id_zone_geo' => $p['masque'],
414
 
415
							   // tous-deux remplacent masque.tag
416
							   'masque.tag_cel' => $p['masque'],
417
							   'masque.tag_pictoflora' => $p['masque'],
418
 
419
							   'masque.ns' => $p['masque'],
420
							   'masque.famille' => $p['masque'],
421
							   'masque.date' => $p['masque'],
422
							   'masque.genre' => $p['masque'],
423
							   'masque.milieu' => $p['masque'],
424
 
425
							   // tri est aussi nécessaire car affecte les contraintes de JOIN
426
							   'tri' => $p['tri'],
427
							   'ordre' => $p['ordre']);
428
 
429
			$or_masque = array_merge(
430
				ListeObservations2::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
431
				self::requestFilterParams($or_params),
432
				array('tag_explode_char' => ' ', 'tag_explode_semantic' => 'AND')); // spéciaux
433
 
434
			// pas de select, groupby & co ici
435
			$or_req = array('join' => array(), 'where' => array());
436
			ListeObservations2::sqlAddConstraint($or_masque, $db, $or_req);
437
			self::sqlAddConstraint($or_masque, $db, $or_req);
438
 
439
			if($or_req['where']) {
440
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
441
				// utile au cas ou des jointures seraient rajoutées
442
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
443
			}
444
		}
445
	}
446
 
447
	// complete & override ListeObservations2::requestFilterParams()
448
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
449
		if($parametres_autorises) { // filtrage de toute clef inconnue
450
			$params = array_intersect_key($params, array_flip($parametres_autorises));
451
		}
452
 
453
		$p = array();
454
		$p['tri'] = ListeObservations2::unsetIfInvalid($params, 'tri', self::$tri_possible);
1390 raphael 455
		$p['format'] = ListeObservations2::unsetIfInvalid($params, 'format', self::$format_image_possible);
1422 raphael 456
 
457
		// et pour identiplantes ?
458
		if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
459
 
460
		// compatibilité
461
		if(isset($params['masque.tag'])) {
462
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
463
		}
464
 
465
		/* Nous n'implod()ons pas ici, car la sémantique des éléments multiples est
466
		   variable (tag_explode_semantic) et considérée plus tard, notamment lors de la
467
		   construction du SQL (sqlAddConstraint()).
468
		   Cette sémantique peut varier selon que ces tags proviennent du formulaire de
469
		   recherche avancée ou via le masque général (et l'appel récursif à requestFilterParams()
470
		   qu'il implique) */
471
		if(isset($params['masque.tag_cel'])) {
472
			if(isset($params['tag_explode_char'])) {
473
				$x = explode($params['tag_explode_char'], $params['masque.tag_cel']);
474
				$x = array_map('trim', $x);
475
				$p['masque.tag_cel'] = array_filter($x);
476
			} else {
477
				// toujours un tableau
478
				$p['masque.tag_cel'] = array(trim($params['masque.tag_cel']));
479
			}
480
 
481
		}
482
 
483
		if(isset($params['masque.tag_pictoflora'])) {
484
			if(isset($params['tag_explode_char'])) {
485
				$x = explode($params['tag_explode_char'], $params['masque.tag_pictoflora']);
486
				$x = array_map('trim', $x);
487
				$p['masque.tag_pictoflora'] = array_filter($x);
488
			} else {
489
				// toujours un tableau
490
				$p['masque.tag_pictoflora'] = array(trim($params['masque.tag_pictoflora']));
491
			}
492
		}
493
 
494
		if($p['tri'] == 'votes' || $p['tri'] == 'tags') {
495
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
496
			if(!isset($params['protocole']) || !is_numeric($params['protocole']))
497
				$p['protocole'] = self::$default_proto;
498
			else
499
				$p['protocole'] = intval($params['protocole']);
500
		}
501
 
502
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
1390 raphael 503
	}
1422 raphael 504
 
505
 
506
 
507
	// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
508
	static function _update_statistics($db) {
509
		$db->requeter("TRUNCATE TABLE del_image_stat");
510
		$db->requeter(<<<EOF
511
INSERT INTO `del_image_stat` (
512
       SELECT id_image, divo.ce_protocole, divo.moyenne, dit.ctags
513
       FROM `BASECEL`.`cel_images` ci
514
       LEFT JOIN
515
            ( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne FROM del_image_vote
516
              GROUP BY ce_image, ce_protocole ) AS divo
517
       ON ci.id_image = divo.ce_image
518
       LEFT JOIN
519
            ( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag
520
              GROUP BY ce_image ) AS dit
521
       ON ci.id_image = dit.ce_image )
522
EOF
523
		);
524
	}
1390 raphael 525
}