Subversion Repositories eFlore/Applications.del

Rev

Rev 1492 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1492 Rev 1495
Line 3... Line 3...
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 ==
Line 76... Line 77...
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
Line 78... Line 79...
78
 
79
 
Line 79... Line 80...
79
class ListeImages2 {
80
class ListeImages2 {
80
 
81
 
Line 81... Line 82...
81
	// TODO: PHP-x.y, ces variables devrait être des "const"
82
    // TODO: PHP-x.y, ces variables devrait être des "const"
Line 82... Line 83...
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
 
Line 84... Line 85...
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
 
Line 164... Line 165...
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', ',');
Line 169... Line 170...
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')),
Line 197... Line 198...
197
            'resultats' => array());
198
				     'resultats' => array());
198
			return $resultat;
199
	    return $resultat;
199
			/*
200
	    /*
Line 200... Line 201...
200
              header('HTTP/1.0 404 Not Found');
201
              header('HTTP/1.0 404 Not Found');
Line 201... Line 202...
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)
Line 249... Line 250...
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'];
-
 
352
		array_walk($tags, create_function('&$val, $k, $db',
-
 
353
						  '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
-
 
354
																  $db->proteger("%".$val."%"));'),
351
				array_walk($tags, create_function('&$val, $k, $db',
355
			   $db);
-
 
356
		$req['where'][] = '(' . implode(' AND ', $tags) . ')';
352
                '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
357
	    }
-
 
358
	    else {
353
																  $db->proteger("%".$val."%"));'),
359
		$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
-
 
360
					  $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
-
 
361
	    }
-
 
362
	}
-
 
363
 
-
 
364
	if($p['masque.tag_pictoflora']) {
354
                $db);
365
	    // inutilisable pour l'instant
-
 
366
	    // self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
-
 
367
 
-
 
368
	    // intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
355
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
369
	    // self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
356
			}
370
 
-
 
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:
-
 
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 ?
357
			else {
381
	$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
-
 
382
	$req['where'][] = 'dit.actif = 1';
358
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
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'])) {
359
                $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
386
	    // TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
360
			}
387
	    // ... le LEFT-JOIN n'y semble pas adapté
361
		}
388
	}
362
 
389
	else {
363
 
390
	    $protected_tags = array();
364
		// XXX: utiliser tag plutôt que tag_normalise ?
391
	    foreach($p['masque.tag_pictoflora']['OR'] as $tag) $protected_tags[] = $db->proteger(strtolower($tag));
365
		if($p['masque.tag_pictoflora']) {
392
	    $req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
366
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
-
 
367
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
-
 
368
			   $req['where'][] = 'dit.actif = 1'; */
-
 
369
 
-
 
370
 
393
	}
371
            // Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
394
    }
372
            // REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
395
 
373
            // l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
396
    // inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
374
            // ex: REGEX "^(".
397
    static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
375
            // Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
398
	// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
376
            // ne peuvent (ou ne devrait) comporter des meta-caractères
399
	// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
377
            // ([])?*+\\
400
	// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
378
 
401
	// ex: REGEX "^(".
379
 
402
	// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
380
			// ==== commenté pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro) ====
403
	// ne peuvent (ou ne devrait) comporter des meta-caractères
381
			/*
404
	// ([])?*+\\
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".
-
 
411
				      " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
388
              " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
412
				      $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
389
              $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
413
	}
390
              }
414
	else {
391
              else {
415
	    $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
392
              $req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
416
				      " GROUP BY ce_image".
393
              " GROUP BY ce_image".
417
				      " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
394
              " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
418
				      $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
395
              $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
419
	}
396
              }
420
    }
397
			*/
421
 
398
 
422
    // si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
399
			// ==== XXX: puisque on est bassiné par cette "DEPENDENT SUBQUERY", nous la faisons donc indépendemment ====
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));
-
 
415
 
-
 
416
			}
438
	    if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
417
			else {
439
 
418
				$ids = @$db->recupererTous(sprintf(
-
 
419
					"SELECT ce_image FROM del_image_tag WHERE actif = 1".
440
	}
420
					" GROUP BY ce_image".
441
	else {
421
					" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
442
	    $ids = @$db->recupererTous(sprintf(
422
					$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
443
		"SELECT ce_image FROM del_image_tag WHERE actif = 1".
423
 
444
		" GROUP BY ce_image".
424
				$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
445
		" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
425
				if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
446
		$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
426
			}
447
 
427
 
448
	    $ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
428
		}
449
	    if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
Line 429... Line 450...
429
	}
450
	}
430
 
451
    }
Line 431... Line 452...
431
 
452
 
Line 432... Line 453...
432
	static function getIdImages($p, $req, $db) {
453
    static function getIdImages($p, $req, $db) {
Line 433... Line 454...
433
		return $db->recupererTous(sprintf(
454
	return $db->recupererTous(sprintf(
Line 434... Line 455...
434
			'SELECT SQL_CALC_FOUND_ROWS id_image' .
455
	    'SELECT SQL_CALC_FOUND_ROWS id_image' .
Line 435... Line 456...
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
Line 438... Line 459...
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 */),
-
 
499
				self::requestFilterParams($or_params));
519
		DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
-
 
520
		self::requestFilterParams($or_params));
Line 500... Line 521...
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);
531
	    self::sqlAddConstraint($or_masque, $db, $or_req);
511
 
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()
Line 522... Line 543...
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') {
-
 
524
		// XXX: cf Observation.php::consulter(), nous pourriouns ici
-
 
525
		// conserver les valeurs vides (pour les phptests notamment, ou non)
-
 
526
		// $obs = array_map('array_filter', $obs);
544
    static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
Line -... Line 545...
-
 
545
	// XXX: cf Observation.php::consulter(), nous pourriouns ici
-
 
546
	// conserver les valeurs vides (pour les phptests notamment, ou non)
Line 527... Line -...
527
		$obs_merged = $obs_keyed_by_id_image = array();
-
 
528
		foreach($obs as $o) {
-
 
529
			// ceci nous complique la tâche pour le reste du processing...
-
 
530
			$id = $o['jsonindex'];
-
 
531
			// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
-
 
Line 532... Line 547...
532
			// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
547
	// $obs = array_map('array_filter', $obs);
533
			// mais tout deux partage leur référence à "protocole"
548
	$obs_merged = $obs_keyed_by_id_image = array();
-
 
549
	foreach($obs as $o) {
534
			$image = array(
550
	    // ceci nous complique la tâche pour le reste du processing...
-
 
551
	    $id = $o['jsonindex'];
Line -... Line 552...
-
 
552
	    // ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
535
				'id_image' => $o['id_image'],
553
	    // et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
536
				'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
554
	    // mais tout deux partage leur référence à "protocole"
Line 537... Line -...
537
				'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
-
 
538
			);
555
	    $image = array(
539
			unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
556
		'id_image' => $o['id_image'],
540
			if(!isset($obs_merged[$id])) $obs_merged[$id] = $image;
-
 
Line 541... Line 557...
541
			$obs_merged[$id]['observation'] = $o;
557
		'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
542
			$obs_merged[$id]['protocoles_votes'] = array();
-
 
543
			
558
		'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
544
			$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] = &$obs_merged[$id]['protocoles_votes'];
-
 
545
		}
-
 
546
 
559
	    );
547
		return array($obs_merged,$obs_keyed_by_id_image);
560
	    unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
Line -... Line 561...
-
 
561
	    if(!isset($obs_merged[$id])) $obs_merged[$id] = $image;
-
 
562
	    $obs_merged[$id]['observation'] = $o;
548
	}
563
	    $obs_merged[$id]['protocoles_votes'] = array();
-
 
564
			
-
 
565
	    $obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] = &$obs_merged[$id]['protocoles_votes'];
-
 
566
	}
549
 
567
 
Line -... Line 568...
-
 
568
	return array($obs_merged,$obs_keyed_by_id_image);
-
 
569
    }
Line -... Line 570...
-
 
570
 
550
 
571
 
551
 
572
 
552
	// complete & override DelTk::requestFilterParams() (même usage)
573
    // complete & override DelTk::requestFilterParams() (même usage)
553
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
574
    static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
554
		if($parametres_autorises) { // filtrage de toute clef inconnue
575
	if($parametres_autorises) { // filtrage de toute clef inconnue
555
			$params = array_intersect_key($params, array_flip($parametres_autorises));
576
	    $params = array_intersect_key($params, array_flip($parametres_autorises));
556
		}
577
	}
557
 
578
 
558
		$p = array();
579
	$p = array();
Line 594... Line 615...
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
    }
Line 601... Line 622...
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
    }