Subversion Repositories eFlore/Applications.del

Rev

Rev 1748 | Rev 1827 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1748 Rev 1793
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->conteneur->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
 
234
 
235
 
235
 
236
	/**
236
	/**
237
	 * Supprime une image directement dans le CEL en faisant un appel à un web service du CEL.
237
	 * Supprime une image directement dans le CEL en faisant un appel à un web service du CEL.
238
	 * Utilisé uniquement par les admins.
238
	 * Utilisé uniquement par les admins.
239
	 *
239
	 *
240
	 * @param array		$ressources tableau des informations contenues dans l'url après le nom du service
240
	 * @param array		$ressources tableau des informations contenues dans l'url après le nom du service
241
	 * @param array		$parametres contenu du post
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
242
	 * @return mixed	Chaine "OK" (en majuscule) en cas de succès, booléen "false" en cas d'échec
243
	 */
243
	 */
244
	public function supprimer($ressources) {
244
	public function supprimer($ressources) {
245
		$controlAcces = $this->conteneur->getControleAcces();
245
		$controlAcces = $this->conteneur->getControleAcces();
246
		$controlAcces->etreUtilisateurAvecDroitAdmin();
246
		$controlAcces->etreUtilisateurAvecDroitAdmin();
247
 
247
 
248
		$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage');
248
		$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage');
249
		$idImage = $ressources[0];
249
		$idImage = $ressources[0];
250
		$url = $urlServiceBase.$idImage;
250
		$url = $urlServiceBase.$idImage;
251
 
251
 
252
		$clientHttp = $this->conteneur->getRestClient();
252
		$clientHttp = $this->conteneur->getRestClient();
253
		$retourCel = $clientHttp->supprimer($url);
253
		$retourCel = $clientHttp->supprimer($url);
254
		$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false;
254
		$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false;
255
		return $retour;
255
		return $retour;
256
	}
256
	}
257
 
257
 
258
	/**
258
	/**
259
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
259
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
260
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
260
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
261
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
261
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
262
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
262
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
263
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
263
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
264
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
264
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
265
	 */
265
	 */
266
	static function sqlOrderBy($p, $db, &$req) {
266
	static function sqlOrderBy($p, $db, &$req) {
267
		// parmi self::$tri_possible
267
		// parmi self::$tri_possible
268
		if ($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
268
		if ($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
269
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
269
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
270
			return;
270
			return;
271
		}
271
		}
272
 
272
 
273
		if ($p['tri'] == 'points') { // LEFT JOIN sur "dis" ci-dessous
273
		if ($p['tri'] == 'points') { // LEFT JOIN sur "dis" ci-dessous
274
			$req['orderby'] = 'dis.nb_points ' . $p['ordre'] . ', dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
274
			$req['orderby'] = 'dis.nb_points ' . $p['ordre'] . ', dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
275
			return;
275
			return;
276
		}
276
		}
277
 
277
 
278
		if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
278
		if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
279
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
279
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
280
			return;
280
			return;
281
		}
281
		}
282
 
282
 
283
		if ($p['tri'] == 'date_observation') {
283
		if ($p['tri'] == 'date_observation') {
284
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
284
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
285
			return;
285
			return;
286
		}
286
		}
287
 
287
 
288
		// tri == 'date_transmission'
288
		// tri == 'date_transmission'
289
		// avant cel:r1860, date_transmission pouvait être NULL
289
		// avant cel:r1860, date_transmission pouvait être NULL
290
		// or nous voulons de la cohérence (notamment pour phpunit)
290
		// or nous voulons de la cohérence (notamment pour phpunit)
291
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
291
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
292
	}
292
	}
293
 
293
 
294
	/*
294
	/*
295
	 * in $p: un tableau de paramètres, dont:
295
	 * in $p: un tableau de paramètres, dont:
296
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
296
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
297
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
297
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
298
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
298
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
299
	 *
299
	 *
300
	 * in/ou: $req: un tableau de structure de requête MySQL
300
	 * in/ou: $req: un tableau de structure de requête MySQL
301
	 *
301
	 *
302
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
302
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
303
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
303
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
304
	 * Soit directement $this->consulter() si des masque.tag* sont passés
304
	 * Soit directement $this->consulter() si des masque.tag* sont passés
305
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
305
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
306
	 * Soit via sqlAddMasqueConstraint():
306
	 * Soit via sqlAddMasqueConstraint():
307
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
307
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
308
	 * équivalent à:
308
	 * équivalent à:
309
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
309
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
310
	 *
310
	 *
311
	 */
311
	 */
312
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
312
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
313
		// TODO implement dans DelTk ?
313
		// TODO implement dans DelTk ?
314
		if (!empty($p['masque.milieu'])) {
314
		if (!empty($p['masque.milieu'])) {
315
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
315
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
316
		}
316
		}
317
 
317
 
318
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
318
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
319
			celui-ci indique sur quels votes porte l'AVG.
319
			celui-ci indique sur quels votes porte l'AVG.
320
			(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
320
			(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
321
		/* TODO: perf problème:
321
		/* TODO: perf problème:
322
			1) SQL_CALC_FOUND_ROWS: fixable en:
322
			1) SQL_CALC_FOUND_ROWS: fixable en:
323
				- dissociant le comptage de la récup d'id + javascript async
323
				- dissociant le comptage de la récup d'id + javascript async
324
				- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
324
				- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
325
				(paramètre booléen "with-total" par exemple)
325
				(paramètre booléen "with-total" par exemple)
326
			2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
326
			2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
327
				JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
327
				JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
328
				Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
328
				Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
329
				jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
329
				jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
330
			3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
330
			3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
331
				(cel_images/cel_obs_images/cel_obs/del_image_stat)
331
				(cel_images/cel_obs_images/cel_obs/del_image_stat)
332
				Cependant c'est à l'optimiseur de définir son ordre préféré. */
332
				Cependant c'est à l'optimiseur de définir son ordre préféré. */
333
		if ($p['tri'] == 'votes' || $p['tri'] == 'points') {
333
		if ($p['tri'] == 'votes' || $p['tri'] == 'points') {
334
			// $p['protocole'] *est* défini (cf requestFilterParams())
334
			// $p['protocole'] *est* défini (cf requestFilterParams())
335
			$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
335
			$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
336
				 ' ON vdi.id_image = dis.ce_image'.
336
				 ' ON vdi.id_image = dis.ce_image'.
337
				 ' AND dis.ce_protocole = %d',
337
				 ' AND dis.ce_protocole = %d',
338
				 $p['protocole']);
338
				 $p['protocole']);
339
		}
339
		}
340
 
340
 
341
		if ($p['tri'] == 'tags') {
341
		if ($p['tri'] == 'tags') {
342
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
342
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
343
				($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
343
				($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
344
			// nécessaire (dup ce_image dans del_image_stat)
344
			// nécessaire (dup ce_image dans del_image_stat)
345
			$req['groupby'][] = 'vdi.id_observation';
345
			$req['groupby'][] = 'vdi.id_observation';
346
		}
346
		}
347
 
347
 
348
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
348
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
349
		// sont toujours présentes; bien que parfois NULL.
349
		// sont toujours présentes; bien que parfois NULL.
350
		if ($p['masque.tag_cel']) {
350
		if ($p['masque.tag_cel']) {
351
			if (isset($p['masque.tag_cel']['AND'])) {
351
			if (isset($p['masque.tag_cel']['AND'])) {
352
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
352
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
353
				// et auquel cas laisser au client le choix du couteux "%" ?
353
				// et auquel cas laisser au client le choix du couteux "%" ?
354
				$tags = $p['masque.tag_cel']['AND'];
354
				$tags = $p['masque.tag_cel']['AND'];
355
				array_walk($tags, create_function('&$val, $k, $db',
355
				array_walk($tags, create_function('&$val, $k, $db',
356
					'$val = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,\'\'),IFNULL(vdi.i_mots_cles_texte,\'\')) LIKE %s",
356
					'$val = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,\'\'),IFNULL(vdi.i_mots_cles_texte,\'\')) LIKE %s",
357
					$db->proteger("%".$val."%"));'),
357
					$db->proteger("%".$val."%"));'),
358
					$db);
358
					$db);
359
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
359
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
360
			} else {
360
			} else {
361
				$req['where'][] = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s",
361
				$req['where'][] = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s",
362
					$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
362
					$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
363
			}
363
			}
364
		}
364
		}
365
 
365
 
366
		if ($p['masque.tag_pictoflora']) {
366
		if ($p['masque.tag_pictoflora']) {
367
			// inutilisable pour l'instant
367
			// inutilisable pour l'instant
368
			// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
368
			// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
369
 
369
 
370
			// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
370
			// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
371
			// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
371
			// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
372
 
372
 
373
			// approche fiable mais sous-optimale
373
			// approche fiable mais sous-optimale
374
			self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
374
			self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
375
		}
375
		}
376
	}
376
	}
377
 
377
 
378
	/* approche intéressante si les deux problèmes suivants peuvent être résolu:
378
	/* approche intéressante si les deux problèmes suivants peuvent être résolu:
379
		- LEFT JOIN => dup => *gestion de multiples GROUP BY* (car in-fine un LIMIT est utilisé)
379
		- LEFT JOIN => dup => *gestion de multiples GROUP BY* (car in-fine un LIMIT est utilisé)
380
		- dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
380
		- dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
381
	static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
381
	static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
382
		// XXX: utiliser tag plutôt que tag_normalise ?
382
		// XXX: utiliser tag plutôt que tag_normalise ?
383
		$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
383
		$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
384
		$req['where'][] = 'dit.actif = 1';
384
		$req['where'][] = 'dit.actif = 1';
385
		$req['groupby'][] = 'vdi.id_image'; // TODO: nécessaire (car dup') mais risque de conflict en cas de tri (multiple GROUP BY)
385
		$req['groupby'][] = 'vdi.id_image'; // TODO: nécessaire (car dup') mais risque de conflict en cas de tri (multiple GROUP BY)
386
		// XXX: en cas de ET, possibilité du GROUP_CONCAT(), mais probablement sans grand intérêt, d'où une boucle
386
		// XXX: en cas de ET, possibilité du GROUP_CONCAT(), mais probablement sans grand intérêt, d'où une boucle
387
		if (isset($p['masque.tag_pictoflora']['AND'])) {
387
		if (isset($p['masque.tag_pictoflora']['AND'])) {
388
			// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
388
			// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
389
			// ... le LEFT-JOIN n'y semble pas adapté
389
			// ... le LEFT-JOIN n'y semble pas adapté
390
		} else {
390
		} else {
391
			$protected_tags = array();
391
			$protected_tags = array();
392
			foreach ($p['masque.tag_pictoflora']['OR'] as $tag) {
392
			foreach ($p['masque.tag_pictoflora']['OR'] as $tag) {
393
				$protected_tags[] = $db->proteger(strtolower($tag));
393
				$protected_tags[] = $db->proteger(strtolower($tag));
394
			}
394
			}
395
			$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
395
			$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
396
		}
396
		}
397
	}
397
	}
398
 
398
 
399
	// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
399
	// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
400
	static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
400
	static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
401
		// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
401
		// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
402
		// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
402
		// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
403
		// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
403
		// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
404
		// ex: REGEX "^(".
404
		// ex: REGEX "^(".
405
		// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
405
		// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
406
		// ne peuvent (ou ne devrait) comporter des meta-caractères
406
		// ne peuvent (ou ne devrait) comporter des meta-caractères
407
		// ([])?*+\\
407
		// ([])?*+\\
408
		if (isset($p['masque.tag_pictoflora']['AND'])) {
408
		if (isset($p['masque.tag_pictoflora']['AND'])) {
409
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
409
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
410
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
410
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
411
			sort($p['masque.tag_pictoflora']['AND']);
411
			sort($p['masque.tag_pictoflora']['AND']);
412
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
412
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
413
				" GROUP BY ce_image".
413
				" GROUP BY ce_image".
414
				" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
414
				" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
415
				$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
415
				$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
416
		} else {
416
		} else {
417
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
417
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
418
				" GROUP BY ce_image".
418
				" GROUP BY ce_image".
419
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
419
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
420
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
420
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
421
		}
421
		}
422
	}
422
	}
423
 
423
 
424
	// si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
424
	// si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
425
	static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
425
	static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
426
		if (isset($p['masque.tag_pictoflora']['AND'])) {
426
		if (isset($p['masque.tag_pictoflora']['AND'])) {
427
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
427
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
428
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
428
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
429
			sort($p['masque.tag_pictoflora']['AND']);
429
			sort($p['masque.tag_pictoflora']['AND']);
430
 
430
 
431
			// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
431
			// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
432
			$ids = @$db->recupererTous(sprintf(
432
			$ids = @$db->recupererTous(sprintf(
433
			"SELECT ce_image FROM del_image_tag WHERE actif = 1".
433
			"SELECT ce_image FROM del_image_tag WHERE actif = 1".
434
			" GROUP BY ce_image".
434
			" GROUP BY ce_image".
435
			" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
435
			" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
436
			$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
436
			$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
437
 
437
 
438
			// puis:
438
			// puis:
439
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
439
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
440
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
440
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
441
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
441
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
442
		} else {
442
		} else {
443
			$ids = @$db->recupererTous(sprintf(
443
			$ids = @$db->recupererTous(sprintf(
444
				"SELECT ce_image FROM del_image_tag WHERE actif = 1".
444
				"SELECT ce_image FROM del_image_tag WHERE actif = 1".
445
				" GROUP BY ce_image".
445
				" GROUP BY ce_image".
446
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
446
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
447
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
447
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
448
 
448
 
449
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
449
			$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';
450
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
451
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
451
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
452
		}
452
		}
453
	}
453
	}
454
 
454
 
455
	static function getIdImages($p, $req, $db) {
455
	static function getIdImages($p, $req, $db) {
456
		$req = sprintf(
456
		$req = sprintf(
457
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
457
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
458
			//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug
458
			//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug
459
			' FROM v_del_image vdi'.
459
			' FROM v_del_image vdi'.
460
			' %s' . // LEFT JOIN if any
460
			' %s' . // LEFT JOIN if any
461
			' WHERE %s'. // where-clause ou TRUE
461
			' WHERE %s'. // where-clause ou TRUE
462
			' %s'. // group-by
462
			' %s'. // group-by
463
			' ORDER BY %s'.
463
			' ORDER BY %s'.
464
			' LIMIT %d, %d -- %s',
464
			' LIMIT %d, %d -- %s',
465
 
465
 
466
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
466
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
467
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
467
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
468
 
468
 
469
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
469
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
470
 
470
 
471
			$req['orderby'],
471
			$req['orderby'],
472
 
472
 
473
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
473
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
474
		return $db->recupererTous($req);
474
		return $db->recupererTous($req);
475
	}
475
	}
476
 
476
 
477
	static function chargerImages($db, $idImg) {
477
	static function chargerImages($db, $idImg) {
478
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
478
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
479
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
479
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
480
 
480
 
481
		return $db->recupererTous(sprintf('SELECT '.
481
		return $db->recupererTous(sprintf('SELECT '.
482
			' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
482
			' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
483
			' %1$s, %2$s FROM v_del_image '.
483
			' %1$s, %2$s FROM v_del_image '.
484
			' WHERE %3$s'.
484
			' WHERE %3$s'.
485
			' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN()
485
			' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN()
486
			' -- %5$s',
486
			' -- %5$s',
487
			$obs_fields, $image_fields,
487
			$obs_fields, $image_fields,
488
			sprintf('id_image IN (%s)', implode(',', $idImg)),
488
			sprintf('id_image IN (%s)', implode(',', $idImg)),
489
			sprintf('FIELD(id_image, %s)', implode(',', $idImg)),
489
			sprintf('FIELD(id_image, %s)', implode(',', $idImg)),
490
			__FILE__ . ':' . __LINE__));
490
			__FILE__ . ':' . __LINE__));
491
	}
491
	}
492
 
492
 
493
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
493
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
494
		de manière identique à la seule différence que:
494
		de manière identique à la seule différence que:
495
		1) ils sont combinés par des "OU" logiques plutôt que des "ET".
495
		1) ils sont combinés par des "OU" logiques plutôt que des "ET".
496
		2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
496
		2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
497
		Tous les mots-clefs doivent matcher et sont séparés par des espaces
497
		Tous les mots-clefs doivent matcher et sont séparés par des espaces
498
		(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
498
		(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
499
		Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
499
		Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
500
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
500
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
501
		if (!empty($p['masque'])) {
501
		if (!empty($p['masque'])) {
502
			$or_params = array('masque.auteur' => $p['masque'],
502
			$or_params = array('masque.auteur' => $p['masque'],
503
				'masque.departement' => $p['masque'],
503
				'masque.departement' => $p['masque'],
504
				'masque.commune' => $p['masque'], // TODO/XXX ?
504
				'masque.commune' => $p['masque'], // TODO/XXX ?
505
				'masque.id_zone_geo' => $p['masque'],
505
				'masque.id_zone_geo' => $p['masque'],
506
 
506
 
507
				/* tous-deux remplacent masque.tag
507
				/* tous-deux remplacent masque.tag
508
				mais sont traité séparément des requestFilterParams() */
508
				mais sont traité séparément des requestFilterParams() */
509
				// 'masque.tag_cel' => $p['masque'],
509
				// 'masque.tag_cel' => $p['masque'],
510
				// 'masque.tag_pictoflora' => $p['masque'],
510
				// 'masque.tag_pictoflora' => $p['masque'],
511
 
511
 
512
				'masque.ns' => $p['masque'],
512
				'masque.ns' => $p['masque'],
513
				'masque.famille' => $p['masque'],
513
				'masque.famille' => $p['masque'],
514
				'masque.date' => $p['masque'],
514
				'masque.date' => $p['masque'],
515
				'masque.genre' => $p['masque'],
515
				'masque.genre' => $p['masque'],
516
				'masque.milieu' => $p['masque'],
516
				'masque.milieu' => $p['masque'],
517
				'masque.tag_cel' => $p['masque'],
517
				'masque.tag_cel' => $p['masque'],
518
				'masque.tag_pictoflora' => $p['masque'],
518
				'masque.tag_pictoflora' => $p['masque'],
519
 
519
 
520
				// tri est aussi nécessaire car affecte les contraintes de JOIN
520
				// tri est aussi nécessaire car affecte les contraintes de JOIN
521
				'tri' => $p['tri'],
521
				'tri' => $p['tri'],
522
				'ordre' => $p['ordre']);
522
				'ordre' => $p['ordre']);
523
			if (array_key_exists('protocole', $p)) {
523
			if (array_key_exists('protocole', $p)) {
524
				$or_params['protocole'] = $p['protocole'];
524
				$or_params['protocole'] = $p['protocole'];
525
			}
525
			}
526
 
526
 
527
			/* Cependant les champs spécifiques ont priorité sur le masque général.
527
			/* Cependant les champs spécifiques ont priorité sur le masque général.
528
				Pour cette raison nous supprimons la génération de SQL du masque général sur les
528
				Pour cette raison nous supprimons la génération de SQL du masque général sur les
529
				champ spécifiques qui feront l'objet d'un traitement avec une valeur propre. */
529
				champ spécifiques qui feront l'objet d'un traitement avec une valeur propre. */
530
			if(isset($p['masque.auteur'])) unset($or_params['masque.auteur']);
530
			if(isset($p['masque.auteur'])) unset($or_params['masque.auteur']);
531
			if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
531
			if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
532
			if(isset($p['masque.commune'])) unset($or_params['masque.commune']);
532
			if(isset($p['masque.commune'])) unset($or_params['masque.commune']);
533
			if(isset($p['masque.id_zone_geo'])) unset($or_params['masque.id_zone_geo']);
533
			if(isset($p['masque.id_zone_geo'])) unset($or_params['masque.id_zone_geo']);
534
			if(isset($p['masque.ns'])) unset($or_params['masque.ns']);
534
			if(isset($p['masque.ns'])) unset($or_params['masque.ns']);
535
			if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
535
			if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
536
			if(isset($p['masque.date'])) unset($or_params['masque.date']);
536
			if(isset($p['masque.date'])) unset($or_params['masque.date']);
537
			if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
537
			if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
538
			if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
538
			if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
539
			if(isset($p['masque.tag_cel'])) unset($or_params['masque.tag_cel']);
539
			if(isset($p['masque.tag_cel'])) unset($or_params['masque.tag_cel']);
540
			if(isset($p['masque.tag_pictoflora'])) unset($or_params['masque.tag_pictoflora']);
540
			if(isset($p['masque.tag_pictoflora'])) unset($or_params['masque.tag_pictoflora']);
541
 
541
 
542
			$or_masque = array_merge(
542
			$or_masque = array_merge(
543
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
543
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
544
				self::requestFilterParams($or_params)
544
				self::requestFilterParams($or_params)
545
			);
545
			);
546
 
546
 
547
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
547
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
548
				postulés comme séparés par des espaces, et doivent être tous matchés. */
548
				postulés comme séparés par des espaces, et doivent être tous matchés. */
549
			if (isset($or_params['masque.tag_cel'])) {
549
			if (isset($or_params['masque.tag_cel'])) {
550
				$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
550
				$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
551
			}
551
			}
552
			if (isset($or_params['masque.tag_pictoflora'])) {
552
			if (isset($or_params['masque.tag_pictoflora'])) {
553
				$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
553
				$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
554
			}
554
			}
555
 
555
 
556
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
556
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
557
			$or_req = array('join' => array(), 'where' => array());
557
			$or_req = array('join' => array(), 'where' => array());
558
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
558
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
559
 
559
 
560
			self::sqlAddConstraint($or_masque, $db, $or_req);
560
			self::sqlAddConstraint($or_masque, $db, $or_req);
561
 
561
 
562
			if ($or_req['where']) {
562
			if ($or_req['where']) {
563
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
563
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
564
				// utile au cas ou des jointures seraient rajoutées
564
				// utile au cas ou des jointures seraient rajoutées
565
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
565
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
566
			}
566
			}
567
		}
567
		}
568
	}
568
	}
569
 
569
 
570
 
570
 
571
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
571
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
572
	// (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
572
	// (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
573
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
573
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
574
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
574
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
575
		// conserver les valeurs vides (pour les phptests notamment, ou non)
575
		// conserver les valeurs vides (pour les phptests notamment, ou non)
576
		// $obs = array_map('array_filter', $obs);
576
		// $obs = array_map('array_filter', $obs);
577
		$obs_merged = $obs_keyed_by_id_image = array();
577
		$obs_merged = $obs_keyed_by_id_image = array();
578
		foreach ($obs as $o) {
578
		foreach ($obs as $o) {
579
			// ceci nous complique la tâche pour le reste du processing...
579
			// ceci nous complique la tâche pour le reste du processing...
580
			$id = $o['jsonindex'];
580
			$id = $o['jsonindex'];
581
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
581
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
582
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
582
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
583
			// mais tout deux partage leur référence à "protocole"
583
			// mais tout deux partage leur référence à "protocole"
584
			$image = array(
584
			$image = array(
585
				'id_image' => $o['id_image'],
585
				'id_image' => $o['id_image'],
586
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
586
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
587
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
587
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
588
			);
588
			);
589
 
589
 
590
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
590
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
591
			if (!isset($obs_merged[$id])) {
591
			if (!isset($obs_merged[$id])) {
592
				$obs_merged[$id] = $image;
592
				$obs_merged[$id] = $image;
593
			}
593
			}
594
			$obs_merged[$id]['observation'] = $o;
594
			$obs_merged[$id]['observation'] = $o;
595
			$obs_merged[$id]['protocoles_votes'] = array();
595
			$obs_merged[$id]['protocoles_votes'] = array();
596
 
596
 
597
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes'];
597
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes'];
598
		}
598
		}
599
 
599
 
600
		return array($obs_merged,$obs_keyed_by_id_image);
600
		return array($obs_merged,$obs_keyed_by_id_image);
601
	}
601
	}
602
 
602
 
603
	// complete & override DelTk::requestFilterParams() (même usage)
603
	// complete & override DelTk::requestFilterParams() (même usage)
604
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
604
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
605
		if ($parametres_autorises) { // filtrage de toute clef inconnue
605
		if ($parametres_autorises) { // filtrage de toute clef inconnue
606
			$params = array_intersect_key($params, array_flip($parametres_autorises));
606
			$params = array_intersect_key($params, array_flip($parametres_autorises));
607
		}
607
		}
608
 
608
 
609
		$p = array();
609
		$p = array();
610
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
610
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
611
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
611
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
612
 
612
 
613
		// "milieu" inutile pour IdentiPlantes ?
613
		// "milieu" inutile pour IdentiPlantes ?
614
		if (isset($params['masque.milieu'])) {
614
		if (isset($params['masque.milieu'])) {
615
			$p['masque.milieu'] = trim($params['masque.milieu']);
615
			$p['masque.milieu'] = trim($params['masque.milieu']);
616
		}
616
		}
617
 
617
 
618
		// compatibilité
618
		// compatibilité
619
		if (isset($params['masque.tag'])) {
619
		if (isset($params['masque.tag'])) {
620
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
620
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
621
		}
621
		}
622
 
622
 
623
		if ($p['tri'] == 'votes' || $p['tri'] == 'tags' || $p['tri'] == 'points') {
623
		if ($p['tri'] == 'votes' || $p['tri'] == 'tags' || $p['tri'] == 'points') {
624
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
624
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
625
			if(!isset($params['protocole']) || !is_numeric($params['protocole'])) {
625
			if(!isset($params['protocole']) || !is_numeric($params['protocole'])) {
626
				$p['protocole'] = self::$default_proto;
626
				$p['protocole'] = self::$default_proto;
627
			} else {
627
			} else {
628
				$p['protocole'] = intval($params['protocole']);
628
				$p['protocole'] = intval($params['protocole']);
629
			}
629
			}
630
		}
630
		}
631
 
631
 
632
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
632
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
633
	}
633
	}
634
 
634
 
635
}
635
}
636
?>
636
?>