Subversion Repositories eFlore/Applications.del

Rev

Rev 1827 | Rev 1845 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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