Subversion Repositories eFlore/Applications.del

Rev

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

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