Subversion Repositories eFlore/Applications.del

Rev

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

Rev 1793 Rev 1827
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) {
126
		$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
-
 
127
		$this->conteneur->chargerConfiguration('config_images.ini');
126
		$this->conteneur = $conteneur;
128
		$this->bdd = $conteneur->conteneur->getBdd();
127
		$this->bdd = $this->conteneur->getBdd();
129
	}
128
	}
130
 
129
 
131
	public function consulter($ressources, $parametres) {
130
	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
131
		/* 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.
132
		Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
134
		Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
133
		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. */
134
		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;'));
135
		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;'));
136
		array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
138
		// pour les votes, les mappings de "Observation" nous suffisent
137
		// pour les votes, les mappings de "Observation" nous suffisent
139
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
138
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
140
 
139
 
141
		// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
140
		// 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.
141
		// de `ce_image` dans ces tables jointes.
143
		// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
142
		// 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());
143
		$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
145
 
144
 
146
		$db = $this->bdd;
145
		$db = $this->bdd;
147
 
146
 
148
		// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
147
		// 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.
148
		// ($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
149
		// É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).
150
		// même si ceux-ci sont improbables (pas d'<input> pour cela).
152
		$params_ip = DelTk::requestFilterParams($parametres,
151
		$params_ip = DelTk::requestFilterParams($parametres,
153
			array_diff(DelTk::$parametres_autorises, array('masque.type')),
152
			array_diff(DelTk::$parametres_autorises, array('masque.type')),
154
			$this->conteneur);
153
			$this->conteneur);
155
 
154
 
156
		// notre propre filtrage sur l'INPUT
155
		// notre propre filtrage sur l'INPUT
157
		$params_pf = self::requestFilterParams($parametres,
156
		$params_pf = self::requestFilterParams($parametres,
158
			array_merge(DelTk::$parametres_autorises, self::$parametres_autorises));
157
			array_merge(DelTk::$parametres_autorises, self::$parametres_autorises));
159
 
158
 
160
		/* filtrage des tags + sémantique des valeurs multiples:
159
		/* filtrage des tags + sémantique des valeurs multiples:
161
		Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
160
		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. */
161
		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', ',');
162
		$params_pf['masque.tag_cel'] = DelTk::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
164
 
163
 
165
		if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) {
164
		if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) {
166
			$parametres['masque.tag_pictoflora'] = $parametres['masque.tag'];
165
			$parametres['masque.tag_pictoflora'] = $parametres['masque.tag'];
167
		}
166
		}
168
		$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
167
		$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
169
 
168
 
170
		$params = array_merge(
169
		$params = array_merge(
171
			DelTk::$default_params, // paramètre par défaut Identiplante
170
			DelTk::$default_params, // paramètre par défaut Identiplante
172
			self::$default_params, // paramètres par défaut PictoFlora
171
			self::$default_params, // paramètres par défaut PictoFlora
173
			$params_ip, // les paramètres passés, traités par Identiplante
172
			$params_ip, // les paramètres passés, traités par Identiplante
174
			$params_pf); // les paramètres passés, traités par PictoFlora
173
			$params_pf); // les paramètres passés, traités par PictoFlora
175
 
174
 
176
		if (isset($parametres['format'])) {
175
		if (isset($parametres['format'])) {
177
			$params['format'] = $parametres['format'];
176
			$params['format'] = $parametres['format'];
178
		}
177
		}
179
 
178
 
180
		// création des contraintes (génériques de DelTk)
179
		// création des contraintes (génériques de DelTk)
181
		DelTk::sqlAddConstraint($params, $db, $req);
180
		DelTk::sqlAddConstraint($params, $db, $req);
182
		// création des contraintes spécifiques (sur les tags essentiellement)
181
		// création des contraintes spécifiques (sur les tags essentiellement)
183
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
182
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
184
		// création des contraintes spécifiques impliquées par le masque général
183
		// création des contraintes spécifiques impliquées par le masque général
185
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
184
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
186
		// l'ORDER BY s'avére complexe
185
		// l'ORDER BY s'avére complexe
187
		self::sqlOrderBy($params, $db, $req);
186
		self::sqlOrderBy($params, $db, $req);
188
 
187
 
189
		// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
188
		// 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);
189
		// $idobs_tab = ListeObservations::getIdObs($params, $req, $db);
191
		$idobs_tab = self::getIdImages($params, $req, $db);
190
		$idobs_tab = self::getIdImages($params, $req, $db);
192
 
191
 
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
192
		// 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) {
193
		if (!$idobs_tab) {
195
			$resultat = new ResultatService();
194
			$resultat = new ResultatService();
196
			$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
195
			$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
197
				'resultats' => array());
196
				'resultats' => array());
198
			return $resultat;
197
			return $resultat;
199
		}
198
		}
200
 
199
 
201
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
200
		// 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));
201
		$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']);
202
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
204
 
203
 
205
		$liaisons = self::chargerImages($db, $idobs);
204
		$liaisons = self::chargerImages($db, $idobs);
206
 
205
 
207
		list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
206
		list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
208
			$liaisons,
207
			$liaisons,
209
			$this->conteneur->getParametre('images.url_images'),
208
			$this->conteneur->getParametre('images.url_images'),
210
			$params['format']);
209
			$params['format']);
211
 
210
 
212
		// on charge les votes pour ces images et pour *tous* les protocoles
211
		// on charge les votes pour ces images et pour *tous* les protocoles
213
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
212
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
214
 
213
 
215
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
214
		// 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.
215
		// 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)
216
		// 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
217
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
219
		// c'est encore possible.
218
		// c'est encore possible.
220
		if ($votes) {
219
		if ($votes) {
221
			Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
220
			Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
222
		}
221
		}
223
 
222
 
224
		// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
223
		// 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)
224
		// 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'],
225
		$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
227
			'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
226
			'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
228
		$resultat = new ResultatService();
227
		$resultat = new ResultatService();
229
		$resultat->corps = array(
228
		$resultat->corps = array(
230
			'entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
229
			'entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
231
			'resultats' => $images);
230
			'resultats' => $images);
232
		return $resultat;
231
		return $resultat;
233
	}
232
	}
234
 
233
 
235
 
234
 
236
	/**
235
	/**
237
	 * Supprime une image directement dans le CEL en faisant un appel à un web service du CEL.
236
	 * Supprime une image directement dans le CEL en faisant un appel à un web service du CEL.
238
	 * Utilisé uniquement par les admins.
237
	 * Utilisé uniquement par les admins.
239
	 *
238
	 *
240
	 * @param array		$ressources tableau des informations contenues dans l'url après le nom du service
239
	 * @param array		$ressources tableau des informations contenues dans l'url après le nom du service
241
	 * @param array		$parametres contenu du post
240
	 * @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
241
	 * @return mixed	Chaine "OK" (en majuscule) en cas de succès, booléen "false" en cas d'échec
243
	 */
242
	 */
244
	public function supprimer($ressources) {
243
	public function supprimer($ressources) {
245
		$controlAcces = $this->conteneur->getControleAcces();
244
		$controlAcces = $this->conteneur->getControleAcces();
246
		$controlAcces->etreUtilisateurAvecDroitAdmin();
245
		$controlAcces->etreUtilisateurAvecDroitAdmin();
247
 
246
 
248
		$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage');
247
		$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage');
249
		$idImage = $ressources[0];
248
		$idImage = $ressources[0];
250
		$url = $urlServiceBase.$idImage;
249
		$url = $urlServiceBase.$idImage;
251
 
250
 
252
		$clientHttp = $this->conteneur->getRestClient();
251
		$clientHttp = $this->conteneur->getRestClient();
253
		$retourCel = $clientHttp->supprimer($url);
252
		$retourCel = $clientHttp->supprimer($url);
254
		$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false;
253
		$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false;
255
		return $retour;
254
		return $retour;
256
	}
255
	}
257
 
256
 
258
	/**
257
	/**
259
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
258
	 * 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)
259
	 * 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 ...).
260
	 * 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
261
	 * 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
262
	 * *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())
263
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
265
	 */
264
	 */
266
	static function sqlOrderBy($p, $db, &$req) {
265
	static function sqlOrderBy($p, $db, &$req) {
267
		// parmi self::$tri_possible
266
		// parmi self::$tri_possible
268
		if ($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
267
		if ($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
269
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
268
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
270
			return;
269
			return;
271
		}
270
		}
272
 
271
 
273
		if ($p['tri'] == 'points') { // LEFT JOIN sur "dis" ci-dessous
272
		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'];
273
			$req['orderby'] = 'dis.nb_points ' . $p['ordre'] . ', dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
275
			return;
274
			return;
276
		}
275
		}
277
 
276
 
278
		if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
277
		if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
279
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
278
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
280
			return;
279
			return;
281
		}
280
		}
282
 
281
 
283
		if ($p['tri'] == 'date_observation') {
282
		if ($p['tri'] == 'date_observation') {
284
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
283
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
285
			return;
284
			return;
286
		}
285
		}
287
 
286
 
288
		// tri == 'date_transmission'
287
		// tri == 'date_transmission'
289
		// avant cel:r1860, date_transmission pouvait être NULL
288
		// avant cel:r1860, date_transmission pouvait être NULL
290
		// or nous voulons de la cohérence (notamment pour phpunit)
289
		// or nous voulons de la cohérence (notamment pour phpunit)
291
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
290
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
292
	}
291
	}
293
 
292
 
294
	/*
293
	/*
295
	 * in $p: un tableau de paramètres, dont:
294
	 * in $p: un tableau de paramètres, dont:
296
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
295
	 * - '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
296
	 * - '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
297
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
299
	 *
298
	 *
300
	 * in/ou: $req: un tableau de structure de requête MySQL
299
	 * in/ou: $req: un tableau de structure de requête MySQL
301
	 *
300
	 *
302
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
301
	 * 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:
302
	 * 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
303
	 * Soit directement $this->consulter() si des masque.tag* sont passés
305
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
304
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
306
	 * Soit via sqlAddMasqueConstraint():
305
	 * Soit via sqlAddMasqueConstraint():
307
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
306
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
308
	 * équivalent à:
307
	 * équivalent à:
309
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
308
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
310
	 *
309
	 *
311
	 */
310
	 */
312
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
311
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
313
		// TODO implement dans DelTk ?
312
		// TODO implement dans DelTk ?
314
		if (!empty($p['masque.milieu'])) {
313
		if (!empty($p['masque.milieu'])) {
315
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
314
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
316
		}
315
		}
317
 
316
 
318
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
317
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
319
			celui-ci indique sur quels votes porte l'AVG.
318
			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) */
319
			(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
321
		/* TODO: perf problème:
320
		/* TODO: perf problème:
322
			1) SQL_CALC_FOUND_ROWS: fixable en:
321
			1) SQL_CALC_FOUND_ROWS: fixable en:
323
				- dissociant le comptage de la récup d'id + javascript async
322
				- 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
323
				- 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)
324
				(paramètre booléen "with-total" par exemple)
326
			2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
325
			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".
326
				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
327
				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é
328
				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:
329
			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)
330
				(cel_images/cel_obs_images/cel_obs/del_image_stat)
332
				Cependant c'est à l'optimiseur de définir son ordre préféré. */
331
				Cependant c'est à l'optimiseur de définir son ordre préféré. */
333
		if ($p['tri'] == 'votes' || $p['tri'] == 'points') {
332
		if ($p['tri'] == 'votes' || $p['tri'] == 'points') {
334
			// $p['protocole'] *est* défini (cf requestFilterParams())
333
			// $p['protocole'] *est* défini (cf requestFilterParams())
335
			$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
334
			$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
336
				 ' ON vdi.id_image = dis.ce_image'.
335
				 ' ON vdi.id_image = dis.ce_image'.
337
				 ' AND dis.ce_protocole = %d',
336
				 ' AND dis.ce_protocole = %d',
338
				 $p['protocole']);
337
				 $p['protocole']);
339
		}
338
		}
340
 
339
 
341
		if ($p['tri'] == 'tags') {
340
		if ($p['tri'] == 'tags') {
342
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
341
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
343
				($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
342
				($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
344
			// nécessaire (dup ce_image dans del_image_stat)
343
			// nécessaire (dup ce_image dans del_image_stat)
345
			$req['groupby'][] = 'vdi.id_observation';
344
			$req['groupby'][] = 'vdi.id_observation';
346
		}
345
		}
347
 
346
 
348
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
347
		// 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.
348
		// sont toujours présentes; bien que parfois NULL.
350
		if ($p['masque.tag_cel']) {
349
		if ($p['masque.tag_cel']) {
351
			if (isset($p['masque.tag_cel']['AND'])) {
350
			if (isset($p['masque.tag_cel']['AND'])) {
352
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
351
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
353
				// et auquel cas laisser au client le choix du couteux "%" ?
352
				// et auquel cas laisser au client le choix du couteux "%" ?
354
				$tags = $p['masque.tag_cel']['AND'];
353
				$tags = $p['masque.tag_cel']['AND'];
355
				array_walk($tags, create_function('&$val, $k, $db',
354
				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",
355
					'$val = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,\'\'),IFNULL(vdi.i_mots_cles_texte,\'\')) LIKE %s",
357
					$db->proteger("%".$val."%"));'),
356
					$db->proteger("%".$val."%"));'),
358
					$db);
357
					$db);
359
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
358
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
360
			} else {
359
			} else {
361
				$req['where'][] = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s",
360
				$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'])));
361
					$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
363
			}
362
			}
364
		}
363
		}
365
 
364
 
366
		if ($p['masque.tag_pictoflora']) {
365
		if ($p['masque.tag_pictoflora']) {
367
			// inutilisable pour l'instant
366
			// inutilisable pour l'instant
368
			// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
367
			// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
369
 
368
 
370
			// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
369
			// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
371
			// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
370
			// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
372
 
371
 
373
			// approche fiable mais sous-optimale
372
			// approche fiable mais sous-optimale
374
			self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
373
			self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
375
		}
374
		}
376
	}
375
	}
377
 
376
 
378
	/* approche intéressante si les deux problèmes suivants peuvent être résolu:
377
	/* 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é)
378
		- 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 ? */
379
		- dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
381
	static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
380
	static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
382
		// XXX: utiliser tag plutôt que tag_normalise ?
381
		// XXX: utiliser tag plutôt que tag_normalise ?
383
		$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
382
		$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
384
		$req['where'][] = 'dit.actif = 1';
383
		$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)
384
		$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
385
		// 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'])) {
386
		if (isset($p['masque.tag_pictoflora']['AND'])) {
388
			// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
387
			// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
389
			// ... le LEFT-JOIN n'y semble pas adapté
388
			// ... le LEFT-JOIN n'y semble pas adapté
390
		} else {
389
		} else {
391
			$protected_tags = array();
390
			$protected_tags = array();
392
			foreach ($p['masque.tag_pictoflora']['OR'] as $tag) {
391
			foreach ($p['masque.tag_pictoflora']['OR'] as $tag) {
393
				$protected_tags[] = $db->proteger(strtolower($tag));
392
				$protected_tags[] = $db->proteger(strtolower($tag));
394
			}
393
			}
395
			$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
394
			$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
396
		}
395
		}
397
	}
396
	}
398
 
397
 
399
	// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
398
	// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
400
	static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
399
	static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
401
		// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
400
		// 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
401
		// 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
402
		// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
404
		// ex: REGEX "^(".
403
		// ex: REGEX "^(".
405
		// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
404
		// 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
405
		// ne peuvent (ou ne devrait) comporter des meta-caractères
407
		// ([])?*+\\
406
		// ([])?*+\\
408
		if (isset($p['masque.tag_pictoflora']['AND'])) {
407
		if (isset($p['masque.tag_pictoflora']['AND'])) {
409
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
408
			// 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 "|"
409
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
411
			sort($p['masque.tag_pictoflora']['AND']);
410
			sort($p['masque.tag_pictoflora']['AND']);
412
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
411
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
413
				" GROUP BY ce_image".
412
				" GROUP BY ce_image".
414
				" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
413
				" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
415
				$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
414
				$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
416
		} else {
415
		} else {
417
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
416
			$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
418
				" GROUP BY ce_image".
417
				" GROUP BY ce_image".
419
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
418
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
420
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
419
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
421
		}
420
		}
422
	}
421
	}
423
 
422
 
424
	// si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
423
	// 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) {
424
	static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
426
		if (isset($p['masque.tag_pictoflora']['AND'])) {
425
		if (isset($p['masque.tag_pictoflora']['AND'])) {
427
			// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
426
			// 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 "|"
427
			// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
429
			sort($p['masque.tag_pictoflora']['AND']);
428
			sort($p['masque.tag_pictoflora']['AND']);
430
 
429
 
431
			// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
430
			// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
432
			$ids = @$db->recupererTous(sprintf(
431
			$ids = @$db->recupererTous(sprintf(
433
			"SELECT ce_image FROM del_image_tag WHERE actif = 1".
432
			"SELECT ce_image FROM del_image_tag WHERE actif = 1".
434
			" GROUP BY ce_image".
433
			" GROUP BY ce_image".
435
			" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
434
			" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
436
			$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
435
			$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
437
 
436
 
438
			// puis:
437
			// puis:
439
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
438
			$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';
439
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
441
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
440
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
442
		} else {
441
		} else {
443
			$ids = @$db->recupererTous(sprintf(
442
			$ids = @$db->recupererTous(sprintf(
444
				"SELECT ce_image FROM del_image_tag WHERE actif = 1".
443
				"SELECT ce_image FROM del_image_tag WHERE actif = 1".
445
				" GROUP BY ce_image".
444
				" GROUP BY ce_image".
446
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
445
				" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
447
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
446
				$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
448
 
447
 
449
			$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
448
			$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';
449
			$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag  WHERE false';
451
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
450
			$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
452
		}
451
		}
453
	}
452
	}
454
 
453
 
455
	static function getIdImages($p, $req, $db) {
454
	static function getIdImages($p, $req, $db) {
456
		$req = sprintf(
455
		$req = sprintf(
457
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
456
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
458
			//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug
457
			//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug
459
			' FROM v_del_image vdi'.
458
			' FROM v_del_image vdi'.
460
			' %s' . // LEFT JOIN if any
459
			' %s' . // LEFT JOIN if any
461
			' WHERE %s'. // where-clause ou TRUE
460
			' WHERE %s'. // where-clause ou TRUE
462
			' %s'. // group-by
461
			' %s'. // group-by
463
			' ORDER BY %s'.
462
			' ORDER BY %s'.
464
			' LIMIT %d, %d -- %s',
463
			' LIMIT %d, %d -- %s',
465
 
464
 
466
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
465
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
467
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
466
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
468
 
467
 
469
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
468
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
470
 
469
 
471
			$req['orderby'],
470
			$req['orderby'],
472
 
471
 
473
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
472
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
474
		return $db->recupererTous($req);
473
		return $db->recupererTous($req);
475
	}
474
	}
476
 
475
 
477
	static function chargerImages($db, $idImg) {
476
	static function chargerImages($db, $idImg) {
478
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
477
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
479
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
478
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
480
 
479
 
481
		return $db->recupererTous(sprintf('SELECT '.
480
		return $db->recupererTous(sprintf('SELECT '.
482
			' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
481
			' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
483
			' %1$s, %2$s FROM v_del_image '.
482
			' %1$s, %2$s FROM v_del_image '.
484
			' WHERE %3$s'.
483
			' WHERE %3$s'.
485
			' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN()
484
			' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN()
486
			' -- %5$s',
485
			' -- %5$s',
487
			$obs_fields, $image_fields,
486
			$obs_fields, $image_fields,
488
			sprintf('id_image IN (%s)', implode(',', $idImg)),
487
			sprintf('id_image IN (%s)', implode(',', $idImg)),
489
			sprintf('FIELD(id_image, %s)', implode(',', $idImg)),
488
			sprintf('FIELD(id_image, %s)', implode(',', $idImg)),
490
			__FILE__ . ':' . __LINE__));
489
			__FILE__ . ':' . __LINE__));
491
	}
490
	}
492
 
491
 
493
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
492
	/* "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:
493
		de manière identique à la seule différence que:
495
		1) ils sont combinés par des "OU" logiques plutôt que des "ET".
494
		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:
495
		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
496
		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)
497
		(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
499
		Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
498
		Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
500
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
499
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
501
		if (!empty($p['masque'])) {
500
		if (!empty($p['masque'])) {
502
			$or_params = array('masque.auteur' => $p['masque'],
501
			$or_params = array('masque.auteur' => $p['masque'],
503
				'masque.departement' => $p['masque'],
502
				'masque.departement' => $p['masque'],
504
				'masque.commune' => $p['masque'], // TODO/XXX ?
503
				'masque.commune' => $p['masque'], // TODO/XXX ?
505
				'masque.id_zone_geo' => $p['masque'],
504
				'masque.id_zone_geo' => $p['masque'],
506
 
505
 
507
				/* tous-deux remplacent masque.tag
506
				/* tous-deux remplacent masque.tag
508
				mais sont traité séparément des requestFilterParams() */
507
				mais sont traité séparément des requestFilterParams() */
509
				// 'masque.tag_cel' => $p['masque'],
508
				// 'masque.tag_cel' => $p['masque'],
510
				// 'masque.tag_pictoflora' => $p['masque'],
509
				// 'masque.tag_pictoflora' => $p['masque'],
511
 
510
 
512
				'masque.ns' => $p['masque'],
511
				'masque.ns' => $p['masque'],
513
				'masque.famille' => $p['masque'],
512
				'masque.famille' => $p['masque'],
514
				'masque.date' => $p['masque'],
513
				'masque.date' => $p['masque'],
515
				'masque.genre' => $p['masque'],
514
				'masque.genre' => $p['masque'],
516
				'masque.milieu' => $p['masque'],
515
				'masque.milieu' => $p['masque'],
517
				'masque.tag_cel' => $p['masque'],
516
				'masque.tag_cel' => $p['masque'],
518
				'masque.tag_pictoflora' => $p['masque'],
517
				'masque.tag_pictoflora' => $p['masque'],
519
 
518
 
520
				// tri est aussi nécessaire car affecte les contraintes de JOIN
519
				// tri est aussi nécessaire car affecte les contraintes de JOIN
521
				'tri' => $p['tri'],
520
				'tri' => $p['tri'],
522
				'ordre' => $p['ordre']);
521
				'ordre' => $p['ordre']);
523
			if (array_key_exists('protocole', $p)) {
522
			if (array_key_exists('protocole', $p)) {
524
				$or_params['protocole'] = $p['protocole'];
523
				$or_params['protocole'] = $p['protocole'];
525
			}
524
			}
526
 
525
 
527
			/* Cependant les champs spécifiques ont priorité sur le masque général.
526
			/* 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
527
				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. */
528
				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']);
529
			if(isset($p['masque.auteur'])) unset($or_params['masque.auteur']);
531
			if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
530
			if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
532
			if(isset($p['masque.commune'])) unset($or_params['masque.commune']);
531
			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']);
532
			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']);
533
			if(isset($p['masque.ns'])) unset($or_params['masque.ns']);
535
			if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
534
			if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
536
			if(isset($p['masque.date'])) unset($or_params['masque.date']);
535
			if(isset($p['masque.date'])) unset($or_params['masque.date']);
537
			if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
536
			if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
538
			if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
537
			if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
539
			if(isset($p['masque.tag_cel'])) unset($or_params['masque.tag_cel']);
538
			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']);
539
			if(isset($p['masque.tag_pictoflora'])) unset($or_params['masque.tag_pictoflora']);
541
 
540
 
542
			$or_masque = array_merge(
541
			$or_masque = array_merge(
543
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
542
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
544
				self::requestFilterParams($or_params)
543
				self::requestFilterParams($or_params)
545
			);
544
			);
546
 
545
 
547
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
546
			/* 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. */
547
				postulés comme séparés par des espaces, et doivent être tous matchés. */
549
			if (isset($or_params['masque.tag_cel'])) {
548
			if (isset($or_params['masque.tag_cel'])) {
550
				$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
549
				$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
551
			}
550
			}
552
			if (isset($or_params['masque.tag_pictoflora'])) {
551
			if (isset($or_params['masque.tag_pictoflora'])) {
553
				$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
552
				$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
554
			}
553
			}
555
 
554
 
556
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
555
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
557
			$or_req = array('join' => array(), 'where' => array());
556
			$or_req = array('join' => array(), 'where' => array());
558
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
557
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
559
 
558
 
560
			self::sqlAddConstraint($or_masque, $db, $or_req);
559
			self::sqlAddConstraint($or_masque, $db, $or_req);
561
 
560
 
562
			if ($or_req['where']) {
561
			if ($or_req['where']) {
563
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
562
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
564
				// utile au cas ou des jointures seraient rajoutées
563
				// utile au cas ou des jointures seraient rajoutées
565
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
564
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
566
			}
565
			}
567
		}
566
		}
568
	}
567
	}
569
 
568
 
570
 
569
 
571
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
570
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
572
	// (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
571
	// (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') {
572
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
574
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
573
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
575
		// conserver les valeurs vides (pour les phptests notamment, ou non)
574
		// conserver les valeurs vides (pour les phptests notamment, ou non)
576
		// $obs = array_map('array_filter', $obs);
575
		// $obs = array_map('array_filter', $obs);
577
		$obs_merged = $obs_keyed_by_id_image = array();
576
		$obs_merged = $obs_keyed_by_id_image = array();
578
		foreach ($obs as $o) {
577
		foreach ($obs as $o) {
579
			// ceci nous complique la tâche pour le reste du processing...
578
			// ceci nous complique la tâche pour le reste du processing...
580
			$id = $o['jsonindex'];
579
			$id = $o['jsonindex'];
581
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
580
			// 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()
581
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
583
			// mais tout deux partage leur référence à "protocole"
582
			// mais tout deux partage leur référence à "protocole"
584
			$image = array(
583
			$image = array(
585
				'id_image' => $o['id_image'],
584
				'id_image' => $o['id_image'],
586
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
585
				'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
586
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
588
			);
587
			);
589
 
588
 
590
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
589
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
591
			if (!isset($obs_merged[$id])) {
590
			if (!isset($obs_merged[$id])) {
592
				$obs_merged[$id] = $image;
591
				$obs_merged[$id] = $image;
593
			}
592
			}
594
			$obs_merged[$id]['observation'] = $o;
593
			$obs_merged[$id]['observation'] = $o;
595
			$obs_merged[$id]['protocoles_votes'] = array();
594
			$obs_merged[$id]['protocoles_votes'] = array();
596
 
595
 
597
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes'];
596
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes'];
598
		}
597
		}
599
 
598
 
600
		return array($obs_merged,$obs_keyed_by_id_image);
599
		return array($obs_merged,$obs_keyed_by_id_image);
601
	}
600
	}
602
 
601
 
603
	// complete & override DelTk::requestFilterParams() (même usage)
602
	// complete & override DelTk::requestFilterParams() (même usage)
604
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
603
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
605
		if ($parametres_autorises) { // filtrage de toute clef inconnue
604
		if ($parametres_autorises) { // filtrage de toute clef inconnue
606
			$params = array_intersect_key($params, array_flip($parametres_autorises));
605
			$params = array_intersect_key($params, array_flip($parametres_autorises));
607
		}
606
		}
608
 
607
 
609
		$p = array();
608
		$p = array();
610
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
609
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
611
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
610
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
612
 
611
 
613
		// "milieu" inutile pour IdentiPlantes ?
612
		// "milieu" inutile pour IdentiPlantes ?
614
		if (isset($params['masque.milieu'])) {
613
		if (isset($params['masque.milieu'])) {
615
			$p['masque.milieu'] = trim($params['masque.milieu']);
614
			$p['masque.milieu'] = trim($params['masque.milieu']);
616
		}
615
		}
617
 
616
 
618
		// compatibilité
617
		// compatibilité
619
		if (isset($params['masque.tag'])) {
618
		if (isset($params['masque.tag'])) {
620
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
619
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
621
		}
620
		}
622
 
621
 
623
		if ($p['tri'] == 'votes' || $p['tri'] == 'tags' || $p['tri'] == 'points') {
622
		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é
623
			// 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'])) {
624
			if(!isset($params['protocole']) || !is_numeric($params['protocole'])) {
626
				$p['protocole'] = self::$default_proto;
625
				$p['protocole'] = self::$default_proto;
627
			} else {
626
			} else {
628
				$p['protocole'] = intval($params['protocole']);
627
				$p['protocole'] = intval($params['protocole']);
629
			}
628
			}
630
		}
629
		}
631
 
630
 
632
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
631
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
633
	}
632
	}
634
 
-
 
635
}
-
 
636
?>
633
 
-
 
634
}
637
635