Subversion Repositories eFlore/Applications.del

Rev

Rev 2189 | Rev 2199 | 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.
1881 jpm 5
 * Rempli un tableau des clauses "join", "where", "group by" et "oder by" nécessaire à la *recherche* des ids des
6
 * observations/images correspondantes aux filtres passés dans l'url du web service de recherche
7
 * (ListeImages et ListeObservations).
1840 jpm 8
 *
1881 jpm 9
 * Attention, cela signifie que toutes les tables ne sont pas *forcément* jointées, par exemple si aucune
10
 * contrainte ne le nécessite.
11
 * La requête construite ici est utile pour récupérer la liste des ids d'observations/images qui match.
12
 * Pour la récupération effective de "toutes" les données nécessaire au retour du web service en json, c'est une autre
13
 * requête directement dans le web service qui s'en charge. Cette technique en deux étapes est la plus rapide !
14
 *
15
 * Note: toujours rajouter les préfixes de table (di, do ou du), en fonction de ce que défini
16
 * les JOIN qui sont utilisés :
2156 mathias 17
 * - le préfixe de del_image est "di"
18
 * - le préfixe de del_observation est "do"
19
 * - le préfixe de del_utilisateur est "du"
1881 jpm 20
 *
1840 jpm 21
 * @category  DEL
22
 * @package   Services
23
 * @package   Bibliotheque
24
 * @version   0.1
25
 * @author    Mathias CHOUET <mathias@tela-botanica.org>
26
 * @author    Jean-Pascal MILCENT <jpm@tela-botanica.org>
27
 * @author    Aurelien PERONNET <aurelien@tela-botanica.org>
28
 * @license   GPL v3 <http://www.gnu.org/licenses/gpl.txt>
29
 * @license   CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt>
30
 * @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org>
31
 */
32
class Sql {
33
 
1871 jpm 34
	const APPLI_IMG = 'IMG';
35
	const APPLI_OBS = 'OBS';
36
 
1840 jpm 37
	private $conteneur;
38
	private $bdd;
39
	private $parametres = array();
1871 jpm 40
	private $appli;
1840 jpm 41
	private $requete = array(
42
		'join' => array(),
43
		'where' => array(),
44
		'groupby' => array(),
45
		'orderby' => array());
46
 
2004 mathias 47
	private $champsPrenom = array('prenom_utilisateur');
48
	private $champsNom = array('nom_utilisateur');
1985 aurelien 49
	private $champsSousRequeteObs = array('masque.genre', 'masque.famille', 'masque.ns', 'masque.commune', 'masque.milieu', 'masque.pays');
1840 jpm 50
 
51
	public function __construct(Conteneur $conteneur) {
52
		$this->conteneur = $conteneur;
53
		$this->bdd = $this->conteneur->getBdd();
54
	}
55
 
56
	public function setParametres(Array $parametres) {
57
		$this->parametres = $parametres;
58
	}
59
 
1871 jpm 60
	public function setAppli($appliType) {
61
		if ($appliType == 'IMG' || $appliType == 'OBS') {
62
			$this->appli = $appliType;
63
		} else {
64
			throw new Exception("Les types d'appli disponible sont : IMG (pour PictoFlora) et OBS (pour IdentiPlante)");
65
		}
66
	}
67
 
68
	private function getPrefixe() {
69
		return $this->appli === 'IMG' ? 'di' : 'do';
70
	}
71
 
72
	private function etreAppliImg() {
2140 mathias 73
		return $this->appli === 'IMG';
1871 jpm 74
	}
75
 
76
	private function etreAppliObs() {
2140 mathias 77
		return $this->appli === 'OBS';
1871 jpm 78
	}
79
 
1840 jpm 80
	public function getRequeteSql() {
81
		return $this->requete;
82
	}
83
 
84
	private function addJoin($join) {
1871 jpm 85
		if (!isset($this->requete['join'][$join])) {
1922 jpm 86
			$this->requete['join'][$join] = $join;
1871 jpm 87
		}
1840 jpm 88
	}
89
 
90
	public function getJoin() {
91
		return ($this->requete['join'] ? implode(' ', array_unique($this->requete['join'])).' ' : '');
92
	}
93
 
94
	private function addJoinDis($join) {
95
		$this->requete['join']['dis'] = $join;
96
	}
97
 
98
	private function addWhere($idParam, $where) {
99
		if (isset($this->parametres['_parametres_condition_or_'])
100
				&& in_array($idParam, $this->parametres['_parametres_condition_or_'])) {
1922 jpm 101
			if ($this->etreAppliImg() && in_array($idParam, $this->champsSousRequeteObs)) {
102
				$this->requete['where']['OR_SOUS_REQUETE'][] = $where;
103
			} else {
104
				$this->requete['where']['OR'][] = $where;
105
			}
1840 jpm 106
		} else {
107
			$this->requete['where']['AND'][] = $where;
108
		}
109
	}
1922 jpm 110
 
1840 jpm 111
	public function getWhere() {
1922 jpm 112
		// Sous-requete spéciale pour éviter de rechercher dans la table obs jointe à img depuis Pictoflora...
113
		if (isset($this->requete['where']['OR_SOUS_REQUETE']) && count($this->requete['where']['OR_SOUS_REQUETE']) > 0) {
114
			$clauseWhereSousRequete = implode(' OR ', $this->requete['where']['OR_SOUS_REQUETE']);
115
			$sousRequete = 'di.ce_observation IN '.
1981 aurelien 116
				"(SELECT id_observation FROM del_observation AS do WHERE $clauseWhereSousRequete ) ";
1922 jpm 117
			$this->requete['where']['OR'][] = "( $sousRequete )";
2033 aurelien 118
			unset($this->requete['join'][$this->getSqlJointureObs()]);
1922 jpm 119
		}
120
 
1840 jpm 121
		if (isset($this->requete['where']['OR']) && count($this->requete['where']['OR']) > 0) {
122
			$this->requete['where']['AND'][] = '('.implode(' OR ', $this->requete['where']['OR']).')';
123
		}
124
 
125
		$where = ' TRUE ';
126
		if (isset($this->requete['where']['AND']) && count($this->requete['where']['AND']) > 0) {
127
			$where = implode(' AND ', $this->requete['where']['AND']).' ';
128
		}
129
		return $where;
130
	}
131
 
132
	private function addGroupBy($groupBy) {
133
		$this->requete['groupby'][] = $groupBy;
134
	}
135
 
136
	public function getGroupBy() {
137
		$groupby = '';
138
		if (isset($this->requete['groupby']) && count($this->requete['groupby']) > 0) {
139
			$groupby = 'GROUP BY '.implode(', ', array_unique($this->requete['groupby'])).' ';
140
		}
141
		return $groupby;
142
	}
143
 
144
	private function addOrderBy($orderby) {
145
		$this->requete['orderby'][] = $orderby;
146
	}
147
 
148
	public function getOrderBy() {
149
		$orderby = '';
150
		if (isset($this->requete['orderby']) && count($this->requete['orderby']) > 0) {
151
			$orderby = 'ORDER BY '.implode(', ', array_unique($this->requete['orderby'])).' ';
152
		}
153
		return $orderby;
154
	}
155
 
156
	public function getLimit() {
157
		return 'LIMIT '.$this->parametres['navigation.depart'].','.$this->parametres['navigation.limite'].' ';
158
	}
159
 
160
	/**
161
	 * @param $p les paramètres (notamment de masque) passés par l'URL et déjà traités/filtrés (sauf quotes)
162
	 * @param $req le tableau, passé par référence représentant les composants de la requête à bâtir
163
	 */
164
	public function ajouterContraintes() {
165
		$this->ajouterContrainteAuteur();
166
		$this->ajouterContrainteDate();
1985 aurelien 167
		$this->ajouterContraintePays();
1840 jpm 168
		$this->ajouterContrainteDepartement();
169
		$this->ajouterContrainteIdZoneGeo();
170
		$this->ajouterContrainteGenre();
171
		$this->ajouterContrainteFamille();
172
		$this->ajouterContrainteNs();
173
		$this->ajouterContrainteNn();
174
		$this->ajouterContrainteReferentiel();
175
		$this->ajouterContrainteCommune();
2140 mathias 176
		$this->ajouterContraintePnInscrits();
1840 jpm 177
	}
178
 
179
	private function ajouterContrainteAuteur() {
180
		if (isset($this->parametres['masque.auteur'])) {
181
			$auteur = $this->parametres['masque.auteur'];
182
			// id du poster de l'obs
1871 jpm 183
			$prefixe = $this->getPrefixe();
1840 jpm 184
 
185
			if (is_numeric($auteur)) {
186
				$this->ajouterContrainteAuteurId();
1871 jpm 187
			} elseif(preg_match('/@[a-z0-9-]+(?:\.[a-z0-9-]+)*\.[a-z]{2,}$/i', $auteur)) {
1840 jpm 188
				$this->ajouterContrainteAuteurEmail();
189
			} else {
190
				$this->ajouterContrainteAuteurIntitule();
191
			}
192
		}
193
	}
194
 
195
	private function ajouterContrainteAuteurId() {
196
		$id = $this->parametres['masque.auteur'];
1871 jpm 197
		$prefixe = $this->getPrefixe();
2048 mathias 198
		$sqlTpl = "($prefixe.ce_utilisateur = %1\$d)";
1840 jpm 199
		$whereAuteur = sprintf($sqlTpl, $id);
200
		$this->addWhere('masque.auteur', $whereAuteur);
201
	}
202
 
203
	private function ajouterContrainteAuteurEmail() {
204
		$email = $this->parametres['masque.auteur'];
1871 jpm 205
		$prefixe = $this->getPrefixe();
2048 mathias 206
		$sqlTpl = "($prefixe.courriel_utilisateur LIKE %1\$s )";
1840 jpm 207
		$emailP = $this->bdd->proteger("$email%");
208
		$whereAuteur = sprintf($sqlTpl, $emailP);
209
		$this->addWhere('masque.auteur', $whereAuteur);
210
	}
211
 
212
	/**
213
	 * Retourne une clause where du style:
214
	 * CONCAT(IF(du.prenom IS NULL, "", du.prenom), [...] vdi.i_nomutilisateur) REGEXP 'xxx'
215
	 */
216
	private function ajouterContrainteAuteurIntitule() {
217
		$auteurExplode = explode(' ', $this->parametres['masque.auteur']);
218
		$nbreMots = count($auteurExplode);
219
 
220
		if ($nbreMots == 1) {
221
			$this->ajouterContrainteAuteurPrenomOuNom();
222
		} else if ($nbreMots == 2) {
223
			$this->ajouterContrainteAuteurPrenomEtNom();
224
		}
225
	}
226
 
227
	private function ajouterContrainteAuteurPrenomOuNom() {
228
		$prenomOuNom = $this->parametres['masque.auteur'];
229
 
230
		$sqlTpl = 'CONCAT(%s,%s) LIKE %s';
1871 jpm 231
		$prefixe = $this->getPrefixe();
232
		$champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom, $prefixe);
233
		$champsNomSql = self::ajouterIfNullPourConcat($this->champsNom, $prefixe);
1840 jpm 234
		$auteurMotif = $this->bdd->proteger("%$prenomOuNom%");
235
 
236
		$auteurWhere = sprintf($sqlTpl, $champsPrenomSql, $champsNomSql, $auteurMotif);
237
		$this->addWhere('masque.auteur', $auteurWhere);
238
	}
239
 
240
	private function ajouterContrainteAuteurPrenomEtNom() {
241
		list($prenom, $nom) = explode(' ', $this->parametres['masque.auteur']);
242
 
243
		$sqlTpl = '(CONCAT(%1$s,%2$s) LIKE %3$s AND CONCAT(%1$s,%2$s) LIKE %4$s)';
1871 jpm 244
		$prefixe = $this->getPrefixe();
245
		$champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom, $prefixe);
246
		$champsNomSql = self::ajouterIfNullPourConcat($this->champsNom, $prefixe);
1840 jpm 247
		$prenomMotif = $this->bdd->proteger("%$prenom%");
248
		$nomMotif = $this->bdd->proteger("%$nom%");
249
 
250
		$auteurWhere = sprintf($sqlTpl, $champsPrenomSql, $champsNomSql, $prenomMotif, $nomMotif);
251
		$this->addWhere('masque.auteur', $auteurWhere);
252
	}
253
 
1881 jpm 254
	/**
255
	 * Lorsque l'on concatène des champs, un seul NULL prend le dessus.
1840 jpm 256
	 * Il faut donc utiliser la syntaxe IFNULL(%s, "").
1881 jpm 257
	 * Cette fonction effectue aussi l'implode() "final".
1840 jpm 258
	 */
1871 jpm 259
	private static function ajouterIfNullPourConcat($champs, $prefixe) {
1840 jpm 260
		$champsProteges = array();
261
		foreach ($champs as $champ) {
1871 jpm 262
			if (strstr($champ, '.') === false) {
263
				$champ = "$prefixe.$champ";
264
			}
1840 jpm 265
			$champsProteges[] = "IFNULL($champ, '')";
266
		}
267
		return implode(',', $champsProteges);
268
	}
269
 
270
	private function ajouterContrainteDate() {
271
		if (isset($this->parametres['masque.date'])) {
272
			$date = $this->parametres['masque.date'];
1871 jpm 273
			if (preg_match('/^\d{4}$/', $date) && $date < 2030 && $date > 1600) {
274
				$sqlTpl = "YEAR(do.date_observation) = %d";
1840 jpm 275
				$dateWhere = sprintf($sqlTpl, $date);
276
				$this->addWhere('masque.date', $dateWhere);
277
			} else {
1871 jpm 278
				$sqlTpl = "do.date_observation = %s";
279
				$dateP = $this->bdd->proteger($date);
1840 jpm 280
				$dateWhere = sprintf($sqlTpl, $dateP);
281
				$this->addWhere('masque.date', $dateWhere);
282
			}
1871 jpm 283
 
2031 aurelien 284
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 285
		}
286
	}
287
 
288
	private function ajouterContrainteDepartement() {
289
		if (isset($this->parametres['masque.departement'])) {
290
			$dept = $this->parametres['masque.departement'];
2183 delphine 291
			$deptMotif = $this->bdd->proteger("$dept");
2186 delphine 292
			$this->addWhere('masque.departement', "do.ce_zone_geo like $deptMotif");
1871 jpm 293
 
2031 aurelien 294
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 295
		}
296
	}
1985 aurelien 297
 
2048 mathias 298
	private function ajouterContraintePays() {
1985 aurelien 299
		if (isset($this->parametres['masque.pays'])) {
300
			// Attention le standard contient parfois FX pour la france métropolitaine
301
			// Dans ce cas particulier on cherche donc FR et FX
302
			$this->parametres['masque.pays'] = strtoupper($this->parametres['masque.pays']);
303
			if(strpos($this->parametres['masque.pays'], 'FR') !== false) {
304
				$this->parametres['masque.pays'] = str_replace('FR', 'FR,FX', $this->parametres['masque.pays']);
305
			}
2048 mathias 306
			$pays = explode(',', $this->parametres['masque.pays']);
307
			$pays = implode(',', $this->bdd->proteger($pays));
308
			$this->addWhere('masque.pays', "do.pays IN ($pays)");
309
 
310
			$this->ajouterJoinObsSiNecessaire();
311
		}
1985 aurelien 312
	}
1840 jpm 313
 
314
	private function ajouterContrainteIdZoneGeo() {
315
		if (isset($this->parametres['masque.id_zone_geo'])) {
316
			$idZgMotif = $this->bdd->proteger($this->parametres['masque.id_zone_geo']);
1871 jpm 317
			$this->addWhere('masque.id_zone_geo', "do.ce_zone_geo = $idZgMotif");
2031 aurelien 318
 
319
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 320
		}
321
	}
322
 
323
	private function ajouterContrainteGenre() {
324
		if (isset($this->parametres['masque.genre'])) {
325
			$genre = $this->parametres['masque.genre'];
2004 mathias 326
			$genreMotif = $this->bdd->proteger("$genre%");
1871 jpm 327
			$this->addWhere('masque.genre', "do.nom_sel LIKE $genreMotif");
328
 
2031 aurelien 329
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 330
		}
331
	}
332
 
333
	private function ajouterContrainteFamille() {
334
		if (isset($this->parametres['masque.famille'])) {
335
			$familleMotif = $this->bdd->proteger($this->parametres['masque.famille']);
1871 jpm 336
			$this->addWhere('masque.famille', "do.famille = $familleMotif");
337
 
2031 aurelien 338
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 339
		}
340
	}
341
 
342
	private function ajouterContrainteNs() {
343
		if (isset($this->parametres['masque.ns'])) {
344
			$ns = $this->parametres['masque.ns'];
345
			$nsMotif = $this->bdd->proteger("$ns%");
1871 jpm 346
			$this->addWhere('masque.ns', "do.nom_sel LIKE $nsMotif");
2031 aurelien 347
 
348
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 349
		}
350
	}
351
 
352
	private function ajouterContrainteNn() {
353
		if (isset($this->parametres['masque.nn'])) {
1871 jpm 354
			$sqlTpl = '(do.nom_sel_nn = %1$d OR do.nom_ret_nn = %1$d)';
1840 jpm 355
			$nnWhere = sprintf($sqlTpl, $this->parametres['masque.nn']);
356
			$this->addWhere('masque.nn', $nnWhere);
1871 jpm 357
 
2031 aurelien 358
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 359
		}
360
	}
361
 
362
	private function ajouterContrainteReferentiel() {
363
		if (isset($this->parametres['masque.referentiel'])) {
364
			$ref = $this->parametres['masque.referentiel'];
2181 delphine 365
			$refMotif = $this->bdd->proteger("$ref");
366
			$this->addWhere('masque.referentiel', "do.nom_referentiel = $refMotif");
1871 jpm 367
 
2031 aurelien 368
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 369
		}
370
	}
371
 
372
	private function ajouterContrainteCommune() {
373
		if (isset($this->parametres['masque.commune'])) {
374
			$commune = $this->parametres['masque.commune'];
375
			$communeMotif = $this->bdd->proteger("$commune%");
1871 jpm 376
			$this->addWhere('masque.commune', "do.zone_geo LIKE $communeMotif");
377
 
2031 aurelien 378
			$this->ajouterJoinObsSiNecessaire();
379
		}
380
	}
2140 mathias 381
 
382
	/**
383
	 * Si masque.pninscritsseulement vaut true, les observations ayant un tag
384
	 * "plantnet" mais dont l'auteur n'est pas inscrit à TB seront éliminées
385
	 * (décision FlorisTic 2016-09)
386
	 */
387
	protected function ajouterContraintePnInscrits() {
388
		if (isset($this->parametres['masque.pninscritsseulement'])) {
389
			// avec la classe ParametresFiltrage, on ne passe là que si le masque vaut 1
2189 delphine 390
			$motifMotClePlantnet = "'plantnet'";
391
			$this->addWhere('masque.pninscritsseulement', "((do.input_source != $motifMotClePlantnet) OR
2190 delphine 392
			    (do.input_source = $motifMotClePlantnet AND do.ce_utilisateur != 0))");
2140 mathias 393
 
394
			$this->ajouterJoinObsSiNecessaire();
395
		}
396
	}
2031 aurelien 397
 
398
	private function ajouterJoinObsSiNecessaire() {
1871 jpm 399
			if ($this->etreAppliImg()) {
2033 aurelien 400
				$this->addJoin($this->getSqlJointureObs());
1871 jpm 401
			}
1840 jpm 402
	}
2033 aurelien 403
 
2035 aurelien 404
	private function getSqlJointureObs() {
405
		$typeJointure = !empty($this->parametres['masque']) ? 'LEFT' : 'INNER';
406
		return $typeJointure.' JOIN del_observation AS do ON (di.ce_observation = do.id_observation) ';
2033 aurelien 407
	}
1840 jpm 408
 
2140 mathias 409
	// la constrainte de nostre bon roy
1845 jpm 410
	public function ajouterConstrainteAppliObs() {
411
		$this->ajouterContrainteTagCel();
412
		$this->ajouterContrainteType();
2140 mathias 413
		// TODO : ATTENTION -> vu que l'on utilise une vue basée sur les images, nous devons grouper par obs
1871 jpm 414
		$this->addGroupBy('do.id_observation');
1845 jpm 415
	}
416
 
417
	private function ajouterContrainteType() {
1933 aurelien 418
		// Les contraintes régissant les onglets sont issus de la réunion dont le compte rendu
419
		// disponible ici : http://tela-botanica.net/intranet/wakka.php?wiki=Octobre2014
420
		// Ce lien est à modifier pour pointer vers toute nouvelle réunion modifiant ce fonctionnement
421
 
1871 jpm 422
		if (isset($this->parametres['masque.type'])) {
423
			if (array_key_exists('adeterminer', $this->parametres['masque.type'])) {
1933 aurelien 424
				// A DETERMINER : toutes les observations qui ont le tag "aDeterminer"
425
				// *ou* qui n'ont pas de nom d'espèce
1871 jpm 426
				// *ou* qui ont la "certitude" à ("aDeterminer" *ou* "douteux")
427
				$this->addWhere('masque.type', '('.
2188 delphine 428
					'do.certitude = "à déterminer" '.
1871 jpm 429
					'OR do.certitude = "douteux" '.
430
					'OR do.mots_cles_texte LIKE "%aDeterminer%" '.
431
					'OR do.nom_sel_nn IS NULL '.
432
					'OR do.nom_sel_nn = 0 '.// il ne DEVRAIT pas y avoir d'entrées à 0, mais il y en a quand-même !!
433
					')');
434
			}
1933 aurelien 435
 
1871 jpm 436
			if (array_key_exists('validees', $this->parametres['masque.type'])) {
1933 aurelien 437
				// VALIDEES : toutes les observations ayant un commentaire doté de proposition_retenue = 1
438
				// ou bien possédant une proposition initiale avec un nom valide ayant totalisé un score d'au moins 4
439
				// (ce qui correspond à au moins deux votes positifs dans la plupart des cas, dont un identifié)
440
				$sous_requete_score_prop_votees = $this->getSousRequeteSommeVotesPropositions();
1871 jpm 441
				$this->addJoin('INNER JOIN del_commentaire AS dc '.
1933 aurelien 442
					'ON ( '.
443
						'do.id_observation = dc.ce_observation '.
444
						'AND ( '.
445
							'dc.proposition_retenue = 1 OR '.
446
							'( '.
447
								'dc.proposition_initiale = 1 '.
448
								'AND dc.nom_sel_nn != 0 '.
449
								'AND dc.nom_sel_nn IS NOT NULL '.
450
								' AND dc.id_commentaire IN ('.$sous_requete_score_prop_votees.' >= 4) '.
451
							') '.
452
						') '.
453
					')'
454
				);
1871 jpm 455
			}
1933 aurelien 456
 
457
			if(array_key_exists('aconfirmer', $this->parametres['masque.type'])) {
458
				// A CONFIRMER : toutes les observations moins les validées et à confirmer
459
				// i.e. : des observations avec un nom valide, qui ne sont pas à déterminer
460
				// (ni certitude "aDeterminer" ou "douteuse", ni mot clé),
461
				// ne possédant pas de proposition officiellement retenue
462
				// et ayant une proposition initiale totalisant un score de moins de 4
2048 mathias 463
				$sous_requete_score_prop_votees = $this->getSousRequeteSommeVotesPropositions();
1933 aurelien 464
				$this->addWhere('masque.type',
465
						'('.
466
							'do.id_observation IN ('.
467
								'SELECT dc.ce_observation FROM del_commentaire dc WHERE dc.proposition_retenue = 0'.
2048 mathias 468
								' AND ( '.
469
									'dc.proposition_initiale = 1 '.
470
									'AND dc.nom_sel_nn != 0 '.
471
									'AND dc.nom_sel_nn IS NOT NULL '.
472
									'AND dc.id_commentaire IN ('.$sous_requete_score_prop_votees.' < 4) '.
1933 aurelien 473
								') '.
474
							') AND do.id_observation NOT IN ('.
475
								'SELECT dc.ce_observation FROM del_commentaire dc '.
476
								'WHERE '.
477
								' dc.proposition_retenue = 1'.
478
							') '.
2048 mathias 479
							'AND do.certitude != "douteux" AND do.certitude != "aDeterminer" '.
480
							'AND do.mots_cles_texte NOT LIKE "%aDeterminer%" '.
481
							'AND do.nom_sel_nn != 0 '.
1933 aurelien 482
							'AND do.nom_sel_nn IS NOT NULL'.
483
						') '
484
					);
485
			}
1845 jpm 486
 
2033 aurelien 487
			$this->ajouterJoinObsSiNecessaire();
1845 jpm 488
		}
489
	}
1933 aurelien 490
 
491
	private function getSousRequeteSommeVotesPropositions() {
492
		// ATTENTION : un vote identifié compte 3 votes anonymes (dans les deux sens)
2048 mathias 493
		return  'SELECT ce_proposition '.
494
				'FROM del_commentaire_vote dcv '.
495
				'GROUP BY ce_proposition HAVING '.
496
				'SUM(CASE '.
497
				'	WHEN valeur = 1 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' != 0 THEN 3 '.
498
				'	WHEN valeur = 0 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' != 0 THEN -3 '.
499
				'	WHEN valeur = 1 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' = 0 THEN 1 '.
500
				'	WHEN valeur = 0 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' = 0 THEN -1 '.
501
				'END '.
1933 aurelien 502
			') ';
503
	}
1845 jpm 504
 
1840 jpm 505
	public function ajouterConstrainteAppliImg() {
506
		$this->ajouterContrainteMilieu();
507
		$this->ajouterContrainteTri();
508
		$this->ajouterContrainteTagCel();
509
		$this->ajouterContrainteTagDel();
2140 mathias 510
		$this->ajouterContraintePnInscrits();
1840 jpm 511
	}
512
 
513
	private function ajouterContrainteMilieu() {
514
		if (isset($this->parametres['masque.milieu'])) {
515
			$milieu = $this->parametres['masque.milieu'];
516
			$milieuMotif = $this->bdd->proteger("%$milieu%");
1871 jpm 517
			$this->addWhere('masque.milieu', "do.milieu LIKE $milieuMotif");
518
 
2033 aurelien 519
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 520
		}
521
	}
522
 
523
	private function ajouterContrainteTri() {
524
		if (isset($this->parametres['tri'])) {
525
			$tri = $this->parametres['tri'];
526
 
1863 jpm 527
			if (isset($this->parametres['protocole'])  && ($tri == 'moyenne-arithmetique' || $tri == 'points')) {
1840 jpm 528
				// $this->parametres['protocole'] *est* défini (cf Outils::filtrerUrlsParams...())
1871 jpm 529
				$sqlTpl = 'LEFT JOIN del_image_stat AS dis ON di.id_image = dis.ce_image AND dis.ce_protocole = %d';
1840 jpm 530
				$triSql = sprintf($sqlTpl, $this->parametres['protocole']);
531
				$this->addJoinDis($triSql);
532
			}
533
 
534
			if (isset($this->parametres['ordre']) && $tri == 'tags') {
535
				$typeJointure = ($this->parametres['ordre'] == 'desc') ? 'INNER' : 'LEFT';
1871 jpm 536
				$this->addJoin("$typeJointure JOIN del_image_stat AS dis ON di.id_image = dis.ce_image");
1840 jpm 537
			}
538
		}
539
	}
540
 
541
	private function ajouterContrainteTagCel() {
1845 jpm 542
		if (isset($this->parametres['masque.tag_cel'])) {
1840 jpm 543
			if (isset($this->parametres['masque.tag_cel']['AND'])) {
544
				$tags = $this->parametres['masque.tag_cel']['AND'];
545
				$clausesWhere = array();
546
				foreach ($tags as $tag) {
547
					$tagMotif = $this->bdd->proteger("%$tag%");
1922 jpm 548
					if ($this->etreAppliImg()) {
1981 aurelien 549
						$sousRequete = 'SELECT id_observation '.
1922 jpm 550
							'FROM del_observation '.
551
							"WHERE mots_cles_texte LIKE $tagMotif ";
552
						$sql = " (di.mots_cles_texte LIKE $tagMotif OR di.id_image IN ($sousRequete) ) ";
553
					} else {
554
						// WARNING : la sous-requête est la meilleure solution trouvée pour contrer le fonctionnement
555
						// étrange de l'optimiseur de MYSQL 5.6 (à retester avec Mysql 5.7 et suivant).
556
						$sousRequete = 'SELECT DISTINCT ce_observation '.
557
							'FROM del_image '.
558
							"WHERE mots_cles_texte LIKE $tagMotif ".
559
							'AND ce_observation IS NOT NULL';
560
						$sql = " (do.mots_cles_texte LIKE $tagMotif OR do.id_observation IN ($sousRequete)) ";
561
					}
562
					$clausesWhere[] = $sql;
1840 jpm 563
				}
564
				$whereTags = implode(' AND ', $clausesWhere);
565
				$this->addWhere('masque.tag_cel', "($whereTags)");
566
			} else if (isset($this->parametres['masque.tag_cel']['OR'])) {
567
				$tags = $this->parametres['masque.tag_cel']['OR'];
1922 jpm 568
				$tagMotif = $this->bdd->proteger(implode('|', $tags));
1871 jpm 569
				$sqlTpl = "CONCAT(IFNULL(do.mots_cles_texte,''),IFNULL(di.mots_cles_texte,'')) REGEXP %s";
1840 jpm 570
				$tagSql = sprintf($sqlTpl, $tagMotif);
1922 jpm 571
 
1840 jpm 572
				$this->addWhere('masque.tag_cel', $tagSql);
1922 jpm 573
 
574
				if ($this->etreAppliObs()) {
575
					$this->addJoin('LEFT JOIN del_image AS di ON (di.ce_observation = do.id_observation) ');
576
				}
1840 jpm 577
			}
2033 aurelien 578
			$this->ajouterJoinObsSiNecessaire();
1840 jpm 579
		}
580
	}
581
 
582
	/**
1881 jpm 583
	 * Plusieurs solutions sont disponibles dans les anciennes versions (voir DelTk et l'historique SVN de ce fichier).
1840 jpm 584
	 */
585
	private function ajouterContrainteTagDel() {
586
		if (isset($this->parametres['masque.tag_del'])) {
2029 aurelien 587
			$nbTags = $this->getNombreDeTags();
588
			if($nbTags > 1) {
2121 mathias 589
				// sous-requêtes car le GROUP BY avec GROUP_CONCAT est *trop* lent
590
				if (isset($this->parametres['masque.tag_del']['AND'])) {
591
					foreach ($this->parametres['masque.tag_del']['AND'] as $mc) {
592
						$sousRequete = 'SELECT ce_image '.
593
							'FROM del_image_tag '.
594
							'WHERE actif = 1 '.
595
							"AND tag_normalise LIKE '%$mc%' ";
596
						$this->addWhere('masque.tag_del', "di.id_image IN ($sousRequete)");
597
					}
598
				} else if (isset($this->parametres['masque.tag_del']['OR'])) {
599
					$tagsMotif = "'(" . implode('|', $this->parametres['masque.tag_del']['OR']) . ")'";
2029 aurelien 600
					$sousRequete = 'SELECT ce_image '.
601
						'FROM del_image_tag '.
602
						'WHERE actif = 1 '.
2121 mathias 603
						"AND tag_normalise REGEXP $tagsMotif ";
604
 
605
					$this->addWhere('masque.tag_del', "di.id_image IN ($sousRequete)");
2029 aurelien 606
				}
607
			} else {
2033 aurelien 608
				// Si un seul tag est demandé il se trouve dans le OR dans le cas de la recherche
609
				// spécifique et dans le AND dans le cas de la recherche générale
610
				// WTF?
611
				$tag = "";
612
				if(isset($this->parametres['masque.tag_del']['OR'][0])) {
613
					$tag = $this->parametres['masque.tag_del']['OR'][0];
614
				} else if(isset($this->parametres['masque.tag_del']['AND'][0])) {
615
					$tag = $this->parametres['masque.tag_del']['AND'][0];
616
				}
617
 
1922 jpm 618
				$sousRequete = 'SELECT ce_image '.
2029 aurelien 619
						'FROM del_image_tag '.
620
						'WHERE actif = 1 '.
621
						'AND tag_normalise LIKE '.$this->bdd->proteger($tag.'%');
2121 mathias 622
 
623
				$this->addWhere('masque.tag_del', "di.id_image IN ($sousRequete)");
1840 jpm 624
			}
2029 aurelien 625
 
1840 jpm 626
		}
627
	}
2029 aurelien 628
 
629
	private function getNombreDeTags() {
630
		$somme = 0;
631
		if (isset($this->parametres['masque.tag_del']['AND'])) {
632
			$somme = count($this->parametres['masque.tag_del']['AND']);
2048 mathias 633
		} else {
634
			$somme = count($this->parametres['masque.tag_del']['OR']);
2029 aurelien 635
		}
636
		return $somme;
637
	}
1840 jpm 638
 
639
	/**
640
	 * Partie spécifique à PictoFlora:
1863 jpm 641
	 * Attention : si le critère de tri n'est pas suffisant, les résultats affichés peuvent varier à chaque appel
642
	 * de la même page de résultat de PictoFlora.
1840 jpm 643
	 */
644
	public function definirOrdreSqlAppliImg() {
645
		$ordre = $this->parametres['ordre'];
1968 aurelien 646
 
647
		$tri = isset($this->parametres['tri']) ? $this->parametres['tri'] : '';
648
		switch ($tri) {
1863 jpm 649
			case 'moyenne-arithmetique' :
1888 jpm 650
				$this->addOrderBy("dis.moyenne $ordre, dis.nb_votes $ordre, id_image $ordre");
1840 jpm 651
				break;
652
			case 'points' :
1888 jpm 653
				$this->addOrderBy("dis.nb_points $ordre, dis.moyenne $ordre, dis.nb_votes $ordre, id_image $ordre");
1840 jpm 654
				break;
655
			case 'tags' :
1888 jpm 656
				$this->addOrderBy("dis.nb_tags $ordre, id_image $ordre");
1840 jpm 657
				break;
658
			case 'date_observation' :
1871 jpm 659
				$this->addOrderBy("date_observation $ordre, ce_observation $ordre");
1840 jpm 660
				break;
1863 jpm 661
			case 'date_transmission' :
1840 jpm 662
			default:
1871 jpm 663
				$this->addOrderBy("di.date_transmission $ordre, ce_observation $ordre");
1840 jpm 664
		}
665
	}
666
 
1845 jpm 667
	public function definirOrdreSqlAppliObs() {
668
		$ordre = $this->parametres['ordre'];
669
 
670
		// parmi self::$tri_possible
1968 aurelien 671
		$tri = isset($this->parametres['tri']) ? $this->parametres['tri'] : '';
672
			switch ($tri) {
1845 jpm 673
			case 'date_observation' :
674
				$this->addOrderBy("date_observation $ordre, id_observation $ordre");
1933 aurelien 675
				break;
2048 mathias 676
			case 'nb_commentaires' :
677
				$sql_nb_comms = '(SELECT COUNT(id_commentaire) FROM del_commentaire AS dc WHERE ce_observation = id_observation)';
678
				$this->addOrderBy("$sql_nb_comms $ordre, id_observation $ordre");
1845 jpm 679
				break;
1933 aurelien 680
			case 'date_transmission' :
1845 jpm 681
			default:
1871 jpm 682
				$this->addOrderBy("do.date_transmission $ordre, id_observation $ordre");
1845 jpm 683
		}
684
	}
685
 
1840 jpm 686
	public function getAliasDesChamps($champsEtAlias, $select = null, $prefix = null) {
687
		$arr = ($select) ? array_intersect_key($champsEtAlias, array_flip($select)) :  $champsEtAlias;
688
		$keys = array_keys($arr);
689
 
690
		if ($prefix) {
2184 delphine 691
		    array_walk($keys, function(&$val, $k, $prefix) { $val = sprintf("%s.`%s`", $prefix, $val);}, $prefix);
1840 jpm 692
		} else {
2184 delphine 693
		    array_walk($keys, function(&$val, $k) { $val = sprintf("`%s`", $val);});
1840 jpm 694
		}
695
 
2184 delphine 696
		return implode(', ', array_map(function($v, $k) {return sprintf("%s AS `%s`", $k, $v);}, $arr, $keys));
1840 jpm 697
	}
698
 
699
	public function getVotesDesImages($idsImages, $protocole = null) {
700
		if (!$idsImages) return;
701
 
702
		$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping');
703
		$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping');
704
 		$selectVotes = array('id_vote', 'ce_image', 'ce_protocole', 'ce_utilisateur', 'valeur', 'date');
705
		$selectProtocole = array('id_protocole', 'intitule', 'descriptif', 'tag');
706
		$voteChamps = $this->getAliasDesChamps($mappingVotes, $selectVotes, 'v'); // "v": cf alias dans la requête
707
		$protoChamps = $this->getAliasDesChamps($mappingProtocoles, $selectProtocole, 'p');
708
		$idImgsConcat = implode(',', $idsImages);
709
 
710
		$requete = "SELECT $voteChamps, $protoChamps ".
711
			'FROM del_image_vote AS v '.
712
			'	INNER JOIN del_image_protocole AS p ON (v.ce_protocole = p.id_protocole) '.
713
			"WHERE v.ce_image IN ($idImgsConcat) ".
714
			($protocole ? "	AND v.ce_protocole = $protocole " : '').
715
			"ORDER BY FIELD(v.ce_image, $idImgsConcat) ".
716
			'-- '.__FILE__.':'.__LINE__;
717
		return $this->bdd->recupererTous($requete);
718
	}
719
 
720
	/**
721
	 * Ajoute les informations sur le protocole et les votes aux images.
722
	 *
723
	 * ATTENTION : Subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien
724
	 * plus pratique pour associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
725
	 * Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
726
	 * cf ListeImages::reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions
727
	 * simultanément lorsque c'est encore possible.
728
	 */
1881 jpm 729
	// TODO : supprimer cette "subtilité" source d'erreurs
1840 jpm 730
	public function ajouterInfosVotesProtocoles($votes, &$images) {
731
		if (!$votes) return;
1845 jpm 732
 
1840 jpm 733
		$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping');
734
		$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping');
735
 
736
		// pour chaque vote
737
		foreach ($votes as $vote) {
738
			$imgId = $vote['image.id'];
739
			$protoId = $vote['protocole.id'];
740
 
741
			if (!array_key_exists('protocoles_votes', $images[$imgId]) ||
1863 jpm 742
					!array_key_exists($protoId, $images[$imgId]['protocoles_votes'])) {
1840 jpm 743
				// extrait les champs spécifique au protocole (le LEFT JOIN de chargerVotesImage les ramène en doublons
744
				$protocole = array_intersect_key($vote, array_flip($mappingProtocoles));
745
				$images[$imgId]['protocoles_votes'][$protoId] = $protocole;
746
			}
747
 
748
			$chpsVotes = array('id_vote', 'ce_image', 'ce_utilisateur', 'valeur', 'date');
749
			$voteSelection = array_intersect_key($mappingVotes, array_flip($chpsVotes));
750
			$vote = array_intersect_key($vote, array_flip($voteSelection));
751
			$images[$imgId]['protocoles_votes'][$protoId]['votes'][$vote['vote.id']] = $vote;
752
		}
753
	}
1845 jpm 754
 
755
	public function getTotalLignesTrouvees() {
1888 jpm 756
		$resultat = $this->bdd->recuperer('SELECT FOUND_ROWS() AS nbre -- '.__FILE__.':'.__LINE__);
1845 jpm 757
		return intval($resultat['nbre']);
758
	}
2121 mathias 759
 
760
	public function getRequeteIdObsMonactiviteTout($id_utilisateur, $limite = "") {
761
		/*
762
		Une action c'est :
763
		- Quelqu'un commente mon observation
764
		- Quelqu'un fait une proposition sur mon observation
765
 
766
		- Quelqu'un vote pour ma proposition
767
		- Quelqu'un commente ma proposition ou mon commentaire
768
 
769
		- Quelqu'un vote pour une proposition sur mon observation
770
		- Quelqu'un commente une proposition ou un commentaire sur mon observation
2072 aurelien 771
		*/
772
 
773
		// on selectionne aussi la combinaison des champs de date afin que la liste soit triée correctement
2121 mathias 774
		// pas d'autre meilleure solution mais attention ce comportement dépend entièrement de mysql
775
		$requete = "SELECT SQL_CALC_FOUND_ROWS DISTINCT id_observation, ".$this->getCombinaisonChampsDateMax()." as date_max FROM del_observation do ".
776
				$this->getJointureMonActivite($id_utilisateur).
777
				$this->getConditionMonActivite($id_utilisateur).
778
				"ORDER BY date_max DESC ".
2049 aurelien 779
				$limite;
2121 mathias 780
 
781
		return $requete;
2049 aurelien 782
	}
783
 
784
	public function getRequeteNbEvenementsDepuisDate($id_utilisateur, $date) {
2121 mathias 785
		$requete = "SELECT COUNT(DISTINCT id_observation) as nb_evenements FROM del_observation do ".
786
				$this->getJointureMonActivite($id_utilisateur).
2049 aurelien 787
				$this->getConditionMonActivite($id_utilisateur).
2121 mathias 788
				"AND ".$this->getCombinaisonChampsDateMax()." > '".$date."' ".
789
				"ORDER BY ".$this->getCombinaisonChampsDateMax()." DESC ";
790
 
2049 aurelien 791
		return $requete;
792
	}
793
 
794
	public function getEvenementsObs($idsObsConcat, $id_utilisateur) {
2121 mathias 795
		$sous_champ_date_max = $this->getCombinaisonChampsDateMax()." as date_max";
2063 aurelien 796
		$sous_champ_date = "dc.date as date_com, dc.nom_sel as nom_sel_com, dcpr.ce_commentaire_parent as parent_com, dcv.date as date_vote, ".
797
							"do.date_observation as date_obs, dcp.date_validation as date_validation, dcpr.date as date_com_reponse, ".
2121 mathias 798
							"dcvp.nom_sel as nom_sel_com_parent";
2056 aurelien 799
		$sous_champs_utilisateurs = "dc.ce_utilisateur as utilisateur_commentaire, dcp.ce_utilisateur as utilisateur_commentaire_valide, ".
800
									"dcv.ce_utilisateur as utilisateur_vote_commentaire, do.ce_utilisateur as utilisateur_observation, ".
2063 aurelien 801
									"dcp.ce_validateur as utilisateur_validation, dcvp.ce_utilisateur as utilisateur_commentaire_vote, ".
2121 mathias 802
									"dcpr.ce_utilisateur as utilisateur_commentaire_reponse";
2067 aurelien 803
 
804
		$sous_champs_infos = "dc.nom_sel as proposition_commentaire_nom_sel, dc.texte as proposition_commentaire_texte, dcp.nom_sel as proposition_validee_nom_sel, ".
2121 mathias 805
							 "dcvp.nom_sel as proposition_commentaire_nom_sel_votee, dcpr.texte as proposition_commentaire_texte_commente";
2067 aurelien 806
 
2121 mathias 807
		$requete = "SELECT DISTINCT id_observation, ".$sous_champs_utilisateurs.", ".$sous_champ_date_max.", ".$sous_champ_date.", ".$sous_champs_infos." ".
808
				"FROM del_observation do ".
809
				$this->getJointureMonActivite($id_utilisateur).
810
				$this->getConditionMonActivite($id_utilisateur).
811
				"AND id_observation IN ($idsObsConcat) ORDER BY date_max DESC";
812
 
2049 aurelien 813
		$evenements = $this->bdd->recupererTous($requete);
814
		return $evenements;
815
	}
816
 
2121 mathias 817
	public function getJointureMonActivite($id_utilisateur) {
818
		return 	// quelqu'un commente mon observation ou fait une proposition
819
		"LEFT JOIN del_commentaire dc ON do.id_observation = dc.ce_observation ".
820
		"	AND do.ce_utilisateur = ".$id_utilisateur." ".
821
		"	AND dc.ce_utilisateur != ".$id_utilisateur." ".
822
		// quelqu'un valide ma proposition (et ce n'est pas moi qui l'ai validée)
823
		"LEFT JOIN del_commentaire dcp ON do.id_observation = dcp.ce_observation ".
824
		"	AND dcp.nom_sel IS NOT NULL AND dcp.ce_validateur != ".$id_utilisateur." ".
825
		"	AND dcp.ce_validateur != 0 ".
826
		"	AND dcp.date_validation IS NOT NULL ".
827
		"	AND dcp.ce_utilisateur = ".$id_utilisateur." ".
828
		// quelqu'un vote pour ma proposition ou sur une proposition sur une de mes observations
829
		"LEFT JOIN del_commentaire dcvp ON do.id_observation = dcvp.ce_observation ".
830
		"LEFT JOIN del_commentaire_vote dcv ON dcv.ce_proposition = dcvp.id_commentaire ".
2056 aurelien 831
		"AND (dcvp.ce_utilisateur = $id_utilisateur OR do.ce_utilisateur = $id_utilisateur) ".
2121 mathias 832
		"AND dcv.ce_utilisateur != $id_utilisateur ".
2063 aurelien 833
		"AND dcv.ce_utilisateur != dcvp.ce_utilisateur ".
2059 aurelien 834
		// Quelqu'un répond à l'un de mes commentaires ou commente une de mes propositions
2066 aurelien 835
		"LEFT JOIN del_commentaire dcpr ON do.id_observation = dcpr.ce_observation ".
2121 mathias 836
		"AND dcpr.ce_commentaire_parent = dcvp.id_commentaire AND dcvp.ce_utilisateur = $id_utilisateur ";
837
	}
838
 
839
	public function getConditionMonActivite($id_utilisateur, $type = "autres") {
840
		//TODO: gérer les cas suivants :
841
		// demander les activités des autres sur mes obs ou propositions (c'est dejà le cas)
842
		// demander mes activités
843
		// demander toutes les activités (combinaisons des deux cas ci dessus)
844
		return 	// Vérification que l'évènement me concerne (de près ou ou de loin)
845
		"WHERE (do.ce_utilisateur = $id_utilisateur OR dc.ce_utilisateur = $id_utilisateur ".
2056 aurelien 846
		"OR dcp.ce_utilisateur = $id_utilisateur OR dcv.ce_utilisateur = $id_utilisateur ".
2121 mathias 847
		"OR dcvp.ce_utilisateur = $id_utilisateur) AND ".
848
		// mais qu'il y a au moins eu une action de la part d'une autre personne
849
		"(dc.ce_utilisateur IS NOT NULL OR dcp.ce_utilisateur IS NOT NULL OR dcv.ce_utilisateur IS NOT NULL) ";
2049 aurelien 850
	}
2121 mathias 851
 
852
	private function getCombinaisonChampsDateMax() {
853
		return "GREATEST(IFNULL(dc.date,0), IFNULL(dcv.date,0), IFNULL(do.date_observation,0), IFNULL(dcp.date_validation,0), IFNULL(dcpr.date,0))";
2049 aurelien 854
	}
1840 jpm 855
}