Subversion Repositories eFlore/Applications.del

Rev

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

Rev 1486 Rev 1490
1
<?php
1
<?php
2
/**
2
/**
3
 * @author		Raphaël Droz <raphael@tela-botanica.org>
3
 * @author		Raphaël Droz <raphael@tela-botanica.org>
4
 * @copyright	Copyright (c) 2013, Tela Botanica (accueil@tela-botanica.org)
4
 * @copyright	Copyright (c) 2013, Tela Botanica (accueil@tela-botanica.org)
5
 * @license	http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
5
 * @license	http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
6
 * @license	http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
6
 * @license	http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
7
 * @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Images
7
 * @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Images
8
 *
8
 *
9
 * Backend pour PictoFlora (del.html#page_recherche_images)
9
 * Backend pour PictoFlora (del.html#page_recherche_images)
10
 *
10
 *
11
 *
11
 *
12
 * == Notes ==
12
 * == Notes ==
13
 *
13
 *
14
 * tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages())
14
 * tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages())
15
 * Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe).
15
 * Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe).
16
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
16
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
17
 *
17
 *
18
 * protocole: il affecte l'affichage des information, mais le JSON contient déjà
18
 * protocole: il affecte l'affichage des information, mais le JSON contient déjà
19
 * l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique.
19
 * l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique.
20
 * Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné".
20
 * Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné".
21
 * Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY.
21
 * Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY.
22
 * (cf requestFilterParams())
22
 * (cf requestFilterParams())
23
 *
23
 *
24
 * Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs
24
 * Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs
25
 * utilsées ensuite pour l'ORDER BY. La situation à base de del_image_stat
25
 * utilisées ensuite pour l'ORDER BY. La situation à base de del_image_stat
26
 * est déjà bien meilleur sans être pour autant optimale. cf commentaire de sqlAddConstraint()
26
 * est déjà bien meilleure sans être pour autant optimale. cf commentaire de sqlAddConstraint()
27
 *
27
 *
28
 *
28
 *
29
 * Tags:
29
 * Tags:
30
 * Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces,
30
 * Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces,
31
 * implod()ed par des AND (tous les mots doivent matcher).
31
 * implod()ed par des AND (tous les mots doivent matcher).
32
 * Et le test effectué doit matcher sur:
32
 * Et le test effectué doit matcher sur:
33
 * %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)%
33
 * %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)%
34
 *
34
 *
35
 * Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression),
35
 * Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression),
36
 * Et le test effectué doit matcher sur:
36
 * Et le test effectué doit matcher sur:
37
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
37
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
38
 *
38
 *
39
 * Par défaut les tags sont comma-separated (OU logique).
39
 * Par défaut les tags sont comma-separated (OU logique).
40
 * Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur
40
 * Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur
41
 * des tags séparés par des espaces recherche 
41
 * des tags séparés par des espaces recherche 
42
 *
42
 *
43
 * TODO:
43
 * TODO:
44
 * -affiner la gestion de passage de mots-clefs dans le masque général.
44
 * -affiner la gestion de passage de mots-clefs dans le masque général.
45
 * - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image
45
 * - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image
46
 * plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN)
46
 * plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN)
47
 * (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/)
47
 * (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/)
48
 * - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission
48
 * - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission
49
 * (cf VIEW del_image)
49
 * (cf VIEW del_image)
50
 * - réorganiser les méthodes statiques parmis Observation, ListeObservations et ListImages2
50
 * - poursuivre la réorganisation des méthodes statiques parmis Observation, ListeObservations et ListImages2
51
 * - *peut-être*: passer requestFilterParams() en méthode de classe
51
 * - *peut-être*: passer requestFilterParams() en méthode de classe
52
 *
52
 *
53
 *
53
 *
54
 * MySQL sux:
54
 * MySQL sux:
55
 * 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);
55
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1 LIMIT 1);
56
 *	MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
56
 *	MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
57
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
57
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
58
 *	PRIMARY
58
 *	PRIMARY
59
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
59
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
60
 *	DEPENDENT SUBQUERY ... ... ... mwarf !
60
 *	DEPENDENT SUBQUERY ... ... ... mwarf !
61
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1);
61
 * EXPLAIN SELECT  id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1);
62
 *	5.5: MATERIALIZED		del_image_tag	ALL				ce_image NULL NULL NULL 38276 Using where
62
 *	5.5: MATERIALIZED		del_image_tag	ALL				ce_image NULL NULL NULL 38276 Using where
63
 *	5.1: DEPENDENT SUBQUERY	del_image_tag	index_subquery	ce_image ce_image 8 func 1 Using where
63
 *	5.1: DEPENDENT SUBQUERY	del_image_tag	index_subquery	ce_image ce_image 8 func 1 Using where
64
 * FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
64
 * FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
65
 *
65
 *
66
 */
66
 */
-
 
67
 
67
 
68
require_once(dirname(__FILE__) . '/../DelTk.php');
68
require_once(dirname(__FILE__) . '/../observations/ListeObservations.php');
69
require_once(dirname(__FILE__) . '/../observations/ListeObservations.php');
69
require_once(dirname(__FILE__) . '/../observations/Observation.php');
70
require_once(dirname(__FILE__) . '/../observations/Observation.php');
70
restore_error_handler();
71
restore_error_handler();
71
restore_exception_handler();
72
restore_exception_handler();
72
error_reporting(E_ALL);
73
error_reporting(E_ALL);
73
 
74
 
74
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc
75
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc
75
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop
76
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop
76
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
77
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
77
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
78
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
78
 
79
 
79
class ListeImages2 {
80
class ListeImages2 {
80
 
81
 
81
	// TODO: PHP-x.y, ces variables devrait être des "const"
82
	// TODO: PHP-x.y, ces variables devrait être des "const"
82
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
83
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
83
 
84
 
84
	static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags');
85
	static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags');
85
 
86
 
86
	// en plus de ceux dans ListeObservations
87
	// en plus de ceux dans DelTk
87
	static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
88
	static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
88
 
89
 
89
	static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
90
	static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
90
    'tri' => 'date_transmission', 'ordre' => 'desc',
91
    'tri' => 'date_transmission', 'ordre' => 'desc',
91
    // spécifiques à PictoFlora:
92
    // spécifiques à PictoFlora:
92
    'format' => 'XL');
93
    'format' => 'XL');
93
 
94
 
94
	static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes))
95
	static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes))
95
 
96
 
96
	static $mappings = array(
97
	static $mappings = array(
97
		'observations' => array( // v_del_image
98
		'observations' => array( // v_del_image
98
			"id_observation" => 1,
99
			"id_observation" => 1,
99
			"date_observation" => 1,
100
			"date_observation" => 1,
100
			"date_transmission" => 1, 
101
			"date_transmission" => 1, 
101
			"famille" => "determination.famille",
102
			"famille" => "determination.famille",
102
			"nom_sel" => "determination.ns",
103
			"nom_sel" => "determination.ns",
103
			"nom_sel_nn" => "determination.nn",
104
			"nom_sel_nn" => "determination.nn",
104
			"nom_referentiel" => "determination.referentiel",
105
			"nom_referentiel" => "determination.referentiel",
105
			"nt" => "determination.nt",
106
			"nt" => "determination.nt",
106
			"ce_zone_geo" => "id_zone_geo",
107
			"ce_zone_geo" => "id_zone_geo",
107
			"zone_geo" => 1,
108
			"zone_geo" => 1,
108
			"lieudit" => 1,
109
			"lieudit" => 1,
109
			"station" => 1,
110
			"station" => 1,
110
			"milieu" => 1,
111
			"milieu" => 1,
111
			"mots_cles_texte" => "mots_cles_texte",
112
			"mots_cles_texte" => "mots_cles_texte",
112
			"commentaire" => 1,
113
			"commentaire" => 1,
113
			"ce_utilisateur" => "auteur.id",
114
			"ce_utilisateur" => "auteur.id",
114
			"nom_utilisateur" => "auteur.nom",
115
			"nom_utilisateur" => "auteur.nom",
115
			"prenom_utilisateur" => "auteur.prenom",
116
			"prenom_utilisateur" => "auteur.prenom",
116
		),
117
		),
117
		'images' => array( // v_del_image
118
		'images' => array( // v_del_image
118
			'id_image' => 1,
119
			'id_image' => 1,
119
			// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte
120
			// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte
120
			// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex)
121
			// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex)
121
			'i_mots_cles_texte' => 1
122
			'i_mots_cles_texte' => 1
122
		));
123
		));
123
 
124
 
124
 
125
 
125
	public function __construct(Conteneur $conteneur = null) {
126
	public function __construct(Conteneur $conteneur = null) {
126
		$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
127
		$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
127
		$this->conteneur->chargerConfiguration('config_images.ini');
128
		$this->conteneur->chargerConfiguration('config_images.ini');
128
		$this->gestionBdd = $conteneur->getGestionBdd();
129
		$this->gestionBdd = $conteneur->getGestionBdd();
129
		$this->bdd = $this->gestionBdd->getBdd();	
130
		$this->bdd = $this->gestionBdd->getBdd();	
130
	}
131
	}
131
 
132
 
132
	public function consulter($ressources, $parametres) {
133
	public function consulter($ressources, $parametres) {
133
		/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas
134
		/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas
134
		   Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
135
		   Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
135
		   Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
136
		   Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
136
		   Le propriétaire d'une obs et de l'image associée est *toujours* le même. */
137
		   Le propriétaire d'une obs et de l'image associée est *toujours* le même. */
137
		array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
138
		array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
138
		array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
139
		array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
139
		// pour les votes, les mappings de "Observation" nous suffisent
140
		// pour les votes, les mappings de "Observation" nous suffisent
140
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
141
		array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
141
 
142
 
142
		// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
143
		// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
143
		// de `ce_image` dans ces tables jointes.
144
		// de `ce_image` dans ces tables jointes.
144
		// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
145
		// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
145
		$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
146
		$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
146
 
147
 
147
 
148
 
148
		$db = $this->bdd;
149
		$db = $this->bdd;
149
 
150
 
150
		// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
151
		// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
151
		// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs.
152
		// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs.
152
		// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
153
		// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
153
		// même si ceux-ci sont improbables (pas d'<input> pour cela).
154
		// même si ceux-ci sont improbables (pas d'<input> pour cela).
154
		$params_ip = ListeObservations::requestFilterParams($parametres,
155
		$params_ip = DelTk::requestFilterParams($parametres,
155
        array_diff(ListeObservations::$parametres_autorises,
156
        array_diff(DelTk::$parametres_autorises,
156
        array('masque.type')),
157
        array('masque.type')),
157
        $this->conteneur);
158
        $this->conteneur);
158
 
159
 
159
		// notre propre filtrage sur l'INPUT
160
		// notre propre filtrage sur l'INPUT
160
		$params_pf = self::requestFilterParams($parametres,
161
		$params_pf = self::requestFilterParams($parametres,
161
        array_merge(ListeObservations::$parametres_autorises,
162
        array_merge(DelTk::$parametres_autorises,
162
        self::$parametres_autorises));
163
        self::$parametres_autorises));
163
 
164
 
164
		/* filtrage des tags + sémantique des valeurs multiples:
165
		/* filtrage des tags + sémantique des valeurs multiples:
165
		   Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
166
		   Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
166
		   postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
167
		   postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
167
		$params_pf['masque.tag_cel'] = self::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
168
		$params_pf['masque.tag_cel'] = DelTk::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
168
		$params_pf['masque.tag_pictoflora'] = self::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
169
		$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
-
 
170
 
169
 
171
		$params = array_merge(
170
		$params = array_merge(ListeObservations::$default_params, // paramètre par défaut Identiplante
172
            DelTk::$default_params, // paramètre par défaut Identiplante
171
        self::$default_params, // paramètres par défaut PictoFlora
173
            self::$default_params, // paramètres par défaut PictoFlora
172
        $params_ip, // les paramètres passés, traités par Identiplante
174
            $params_ip, // les paramètres passés, traités par Identiplante
173
        $params_pf); // les paramètres passés, traités par PictoFlora
175
            $params_pf); // les paramètres passés, traités par PictoFlora
174
 
176
 
175
		// XXX: temp tweak
177
		// XXX: temp tweak
176
		/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'),
178
		/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'),
177
		   "%09d", $params['format']));*/
179
		   "%09d", $params['format']));*/
178
 
180
 
-
 
181
		// création des contraintes (génériques de DelTk)
-
 
182
		DelTk::sqlAddConstraint($params, $db, $req);
179
		// création des contraintes (génériques, de ListeObservations)
183
		// création des contraintes héritées de Identiplante (TODO: needed ??)
180
		ListeObservations::sqlAddConstraint($params, $db, $req, $this->conteneur);
184
		ListeObservations::sqlAddConstraint($params, $db, $req, $this->conteneur);
181
		// création des contraintes spécifiques (sur les tags essentiellement)
185
		// création des contraintes spécifiques (sur les tags essentiellement)
182
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
186
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
183
		// création des contraintes spécifiques impliquées par le masque général
187
		// création des contraintes spécifiques impliquées par le masque général
184
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
188
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
185
		// l'ORDER BY s'avére complexe
189
		// l'ORDER BY s'avére complexe
186
		self::sqlOrderBy($params, $db, $req);
190
		self::sqlOrderBy($params, $db, $req);
187
 
191
 
188
		// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
192
		// 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);
193
		// $idobs_tab = ListeObservations::getIdObs($params, $req, $db);
190
		$idobs_tab = self::getIdImages($params, $req, $db);
194
		$idobs_tab = self::getIdImages($params, $req, $db);
191
 
195
 
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
196
		// 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) {
197
		if(!$idobs_tab) {
194
			$resultat = new ResultatService();
198
			$resultat = new ResultatService();
195
			$resultat->corps = array('entete' => ListeObservations::makeJSONHeader(0, $params, Config::get('url_service')),
199
			$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
196
            'resultats' => array());
200
            'resultats' => array());
197
			return $resultat;
201
			return $resultat;
198
			/*
202
			/*
199
              header('HTTP/1.0 404 Not Found');
203
              header('HTTP/1.0 404 Not Found');
200
              // don't die (phpunit)
204
              // don't die (phpunit)
201
              throw(new Exception()); */
205
              throw(new Exception()); */
202
		}
206
		}
203
 
207
 
204
 
208
 
205
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
209
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
206
		$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
210
		$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
207
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
211
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
208
 
212
 
209
		$liaisons = self::chargerImages($db, $idobs);
213
		$liaisons = self::chargerImages($db, $idobs);
210
 
214
 
211
		/* 
215
		/* 
212
        // Q&D
216
        // Q&D
213
        $images = array();
217
        $images = array();
214
        $o = new Observation($this->conteneur);
218
        $o = new Observation($this->conteneur);
215
        foreach($idobs as $i) {
219
        foreach($idobs as $i) {
216
        $images[$i] = $o->consulter(array($i), array('justthrow' => 1));
220
        $images[$i] = $o->consulter(array($i), array('justthrow' => 1));
217
        }
221
        }
218
		*/
222
		*/
219
		list($images, $images_keyed_by_id_image) = ListeObservations::reformateImagesDoubleIndex(
223
		list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
220
			$liaisons,
224
			$liaisons,
221
			$this->conteneur->getParametre('url_images'),
225
			$this->conteneur->getParametre('url_images'),
222
			$params['format']);
226
			$params['format']);
223
 
227
 
224
 
228
 
225
		// on charge les votes pour ces images et pour *tous* les protocoles
229
		// on charge les votes pour ces images et pour *tous* les protocoles
226
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
230
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
227
 
231
 
228
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
232
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
229
		// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
233
		// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
230
		// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
234
		// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
231
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
235
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
232
		// c'est encore possible.
236
		// c'est encore possible.
233
		if($votes) Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
237
		if($votes) Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
234
 
238
 
235
		// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
239
		// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
236
		// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
240
		// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
237
		$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
241
		$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
238
        'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
242
        'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
239
		$resultat = new ResultatService();
243
		$resultat = new ResultatService();
240
		$resultat->corps = array('entete' => ListeObservations::makeJSONHeader($total, $params_header, Config::get('url_service')),
244
		$resultat->corps = array('entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
241
        'resultats' => $images);
245
        'resultats' => $images);
242
		return $resultat;
246
		return $resultat;
243
	}
247
	}
244
 
248
 
245
	/**
249
	/**
246
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
250
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
247
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
251
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
248
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
252
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
249
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
253
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
250
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
254
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
251
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
255
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
252
	 */
256
	 */
253
	static function sqlOrderBy($p, $db, &$req) {
257
	static function sqlOrderBy($p, $db, &$req) {
254
		// parmi self::$tri_possible
258
		// parmi self::$tri_possible
255
		if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
259
		if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
256
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
260
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
257
			return;
261
			return;
258
		}
262
		}
259
		
263
		
260
		if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
264
		if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
261
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
265
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
262
			return;
266
			return;
263
		}
267
		}
264
 
268
 
265
		if($p['tri'] == 'date_observation') {
269
		if($p['tri'] == 'date_observation') {
266
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
270
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
267
			return;
271
			return;
268
		}
272
		}
269
 
273
 
270
		// tri == 'date_transmission'
274
		// tri == 'date_transmission'
271
		// avant cel:r1860, date_transmission pouvait être NULL
275
		// avant cel:r1860, date_transmission pouvait être NULL
272
		// or nous voulons de la consistence (notamment pour phpunit)
276
		// or nous voulons de la consistence (notamment pour phpunit)
273
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
277
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
274
	}
278
	}
275
 
279
 
276
	/*
280
	/*
277
	 * in $p: un tableau de paramètres, dont:
281
	 * in $p: un tableau de paramètres, dont:
278
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
282
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
279
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
283
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
280
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
284
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
281
	 *
285
	 *
282
	 * in/ou: $req: un tableau de structure de requête MySQL
286
	 * in/ou: $req: un tableau de structure de requête MySQL
283
	 *
287
	 *
284
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
288
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
285
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
289
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
286
	 * Soit directement $this->consulter() si des masque.tag* sont passés
290
	 * Soit directement $this->consulter() si des masque.tag* sont passés
287
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
291
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
288
	 * Soit via sqlAddMasqueConstraint():
292
	 * Soit via sqlAddMasqueConstraint():
289
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
293
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
290
	 * équivalent à:
294
	 * équivalent à:
291
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
295
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
292
	 *
296
	 *
293
	 */
297
	 */
294
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
298
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
295
		// TODO implement dans ListeObservations ?
299
		// TODO implement dans DelTk ?
296
		if(!empty($p['masque.milieu'])) {
300
		if(!empty($p['masque.milieu'])) {
297
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
301
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
298
		}
302
		}
299
 
303
 
300
 
304
 
301
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
305
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
302
		   celui-ci indique sur quels votes porte l'AVG.
306
		   celui-ci indique sur quels votes porte l'AVG.
303
		   (c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
307
		   (c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
304
		/* TODO: perf problème:
308
		/* TODO: perf problème:
305
		   1) SQL_CALC_FOUND_ROWS: fixable en:
309
		   1) SQL_CALC_FOUND_ROWS: fixable en:
306
           - dissociant le comptage de la récup d'id + javascript async
310
           - dissociant le comptage de la récup d'id + javascript async
307
           - ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
311
           - ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
308
           (paramètre booléen "with-total" par exemple)
312
           (paramètre booléen "with-total" par exemple)
309
		   2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
313
		   2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
310
           JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
314
           JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
311
           Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
315
           Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
312
           jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
316
           jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
313
		   3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
317
		   3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
314
           (cel_images/cel_obs_images/cel_obs/del_image_stat)
318
           (cel_images/cel_obs_images/cel_obs/del_image_stat)
315
           Cependant c'est à l'optimiseur de définir son ordre préféré. */
319
           Cependant c'est à l'optimiseur de définir son ordre préféré. */
316
		if($p['tri'] == 'votes') {
320
		if($p['tri'] == 'votes') {
317
			// $p['protocole'] *est* défini (cf requestFilterParams())
321
			// $p['protocole'] *est* défini (cf requestFilterParams())
318
			// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
322
			// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
319
			if($p['ordre'] == 'desc') {
323
			if($p['ordre'] == 'desc') {
320
				// pas de group by nécessaire pour cette jointure
324
				// pas de group by nécessaire pour cette jointure
321
				// PRIMARY KEY (`ce_image`, `ce_protocole`)
325
				// PRIMARY KEY (`ce_image`, `ce_protocole`)
322
				$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
326
				$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
323
                ' ON vdi.id_image = dis.ce_image'.
327
                ' ON vdi.id_image = dis.ce_image'.
324
                ' AND dis.ce_protocole = %d',
328
                ' AND dis.ce_protocole = %d',
325
                $p['protocole']);
329
                $p['protocole']);
326
			} else {
330
			} else {
327
				$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
331
				$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
328
                ' ON vdi.id_image = dis.ce_image'.
332
                ' ON vdi.id_image = dis.ce_image'.
329
                ' AND dis.ce_protocole = %d',
333
                ' AND dis.ce_protocole = %d',
330
                $p['protocole']);
334
                $p['protocole']);
331
				// nécessaire (dup ce_image dans del_image_stat)
335
				// nécessaire (dup ce_image dans del_image_stat)
332
				$req['groupby'][] = 'vdi.id_observation';
336
				$req['groupby'][] = 'vdi.id_observation';
333
			}
337
			}
334
		}
338
		}
335
 
339
 
336
		if($p['tri'] == 'tags') {
340
		if($p['tri'] == 'tags') {
337
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
341
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
338
            ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
342
            ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
339
			// nécessaire (dup ce_image dans del_image_stat)
343
			// nécessaire (dup ce_image dans del_image_stat)
340
			$req['groupby'][] = 'vdi.id_observation';
344
			$req['groupby'][] = 'vdi.id_observation';
341
		}
345
		}
342
 
346
 
343
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
347
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
344
		// sont toujours présentes; bien que parfois NULL.
348
		// sont toujours présentes; bien que parfois NULL.
345
		if($p['masque.tag_cel']) {
349
		if($p['masque.tag_cel']) {
346
			if(isset($p['masque.tag_cel']['AND'])) {
350
			if(isset($p['masque.tag_cel']['AND'])) {
347
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
351
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
348
				// et auquel cas laisser au client le choix du couteux "%" ?
352
				// et auquel cas laisser au client le choix du couteux "%" ?
349
				$tags = $p['masque.tag_cel']['AND'];
353
				$tags = $p['masque.tag_cel']['AND'];
350
				array_walk($tags, create_function('&$val, $k, $db',
354
				array_walk($tags, create_function('&$val, $k, $db',
351
                '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
355
                '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
352
																  $db->proteger("%".$val."%"));'),
356
																  $db->proteger("%".$val."%"));'),
353
                $db);
357
                $db);
354
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
358
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
355
			}
359
			}
356
			else {
360
			else {
357
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
361
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
358
                $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
362
                $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
359
			}
363
			}
360
		}
364
		}
361
 
365
 
362
 
366
 
363
		// XXX: utiliser tag plutôt que tag_normalise ?
367
		// XXX: utiliser tag plutôt que tag_normalise ?
364
		if($p['masque.tag_pictoflora']) {
368
		if($p['masque.tag_pictoflora']) {
365
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
369
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
366
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
370
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
367
			   $req['where'][] = 'dit.actif = 1'; */
371
			   $req['where'][] = 'dit.actif = 1'; */
368
 
372
 
369
 
373
 
370
 
374
 
371
			// ==== commenté pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro) ====
375
			// ==== commenté pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro) ====
372
			/*
376
			/*
373
              if(isset($p['masque.tag_pictoflora']['AND'])) {
377
              if(isset($p['masque.tag_pictoflora']['AND'])) {
374
              // optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
378
              // optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
375
              // donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
379
              // donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
376
              sort($p['masque.tag_pictoflora']['AND']);
380
              sort($p['masque.tag_pictoflora']['AND']);
377
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
381
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
378
              " GROUP BY ce_image".
382
              " GROUP BY ce_image".
379
              " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
383
              " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
380
              $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
384
              $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
381
              }
385
              }
382
              else {
386
              else {
383
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
387
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
384
              " GROUP BY ce_image".
388
              " GROUP BY ce_image".
385
              " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
389
              " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
386
              $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
390
              $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
387
              }
391
              }
388
			*/
392
			*/
389
 
393
 
390
			// ==== XXX: puisque on est bassiné par cette "DEPENDENT SUBQUERY", nous la faisons donc indépendemment ====
394
			// ==== XXX: puisque on est bassiné par cette "DEPENDENT SUBQUERY", nous la faisons donc indépendemment ====
391
			if(isset($p['masque.tag_pictoflora']['AND'])) {
395
			if(isset($p['masque.tag_pictoflora']['AND'])) {
392
				// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
396
				// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
393
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
397
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
394
				sort($p['masque.tag_pictoflora']['AND']);
398
				sort($p['masque.tag_pictoflora']['AND']);
395
 
399
 
396
				// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
400
				// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
397
				$ids = $db->recupererTous(sprintf(
401
				$ids = $db->recupererTous(sprintf(
398
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
402
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
399
					" GROUP BY ce_image".
403
					" GROUP BY ce_image".
400
					" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
404
					" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
401
					$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
405
					$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
402
				// puis:
406
				// puis:
403
				$ids = array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
407
				$ids = array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
404
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
408
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
405
 
409
 
406
			}
410
			}
407
			else {
411
			else {
408
				$ids = $db->recupererTous(sprintf(
412
				$ids = $db->recupererTous(sprintf(
409
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
413
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
410
					" GROUP BY ce_image".
414
					" GROUP BY ce_image".
411
					" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
415
					" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
412
					$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
416
					$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
413
				$ids = array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
417
				$ids = array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
414
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
418
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
415
			}
419
			}
416
 
420
 
417
		}
421
		}
418
	}
422
	}
419
 
423
 
420
 
424
 
421
	static function getIdImages($p, $req, $db) {
425
	static function getIdImages($p, $req, $db) {
422
		return $db->recupererTous(sprintf(
426
		return $db->recupererTous(sprintf(
423
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
427
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
424
			' FROM v_del_image vdi'.
428
			' FROM v_del_image vdi'.
425
			' %s' . // LEFT JOIN if any
429
			' %s' . // LEFT JOIN if any
426
			' WHERE %s'. // where-clause ou TRUE
430
			' WHERE %s'. // where-clause ou TRUE
427
			' %s'. // group-by
431
			' %s'. // group-by
428
			' ORDER BY %s'.
432
			' ORDER BY %s'.
429
			' LIMIT %d, %d -- %s',
433
			' LIMIT %d, %d -- %s',
430
						 
434
						 
431
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
435
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
432
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
436
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
433
			
437
			
434
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
438
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
435
			
439
			
436
			$req['orderby'],
440
			$req['orderby'],
437
			
441
			
438
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__));
442
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__));
439
 
443
 
440
	}
444
	}
441
 
445
 
442
	static function chargerImages($db, $idImg) {
446
	static function chargerImages($db, $idImg) {
443
		$obs_fields = Observation::sqlFieldsToAlias(self::$mappings['observations'], NULL);
447
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
444
		$image_fields = Observation::sqlFieldsToAlias(self::$mappings['images'], NULL);
448
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
445
	
449
	
446
		return $db->recupererTous(sprintf('SELECT '.
450
		return $db->recupererTous(sprintf('SELECT '.
447
        ' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
451
        ' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
448
        ' %1$s, %2$s FROM v_del_image '.
452
        ' %1$s, %2$s FROM v_del_image '.
449
        ' WHERE %3$s'.
453
        ' WHERE %3$s'.
450
        ' -- %4$s',
454
        ' -- %4$s',
451
        $obs_fields, $image_fields,
455
        $obs_fields, $image_fields,
452
        sprintf('id_image IN (%s)', implode(',', $idImg)),
456
        sprintf('id_image IN (%s)', implode(',', $idImg)),
453
        __FILE__ . ':' . __LINE__));
457
        __FILE__ . ':' . __LINE__));
454
 
458
 
455
	}
459
	}
456
 
460
 
457
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
461
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
458
	   de manière identique à la seule différence que:
462
	   de manière identique à la seule différence que:
459
	   1) ils sont combinés par des "OU" logiques plutôt que des "ET".
463
	   1) ils sont combinés par des "OU" logiques plutôt que des "ET".
460
	   2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
464
	   2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
461
       Tous les mots-clefs doivent matcher et sont séparés par des espaces
465
       Tous les mots-clefs doivent matcher et sont séparés par des espaces
462
       (dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
466
       (dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
463
	   Pour plus d'information: ListeObservations::sqlAddMasqueConstraint() */
467
	   Pour plus d'information: ListeObservations::sqlAddMasqueConstraint() */
464
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
468
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
465
		if(!empty($p['masque'])) {
469
		if(!empty($p['masque'])) {
466
			$or_params = array('masque.auteur' => $p['masque'],
470
			$or_params = array('masque.auteur' => $p['masque'],
467
            'masque.departement' => $p['masque'],
471
            'masque.departement' => $p['masque'],
468
            'masque.commune' => $p['masque'], // TODO/XXX ?
472
            'masque.commune' => $p['masque'], // TODO/XXX ?
469
            'masque.id_zone_geo' => $p['masque'],
473
            'masque.id_zone_geo' => $p['masque'],
470
 
474
 
471
            /* tous-deux remplacent masque.tag
475
            /* tous-deux remplacent masque.tag
472
               mais sont traité séparément des requestFilterParams() */
476
               mais sont traité séparément des requestFilterParams() */
473
            // 'masque.tag_cel' => $p['masque'],
477
            // 'masque.tag_cel' => $p['masque'],
474
            // 'masque.tag_pictoflora' => $p['masque'],
478
            // 'masque.tag_pictoflora' => $p['masque'],
475
 
479
 
476
            'masque.ns' => $p['masque'],
480
            'masque.ns' => $p['masque'],
477
            'masque.famille' => $p['masque'],
481
            'masque.famille' => $p['masque'],
478
            'masque.date' => $p['masque'],
482
            'masque.date' => $p['masque'],
479
            'masque.genre' => $p['masque'],
483
            'masque.genre' => $p['masque'],
480
            'masque.milieu' => $p['masque'],
484
            'masque.milieu' => $p['masque'],
481
 
485
 
482
            // tri est aussi nécessaire car affecte les contraintes de JOIN
486
            // tri est aussi nécessaire car affecte les contraintes de JOIN
483
            'tri' => $p['tri'],
487
            'tri' => $p['tri'],
484
            'ordre' => $p['ordre']);
488
            'ordre' => $p['ordre']);
485
 
489
 
486
			$or_masque = array_merge(
490
			$or_masque = array_merge(
487
				ListeObservations::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
491
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
488
				self::requestFilterParams($or_params));
492
				self::requestFilterParams($or_params));
489
 
493
 
490
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
494
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
491
			   postulés comme séparés par des espaces, et doivent être tous matchés. */
495
			   postulés comme séparés par des espaces, et doivent être tous matchés. */
492
			$or_masque['masque.tag_cel'] = self::buildTagsAST($p['masque'], 'AND', ' ');
496
			$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
493
			$or_masque['masque.tag_pictoflora'] = self::buildTagsAST($p['masque'], 'AND', ' ');
497
			$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
494
 
498
 
495
 
499
 
496
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
500
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
497
			$or_req = array('join' => array(), 'where' => array());
501
			$or_req = array('join' => array(), 'where' => array());
-
 
502
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
498
			ListeObservations::sqlAddConstraint($or_masque, $db, $or_req);
503
			ListeObservations::sqlAddConstraint($or_masque, $db, $or_req);
499
			self::sqlAddConstraint($or_masque, $db, $or_req);
504
			self::sqlAddConstraint($or_masque, $db, $or_req);
500
 
505
 
501
			if($or_req['where']) {
506
			if($or_req['where']) {
502
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
507
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
503
				// utile au cas ou des jointures seraient rajoutées
508
				// utile au cas ou des jointures seraient rajoutées
504
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
509
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
505
			}
510
			}
506
		}
511
		}
507
	}
512
	}
-
 
513
 
-
 
514
 
-
 
515
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
-
 
516
    // (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
-
 
517
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
-
 
518
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
-
 
519
		// conserver les valeurs vides (pour les phptests notamment, ou non)
-
 
520
		// $obs = array_map('array_filter', $obs);
-
 
521
		$obs_merged = $obs_keyed_by_id_image = array();
-
 
522
		foreach($obs as $o) {
-
 
523
			// ceci nous complique la tâche pour le reste du processing...
-
 
524
			$id = $o['jsonindex'];
-
 
525
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
-
 
526
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
-
 
527
			// mais tout deux partage leur référence à "protocole"
-
 
528
			$image = array(
-
 
529
				'id_image' => $o['id_image'],
-
 
530
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
-
 
531
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
-
 
532
			);
-
 
533
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
-
 
534
			if(!isset($obs_merged[$id])) $obs_merged[$id] = $image;
-
 
535
			$obs_merged[$id]['observation'] = $o;
-
 
536
			$obs_merged[$id]['protocoles_votes'] = array();
-
 
537
			
-
 
538
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] = &$obs_merged[$id]['protocoles_votes'];
-
 
539
		}
-
 
540
 
-
 
541
		return array($obs_merged,$obs_keyed_by_id_image);
-
 
542
	}
-
 
543
 
-
 
544
 
508
 
545
 
509
	// complete & override ListeObservations::requestFilterParams() (même usage)
546
	// complete & override DelTk::requestFilterParams() (même usage)
510
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
547
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
511
		if($parametres_autorises) { // filtrage de toute clef inconnue
548
		if($parametres_autorises) { // filtrage de toute clef inconnue
512
			$params = array_intersect_key($params, array_flip($parametres_autorises));
549
			$params = array_intersect_key($params, array_flip($parametres_autorises));
513
		}
550
		}
514
 
551
 
515
		$p = array();
552
		$p = array();
516
		$p['tri'] = ListeObservations::unsetIfInvalid($params, 'tri', self::$tri_possible);
553
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
517
		$p['format'] = ListeObservations::unsetIfInvalid($params, 'format', self::$format_image_possible);
554
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
518
 
555
 
519
		// "milieu" inutile pour IdentiPlantes ?
556
		// "milieu" inutile pour IdentiPlantes ?
520
		if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
557
		if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
521
 
558
 
522
		// compatibilité
559
		// compatibilité
523
		if(isset($params['masque.tag'])) {
560
		if(isset($params['masque.tag'])) {
524
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
561
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
525
		}
562
		}
526
 
563
 
527
		if($p['tri'] == 'votes' || $p['tri'] == 'tags') {
564
		if($p['tri'] == 'votes' || $p['tri'] == 'tags') {
528
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
565
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
529
			if(!isset($params['protocole']) || !is_numeric($params['protocole']))
566
			if(!isset($params['protocole']) || !is_numeric($params['protocole']))
530
				$p['protocole'] = self::$default_proto;
567
				$p['protocole'] = self::$default_proto;
531
			else
568
			else
532
				$p['protocole'] = intval($params['protocole']);
569
				$p['protocole'] = intval($params['protocole']);
533
		}
570
		}
534
 
571
 
535
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
572
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
536
	}
573
	}
537
 
574
 
538
 
-
 
539
	/* Construit un (vulgaire) abstract syntax tree:
-
 
540
	   "AND" => [ "tag1", "tag2" ]
-
 
541
	   Idéalement (avec un parser simple comme proposé par http://hoa-project.net/Literature/Hack/Compiler.html#Langage_PP)
-
 
542
	   nous aurions:
-
 
543
	   "AND" => [ "tag1", "tag2", "OR" => [ "tag3", "tag4" ] ]
-
 
544
 
-
 
545
	   Ici nous devons traiter les cas suivants:
-
 
546
	   tags séparés par des "ET/AND OU/OR", séparés par des espaces ou des virgules.
-
 
547
	   Mais la chaîne peut aussi avoir été issue du "masque général" (la barre de recherche générique).
-
 
548
	   ce qui implique des comportement par défaut différents afin de préserver la compatibilité.
-
 
549
 
-
 
550
	   Théorie:
-
 
551
	   1) tags passés par "champ tag":
-
 
552
	   - support du ET/OU, et explode par virgule.
-
 
553
	   - si pas d'opérande détectée: "OU"
-
 
554
 
-
 
555
	   2) tags passés par "recherche générale":
-
 
556
	   - support du ET/OU, et explode par whitespace.
-
 
557
	   - si pas d'opérande détectée: "ET"
-
 
558
 
-
 
559
	   La présence de $additional_sep s'explique car ET/OU sous-entendent une séparation par des espaces.
-
 
560
	   Mais ce n'est pas toujours pertinent car: 1) la compatibilité suggère de considérer parfois
-
 
561
	   la virgule comme séparateur et 2) les tags *peuvent* contenir des espaces. Par conséquent:
-
 
562
	   * a,b,c => "a" $default_op "b" $default_op "c"
-
 
563
	   * a,b AND c => "a" AND "b" AND "c"
-
 
564
	   * a OR b AND c,d => "a" AND "b" AND "c" AND "d"
-
 
565
	   C'est à dire par ordre décroissant de priorité:
-
 
566
	   1) opérande contenu dans la chaîne
-
 
567
	   2) opérande par défaut
-
 
568
	   3) les séparateurs présents sont substitués par l'opérande déterminée par 1) ou 2)
-
 
569
 
-
 
570
	   // TODO: support des parenthèses, imbrications & co: "(", ")"
-
 
571
	   // http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html
-
 
572
	   // http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/
-
 
573
 
-
 
574
	   @param $str: la chaîne à "parser"
-
 
575
	   @param $default_op: "AND" ou "OR"
-
 
576
	   @param $additional_sep: séparateur de mots:
-
 
577
	*/
-
 
578
	static function buildTagsAST($str = NULL, $default_op, $additional_sep = ',') {
-
 
579
		if(!$str) return;
-
 
580
		$words = preg_split('/ (OR|AND|ET|OU) /', $str, -1, PREG_SPLIT_NO_EMPTY);
-
 
581
 
-
 
582
		if(preg_match('/\b(ET|AND)\b/', $str)) $op = 'AND';
-
 
583
		elseif(preg_match('/\b(OU|OR)\b/', $str)) $op = 'OR';
-
 
584
		else $op = $default_op;
-
 
585
 
-
 
586
		if($additional_sep) {
-
 
587
			array_walk($words,
-
 
588
            create_function('&$v, $k, $sep', '$v = preg_split("/".$sep."/", $v, -1, PREG_SPLIT_NO_EMPTY);'),
-
 
589
            $additional_sep);
-
 
590
		}
-
 
591
		$words = self::array_flatten($words);
-
 
592
		$words = array_map('trim', $words);
-
 
593
		return array($op => array_filter($words));
-
 
594
	}
-
 
595
 
575
 
596
 
576
 
597
	// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
577
	// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
598
	static function _update_statistics($db) {
578
	static function _update_statistics($db) {
599
		$db->requeter("TRUNCATE TABLE del_image_stat");
579
		$db->requeter("TRUNCATE TABLE del_image_stat");
600
		$db->requeter(<<<EOF
580
		$db->requeter(<<<EOF
601
INSERT INTO `del_image_stat` (
581
INSERT INTO `del_image_stat` (
602
	SELECT id_image, divo.ce_protocole, divo.moyenne, divo.nb_votes, dit.ctags 
582
	SELECT id_image, divo.ce_protocole, divo.moyenne, divo.nb_votes, dit.ctags 
603
	FROM `tb_cel`.`cel_images` ci 
583
	FROM `tb_cel`.`cel_images` ci 
604
	LEFT JOIN 
584
	LEFT JOIN 
605
	( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes FROM del_image_vote 
585
	( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes FROM del_image_vote 
606
	  GROUP BY ce_image, ce_protocole ) AS divo
586
	  GROUP BY ce_image, ce_protocole ) AS divo
607
	ON ci.id_image = divo.ce_image 
587
	ON ci.id_image = divo.ce_image 
608
	LEFT JOIN 
588
	LEFT JOIN 
609
	( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag 
589
	( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag 
610
	  GROUP BY ce_image ) AS dit 
590
	  GROUP BY ce_image ) AS dit 
611
	ON ci.id_image = dit.ce_image )
591
	ON ci.id_image = dit.ce_image )
612
EOF
592
EOF
613
		);
593
		);
614
	}
594
	}
615
 
595
 
616
	static function revOrderBy($orderby) {
596
	static function revOrderBy($orderby) {
617
		return $orderby == 'asc' ? 'desc' : 'asc';
597
		return $orderby == 'asc' ? 'desc' : 'asc';
618
	}
598
	}
619
 
-
 
620
	static function array_flatten($arr) {
-
 
621
		$arr = array_values($arr);
-
 
622
		while (list($k,$v)=each($arr)) {
-
 
623
			if (is_array($v)) {
-
 
624
				array_splice($arr,$k,1,$v);
-
 
625
				next($arr);
-
 
626
			}
-
 
627
		}
-
 
628
		return $arr;
-
 
629
	}
-
 
630
}
599
}