Subversion Repositories eFlore/Applications.del

Rev

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

Rev Author Line No. Line
1840 jpm 1
<?php
2
// declare(encoding='UTF-8');
3
/**
4
 * Classe contenant des méthodes permettant de construire les requêtes SQL complexe concernant les images et obs.
5
 *
6
 * @category  DEL
7
 * @package   Services
8
 * @package   Bibliotheque
9
 * @version   0.1
10
 * @author    Mathias CHOUET <mathias@tela-botanica.org>
11
 * @author    Jean-Pascal MILCENT <jpm@tela-botanica.org>
12
 * @author    Aurelien PERONNET <aurelien@tela-botanica.org>
13
 * @license   GPL v3 <http://www.gnu.org/licenses/gpl.txt>
14
 * @license   CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
15
 * @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org>
16
 */
17
class Sql {
18
 
1871 jpm 19
	const APPLI_IMG = 'IMG';
20
	const APPLI_OBS = 'OBS';
21
 
1840 jpm 22
	private $conteneur;
23
	private $bdd;
24
	private $parametres = array();
1871 jpm 25
	private $appli;
1840 jpm 26
	private $requete = array(
27
		'select' => array(),
28
		'join' => array(),
29
		'where' => array(),
30
		'groupby' => array(),
31
		'orderby' => array());
32
 
1871 jpm 33
	private $champsPrenom = array('du.prenom', 'prenom_utilisateur');
34
	private $champsNom = array('du.nom',  'nom_utilisateur');
1840 jpm 35
 
36
 
37
	public function __construct(Conteneur $conteneur) {
38
		$this->conteneur = $conteneur;
39
		$this->bdd = $this->conteneur->getBdd();
40
	}
41
 
42
	public function setParametres(Array $parametres) {
43
		$this->parametres = $parametres;
44
	}
45
 
1871 jpm 46
	public function setAppli($appliType) {
47
		if ($appliType == 'IMG' || $appliType == 'OBS') {
48
			$this->appli = $appliType;
49
		} else {
50
			throw new Exception("Les types d'appli disponible sont : IMG (pour PictoFlora) et OBS (pour IdentiPlante)");
51
		}
52
	}
53
 
54
	private function getPrefixe() {
55
		return $this->appli === 'IMG' ? 'di' : 'do';
56
	}
57
 
58
	private function etreAppliImg() {
59
		return $this->appli === 'IMG' ? true : false;
60
	}
61
 
62
	private function etreAppliObs() {
63
		return $this->appli === 'OBS' ? true : false;
64
	}
65
 
1840 jpm 66
	public function getRequeteSql() {
67
		return $this->requete;
68
	}
69
 
70
	private function addJoin($join) {
1871 jpm 71
		if (!isset($this->requete['join'][$join])) {
72
			$this->requete['join'][] = $join;
73
		}
1840 jpm 74
	}
75
 
76
	public function getJoin() {
77
		return ($this->requete['join'] ? implode(' ', array_unique($this->requete['join'])).' ' : '');
78
	}
79
 
80
	private function addJoinDis($join) {
81
		$this->requete['join']['dis'] = $join;
82
	}
83
 
84
	private function addWhere($idParam, $where) {
85
		if (isset($this->parametres['_parametres_condition_or_'])
86
				&& in_array($idParam, $this->parametres['_parametres_condition_or_'])) {
87
			$this->requete['where']['OR'][] = $where;
88
		} else {
89
			$this->requete['where']['AND'][] = $where;
90
		}
91
	}
92
	public function getWhere() {
93
		if (isset($this->requete['where']['OR']) && count($this->requete['where']['OR']) > 0) {
94
			$this->requete['where']['AND'][] = '('.implode(' OR ', $this->requete['where']['OR']).')';
95
		}
96
 
97
		$where = ' TRUE ';
98
		if (isset($this->requete['where']['AND']) && count($this->requete['where']['AND']) > 0) {
99
			$where = implode(' AND ', $this->requete['where']['AND']).' ';
100
		}
101
		return $where;
102
	}
103
 
104
	private function addGroupBy($groupBy) {
105
		$this->requete['groupby'][] = $groupBy;
106
	}
107
 
108
	public function getGroupBy() {
109
		$groupby = '';
110
		if (isset($this->requete['groupby']) && count($this->requete['groupby']) > 0) {
111
			$groupby = 'GROUP BY '.implode(', ', array_unique($this->requete['groupby'])).' ';
112
		}
113
		return $groupby;
114
	}
115
 
116
	private function addOrderBy($orderby) {
117
		$this->requete['orderby'][] = $orderby;
118
	}
119
 
120
	public function getOrderBy() {
121
		$orderby = '';
122
		if (isset($this->requete['orderby']) && count($this->requete['orderby']) > 0) {
123
			$orderby = 'ORDER BY '.implode(', ', array_unique($this->requete['orderby'])).' ';
124
		}
125
		return $orderby;
126
	}
127
 
128
	public function getLimit() {
129
		return 'LIMIT '.$this->parametres['navigation.depart'].','.$this->parametres['navigation.limite'].' ';
130
	}
131
 
132
	/**
133
	 * - Rempli le tableau des contraintes "where" et "join" nécessaire
134
	 * à la *recherche* des observations demandées ($req) utilisées par self::getIdObs()
135
	 *
136
	 * Attention, cela signifie que toutes les tables ne sont pas *forcément*
137
	 * join'ées, par exemple si aucune contrainte ne le nécessite.
138
	 * $req tel qu'il est rempli ici est utile pour récupéré la seule liste des
139
	 * id d'observation qui match.
140
	 * Pour la récupération effective de "toutes" les données correspondante, il faut
141
	 * réinitialiser $req["join"] afin d'y ajouter toutes les autres tables.
142
	 *
143
	 * Note: toujours rajouter les préfixes de table (vdi,du,doi ou di), en fonction de ce que défini
144
	 * les JOIN qui sont utilisés.
145
	 * le préfix de v_del_image est "vdi" (cf: "FROM" de self::getIdObs())
146
	 * le préfix de del_utilisateur sur id_utilisateur = vdi.ce_utilisateur est "du"
147
	 *
148
	 * @param $p les paramètres (notamment de masque) passés par l'URL et déjà traités/filtrés (sauf quotes)
149
	 * @param $req le tableau, passé par référence représentant les composants de la requête à bâtir
150
	 */
151
	public function ajouterContraintes() {
152
		$this->ajouterContrainteAuteur();
153
		$this->ajouterContrainteDate();
154
		$this->ajouterContrainteDepartement();
155
		$this->ajouterContrainteIdZoneGeo();
156
		$this->ajouterContrainteGenre();
157
		$this->ajouterContrainteFamille();
158
		$this->ajouterContrainteNs();
159
		$this->ajouterContrainteNn();
160
		$this->ajouterContrainteReferentiel();
161
		$this->ajouterContrainteCommune();
162
	}
163
 
164
	private function ajouterContrainteAuteur() {
165
		if (isset($this->parametres['masque.auteur'])) {
166
			$auteur = $this->parametres['masque.auteur'];
167
			// id du poster de l'obs
1871 jpm 168
			$prefixe = $this->getPrefixe();
169
			$this->addJoin("LEFT JOIN del_utilisateur AS du ON (du.id_utilisateur = $prefixe.ce_utilisateur) ");
1840 jpm 170
 
171
			if (is_numeric($auteur)) {
172
				$this->ajouterContrainteAuteurId();
1871 jpm 173
			} elseif(preg_match('/@[a-z0-9-]+(?:\.[a-z0-9-]+)*\.[a-z]{2,}$/i', $auteur)) {
1840 jpm 174
				$this->ajouterContrainteAuteurEmail();
175
			} else {
176
				$this->ajouterContrainteAuteurIntitule();
177
			}
178
		}
179
	}
180
 
181
	private function ajouterContrainteAuteurId() {
182
		$id = $this->parametres['masque.auteur'];
1871 jpm 183
		$prefixe = $this->getPrefixe();
184
		$sqlTpl = "(du.id_utilisateur = %1\$d OR $prefixe.ce_utilisateur = %1\$d)";
1840 jpm 185
		$whereAuteur = sprintf($sqlTpl, $id);
186
		$this->addWhere('masque.auteur', $whereAuteur);
187
	}
188
 
189
	private function ajouterContrainteAuteurEmail() {
190
		$email = $this->parametres['masque.auteur'];
1871 jpm 191
		$prefixe = $this->getPrefixe();
192
		$sqlTpl = "(du.courriel LIKE %1\$s OR $prefixe.courriel_utilisateur LIKE %1\$s )";
1840 jpm 193
		$emailP = $this->bdd->proteger("$email%");
194
		$whereAuteur = sprintf($sqlTpl, $emailP);
195
		$this->addWhere('masque.auteur', $whereAuteur);
196
	}
197
 
198
	/**
199
	 * Retourne une clause where du style:
200
	 * CONCAT(IF(du.prenom IS NULL, "", du.prenom), [...] vdi.i_nomutilisateur) REGEXP 'xxx'
201
	 * Note; i_(nom|prenom_utilisateur), alias pour cel_images.(nom|prenom), n'est pas traité
202
	 * car cette information est redondante dans cel_image et devrait être supprimée.
203
	 */
204
	private function ajouterContrainteAuteurIntitule() {
205
		$auteurExplode = explode(' ', $this->parametres['masque.auteur']);
206
		$nbreMots = count($auteurExplode);
207
 
208
		if ($nbreMots == 1) {
209
			$this->ajouterContrainteAuteurPrenomOuNom();
210
		} else if ($nbreMots == 2) {
211
			$this->ajouterContrainteAuteurPrenomEtNom();
212
		}
213
	}
214
 
215
	private function ajouterContrainteAuteurPrenomOuNom() {
216
		$prenomOuNom = $this->parametres['masque.auteur'];
217
 
218
		$sqlTpl = 'CONCAT(%s,%s) LIKE %s';
1871 jpm 219
		$prefixe = $this->getPrefixe();
220
		$champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom, $prefixe);
221
		$champsNomSql = self::ajouterIfNullPourConcat($this->champsNom, $prefixe);
1840 jpm 222
		$auteurMotif = $this->bdd->proteger("%$prenomOuNom%");
223
 
224
		$auteurWhere = sprintf($sqlTpl, $champsPrenomSql, $champsNomSql, $auteurMotif);
225
		$this->addWhere('masque.auteur', $auteurWhere);
226
	}
227
 
228
	private function ajouterContrainteAuteurPrenomEtNom() {
229
		list($prenom, $nom) = explode(' ', $this->parametres['masque.auteur']);
230
 
231
		$sqlTpl = '(CONCAT(%1$s,%2$s) LIKE %3$s AND CONCAT(%1$s,%2$s) LIKE %4$s)';
1871 jpm 232
		$prefixe = $this->getPrefixe();
233
		$champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom, $prefixe);
234
		$champsNomSql = self::ajouterIfNullPourConcat($this->champsNom, $prefixe);
1840 jpm 235
		$prenomMotif = $this->bdd->proteger("%$prenom%");
236
		$nomMotif = $this->bdd->proteger("%$nom%");
237
 
238
		$auteurWhere = sprintf($sqlTpl, $champsPrenomSql, $champsNomSql, $prenomMotif, $nomMotif);
239
		$this->addWhere('masque.auteur', $auteurWhere);
240
	}
241
 
242
		/**
243
	 * Lorsque l'on concatène des champs, un seul NULL prend le dessus,
244
	 * Il faut donc utiliser la syntaxe IFNULL(%s, "").
245
	 * (Cette fonction effectue aussi l'implode() "final"
246
	 */
1871 jpm 247
	private static function ajouterIfNullPourConcat($champs, $prefixe) {
1840 jpm 248
		$champsProteges = array();
249
		foreach ($champs as $champ) {
1871 jpm 250
			if (strstr($champ, '.') === false) {
251
				$champ = "$prefixe.$champ";
252
			}
1840 jpm 253
			$champsProteges[] = "IFNULL($champ, '')";
254
		}
255
		return implode(',', $champsProteges);
256
	}
257
 
258
	private function ajouterContrainteDate() {
259
		if (isset($this->parametres['masque.date'])) {
260
			$date = $this->parametres['masque.date'];
1871 jpm 261
			if (preg_match('/^\d{4}$/', $date) && $date < 2030 && $date > 1600) {
262
				$sqlTpl = "YEAR(do.date_observation) = %d";
1840 jpm 263
				$dateWhere = sprintf($sqlTpl, $date);
264
				$this->addWhere('masque.date', $dateWhere);
265
			} else {
1871 jpm 266
				$sqlTpl = "do.date_observation = %s";
267
				$dateP = $this->bdd->proteger($date);
1840 jpm 268
				$dateWhere = sprintf($sqlTpl, $dateP);
269
				$this->addWhere('masque.date', $dateWhere);
270
			}
1871 jpm 271
 
272
			if ($this->etreAppliImg()) {
273
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
274
			}
1840 jpm 275
		}
276
	}
277
 
278
	private function ajouterContrainteDepartement() {
279
		if (isset($this->parametres['masque.departement'])) {
280
			$dept = $this->parametres['masque.departement'];
281
			$deptMotif = $this->bdd->proteger("INSEE-C:$dept");
1871 jpm 282
			$this->addWhere('masque.departement', "do.ce_zone_geo LIKE $deptMotif");
283
 
284
			if ($this->etreAppliImg()) {
285
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
286
			}
1840 jpm 287
		}
288
	}
289
 
290
	private function ajouterContrainteIdZoneGeo() {
291
		if (isset($this->parametres['masque.id_zone_geo'])) {
292
			$idZgMotif = $this->bdd->proteger($this->parametres['masque.id_zone_geo']);
1871 jpm 293
			$this->addWhere('masque.id_zone_geo', "do.ce_zone_geo = $idZgMotif");
294
 
295
			if ($this->etreAppliImg()) {
296
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
297
			}
1840 jpm 298
		}
299
	}
300
 
301
	private function ajouterContrainteGenre() {
302
		if (isset($this->parametres['masque.genre'])) {
303
			$genre = $this->parametres['masque.genre'];
304
			$genreMotif = $this->bdd->proteger("%$genre% %");
1871 jpm 305
			$this->addWhere('masque.genre', "do.nom_sel LIKE $genreMotif");
306
 
307
			if ($this->etreAppliImg()) {
308
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
309
			}
1840 jpm 310
		}
311
	}
312
 
313
	private function ajouterContrainteFamille() {
314
		if (isset($this->parametres['masque.famille'])) {
315
			$familleMotif = $this->bdd->proteger($this->parametres['masque.famille']);
1871 jpm 316
			$this->addWhere('masque.famille', "do.famille = $familleMotif");
317
 
318
			if ($this->etreAppliImg()) {
319
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
320
			}
1840 jpm 321
		}
322
	}
323
 
324
	private function ajouterContrainteNs() {
325
		if (isset($this->parametres['masque.ns'])) {
326
			$ns = $this->parametres['masque.ns'];
327
			$nsMotif = $this->bdd->proteger("$ns%");
1871 jpm 328
			$this->addWhere('masque.ns', "do.nom_sel LIKE $nsMotif");
329
 
330
			if ($this->etreAppliImg()) {
331
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
332
			}
1840 jpm 333
		}
334
	}
335
 
336
	private function ajouterContrainteNn() {
337
		if (isset($this->parametres['masque.nn'])) {
1871 jpm 338
			$sqlTpl = '(do.nom_sel_nn = %1$d OR do.nom_ret_nn = %1$d)';
1840 jpm 339
			$nnWhere = sprintf($sqlTpl, $this->parametres['masque.nn']);
340
			$this->addWhere('masque.nn', $nnWhere);
1871 jpm 341
 
342
			if ($this->etreAppliImg()) {
343
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
344
			}
1840 jpm 345
		}
346
	}
347
 
348
	private function ajouterContrainteReferentiel() {
349
		if (isset($this->parametres['masque.referentiel'])) {
350
			$ref = $this->parametres['masque.referentiel'];
351
			$refMotif = $this->bdd->proteger("$ref%");
1871 jpm 352
			$this->addWhere('masque.referentiel', "do.nom_referentiel LIKE $refMotif");
353
 
354
			if ($this->etreAppliImg()) {
355
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
356
			}
1840 jpm 357
		}
358
	}
359
 
360
	private function ajouterContrainteCommune() {
361
		if (isset($this->parametres['masque.commune'])) {
362
			$commune = $this->parametres['masque.commune'];
363
			$communeMotif = $this->bdd->proteger("$commune%");
1871 jpm 364
			$this->addWhere('masque.commune', "do.zone_geo LIKE $communeMotif");
365
 
366
			if ($this->etreAppliImg()) {
367
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
368
			}
1840 jpm 369
		}
370
	}
371
 
1845 jpm 372
	public function ajouterConstrainteAppliObs() {
373
		$this->ajouterContrainteTagCel();
374
		$this->ajouterContrainteType();
375
		// TODO : ATTENTION -> vue que l'on utilise une vue basée sur les images, nous devons grouper par obs
1871 jpm 376
		$this->addGroupBy('do.id_observation');
1845 jpm 377
	}
378
 
379
		/**
380
	 * @param $req: la représentation de la requête MySQL complète, à amender.
381
	 */
382
	private function ajouterContrainteType() {
1871 jpm 383
		if (isset($this->parametres['masque.type'])) {
384
			if (array_key_exists('adeterminer', $this->parametres['masque.type'])) {
385
				// Récupèration de toutes les observations qui on le tag "aDeterminer" *ou* qui n'ont pas de nom d'espèce
386
				// *ou* qui ont la "certitude" à ("aDeterminer" *ou* "douteux")
387
				$this->addWhere('masque.type', '('.
388
					'do.certitude = "aDeterminer" '.
389
					'OR do.certitude = "douteux" '.
390
					'OR do.mots_cles_texte LIKE "%aDeterminer%" '.
391
					'OR do.nom_sel_nn IS NULL '.
392
					'OR do.nom_sel_nn = 0 '.// il ne DEVRAIT pas y avoir d'entrées à 0, mais il y en a quand-même !!
393
					')');
394
			}
395
			if (array_key_exists('validees', $this->parametres['masque.type'])) {
396
				// Récupèration de toutes les observations ayant un commentaire doté de proposition_retenue = 1
397
				$this->addJoin('INNER JOIN del_commentaire AS dc '.
398
					'ON (do.id_observation = dc.ce_observation AND dc.proposition_retenue = 1) ');
399
			}
1845 jpm 400
 
1871 jpm 401
			if (array_key_exists('endiscussion', $this->parametres['masque.type'])) {
402
				$nbreCommentaire =(int) ($this->conteneur->getParametre('observations.nb_commentaires_discussion'));
403
				$this->addWhere('masque.type', '(SELECT COUNT(id_commentaire) FROM del_commentaire AS dc '.
404
					"WHERE ce_observation = id_observation) > $nbreCommentaire ");
405
			}
406
 
407
			if ($this->etreAppliImg()) {
408
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
409
			}
1845 jpm 410
		}
411
	}
412
 
1840 jpm 413
	/**
414
	 * in $p: un tableau de paramètres, dont:
415
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
416
	 * - 'masque.tag_del': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
417
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
418
	 *
419
	 * in/ou: $req: un tableau de structure de requête MySQL
420
	 *
421
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
422
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
423
	 * Soit directement $this->consulter() si des masque.tag* sont passés
424
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
425
	 * Soit via sqlAddMasqueConstraint():
426
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
427
	 * équivalent à:
428
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
429
	 *
430
	 */
431
	public function ajouterConstrainteAppliImg() {
432
		$this->ajouterContrainteMilieu();
433
		$this->ajouterContrainteTri();
434
		$this->ajouterContrainteTagCel();
435
		$this->ajouterContrainteTagDel();
436
	}
437
 
438
	private function ajouterContrainteMilieu() {
439
		if (isset($this->parametres['masque.milieu'])) {
440
			$milieu = $this->parametres['masque.milieu'];
441
			$milieuMotif = $this->bdd->proteger("%$milieu%");
1871 jpm 442
			$this->addWhere('masque.milieu', "do.milieu LIKE $milieuMotif");
443
 
444
			if ($this->etreAppliImg()) {
445
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
446
			}
1840 jpm 447
		}
448
	}
449
 
450
	/** Pour le tri par AVG() des votes nous avons toujours un protocole donné,
451
	 * celui-ci indique sur quels votes porte l'AVG.
452
	 * (c'est un *vote* qui porte sur un protocole et non l'image elle-même)
453
	 * TODO: perf problème:
454
	 * 1) SQL_CALC_FOUND_ROWS: fixable en:
455
	 * - dissociant le comptage de la récup d'id + javascript async
456
	 * - ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
457
	 * (paramètre booléen "with-total" par exemple)
458
	 * 2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
459
	 * JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
460
	 * Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
461
	 * jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
462
	 * 3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
463
	 * (cel_images/cel_obs_images/cel_obs/del_image_stat)
464
	 * Cependant c'est à l'optimiseur de définir son ordre préféré.
465
	 */
466
	private function ajouterContrainteTri() {
467
		if (isset($this->parametres['tri'])) {
468
			$tri = $this->parametres['tri'];
469
 
1863 jpm 470
			if (isset($this->parametres['protocole'])  && ($tri == 'moyenne-arithmetique' || $tri == 'points')) {
1840 jpm 471
				// $this->parametres['protocole'] *est* défini (cf Outils::filtrerUrlsParams...())
1871 jpm 472
				$sqlTpl = 'LEFT JOIN del_image_stat AS dis ON di.id_image = dis.ce_image AND dis.ce_protocole = %d';
1840 jpm 473
				$triSql = sprintf($sqlTpl, $this->parametres['protocole']);
474
				$this->addJoinDis($triSql);
475
			}
476
 
477
			if (isset($this->parametres['ordre']) && $tri == 'tags') {
478
				$typeJointure = ($this->parametres['ordre'] == 'desc') ? 'INNER' : 'LEFT';
1871 jpm 479
				$this->addJoin("$typeJointure JOIN del_image_stat AS dis ON di.id_image = dis.ce_image");
1840 jpm 480
				// nécessaire (dup ce_image dans del_image_stat)
1871 jpm 481
				$this->addGroupBy('di.ce_observation');
1840 jpm 482
			}
483
		}
484
	}
485
 
486
	/**
487
	 * Car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
488
	 * sont toujours présentes; bien que parfois NULL.
489
	 */
490
	// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ? et auquel cas laisser au client le choix du couteux "%" ?
491
	private function ajouterContrainteTagCel() {
1845 jpm 492
		if (isset($this->parametres['masque.tag_cel'])) {
1840 jpm 493
			if (isset($this->parametres['masque.tag_cel']['AND'])) {
494
				$tags = $this->parametres['masque.tag_cel']['AND'];
495
				$clausesWhere = array();
496
				foreach ($tags as $tag) {
497
					$tagMotif = $this->bdd->proteger("%$tag%");
1871 jpm 498
					$sqlTpl = "CONCAT(IFNULL(do.mots_cles_texte,''),IFNULL(di.mots_cles_texte,'')) LIKE %s";
1840 jpm 499
					$clausesWhere[] = sprintf($sqlTpl, $tagMotif);
500
				}
501
				$whereTags = implode(' AND ', $clausesWhere);
502
				$this->addWhere('masque.tag_cel', "($whereTags)");
503
			} else if (isset($this->parametres['masque.tag_cel']['OR'])) {
504
				$tags = $this->parametres['masque.tag_cel']['OR'];
1871 jpm 505
				$sqlTpl = "CONCAT(IFNULL(do.mots_cles_texte,''),IFNULL(di.mots_cles_texte,'')) REGEXP %s";
1840 jpm 506
				$tagMotif = $this->bdd->proteger(implode('|', $tags));
507
				$tagSql = sprintf($sqlTpl, $tagMotif);
508
				$this->addWhere('masque.tag_cel', $tagSql);
509
			}
1871 jpm 510
			if ($this->etreAppliImg()) {
511
				$this->addJoin('LEFT JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ');
512
			}
513
			if ($this->etreAppliObs()) {
514
				$this->addJoin('LEFT JOIN del_image AS di ON (do.id_observation = di.ce_observation) ');
515
			}
1840 jpm 516
		}
517
	}
518
 
519
	/**
1845 jpm 520
	 * Plusieurs solutions sont disponibles dans les anciennes versions (voir DelTk).
1840 jpm 521
	 */
522
	private function ajouterContrainteTagDel() {
523
		if (isset($this->parametres['masque.tag_del'])) {
1845 jpm 524
			if (isset($this->parametres['masque.tag_del']['AND'])) {
525
				$tags = $this->parametres['masque.tag_del']['AND'];
526
				// optimisation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
527
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
528
				sort($tags);
529
				$tagsMotif = $this->bdd->proteger(implode('.*', $tags));
530
				$requete = 'SELECT ce_image '.
531
					'FROM del_image_tag '.
532
					'WHERE actif = 1 '.
533
					'GROUP BY ce_image '.
534
					"HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP $tagsMotif ".
535
					' -- '.__FILE__.' : '.__LINE__;
536
				$sql = $this->recupererSqlContrainteTag($requete);
537
				$this->addWhere('masque.tag_del', $sql);
1840 jpm 538
 
1845 jpm 539
			} else if (isset($this->parametres['masque.tag_del']['OR'])) {
540
				$tags = $this->parametres['masque.tag_del']['OR'];
541
				$tagsMotif = $this->bdd->proteger(implode('|', $tags));
542
				$requete = 'SELECT ce_image '.
543
					'FROM del_image_tag '.
544
					'WHERE actif = 1 '.
545
					'GROUP BY ce_image '.
546
					"HAVING GROUP_CONCAT(tag_normalise) REGEXP $tagsMotif ".
547
					' -- '.__FILE__.' : '.__LINE__;
1871 jpm 548
 
1845 jpm 549
				$sql = $this->recupererSqlContrainteTag($requete);
550
				$this->addWhere('masque.tag_del', $sql);
1840 jpm 551
			}
552
		}
553
	}
554
 
555
	private function recupererSqlContrainteTag($requete) {
556
		$resultats = $this->bdd->recupererTous($requete);
557
		$ids = array();
558
		foreach ($resultats as $resultat) {
559
			$ids[] = $resultat['ce_image'];
560
		}
561
 
562
		if (!empty($ids)) {
563
			$clauseIn = implode(',', $ids);
564
		} else {
565
			$clauseIn = 'SELECT ce_image FROM del_image_tag WHERE false';
566
		}
1871 jpm 567
		return "di.id_image IN ($clauseIn)";
1840 jpm 568
	}
569
 
570
	/**
571
	 * Partie spécifique à PictoFlora:
1863 jpm 572
	 * Attention : si le critère de tri n'est pas suffisant, les résultats affichés peuvent varier à chaque appel
573
	 * de la même page de résultat de PictoFlora.
1840 jpm 574
	 */
575
	public function definirOrdreSqlAppliImg() {
576
		$ordre = $this->parametres['ordre'];
577
 
578
		switch ($this->parametres['tri']) {
1863 jpm 579
			case 'moyenne-arithmetique' :
580
				$this->addOrderBy("dis.moyenne $ordre, dis.nb_votes $ordre, id_image DESC");
1840 jpm 581
				break;
582
			case 'points' :
1863 jpm 583
				$this->addOrderBy("dis.nb_points $ordre, dis.moyenne $ordre, dis.nb_votes $ordre, id_image DESC");
1840 jpm 584
				break;
585
			case 'tags' :
1863 jpm 586
				$this->addOrderBy("dis.nb_tags $ordre, id_image DESC");
1840 jpm 587
				break;
588
			case 'date_observation' :
1871 jpm 589
				$this->addOrderBy("date_observation $ordre, ce_observation $ordre");
1840 jpm 590
				break;
1863 jpm 591
			case 'date_transmission' :
1840 jpm 592
			default:
1871 jpm 593
				$this->addOrderBy("di.date_transmission $ordre, ce_observation $ordre");
1840 jpm 594
		}
595
	}
596
 
1845 jpm 597
	public function definirOrdreSqlAppliObs() {
598
		$ordre = $this->parametres['ordre'];
599
 
600
		// parmi self::$tri_possible
601
		switch ($this->parametres['tri']) {
602
			case 'date_observation' :
603
				$this->addOrderBy("date_observation $ordre, id_observation $ordre");
604
				break;
605
			default:
1871 jpm 606
				$this->addOrderBy("do.date_transmission $ordre, id_observation $ordre");
1845 jpm 607
		}
608
	}
609
 
1840 jpm 610
	public function getAliasDesChamps($champsEtAlias, $select = null, $prefix = null) {
611
		$arr = ($select) ? array_intersect_key($champsEtAlias, array_flip($select)) :  $champsEtAlias;
612
		$keys = array_keys($arr);
613
 
614
		if ($prefix) {
615
			array_walk($keys, create_function('&$val, $k, $prefix', '$val = sprintf("%s.`%s`", $prefix, $val);'), $prefix);
616
		} else {
617
			array_walk($keys, create_function('&$val, $k', '$val = sprintf("`%s`", $val);'));
618
		}
619
 
620
		return implode(', ', array_map(create_function('$v, $k', 'return sprintf("%s AS `%s`", $k, $v);'), $arr, $keys));
621
	}
622
 
623
		// Charger les images et leurs votes associés
624
	public function getVotesDesImages($idsImages, $protocole = null) {
625
		if (!$idsImages) return;
626
 
627
		$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping');
628
		$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping');
629
 		$selectVotes = array('id_vote', 'ce_image', 'ce_protocole', 'ce_utilisateur', 'valeur', 'date');
630
		$selectProtocole = array('id_protocole', 'intitule', 'descriptif', 'tag');
631
		$voteChamps = $this->getAliasDesChamps($mappingVotes, $selectVotes, 'v'); // "v": cf alias dans la requête
632
		$protoChamps = $this->getAliasDesChamps($mappingProtocoles, $selectProtocole, 'p');
633
		$idImgsConcat = implode(',', $idsImages);
634
 
635
		$requete = "SELECT $voteChamps, $protoChamps ".
636
			'FROM del_image_vote AS v '.
637
			'	INNER JOIN del_image_protocole AS p ON (v.ce_protocole = p.id_protocole) '.
638
			"WHERE v.ce_image IN ($idImgsConcat) ".
639
			($protocole ? "	AND v.ce_protocole = $protocole " : '').
640
			"ORDER BY FIELD(v.ce_image, $idImgsConcat) ".
641
			'-- '.__FILE__.':'.__LINE__;
642
 
643
		return $this->bdd->recupererTous($requete);
644
	}
645
 
646
	/**
647
	 * Ajoute les informations sur le protocole et les votes aux images.
648
	 *
649
	 * ATTENTION : Subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien
650
	 * plus pratique pour associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
651
	 * Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
652
	 * cf ListeImages::reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions
653
	 * simultanément lorsque c'est encore possible.
654
	 */
655
	public function ajouterInfosVotesProtocoles($votes, &$images) {
656
		if (!$votes) return;
1845 jpm 657
 
1840 jpm 658
		$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping');
659
		$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping');
660
 
661
		// pour chaque vote
662
		foreach ($votes as $vote) {
663
			$imgId = $vote['image.id'];
664
			$protoId = $vote['protocole.id'];
665
 
666
			if (!array_key_exists('protocoles_votes', $images[$imgId]) ||
1863 jpm 667
					!array_key_exists($protoId, $images[$imgId]['protocoles_votes'])) {
1840 jpm 668
				// extrait les champs spécifique au protocole (le LEFT JOIN de chargerVotesImage les ramène en doublons
669
				$protocole = array_intersect_key($vote, array_flip($mappingProtocoles));
670
				$images[$imgId]['protocoles_votes'][$protoId] = $protocole;
671
			}
672
 
673
			$chpsVotes = array('id_vote', 'ce_image', 'ce_utilisateur', 'valeur', 'date');
674
			$voteSelection = array_intersect_key($mappingVotes, array_flip($chpsVotes));
675
			$vote = array_intersect_key($vote, array_flip($voteSelection));
676
			$images[$imgId]['protocoles_votes'][$protoId]['votes'][$vote['vote.id']] = $vote;
677
		}
678
	}
1845 jpm 679
 
680
	public function getTotalLignesTrouvees() {
681
		$resultat = $this->bdd->recuperer('SELECT FOUND_ROWS() AS nbre');
682
		return intval($resultat['nbre']);
683
	}
1840 jpm 684
}