Subversion Repositories eFlore/Applications.del

Rev

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

Rev 1492 Rev 1495
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
 *
9
 *
9
 * Backend pour PictoFlora (del.html#page_recherche_images)
10
 * Backend pour PictoFlora (del.html#page_recherche_images)
10
 *
11
 *
11
 *
12
 *
12
 * == Notes ==
13
 * == Notes ==
13
 *
14
 *
14
 * 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())
15
 * 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).
16
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
17
 * Seul tri=date_transmission nous évite l'AVG() + GROUP BY
17
 *
18
 *
18
 * 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à
19
 * 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.
20
 * 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é".
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
 * 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())
23
 * (cf requestFilterParams())
23
 *
24
 *
24
 * 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
25
 * 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
26
 * 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()
27
 *
28
 *
28
 *
29
 *
29
 * Tags:
30
 * Tags:
30
 * 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,
31
 * implod()ed par des AND (tous les mots doivent matcher).
32
 * implod()ed par des AND (tous les mots doivent matcher).
32
 * Et le test effectué doit matcher sur:
33
 * Et le test effectué doit matcher sur:
33
 * %(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)%
34
 *
35
 *
35
 * 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),
36
 * Et le test effectué doit matcher sur:
37
 * Et le test effectué doit matcher sur:
37
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
38
 * ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
38
 *
39
 *
39
 * Par défaut les tags sont comma-separated (OU logique).
40
 * 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
41
 * 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 
42
 * des tags séparés par des espaces recherche 
42
 *
43
 *
43
 * TODO:
44
 * TODO:
44
 * -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.
45
 * - 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
46
 * 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)
47
 * (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/)
48
 * - é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
49
 * (cf VIEW del_image)
50
 * (cf VIEW del_image)
50
 * - 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
51
 * - *peut-être*: passer requestFilterParams() en méthode de classe
52
 * - *peut-être*: passer requestFilterParams() en méthode de classe
52
 *
53
 *
53
 *
54
 *
54
 * MySQL sux:
55
 * 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);
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
 *	MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
57
 *	MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
57
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
58
 * EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
58
 *	PRIMARY
59
 *	PRIMARY
59
 * 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));
60
 *	DEPENDENT SUBQUERY ... ... ... mwarf !
61
 *	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);
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
 *	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
63
 *	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
64
 * 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
65
 *
66
 *
66
 */
67
 */
67
 
68
 
68
require_once(dirname(__FILE__) . '/../DelTk.php');
69
require_once(dirname(__FILE__) . '/../DelTk.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 DelTk
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 = DelTk::requestFilterParams($parametres,
155
	$params_ip = DelTk::requestFilterParams($parametres,
155
        array_diff(DelTk::$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(DelTk::$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'] = DelTk::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'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
169
	$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
169
 
170
 
170
		$params = array_merge(
171
	$params = array_merge(
171
            DelTk::$default_params, // paramètre par défaut Identiplante
172
            DelTk::$default_params, // paramètre par défaut Identiplante
172
            self::$default_params, // paramètres par défaut PictoFlora
173
            self::$default_params, // paramètres par défaut PictoFlora
173
            $params_ip, // les paramètres passés, traités par Identiplante
174
            $params_ip, // les paramètres passés, traités par Identiplante
174
            $params_pf); // les paramètres passés, traités par PictoFlora
175
            $params_pf); // les paramètres passés, traités par PictoFlora
175
 
176
 
176
		// XXX: temp tweak
177
	// XXX: temp tweak
177
		/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'),
178
	/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'),
178
		   "%09d", $params['format']));*/
179
	   "%09d", $params['format']));*/
179
 
180
 
180
		// création des contraintes (génériques de DelTk)
181
	// création des contraintes (génériques de DelTk)
181
		DelTk::sqlAddConstraint($params, $db, $req);
182
	DelTk::sqlAddConstraint($params, $db, $req);
182
		// création des contraintes spécifiques (sur les tags essentiellement)
183
	// création des contraintes spécifiques (sur les tags essentiellement)
183
		self::sqlAddConstraint($params, $db, $req, $this->conteneur);
184
	self::sqlAddConstraint($params, $db, $req, $this->conteneur);
184
		// création des contraintes spécifiques impliquées par le masque général
185
	// création des contraintes spécifiques impliquées par le masque général
185
		self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
186
	self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
186
		// l'ORDER BY s'avére complexe
187
	// l'ORDER BY s'avére complexe
187
		self::sqlOrderBy($params, $db, $req);
188
	self::sqlOrderBy($params, $db, $req);
188
 
189
 
189
		// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
190
	// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
190
		// $idobs_tab = ListeObservations::getIdObs($params, $req, $db);
191
	// $idobs_tab = ListeObservations::getIdObs($params, $req, $db);
191
		$idobs_tab = self::getIdImages($params, $req, $db);
192
	$idobs_tab = self::getIdImages($params, $req, $db);
192
 
193
 
193
		// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats dans la table del_obs_images
194
	// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats dans la table del_obs_images
194
		if(!$idobs_tab) {
195
	if(!$idobs_tab) {
195
			$resultat = new ResultatService();
196
	    $resultat = new ResultatService();
196
			$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
197
	    $resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
197
            'resultats' => array());
198
				     'resultats' => array());
198
			return $resultat;
199
	    return $resultat;
199
			/*
200
	    /*
200
              header('HTTP/1.0 404 Not Found');
201
              header('HTTP/1.0 404 Not Found');
201
              // don't die (phpunit)
202
              // don't die (phpunit)
202
              throw(new Exception()); */
203
              throw(new Exception()); */
203
		}
204
	}
204
 
205
 
205
 
206
 
206
		// idobs est une liste (toujours ordonnée) des id d'observations recherchées
207
	// idobs est une liste (toujours ordonnée) des id d'observations recherchées
207
		$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
208
	$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab));
208
		$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
209
	$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
209
 
210
 
210
		$liaisons = self::chargerImages($db, $idobs);
211
	$liaisons = self::chargerImages($db, $idobs);
211
 
212
 
212
		/* 
213
	/* 
213
        // Q&D
214
        // Q&D
214
        $images = array();
215
        $images = array();
215
        $o = new Observation($this->conteneur);
216
        $o = new Observation($this->conteneur);
216
        foreach($idobs as $i) {
217
        foreach($idobs as $i) {
217
        $images[$i] = $o->consulter(array($i), array('justthrow' => 1));
218
        $images[$i] = $o->consulter(array($i), array('justthrow' => 1));
218
        }
219
        }
219
		*/
220
	*/
220
		list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
221
	list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex(
221
			$liaisons,
222
	    $liaisons,
222
			$this->conteneur->getParametre('url_images'),
223
	    $this->conteneur->getParametre('url_images'),
223
			$params['format']);
224
	    $params['format']);
224
 
225
 
225
 
226
 
226
		// on charge les votes pour ces images et pour *tous* les protocoles
227
	// on charge les votes pour ces images et pour *tous* les protocoles
227
		$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
228
	$votes = Observation::chargerVotesImage($db, $liaisons, NULL);
228
 
229
 
229
		// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
230
	// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour
230
		// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
231
	// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation.
231
		// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
232
	// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation)
232
		// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
233
	// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque
233
		// c'est encore possible.
234
	// c'est encore possible.
234
		if($votes) Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
235
	if($votes) Observation::mapVotesToImages($votes, $images_keyed_by_id_image);
235
 
236
 
236
		// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
237
	// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
237
		// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
238
	// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
238
		$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
239
	$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
239
        'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
240
								 'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
240
		$resultat = new ResultatService();
241
	$resultat = new ResultatService();
241
		$resultat->corps = array('entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
242
	$resultat->corps = array('entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
242
        'resultats' => $images);
243
				 'resultats' => $images);
243
		return $resultat;
244
	return $resultat;
244
	}
245
    }
245
 
246
 
246
	/**
247
    /**
247
	 * TODO: partie spécifique liées à la complexité de PictoFlora:
248
     * TODO: partie spécifique liées à la complexité de PictoFlora:
248
	 * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
249
     * génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req)
249
	 * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
250
     * nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...).
250
	 * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
251
     * Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour
251
	 * *chaque* couple (id_image, protocole) de la base afin de trouver les images
252
     * *chaque* couple (id_image, protocole) de la base afin de trouver les images
252
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
253
     * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
253
	 */
254
     */
254
	static function sqlOrderBy($p, $db, &$req) {
255
    static function sqlOrderBy($p, $db, &$req) {
255
		// parmi self::$tri_possible
256
	// parmi self::$tri_possible
256
		if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
257
	if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
257
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
258
	    $req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
258
			return;
259
	    return;
259
		}
260
	}
260
		
261
		
261
		if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
262
	if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
262
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
263
	    $req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
263
			return;
264
	    return;
264
		}
265
	}
265
 
266
 
266
		if($p['tri'] == 'date_observation') {
267
	if($p['tri'] == 'date_observation') {
267
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
268
	    $req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
268
			return;
269
	    return;
269
		}
270
	}
270
 
271
 
271
		// tri == 'date_transmission'
272
	// tri == 'date_transmission'
272
		// avant cel:r1860, date_transmission pouvait être NULL
273
	// avant cel:r1860, date_transmission pouvait être NULL
273
		// or nous voulons de la consistence (notamment pour phpunit)
274
	// or nous voulons de la consistence (notamment pour phpunit)
274
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
275
	$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
275
	}
276
    }
276
 
277
 
277
	/*
278
    /*
278
	 * in $p: un tableau de paramètres, dont:
279
     * in $p: un tableau de paramètres, dont:
279
	 * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
280
     * - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
280
	 * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
281
     * - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise
281
	 * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
282
     * - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON
282
	 *
283
     *
283
	 * in/ou: $req: un tableau de structure de requête MySQL
284
     * in/ou: $req: un tableau de structure de requête MySQL
284
	 *
285
     *
285
	 * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
286
     * Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel
286
	 * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
287
     * ne dépend pas de nous, mais du niveau supérieur de construction de la requête:
287
	 * Soit directement $this->consulter() si des masque.tag* sont passés
288
     * Soit directement $this->consulter() si des masque.tag* sont passés
288
	 * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
289
     * (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag)
289
	 * Soit via sqlAddMasqueConstraint():
290
     * Soit via sqlAddMasqueConstraint():
290
	 * (pas de split, "OR" entre chaque condition) [ comportement historique ]
291
     * (pas de split, "OR" entre chaque condition) [ comportement historique ]
291
	 * équivalent à:
292
     * équivalent à:
292
	 * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
293
     * (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag)
293
	 *
294
     *
294
	 */
295
     */
295
	static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
296
    static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
296
		// TODO implement dans DelTk ?
297
	// TODO implement dans DelTk ?
297
		if(!empty($p['masque.milieu'])) {
298
	if(!empty($p['masque.milieu'])) {
298
			$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
299
	    $req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
299
		}
300
	}
300
 
301
 
301
 
302
 
302
		/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
303
	/* Pour le tri par AVG() des votes nous avons toujours un protocole donné,
303
		   celui-ci indique sur quels votes porte l'AVG.
304
	   celui-ci indique sur quels votes porte l'AVG.
304
		   (c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
305
	   (c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
305
		/* TODO: perf problème:
306
	/* TODO: perf problème:
306
		   1) SQL_CALC_FOUND_ROWS: fixable en:
307
	   1) SQL_CALC_FOUND_ROWS: fixable en:
307
           - dissociant le comptage de la récup d'id + javascript async
308
           - dissociant le comptage de la récup d'id + javascript async
308
           - ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
309
           - ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
309
           (paramètre booléen "with-total" par exemple)
310
           (paramètre booléen "with-total" par exemple)
310
		   2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
311
	   2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
311
           JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
312
           JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
312
           Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
313
           Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
313
           jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
314
           jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
314
		   3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
315
	   3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
315
           (cel_images/cel_obs_images/cel_obs/del_image_stat)
316
           (cel_images/cel_obs_images/cel_obs/del_image_stat)
316
           Cependant c'est à l'optimiseur de définir son ordre préféré. */
317
           Cependant c'est à l'optimiseur de définir son ordre préféré. */
317
		if($p['tri'] == 'votes') {
318
	if($p['tri'] == 'votes') {
318
			// $p['protocole'] *est* défini (cf requestFilterParams())
319
	    // $p['protocole'] *est* défini (cf requestFilterParams())
319
			// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
320
	    // petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
320
			if($p['ordre'] == 'desc') {
321
	    if($p['ordre'] == 'desc') {
321
				// pas de group by nécessaire pour cette jointure
322
		// pas de group by nécessaire pour cette jointure
322
				// PRIMARY KEY (`ce_image`, `ce_protocole`)
323
		// PRIMARY KEY (`ce_image`, `ce_protocole`)
323
				$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
324
		$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
324
                ' ON vdi.id_image = dis.ce_image'.
325
					 ' ON vdi.id_image = dis.ce_image'.
325
                ' AND dis.ce_protocole = %d',
326
					 ' AND dis.ce_protocole = %d',
326
                $p['protocole']);
327
					 $p['protocole']);
327
			} else {
328
	    } else {
328
				$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
329
		$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
329
                ' ON vdi.id_image = dis.ce_image'.
330
					 ' ON vdi.id_image = dis.ce_image'.
330
                ' AND dis.ce_protocole = %d',
331
					 ' AND dis.ce_protocole = %d',
331
                $p['protocole']);
332
					 $p['protocole']);
332
				// nécessaire (dup ce_image dans del_image_stat)
333
		// nécessaire (dup ce_image dans del_image_stat)
333
				$req['groupby'][] = 'vdi.id_observation';
334
		$req['groupby'][] = 'vdi.id_observation';
334
			}
335
	    }
335
		}
336
	}
336
 
337
 
337
		if($p['tri'] == 'tags') {
338
	if($p['tri'] == 'tags') {
338
			$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
339
	    $req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
339
            ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
340
				     ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
340
			// nécessaire (dup ce_image dans del_image_stat)
341
	    // nécessaire (dup ce_image dans del_image_stat)
341
			$req['groupby'][] = 'vdi.id_observation';
342
	    $req['groupby'][] = 'vdi.id_observation';
342
		}
343
	}
343
 
344
 
344
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
345
	// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
345
		// sont toujours présentes; bien que parfois NULL.
346
	// sont toujours présentes; bien que parfois NULL.
346
		if($p['masque.tag_cel']) {
347
	if($p['masque.tag_cel']) {
347
			if(isset($p['masque.tag_cel']['AND'])) {
348
	    if(isset($p['masque.tag_cel']['AND'])) {
348
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
349
		// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
349
				// et auquel cas laisser au client le choix du couteux "%" ?
350
		// et auquel cas laisser au client le choix du couteux "%" ?
350
				$tags = $p['masque.tag_cel']['AND'];
351
		$tags = $p['masque.tag_cel']['AND'];
351
				array_walk($tags, create_function('&$val, $k, $db',
352
		array_walk($tags, create_function('&$val, $k, $db',
352
                '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
353
						  '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
353
																  $db->proteger("%".$val."%"));'),
354
																  $db->proteger("%".$val."%"));'),
354
                $db);
355
			   $db);
355
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
356
		$req['where'][] = '(' . implode(' AND ', $tags) . ')';
356
			}
357
	    }
357
			else {
358
	    else {
358
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
359
		$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
359
                $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
360
					  $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
360
			}
361
	    }
361
		}
362
	}
362
 
363
 
-
 
364
	if($p['masque.tag_pictoflora']) {
-
 
365
	    // inutilisable pour l'instant
-
 
366
	    // self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
363
 
367
 
-
 
368
	    // intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
364
		// XXX: utiliser tag plutôt que tag_normalise ?
369
	    // self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
-
 
370
 
365
		if($p['masque.tag_pictoflora']) {
371
	    // approche fiable mais sous-optimale
-
 
372
	    self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
-
 
373
	}
-
 
374
    }
-
 
375
 
-
 
376
    /* approche intéressante si les deux problèmes suivants peuvent être résolu:
366
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
377
       - LEFT JOIN => dup => *gestion de multiples GROUP BY* (car in-fine un LIMIT est utilisé)
-
 
378
       - dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
-
 
379
    static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
-
 
380
	// XXX: utiliser tag plutôt que tag_normalise ?
367
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
381
	$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
368
			   $req['where'][] = 'dit.actif = 1'; */
382
	$req['where'][] = 'dit.actif = 1';
-
 
383
	$req['groupby'][] = 'vdi.id_image'; // TODO: nécessaire (car dup') mais risque de conflict en cas de tri (multiple GROUP BY)
-
 
384
	// XXX: en cas de ET, possibilité du GROUP_CONCAT(), mais probablement sans grand intérêt, d'où une boucle
-
 
385
	if(isset($p['masque.tag_pictoflora']['AND'])) {
-
 
386
	    // TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
-
 
387
	    // ... le LEFT-JOIN n'y semble pas adapté
-
 
388
	}
-
 
389
	else {
-
 
390
	    $protected_tags = array();
-
 
391
	    foreach($p['masque.tag_pictoflora']['OR'] as $tag) $protected_tags[] = $db->proteger(strtolower($tag));
-
 
392
	    $req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
369
 
393
	}
-
 
394
    }
370
 
395
 
-
 
396
    // inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
-
 
397
    static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
371
            // Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
398
	// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
372
            // REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
399
	// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
373
            // l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
400
	// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
374
            // ex: REGEX "^(".
401
	// ex: REGEX "^(".
375
            // Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
402
	// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
376
            // ne peuvent (ou ne devrait) comporter des meta-caractères
403
	// ne peuvent (ou ne devrait) comporter des meta-caractères
377
            // ([])?*+\\
404
	// ([])?*+\\
378
 
-
 
379
 
-
 
380
			// ==== commenté pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro) ====
-
 
381
			/*
-
 
382
              if(isset($p['masque.tag_pictoflora']['AND'])) {
405
	if(isset($p['masque.tag_pictoflora']['AND'])) {
383
              // optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
406
	    // optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
384
              // donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
407
	    // donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
385
              sort($p['masque.tag_pictoflora']['AND']);
408
	    sort($p['masque.tag_pictoflora']['AND']);
386
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
409
	    $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
387
              " GROUP BY ce_image".
410
				      " GROUP BY ce_image".
388
              " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
411
				      " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
389
              $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
412
				      $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
390
              }
413
	}
391
              else {
414
	else {
392
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
415
	    $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
393
              " GROUP BY ce_image".
416
				      " GROUP BY ce_image".
394
              " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
417
				      " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
395
              $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
418
				      $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
396
              }
419
	}
397
			*/
420
    }
398
 
421
 
399
			// ==== XXX: puisque on est bassiné par cette "DEPENDENT SUBQUERY", nous la faisons donc indépendemment ====
422
    // si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
-
 
423
    static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
400
			if(isset($p['masque.tag_pictoflora']['AND'])) {
424
	if(isset($p['masque.tag_pictoflora']['AND'])) {
401
				// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
425
	    // optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
402
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
426
	    // donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
403
				sort($p['masque.tag_pictoflora']['AND']);
427
	    sort($p['masque.tag_pictoflora']['AND']);
404
 
428
 
405
				// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
429
	    // plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
406
				$ids = @$db->recupererTous(sprintf(
430
	    $ids = @$db->recupererTous(sprintf(
407
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
431
		"SELECT ce_image FROM del_image_tag WHERE actif = 1".
408
					" GROUP BY ce_image".
432
		" GROUP BY ce_image".
409
					" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
433
		" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
410
					$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
434
		$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
411
 
435
 
412
				// puis:
436
	    // puis:
413
				$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
437
	    $ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
414
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
438
	    if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
415
 
439
 
416
			}
440
	}
417
			else {
441
	else {
418
				$ids = @$db->recupererTous(sprintf(
442
	    $ids = @$db->recupererTous(sprintf(
419
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
443
		"SELECT ce_image FROM del_image_tag WHERE actif = 1".
420
					" GROUP BY ce_image".
444
		" GROUP BY ce_image".
421
					" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
445
		" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
422
					$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
446
		$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
423
 
447
 
424
				$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
448
	    $ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
425
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
449
	    if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
426
			}
-
 
427
 
-
 
428
		}
450
	}
429
	}
451
    }
430
 
-
 
431
 
452
 
432
	static function getIdImages($p, $req, $db) {
453
    static function getIdImages($p, $req, $db) {
433
		return $db->recupererTous(sprintf(
454
	return $db->recupererTous(sprintf(
434
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
455
	    'SELECT SQL_CALC_FOUND_ROWS id_image' .
435
			' FROM v_del_image vdi'.
456
	    ' FROM v_del_image vdi'.
436
			' %s' . // LEFT JOIN if any
457
	    ' %s' . // LEFT JOIN if any
437
			' WHERE %s'. // where-clause ou TRUE
458
	    ' WHERE %s'. // where-clause ou TRUE
438
			' %s'. // group-by
459
	    ' %s'. // group-by
439
			' ORDER BY %s'.
460
	    ' ORDER BY %s'.
440
			' LIMIT %d, %d -- %s',
461
	    ' LIMIT %d, %d -- %s',
441
						 
462
						 
442
			$req['join'] ? implode(' ', array_unique($req['join'])) : '',
463
	    $req['join'] ? implode(' ', array_unique($req['join'])) : '',
443
			$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
464
	    $req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
444
			
465
			
445
			$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
466
	    $req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
446
			
467
			
447
			$req['orderby'],
468
	    $req['orderby'],
448
			
469
			
449
			$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__));
470
	    $p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__));
450
 
471
 
451
	}
472
    }
452
 
473
 
453
	static function chargerImages($db, $idImg) {
474
    static function chargerImages($db, $idImg) {
454
		$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
475
	$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
455
		$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
476
	$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
456
	
477
	
457
		return $db->recupererTous(sprintf('SELECT '.
478
	return $db->recupererTous(sprintf('SELECT '.
458
        ' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
479
					  ' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
459
        ' %1$s, %2$s FROM v_del_image '.
480
					  ' %1$s, %2$s FROM v_del_image '.
460
        ' WHERE %3$s'.
481
					  ' WHERE %3$s'.
461
        ' -- %4$s',
482
					  ' -- %4$s',
462
        $obs_fields, $image_fields,
483
					  $obs_fields, $image_fields,
463
        sprintf('id_image IN (%s)', implode(',', $idImg)),
484
					  sprintf('id_image IN (%s)', implode(',', $idImg)),
464
        __FILE__ . ':' . __LINE__));
485
					  __FILE__ . ':' . __LINE__));
465
 
486
 
466
	}
487
    }
467
 
488
 
468
	/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
489
    /* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités
469
	   de manière identique à la seule différence que:
490
       de manière identique à la seule différence que:
470
	   1) ils sont combinés par des "OU" logiques plutôt que des "ET".
491
       1) ils sont combinés par des "OU" logiques plutôt que des "ET".
471
	   2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
492
       2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
472
       Tous les mots-clefs doivent matcher et sont séparés par des espaces
493
       Tous les mots-clefs doivent matcher et sont séparés par des espaces
473
       (dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
494
       (dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
474
	   Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
495
       Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
475
	static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
496
    static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
476
		if(!empty($p['masque'])) {
497
	if(!empty($p['masque'])) {
477
			$or_params = array('masque.auteur' => $p['masque'],
498
	    $or_params = array('masque.auteur' => $p['masque'],
478
            'masque.departement' => $p['masque'],
499
			       'masque.departement' => $p['masque'],
479
            'masque.commune' => $p['masque'], // TODO/XXX ?
500
			       'masque.commune' => $p['masque'], // TODO/XXX ?
480
            'masque.id_zone_geo' => $p['masque'],
501
			       'masque.id_zone_geo' => $p['masque'],
481
 
502
 
482
            /* tous-deux remplacent masque.tag
503
			       /* tous-deux remplacent masque.tag
483
               mais sont traité séparément des requestFilterParams() */
504
				  mais sont traité séparément des requestFilterParams() */
484
            // 'masque.tag_cel' => $p['masque'],
505
			       // 'masque.tag_cel' => $p['masque'],
485
            // 'masque.tag_pictoflora' => $p['masque'],
506
			       // 'masque.tag_pictoflora' => $p['masque'],
486
 
507
 
487
            'masque.ns' => $p['masque'],
508
			       'masque.ns' => $p['masque'],
488
            'masque.famille' => $p['masque'],
509
			       'masque.famille' => $p['masque'],
489
            'masque.date' => $p['masque'],
510
			       'masque.date' => $p['masque'],
490
            'masque.genre' => $p['masque'],
511
			       'masque.genre' => $p['masque'],
491
            'masque.milieu' => $p['masque'],
512
			       'masque.milieu' => $p['masque'],
492
 
513
 
493
            // tri est aussi nécessaire car affecte les contraintes de JOIN
514
			       // tri est aussi nécessaire car affecte les contraintes de JOIN
494
            'tri' => $p['tri'],
515
			       'tri' => $p['tri'],
495
            'ordre' => $p['ordre']);
516
			       'ordre' => $p['ordre']);
496
 
517
 
497
			$or_masque = array_merge(
518
	    $or_masque = array_merge(
498
				DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
519
		DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
499
				self::requestFilterParams($or_params));
520
		self::requestFilterParams($or_params));
500
 
521
 
501
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
522
	    /* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
502
			   postulés comme séparés par des espaces, et doivent être tous matchés. */
523
	       postulés comme séparés par des espaces, et doivent être tous matchés. */
503
			$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
524
	    $or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
504
			$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
525
	    $or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
505
 
526
 
506
 
527
 
507
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
528
	    // pas de select, groupby & co ici: uniquement 'join' et 'where'
508
			$or_req = array('join' => array(), 'where' => array());
529
	    $or_req = array('join' => array(), 'where' => array());
509
			DelTk::sqlAddConstraint($or_masque, $db, $or_req);
530
	    DelTk::sqlAddConstraint($or_masque, $db, $or_req);
510
			self::sqlAddConstraint($or_masque, $db, $or_req);
-
 
511
 
531
	    self::sqlAddConstraint($or_masque, $db, $or_req);
-
 
532
 
512
			if($or_req['where']) {
533
	    if($or_req['where']) {
513
				$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
534
		$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')';
514
				// utile au cas ou des jointures seraient rajoutées
535
		// utile au cas ou des jointures seraient rajoutées
515
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
536
		$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
516
			}
537
	    }
517
		}
538
	}
518
	}
539
    }
519
 
540
 
520
 
541
 
521
	// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
542
    // cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
522
    // (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
543
    // (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
523
	static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
544
    static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
524
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
545
	// XXX: cf Observation.php::consulter(), nous pourriouns ici
525
		// conserver les valeurs vides (pour les phptests notamment, ou non)
546
	// conserver les valeurs vides (pour les phptests notamment, ou non)
526
		// $obs = array_map('array_filter', $obs);
547
	// $obs = array_map('array_filter', $obs);
527
		$obs_merged = $obs_keyed_by_id_image = array();
548
	$obs_merged = $obs_keyed_by_id_image = array();
528
		foreach($obs as $o) {
549
	foreach($obs as $o) {
529
			// ceci nous complique la tâche pour le reste du processing...
550
	    // ceci nous complique la tâche pour le reste du processing...
530
			$id = $o['jsonindex'];
551
	    $id = $o['jsonindex'];
531
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
552
	    // ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
532
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
553
	    // et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
533
			// mais tout deux partage leur référence à "protocole"
554
	    // mais tout deux partage leur référence à "protocole"
534
			$image = array(
555
	    $image = array(
535
				'id_image' => $o['id_image'],
-
 
536
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
-
 
537
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
-
 
538
			);
556
		'id_image' => $o['id_image'],
-
 
557
		'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
-
 
558
		'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
539
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
-
 
540
			if(!isset($obs_merged[$id])) $obs_merged[$id] = $image;
-
 
541
			$obs_merged[$id]['observation'] = $o;
-
 
542
			$obs_merged[$id]['protocoles_votes'] = array();
-
 
543
			
-
 
544
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] = &$obs_merged[$id]['protocoles_votes'];
559
	    );
545
		}
560
	    unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
-
 
561
	    if(!isset($obs_merged[$id])) $obs_merged[$id] = $image;
546
 
562
	    $obs_merged[$id]['observation'] = $o;
-
 
563
	    $obs_merged[$id]['protocoles_votes'] = array();
-
 
564
			
547
		return array($obs_merged,$obs_keyed_by_id_image);
565
	    $obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] = &$obs_merged[$id]['protocoles_votes'];
548
	}
566
	}
549
 
-
 
550
 
567
 
551
 
568
	return array($obs_merged,$obs_keyed_by_id_image);
552
	// complete & override DelTk::requestFilterParams() (même usage)
-
 
553
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
569
    }
554
		if($parametres_autorises) { // filtrage de toute clef inconnue
-
 
555
			$params = array_intersect_key($params, array_flip($parametres_autorises));
570
 
556
		}
-
 
557
 
-
 
558
		$p = array();
571
 
559
		$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
572
 
-
 
573
    // complete & override DelTk::requestFilterParams() (même usage)
-
 
574
    static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
560
		$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
575
	if($parametres_autorises) { // filtrage de toute clef inconnue
-
 
576
	    $params = array_intersect_key($params, array_flip($parametres_autorises));
-
 
577
	}
-
 
578
 
561
 
579
	$p = array();
-
 
580
	$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
-
 
581
	$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
-
 
582
 
562
		// "milieu" inutile pour IdentiPlantes ?
583
	// "milieu" inutile pour IdentiPlantes ?
563
		if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
584
	if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
564
 
585
 
565
		// compatibilité
586
	// compatibilité
566
		if(isset($params['masque.tag'])) {
587
	if(isset($params['masque.tag'])) {
567
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
588
	    $params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
568
		}
589
	}
569
 
590
 
570
		if($p['tri'] == 'votes' || $p['tri'] == 'tags') {
591
	if($p['tri'] == 'votes' || $p['tri'] == 'tags') {
571
			// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
592
	    // ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
572
			if(!isset($params['protocole']) || !is_numeric($params['protocole']))
593
	    if(!isset($params['protocole']) || !is_numeric($params['protocole']))
573
				$p['protocole'] = self::$default_proto;
594
		$p['protocole'] = self::$default_proto;
574
			else
595
	    else
575
				$p['protocole'] = intval($params['protocole']);
596
		$p['protocole'] = intval($params['protocole']);
576
		}
597
	}
577
 
598
 
578
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
599
	return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
579
	}
600
    }
580
 
601
 
581
 
602
 
582
 
603
 
583
	// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
604
    // met à jour *toutes* les stats de nombre de tags et de moyenne des votes
584
	static function _update_statistics($db) {
605
    static function _update_statistics($db) {
585
		$db->requeter("TRUNCATE TABLE del_image_stat");
606
	$db->requeter("TRUNCATE TABLE del_image_stat");
586
		$db->requeter(<<<EOF
607
	$db->requeter(<<<EOF
587
INSERT INTO `del_image_stat` (
608
INSERT INTO `del_image_stat` (
588
	SELECT id_image, divo.ce_protocole, divo.moyenne, divo.nb_votes, dit.ctags 
609
	SELECT id_image, divo.ce_protocole, divo.moyenne, divo.nb_votes, dit.ctags 
589
	FROM `tb_cel`.`cel_images` ci 
610
	FROM `tb_cel`.`cel_images` ci 
590
	LEFT JOIN 
611
	LEFT JOIN 
591
	( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes FROM del_image_vote 
612
	( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes FROM del_image_vote 
592
	  GROUP BY ce_image, ce_protocole ) AS divo
613
	  GROUP BY ce_image, ce_protocole ) AS divo
593
	ON ci.id_image = divo.ce_image 
614
	ON ci.id_image = divo.ce_image 
594
	LEFT JOIN 
615
	LEFT JOIN 
595
	( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag 
616
	( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag 
596
	  GROUP BY ce_image ) AS dit 
617
	  GROUP BY ce_image ) AS dit 
597
	ON ci.id_image = dit.ce_image )
618
	ON ci.id_image = dit.ce_image )
598
EOF
619
EOF
599
		);
620
	);
600
	}
621
    }
601
 
622
 
602
	static function revOrderBy($orderby) {
623
    static function revOrderBy($orderby) {
603
		return $orderby == 'asc' ? 'desc' : 'asc';
624
	return $orderby == 'asc' ? 'desc' : 'asc';
604
	}
625
    }
605
}
626
}