Subversion Repositories eFlore/Applications.del

Rev

Rev 1684 | Rev 1705 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1684 Rev 1690
1
<?php
1
<?php
2
/**
2
/**
3
 * @author		Raphaël Droz <raphael@tela-botanica.org>
3
 * @author		Raphaël Droz <raphael@tela-botanica.org>
4
 * @copyright	Copyright (c) 2013, Tela Botanica (accueil@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
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
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
7
 * @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Images
8
 * @see http://www.tela-botanica.org/wikini/identiplante/wakka.php?wiki=IdentiPlante_PictoFlora_MoteurRecherche
8
 * @see http://www.tela-botanica.org/wikini/identiplante/wakka.php?wiki=IdentiPlante_PictoFlora_MoteurRecherche
9
 *
9
 *
10
 * Backend pour PictoFlora (del.html#page_recherche_images)
10
 * Backend pour PictoFlora (del.html#page_recherche_images)
11
 *
11
 *
12
 *
12
 *
13
 * == Notes ==
13
 * == Notes ==
14
 *
14
 *
15
 * tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages())
15
 * tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages())
16
 * Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe).
16
 * Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe).
17
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
17
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
18
 *
18
 *
19
 * protocole: il affecte l'affichage des information, mais le JSON contient déjà
19
 * protocole: il affecte l'affichage des information, mais le JSON contient déjà
20
 * l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique.
20
 * l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique.
21
 * Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné".
21
 * Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné".
22
 * Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY.
22
 * Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY.
23
 * (cf requestFilterParams())
23
 * (cf requestFilterParams())
24
 *
24
 *
25
 * Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs
25
 * Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs
26
 * utilisées ensuite pour l'ORDER BY. La situation à base de del_image_stat
26
 * utilisées ensuite pour l'ORDER BY. La situation à base de del_image_stat
27
 * est déjà bien meilleure sans être pour autant optimale. cf commentaire de sqlAddConstraint()
27
 * est déjà bien meilleure sans être pour autant optimale. cf commentaire de sqlAddConstraint()
28
 *
28
 *
29
 *
29
 *
30
 * Tags:
30
 * Tags:
31
 * Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces,
31
 * Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces,
32
 * implod()ed par des AND (tous les mots doivent matcher).
32
 * implod()ed par des AND (tous les mots doivent matcher).
33
 * Et le test effectué doit matcher sur:
33
 * Et le test effectué doit matcher sur:
34
 * %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)%
34
 * %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)%
35
 *
35
 *
36
 * Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression),
36
 * Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression),
37
 * Et le test effectué doit matcher sur:
37
 * Et le test effectué doit matcher sur:
38
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
38
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
39
 *
39
 *
40
 * Par défaut les tags sont comma-separated (OU logique).
40
 * Par défaut les tags sont comma-separated (OU logique).
41
 * Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur
41
 * Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur
42
 * des tags séparés par des espaces recherche
42
 * des tags séparés par des espaces recherche
43
 *
43
 *
44
 * TODO:
44
 * TODO:
45
 * -affiner la gestion de passage de mots-clefs dans le masque général.
45
 * -affiner la gestion de passage de mots-clefs dans le masque général.
46
 * - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image
46
 * - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image
47
 * plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN)
47
 * plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN)
48
 * (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/)
48
 * (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/)
49
 * - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission
49
 * - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission
50
 * (cf VIEW del_image)
50
 * (cf VIEW del_image)
51
 * - poursuivre la réorganisation des méthodes statiques parmis Observation, ListeObservations et ListeImages2
51
 * - poursuivre la réorganisation des méthodes statiques parmis Observation, ListeObservations et ListeImages2
52
 * - *peut-être*: passer requestFilterParams() en méthode de classe
52
 * - *peut-être*: passer requestFilterParams() en méthode de classe
53
 *
53
 *
54
 *
54
 *
55
 * MySQL sux:
55
 * MySQL sux:
56
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1 LIMIT 1);
56
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1 LIMIT 1);
57
 *	MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
57
 *	MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
58
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
58
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
59
 *	PRIMARY
59
 *	PRIMARY
60
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
60
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
61
 *	DEPENDENT SUBQUERY ... ... ... mwarf !
61
 *	DEPENDENT SUBQUERY ... ... ... mwarf !
62
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1);
62
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1);
63
 *	5.5: MATERIALIZED		del_image_tag	ALL				ce_image NULL NULL NULL 38276 Using where
63
 *	5.5: MATERIALIZED		del_image_tag	ALL				ce_image NULL NULL NULL 38276 Using where
64
 *	5.1: DEPENDENT SUBQUERY	del_image_tag	index_subquery	ce_image ce_image 8 func 1 Using where
64
 *	5.1: DEPENDENT SUBQUERY	del_image_tag	index_subquery	ce_image ce_image 8 func 1 Using where
65
 * FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
65
 * FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
66
 *
66
 *
67
 */
67
 */
68
 
68
 
69
require_once(dirname(__FILE__) . '/../DelTk.php');
69
require_once(dirname(__FILE__) . '/../DelTk.php');
70
require_once(dirname(__FILE__) . '/../observations/Observation.php');
70
require_once(dirname(__FILE__) . '/../observations/Observation.php');
71
restore_error_handler();
71
restore_error_handler();
72
restore_exception_handler();
72
restore_exception_handler();
73
error_reporting(E_ALL);
73
error_reporting(E_ALL);
74
 
74
 
75
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc
75
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc
76
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop
76
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop
77
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
77
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
78
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
78
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
79
 
79
 
80
class ListeImages {
80
class ListeImages {
81
 
81
 
82
	// TODO: PHP-x.y, ces variables devrait être des "const"
82
	// TODO: PHP-x.y, ces variables devrait être des "const"
83
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
83
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
84
 
84
 
85
	static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags', 'points');
85
	static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags', 'points');
86
 
86
 
87
	// en plus de ceux dans DelTk
87
	// en plus de ceux dans DelTk
88
	static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
88
	static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
89
 
89
 
90
	static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
90
	static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
91
		'tri' => 'date_transmission', 'ordre' => 'desc',
91
		'tri' => 'date_transmission', 'ordre' => 'desc',
92
		// spécifiques à PictoFlora:
92
		// spécifiques à PictoFlora:
93
		'format' => 'XL');
93
		'format' => 'XL');
94
 
94
 
95
	static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes|points))
95
	static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes|points))
96
 
96
 
97
	static $mappings = array(
97
	static $mappings = array(
98
		'observations' => array( // v_del_image
98
		'observations' => array( // v_del_image
99
			"id_observation" => 1,
99
			"id_observation" => 1,
100
			"date_observation" => 1,
100
			"date_observation" => 1,
101
			"date_transmission" => 1,
101
			"date_transmission" => 1,
102
			"famille" => "determination.famille",
102
			"famille" => "determination.famille",
103
			"nom_sel" => "determination.ns",
103
			"nom_sel" => "determination.ns",
104
			"nom_sel_nn" => "determination.nn",
104
			"nom_sel_nn" => "determination.nn",
105
			"nom_referentiel" => "determination.referentiel",
105
			"nom_referentiel" => "determination.referentiel",
106
			"nt" => "determination.nt",
106
			"nt" => "determination.nt",
107
			"ce_zone_geo" => "id_zone_geo",
107
			"ce_zone_geo" => "id_zone_geo",
108
			"zone_geo" => 1,
108
			"zone_geo" => 1,
109
			"lieudit" => 1,
109
			"lieudit" => 1,
110
			"station" => 1,
110
			"station" => 1,
111
			"milieu" => 1,
111
			"milieu" => 1,
112
			"mots_cles_texte" => "mots_cles_texte",
112
			"mots_cles_texte" => "mots_cles_texte",
113
			"commentaire" => 1,
113
			"commentaire" => 1,
114
			"ce_utilisateur" => "auteur.id",
114
			"ce_utilisateur" => "auteur.id",
115
			"nom_utilisateur" => "auteur.nom",
115
			"nom_utilisateur" => "auteur.nom",
116
			"prenom_utilisateur" => "auteur.prenom",
116
			"prenom_utilisateur" => "auteur.prenom",
117
			"courriel_utilisateur" => "auteur.courriel",),
117
			"courriel_utilisateur" => "auteur.courriel",),
118
		'images' => array( // v_del_image
118
		'images' => array( // v_del_image
119
			'id_image' => 1,
119
			'id_image' => 1,
120
			// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte
120
			// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte
121
			// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex)
121
			// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex)
122
			'i_mots_cles_texte' => 1)
122
			'i_mots_cles_texte' => 1)
123
	);
123
	);
124
 
124
 
125
	public function __construct(Conteneur $conteneur = null) {
125
	public function __construct(Conteneur $conteneur = null) {
126
		$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
126
		$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
127
		$this->conteneur->chargerConfiguration('config_images.ini');
127
		$this->conteneur->chargerConfiguration('config_images.ini');
128
		$this->bdd = $conteneur->getGestionBdd()->getBdd();
128
		$this->bdd = $conteneur->getGestionBdd()->getBdd();
129
	}
129
	}
130
 
130
 
131
	public function consulter($ressources, $parametres) {
131
	public function consulter($ressources, $parametres) {
132
		/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas
132
		/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas
133
		Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
133
		Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
134
		Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
134
		Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
135
		Le propriétaire d'une obs et de l'image associée est *toujours* le même. */
135
		Le propriétaire d'une obs et de l'image associée est *toujours* le même. */
136
		array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
136
		array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
137
		array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
137
		array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
138
		// pour les votes, les mappings de "Observation" nous suffisent
138
		// pour les votes, les mappings de "Observation" nous suffisent
139
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
139
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
140
 
140
 
141
		// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
141
		// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
142
		// de `ce_image` dans ces tables jointes.
142
		// de `ce_image` dans ces tables jointes.
143
		// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
143
		// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
144
		$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
144
		$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
145
 
145
 
146
		$db = $this->bdd;
146
		$db = $this->bdd;
147
 
147
 
148
		// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
148
		// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
149
		// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs.
149
		// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs.
150
		// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
150
		// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
151
		// même si ceux-ci sont improbables (pas d'<input> pour cela).
151
		// même si ceux-ci sont improbables (pas d'<input> pour cela).
152
		$params_ip = DelTk::requestFilterParams($parametres,
152
		$params_ip = DelTk::requestFilterParams($parametres,
153
			array_diff(DelTk::$parametres_autorises, array('masque.type')),
153
			array_diff(DelTk::$parametres_autorises, array('masque.type')),
154
			$this->conteneur);
154
			$this->conteneur);
155
 
155
 
156
		// notre propre filtrage sur l'INPUT
156
		// notre propre filtrage sur l'INPUT
157
		$params_pf = self::requestFilterParams($parametres,
157
		$params_pf = self::requestFilterParams($parametres,
158
			array_merge(DelTk::$parametres_autorises, self::$parametres_autorises));
158
			array_merge(DelTk::$parametres_autorises, self::$parametres_autorises));
159
 
159
 
160
		/* filtrage des tags + sémantique des valeurs multiples:
160
		/* filtrage des tags + sémantique des valeurs multiples:
161
		Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
161
		Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
162
		postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
162
		postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
163
		$params_pf['masque.tag_cel'] = DelTk::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
163
		$params_pf['masque.tag_cel'] = DelTk::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
164
 
164
 
165
		if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) {
165
		if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) {
166
			$parametres['masque.tag_pictoflora'] = $parametres['masque.tag'];
166
			$parametres['masque.tag_pictoflora'] = $parametres['masque.tag'];
167
		}
167
		}
168
		$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
168
		$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
169
 
169
 
170
		$params = array_merge(
170
		$params = array_merge(
171
			DelTk::$default_params, // paramètre par défaut Identiplante
171
			DelTk::$default_params, // paramètre par défaut Identiplante
172
			self::$default_params, // paramètres par défaut PictoFlora
172
			self::$default_params, // paramètres par défaut PictoFlora
173
			$params_ip, // les paramètres passés, traités par Identiplante
173
			$params_ip, // les paramètres passés, traités par Identiplante
174
			$params_pf); // les paramètres passés, traités par PictoFlora
174
			$params_pf); // les paramètres passés, traités par PictoFlora
175
 
175
 
176
		if (isset($parametres['format'])) {
176
		if (isset($parametres['format'])) {
177
			$params['format'] = $parametres['format'];
177
			$params['format'] = $parametres['format'];
178
		}
178
		}
179
 
179
 
180
		// création des contraintes (génériques de DelTk)
180
		// création des contraintes (génériques de DelTk)
181
		DelTk::sqlAddConstraint($params, $db, $req);
181
		DelTk::sqlAddConstraint($params, $db, $req);
182
		// création des contraintes spécifiques (sur les tags essentiellement)
182
		// création des contraintes spécifiques (sur les tags essentiellement)
183
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
183
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
184
		// création des contraintes spécifiques impliquées par le masque général
184
		// création des contraintes spécifiques impliquées par le masque général
185
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
185
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
186
		// l'ORDER BY s'avére complexe
186
		// l'ORDER BY s'avére complexe
187
		self::sqlOrderBy($params, $db, $req);
187
		self::sqlOrderBy($params, $db, $req);
188
 
188
 
189
		// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
189
		// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
190
		// $idobs_tab = ListeObservations::getIdObs($params, $req, $db);
190
		// $idobs_tab = ListeObservations::getIdObs($params, $req, $db);
191
		$idobs_tab = self::getIdImages($params, $req, $db);
191
		$idobs_tab = self::getIdImages($params, $req, $db);
192
 
192
 
193
		// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats dans la table del_obs_images
193
		// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats dans la table del_obs_images
194
		if (!$idobs_tab) {
194
		if (!$idobs_tab) {
195
			$resultat = new ResultatService();
195
			$resultat = new ResultatService();
196
			$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
196
			$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
197
				'resultats' => array());
197
				'resultats' => array());
198
			return $resultat;
198
			return $resultat;
199
		}
199
		}
200
 
200
 
201
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
201
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
202
		$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
202
		$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
203
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
203
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
204
 
204
 
205
		$liaisons = self::chargerImages($db, $idobs);
205
		$liaisons = self::chargerImages($db, $idobs);
206
 
206
 
207
		list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
207
		list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
208
			$liaisons,
208
			$liaisons,
209
			$this->conteneur->getParametre('images.url_images'),
209
			$this->conteneur->getParametre('images.url_images'),
210
			$params['format']);
210
			$params['format']);
211
 
211
 
212
		// on charge les votes pour ces images et pour *tous* les protocoles
212
		// on charge les votes pour ces images et pour *tous* les protocoles
213
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
213
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
214
 
214
 
215
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
215
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
216
		// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
216
		// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
217
		// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
217
		// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
218
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
218
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
219
		// c'est encore possible.
219
		// c'est encore possible.
220
		if ($votes) {
220
		if ($votes) {
221
			Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
221
			Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
222
		}
222
		}
223
 
223
 
224
		// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
224
		// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
225
		// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
225
		// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
226
		$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
226
		$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
227
			'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
227
			'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
228
		$resultat = new ResultatService();
228
		$resultat = new ResultatService();
229
		$resultat->corps = array(
229
		$resultat->corps = array(
230
			'entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
230
			'entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
231
			'resultats' => $images);
231
			'resultats' => $images);
232
		return $resultat;
232
		return $resultat;
233
	}
233
	}
-
 
234
 
-
 
235
 
-
 
236
	/**
-
 
237
	 * Supprime une image directement dans le CEL en faisant un appel à un web service du CEL.
-
 
238
	 * Utilisé uniquement par les admins.
-
 
239
	 *
-
 
240
	 * @param array		$ressources tableau des informations contenues dans l'url après le nom du service
-
 
241
	 * @param array		$parametres contenu du post
-
 
242
	 * @return mixed	Chaine "OK" (en majuscule) en cas de succès, booléen "false" en cas d'échec
234
 
243
	 */
235
	public function supprimer($ressources) {
-
 
236
		$idImage = $ressources[0];
244
	public function supprimer($ressources) {
-
 
245
		$controlAcces = $this->conteneur->getControleAcces();
-
 
246
		$controlAcces->controlerIpAutorisees();
237
		$controlAcces = $this->conteneur->getControleAcces();
-
 
238
 
-
 
239
		$retour = false;
-
 
240
		if ($controlAcces->controlerIpAutorisees()) {
247
		$controlAcces->etreUtilisateurAvecDroitAdmin();
-
 
248
 
241
			if ($controlAcces->etreUtilisateurAvecDroitAdmin()) {
249
		$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage');
242
				$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage');
250
		$idImage = $ressources[0];
243
				$url = $urlServiceBase.$idImage;
251
		$url = $urlServiceBase.$idImage;
244
 
252
 
245
				$clientHttp = $this->conteneur->getRestClient();
253
		$clientHttp = $this->conteneur->getRestClient();
246
				$retour = $clientHttp->supprimer($url);
254
		$retourCel = $clientHttp->supprimer($url);
247
			} else {
-
 
248
				$message = "Vous ne pouvez pas accéder à ce service car vous n'avez pas les droits d'administrateur !\n";
-
 
249
				$code = RestServeur::HTTP_CODE_ACCES_NON_AUTORISE;
255
		$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false;
250
				throw new Exception($message, $code);
-
 
251
			}
-
 
252
		}
-
 
253
		return $retour;
256
		return $retour;
254
	}
257
	}
255
 
258
 
256
	/**
259
	/**
257
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
260
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
258
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
261
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
259
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
262
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
260
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
263
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
261
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
264
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
262
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
265
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
263
	 */
266
	 */
264
	static function sqlOrderBy($p, $db, &$req) {
267
	static function sqlOrderBy($p, $db, &$req) {
265
		// parmi self::$tri_possible
268
		// parmi self::$tri_possible
266
		if ($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
269
		if ($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
267
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
270
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
268
			return;
271
			return;
269
		}
272
		}
270
 
273
 
271
		if ($p['tri'] == 'points') { // LEFT JOIN sur "dis" ci-dessous
274
		if ($p['tri'] == 'points') { // LEFT JOIN sur "dis" ci-dessous
272
			$req['orderby'] = 'dis.nb_points ' . $p['ordre'] . ', dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
275
			$req['orderby'] = 'dis.nb_points ' . $p['ordre'] . ', dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
273
			return;
276
			return;
274
		}
277
		}
275
 
278
 
276
		if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
279
		if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
277
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
280
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
278
			return;
281
			return;
279
		}
282
		}
280
 
283
 
281
		if ($p['tri'] == 'date_observation') {
284
		if ($p['tri'] == 'date_observation') {
282
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
285
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
283
			return;
286
			return;
284
		}
287
		}
285
 
288
 
286
		// tri == 'date_transmission'
289
		// tri == 'date_transmission'
287
		// avant cel:r1860, date_transmission pouvait être NULL
290
		// avant cel:r1860, date_transmission pouvait être NULL
288
		// or nous voulons de la cohérence (notamment pour phpunit)
291
		// or nous voulons de la cohérence (notamment pour phpunit)
289
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
292
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
290
	}
293
	}
291
 
294
 
292
	/*
295
	/*
293
	 * in $p: un tableau de paramètres, dont:
296
	 * in $p: un tableau de paramètres, dont:
294
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
297
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
295
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
298
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
296
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
299
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
297
	 *
300
	 *
298
	 * in/ou: $req: un tableau de structure de requête MySQL
301
	 * in/ou: $req: un tableau de structure de requête MySQL
299
	 *
302
	 *
300
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
303
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
301
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
304
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
302
	 * Soit directement $this->consulter() si des masque.tag* sont passés
305
	 * Soit directement $this->consulter() si des masque.tag* sont passés
303
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
306
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
304
	 * Soit via sqlAddMasqueConstraint():
307
	 * Soit via sqlAddMasqueConstraint():
305
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
308
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
306
	 * équivalent à:
309
	 * équivalent à:
307
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
310
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
308
	 *
311
	 *
309
	 */
312
	 */
310
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
313
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
311
		// TODO implement dans DelTk ?
314
		// TODO implement dans DelTk ?
312
		if (!empty($p['masque.milieu'])) {
315
		if (!empty($p['masque.milieu'])) {
313
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
316
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
314
		}
317
		}
315
 
318
 
316
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
319
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
317
			celui-ci indique sur quels votes porte l'AVG.
320
			celui-ci indique sur quels votes porte l'AVG.
318
			(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
321
			(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
319
		/* TODO: perf problème:
322
		/* TODO: perf problème:
320
			1) SQL_CALC_FOUND_ROWS: fixable en:
323
			1) SQL_CALC_FOUND_ROWS: fixable en:
321
				- dissociant le comptage de la récup d'id + javascript async
324
				- dissociant le comptage de la récup d'id + javascript async
322
				- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
325
				- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
323
				(paramètre booléen "with-total" par exemple)
326
				(paramètre booléen "with-total" par exemple)
324
			2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
327
			2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
325
				JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
328
				JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
326
				Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
329
				Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
327
				jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
330
				jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
328
			3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
331
			3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
329
				(cel_images/cel_obs_images/cel_obs/del_image_stat)
332
				(cel_images/cel_obs_images/cel_obs/del_image_stat)
330
				Cependant c'est à l'optimiseur de définir son ordre préféré. */
333
				Cependant c'est à l'optimiseur de définir son ordre préféré. */
331
		if ($p['tri'] == 'votes' || $p['tri'] == 'points') {
334
		if ($p['tri'] == 'votes' || $p['tri'] == 'points') {
332
			// $p['protocole'] *est* défini (cf requestFilterParams())
335
			// $p['protocole'] *est* défini (cf requestFilterParams())
333
			// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
336
			// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
334
			if($p['ordre'] == 'desc') {
337
			if($p['ordre'] == 'desc') {
335
				// pas de group by nécessaire pour cette jointure
338
				// pas de group by nécessaire pour cette jointure
336
				// PRIMARY KEY (`ce_image`, `ce_protocole`)
339
				// PRIMARY KEY (`ce_image`, `ce_protocole`)
337
				$req['join']['dis'] = sprintf('INNER JOIN del_image_stat dis'.
340
				$req['join']['dis'] = sprintf('INNER JOIN del_image_stat dis'.
338
					 ' ON vdi.id_image = dis.ce_image'.
341
					 ' ON vdi.id_image = dis.ce_image'.
339
					 ' AND dis.ce_protocole = %d',
342
					 ' AND dis.ce_protocole = %d',
340
					 $p['protocole']);
343
					 $p['protocole']);
341
			} else {
344
			} else {
342
				$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
345
				$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
343
					 ' ON vdi.id_image = dis.ce_image'.
346
					 ' ON vdi.id_image = dis.ce_image'.
344
					 ' AND dis.ce_protocole = %d',
347
					 ' AND dis.ce_protocole = %d',
345
					 $p['protocole']);
348
					 $p['protocole']);
346
				// nécessaire (dup ce_image dans del_image_stat)
349
				// nécessaire (dup ce_image dans del_image_stat)
347
				$req['groupby'][] = 'vdi.id_observation';
350
				$req['groupby'][] = 'vdi.id_observation';
348
			}
351
			}
349
		}
352
		}
350
 
353
 
351
		if ($p['tri'] == 'tags') {
354
		if ($p['tri'] == 'tags') {
352
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
355
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
353
				($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
356
				($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
354
			// nécessaire (dup ce_image dans del_image_stat)
357
			// nécessaire (dup ce_image dans del_image_stat)
355
			$req['groupby'][] = 'vdi.id_observation';
358
			$req['groupby'][] = 'vdi.id_observation';
356
		}
359
		}
357
 
360
 
358
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
361
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
359
		// sont toujours présentes; bien que parfois NULL.
362
		// sont toujours présentes; bien que parfois NULL.
360
		if ($p['masque.tag_cel']) {
363
		if ($p['masque.tag_cel']) {
361
			if (isset($p['masque.tag_cel']['AND'])) {
364
			if (isset($p['masque.tag_cel']['AND'])) {
362
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
365
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
363
				// et auquel cas laisser au client le choix du couteux "%" ?
366
				// et auquel cas laisser au client le choix du couteux "%" ?
364
				$tags = $p['masque.tag_cel']['AND'];
367
				$tags = $p['masque.tag_cel']['AND'];
365
				array_walk($tags, create_function('&$val, $k, $db',
368
				array_walk($tags, create_function('&$val, $k, $db',
366
					'$val = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,\'\'),IFNULL(vdi.i_mots_cles_texte,\'\')) LIKE %s",
369
					'$val = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,\'\'),IFNULL(vdi.i_mots_cles_texte,\'\')) LIKE %s",
367
					$db->proteger("%".$val."%"));'),
370
					$db->proteger("%".$val."%"));'),
368
					$db);
371
					$db);
369
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
372
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
370
			} else {
373
			} else {
371
				$req['where'][] = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s",
374
				$req['where'][] = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s",
372
					$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
375
					$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
373
			}
376
			}
374
		}
377
		}
375
 
378
 
376
		if ($p['masque.tag_pictoflora']) {
379
		if ($p['masque.tag_pictoflora']) {
377
			// inutilisable pour l'instant
380
			// inutilisable pour l'instant
378
			// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
381
			// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
379
 
382
 
380
			// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
383
			// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
381
			// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
384
			// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
382
 
385
 
383
			// approche fiable mais sous-optimale
386
			// approche fiable mais sous-optimale
384
			self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
387
			self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
385
		}
388
		}
386
	}
389
	}
387
 
390
 
388
	/* approche intéressante si les deux problèmes suivants peuvent être résolu:
391
	/* approche intéressante si les deux problèmes suivants peuvent être résolu:
389
		- LEFT JOIN => dup => *gestion de multiples GROUP BY* (car in-fine un LIMIT est utilisé)
392
		- LEFT JOIN => dup => *gestion de multiples GROUP BY* (car in-fine un LIMIT est utilisé)
390
		- dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
393
		- dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
391
	static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
394
	static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
392
		// XXX: utiliser tag plutôt que tag_normalise ?
395
		// XXX: utiliser tag plutôt que tag_normalise ?
393
		$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
396
		$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
394
		$req['where'][] = 'dit.actif = 1';
397
		$req['where'][] = 'dit.actif = 1';
395
		$req['groupby'][] = 'vdi.id_image'; // TODO: nécessaire (car dup') mais risque de conflict en cas de tri (multiple GROUP BY)
398
		$req['groupby'][] = 'vdi.id_image'; // TODO: nécessaire (car dup') mais risque de conflict en cas de tri (multiple GROUP BY)
396
		// XXX: en cas de ET, possibilité du GROUP_CONCAT(), mais probablement sans grand intérêt, d'où une boucle
399
		// XXX: en cas de ET, possibilité du GROUP_CONCAT(), mais probablement sans grand intérêt, d'où une boucle
397
		if (isset($p['masque.tag_pictoflora']['AND'])) {
400
		if (isset($p['masque.tag_pictoflora']['AND'])) {
398
			// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
401
			// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
399
			// ... le LEFT-JOIN n'y semble pas adapté
402
			// ... le LEFT-JOIN n'y semble pas adapté
400
		} else {
403
		} else {
401
			$protected_tags = array();
404
			$protected_tags = array();
402
			foreach ($p['masque.tag_pictoflora']['OR'] as $tag) {
405
			foreach ($p['masque.tag_pictoflora']['OR'] as $tag) {
403
				$protected_tags[] = $db->proteger(strtolower($tag));
406
				$protected_tags[] = $db->proteger(strtolower($tag));
404
			}
407
			}
405
			$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
408
			$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
406
		}
409
		}
407
	}
410
	}
408
 
411
 
409
	// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
412
	// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
410
	static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
413
	static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
411
		// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
414
		// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
412
		// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
415
		// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
413
		// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
416
		// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
414
		// ex: REGEX "^(".
417
		// ex: REGEX "^(".
415
		// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
418
		// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
416
		// ne peuvent (ou ne devrait) comporter des meta-caractères
419
		// ne peuvent (ou ne devrait) comporter des meta-caractères
417
		// ([])?*+\\
420
		// ([])?*+\\
418
		if (isset($p['masque.tag_pictoflora']['AND'])) {
421
		if (isset($p['masque.tag_pictoflora']['AND'])) {
419
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
422
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
420
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
423
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
421
			sort($p['masque.tag_pictoflora']['AND']);
424
			sort($p['masque.tag_pictoflora']['AND']);
422
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
425
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
423
				" GROUP BY ce_image".
426
				" GROUP BY ce_image".
424
				" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
427
				" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
425
				$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
428
				$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
426
		} else {
429
		} else {
427
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
430
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
428
				" GROUP BY ce_image".
431
				" GROUP BY ce_image".
429
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
432
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
430
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
433
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
431
		}
434
		}
432
	}
435
	}
433
 
436
 
434
	// si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
437
	// si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
435
	static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
438
	static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
436
		if (isset($p['masque.tag_pictoflora']['AND'])) {
439
		if (isset($p['masque.tag_pictoflora']['AND'])) {
437
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
440
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
438
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
441
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
439
			sort($p['masque.tag_pictoflora']['AND']);
442
			sort($p['masque.tag_pictoflora']['AND']);
440
 
443
 
441
			// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
444
			// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
442
			$ids = @$db->recupererTous(sprintf(
445
			$ids = @$db->recupererTous(sprintf(
443
			"SELECT ce_image FROM del_image_tag WHERE actif = 1".
446
			"SELECT ce_image FROM del_image_tag WHERE actif = 1".
444
			" GROUP BY ce_image".
447
			" GROUP BY ce_image".
445
			" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
448
			" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
446
			$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
449
			$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
447
 
450
 
448
			// puis:
451
			// puis:
449
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
452
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
450
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
453
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
451
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
454
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
452
		} else {
455
		} else {
453
			$ids = @$db->recupererTous(sprintf(
456
			$ids = @$db->recupererTous(sprintf(
454
				"SELECT ce_image FROM del_image_tag WHERE actif = 1".
457
				"SELECT ce_image FROM del_image_tag WHERE actif = 1".
455
				" GROUP BY ce_image".
458
				" GROUP BY ce_image".
456
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
459
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
457
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
460
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
458
 
461
 
459
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
462
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
460
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
463
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
461
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
464
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
462
		}
465
		}
463
	}
466
	}
464
 
467
 
465
	static function getIdImages($p, $req, $db) {
468
	static function getIdImages($p, $req, $db) {
466
		$req = sprintf(
469
		$req = sprintf(
467
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
470
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
468
			//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug
471
			//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug
469
			' FROM v_del_image vdi'.
472
			' FROM v_del_image vdi'.
470
			' %s' . // LEFT JOIN if any
473
			' %s' . // LEFT JOIN if any
471
			' WHERE %s'. // where-clause ou TRUE
474
			' WHERE %s'. // where-clause ou TRUE
472
			' %s'. // group-by
475
			' %s'. // group-by
473
			' ORDER BY %s'.
476
			' ORDER BY %s'.
474
			' LIMIT %d, %d -- %s',
477
			' LIMIT %d, %d -- %s',
475
 
478
 
476
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
479
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
477
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
480
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
478
 
481
 
479
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
482
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
480
 
483
 
481
			$req['orderby'],
484
			$req['orderby'],
482
 
485
 
483
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
486
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
484
		return $db->recupererTous($req);
487
		return $db->recupererTous($req);
485
	}
488
	}
486
 
489
 
487
	static function chargerImages($db, $idImg) {
490
	static function chargerImages($db, $idImg) {
488
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
491
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
489
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
492
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
490
 
493
 
491
		return $db->recupererTous(sprintf('SELECT '.
494
		return $db->recupererTous(sprintf('SELECT '.
492
			' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
495
			' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
493
			' %1$s, %2$s FROM v_del_image '.
496
			' %1$s, %2$s FROM v_del_image '.
494
			' WHERE %3$s'.
497
			' WHERE %3$s'.
495
			' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN()
498
			' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN()
496
			' -- %5$s',
499
			' -- %5$s',
497
			$obs_fields, $image_fields,
500
			$obs_fields, $image_fields,
498
			sprintf('id_image IN (%s)', implode(',', $idImg)),
501
			sprintf('id_image IN (%s)', implode(',', $idImg)),
499
			sprintf('FIELD(id_image, %s)', implode(',', $idImg)),
502
			sprintf('FIELD(id_image, %s)', implode(',', $idImg)),
500
			__FILE__ . ':' . __LINE__));
503
			__FILE__ . ':' . __LINE__));
501
	}
504
	}
502
 
505
 
503
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
506
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
504
		de manière identique à la seule différence que:
507
		de manière identique à la seule différence que:
505
		1) ils sont combinés par des "OU" logiques plutôt que des "ET".
508
		1) ils sont combinés par des "OU" logiques plutôt que des "ET".
506
		2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
509
		2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
507
		Tous les mots-clefs doivent matcher et sont séparés par des espaces
510
		Tous les mots-clefs doivent matcher et sont séparés par des espaces
508
		(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
511
		(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
509
		Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
512
		Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
510
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
513
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
511
		if (!empty($p['masque'])) {
514
		if (!empty($p['masque'])) {
512
			$or_params = array('masque.auteur' => $p['masque'],
515
			$or_params = array('masque.auteur' => $p['masque'],
513
				'masque.departement' => $p['masque'],
516
				'masque.departement' => $p['masque'],
514
				'masque.commune' => $p['masque'], // TODO/XXX ?
517
				'masque.commune' => $p['masque'], // TODO/XXX ?
515
				'masque.id_zone_geo' => $p['masque'],
518
				'masque.id_zone_geo' => $p['masque'],
516
 
519
 
517
				/* tous-deux remplacent masque.tag
520
				/* tous-deux remplacent masque.tag
518
				mais sont traité séparément des requestFilterParams() */
521
				mais sont traité séparément des requestFilterParams() */
519
				// 'masque.tag_cel' => $p['masque'],
522
				// 'masque.tag_cel' => $p['masque'],
520
				// 'masque.tag_pictoflora' => $p['masque'],
523
				// 'masque.tag_pictoflora' => $p['masque'],
521
 
524
 
522
				'masque.ns' => $p['masque'],
525
				'masque.ns' => $p['masque'],
523
				'masque.famille' => $p['masque'],
526
				'masque.famille' => $p['masque'],
524
				'masque.date' => $p['masque'],
527
				'masque.date' => $p['masque'],
525
				'masque.genre' => $p['masque'],
528
				'masque.genre' => $p['masque'],
526
				'masque.milieu' => $p['masque'],
529
				'masque.milieu' => $p['masque'],
527
				'masque.tag_cel' => $p['masque'],
530
				'masque.tag_cel' => $p['masque'],
528
				'masque.tag_pictoflora' => $p['masque'],
531
				'masque.tag_pictoflora' => $p['masque'],
529
 
532
 
530
				// tri est aussi nécessaire car affecte les contraintes de JOIN
533
				// tri est aussi nécessaire car affecte les contraintes de JOIN
531
				'tri' => $p['tri'],
534
				'tri' => $p['tri'],
532
				'ordre' => $p['ordre']);
535
				'ordre' => $p['ordre']);
533
			if (array_key_exists('protocole', $p)) {
536
			if (array_key_exists('protocole', $p)) {
534
				$or_params['protocole'] = $p['protocole'];
537
				$or_params['protocole'] = $p['protocole'];
535
			}
538
			}
536
 
539
 
537
			/* Cependant les champs spécifiques ont priorité sur le masque général.
540
			/* Cependant les champs spécifiques ont priorité sur le masque général.
538
				Pour cette raison nous supprimons la génération de SQL du masque général sur les
541
				Pour cette raison nous supprimons la génération de SQL du masque général sur les
539
				champ spécifiques qui feront l'objet d'un traitement avec une valeur propre. */
542
				champ spécifiques qui feront l'objet d'un traitement avec une valeur propre. */
540
			if(isset($p['masque.auteur'])) unset($or_params['masque.auteur']);
543
			if(isset($p['masque.auteur'])) unset($or_params['masque.auteur']);
541
			if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
544
			if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
542
			if(isset($p['masque.commune'])) unset($or_params['masque.commune']);
545
			if(isset($p['masque.commune'])) unset($or_params['masque.commune']);
543
			if(isset($p['masque.id_zone_geo'])) unset($or_params['masque.id_zone_geo']);
546
			if(isset($p['masque.id_zone_geo'])) unset($or_params['masque.id_zone_geo']);
544
			if(isset($p['masque.ns'])) unset($or_params['masque.ns']);
547
			if(isset($p['masque.ns'])) unset($or_params['masque.ns']);
545
			if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
548
			if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
546
			if(isset($p['masque.date'])) unset($or_params['masque.date']);
549
			if(isset($p['masque.date'])) unset($or_params['masque.date']);
547
			if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
550
			if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
548
			if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
551
			if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
549
			if(isset($p['masque.tag_cel'])) unset($or_params['masque.tag_cel']);
552
			if(isset($p['masque.tag_cel'])) unset($or_params['masque.tag_cel']);
550
			if(isset($p['masque.tag_pictoflora'])) unset($or_params['masque.tag_pictoflora']);
553
			if(isset($p['masque.tag_pictoflora'])) unset($or_params['masque.tag_pictoflora']);
551
 
554
 
552
			$or_masque = array_merge(
555
			$or_masque = array_merge(
553
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
556
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
554
				self::requestFilterParams($or_params)
557
				self::requestFilterParams($or_params)
555
			);
558
			);
556
 
559
 
557
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
560
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
558
				postulés comme séparés par des espaces, et doivent être tous matchés. */
561
				postulés comme séparés par des espaces, et doivent être tous matchés. */
559
			if (isset($or_params['masque.tag_cel'])) {
562
			if (isset($or_params['masque.tag_cel'])) {
560
				$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
563
				$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
561
			}
564
			}
562
			if (isset($or_params['masque.tag_pictoflora'])) {
565
			if (isset($or_params['masque.tag_pictoflora'])) {
563
				$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
566
				$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
564
			}
567
			}
565
 
568
 
566
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
569
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
567
			$or_req = array('join' => array(), 'where' => array());
570
			$or_req = array('join' => array(), 'where' => array());
568
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
571
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
569
 
572
 
570
			self::sqlAddConstraint($or_masque, $db, $or_req);
573
			self::sqlAddConstraint($or_masque, $db, $or_req);
571
 
574
 
572
			if ($or_req['where']) {
575
			if ($or_req['where']) {
573
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
576
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
574
				// utile au cas ou des jointures seraient rajoutées
577
				// utile au cas ou des jointures seraient rajoutées
575
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
578
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
576
			}
579
			}
577
		}
580
		}
578
	}
581
	}
579
 
582
 
580
 
583
 
581
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
584
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
582
	// (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
585
	// (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
583
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
586
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
584
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
587
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
585
		// conserver les valeurs vides (pour les phptests notamment, ou non)
588
		// conserver les valeurs vides (pour les phptests notamment, ou non)
586
		// $obs = array_map('array_filter', $obs);
589
		// $obs = array_map('array_filter', $obs);
587
		$obs_merged = $obs_keyed_by_id_image = array();
590
		$obs_merged = $obs_keyed_by_id_image = array();
588
		foreach ($obs as $o) {
591
		foreach ($obs as $o) {
589
			// ceci nous complique la tâche pour le reste du processing...
592
			// ceci nous complique la tâche pour le reste du processing...
590
			$id = $o['jsonindex'];
593
			$id = $o['jsonindex'];
591
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
594
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
592
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
595
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
593
			// mais tout deux partage leur référence à "protocole"
596
			// mais tout deux partage leur référence à "protocole"
594
			$image = array(
597
			$image = array(
595
				'id_image' => $o['id_image'],
598
				'id_image' => $o['id_image'],
596
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
599
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
597
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
600
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
598
			);
601
			);
599
 
602
 
600
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
603
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
601
			if (!isset($obs_merged[$id])) {
604
			if (!isset($obs_merged[$id])) {
602
				$obs_merged[$id] = $image;
605
				$obs_merged[$id] = $image;
603
			}
606
			}
604
			$obs_merged[$id]['observation'] = $o;
607
			$obs_merged[$id]['observation'] = $o;
605
			$obs_merged[$id]['protocoles_votes'] = array();
608
			$obs_merged[$id]['protocoles_votes'] = array();
606
 
609
 
607
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes'];
610
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes'];
608
		}
611
		}
609
 
612
 
610
		return array($obs_merged,$obs_keyed_by_id_image);
613
		return array($obs_merged,$obs_keyed_by_id_image);
611
	}
614
	}
612
 
615
 
613
	// complete & override DelTk::requestFilterParams() (même usage)
616
	// complete & override DelTk::requestFilterParams() (même usage)
614
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
617
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
615
		if ($parametres_autorises) { // filtrage de toute clef inconnue
618
		if ($parametres_autorises) { // filtrage de toute clef inconnue
616
			$params = array_intersect_key($params, array_flip($parametres_autorises));
619
			$params = array_intersect_key($params, array_flip($parametres_autorises));
617
		}
620
		}
618
 
621
 
619
		$p = array();
622
		$p = array();
620
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
623
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
621
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
624
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
622
 
625
 
623
		// "milieu" inutile pour IdentiPlantes ?
626
		// "milieu" inutile pour IdentiPlantes ?
624
		if (isset($params['masque.milieu'])) {
627
		if (isset($params['masque.milieu'])) {
625
			$p['masque.milieu'] = trim($params['masque.milieu']);
628
			$p['masque.milieu'] = trim($params['masque.milieu']);
626
		}
629
		}
627
 
630
 
628
		// compatibilité
631
		// compatibilité
629
		if (isset($params['masque.tag'])) {
632
		if (isset($params['masque.tag'])) {
630
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
633
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
631
		}
634
		}
632
 
635
 
633
		if ($p['tri'] == 'votes' || $p['tri'] == 'tags' || $p['tri'] == 'points') {
636
		if ($p['tri'] == 'votes' || $p['tri'] == 'tags' || $p['tri'] == 'points') {
634
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
637
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
635
			if(!isset($params['protocole']) || !is_numeric($params['protocole'])) {
638
			if(!isset($params['protocole']) || !is_numeric($params['protocole'])) {
636
				$p['protocole'] = self::$default_proto;
639
				$p['protocole'] = self::$default_proto;
637
			} else {
640
			} else {
638
				$p['protocole'] = intval($params['protocole']);
641
				$p['protocole'] = intval($params['protocole']);
639
			}
642
			}
640
		}
643
		}
641
 
644
 
642
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
645
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
643
	}
646
	}
644
 
647
 
645
}
648
}
646
?>
649
?>