1390 |
raphael |
1 |
<?php
|
|
|
2 |
/**
|
|
|
3 |
* @author Raphaël Droz <raphael@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
|
|
|
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
|
1495 |
raphael |
8 |
* @see http://www.tela-botanica.org/wikini/identiplante/wakka.php?wiki=IdentiPlante_PictoFlora_MoteurRecherche
|
1422 |
raphael |
9 |
*
|
|
|
10 |
* Backend pour PictoFlora (del.html#page_recherche_images)
|
|
|
11 |
*
|
|
|
12 |
*
|
|
|
13 |
* == Notes ==
|
|
|
14 |
*
|
|
|
15 |
* tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages())
|
|
|
16 |
* Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe).
|
|
|
17 |
* Seul tri=date_transmission nous évite l'AVG() + GROUP BY
|
|
|
18 |
*
|
|
|
19 |
* protocole: il affecte l'affichage des information, mais le JSON contient déjà
|
|
|
20 |
* l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique.
|
|
|
21 |
* Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné".
|
|
|
22 |
* Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY.
|
|
|
23 |
* (cf requestFilterParams())
|
|
|
24 |
*
|
|
|
25 |
* Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs
|
1490 |
raphael |
26 |
* utilisées ensuite pour l'ORDER BY. La situation à base de del_image_stat
|
|
|
27 |
* est déjà bien meilleure sans être pour autant optimale. cf commentaire de sqlAddConstraint()
|
1422 |
raphael |
28 |
*
|
|
|
29 |
*
|
|
|
30 |
* Tags:
|
|
|
31 |
* Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces,
|
|
|
32 |
* implod()ed par des AND (tous les mots doivent matcher).
|
|
|
33 |
* Et le test effectué doit matcher sur:
|
|
|
34 |
* %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)%
|
|
|
35 |
*
|
|
|
36 |
* Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression),
|
|
|
37 |
* Et le test effectué doit matcher sur:
|
|
|
38 |
* ^(expression)% *OU* %(expression)% [cf getConditionsImages()]
|
|
|
39 |
*
|
|
|
40 |
* Par défaut les tags sont comma-separated (OU logique).
|
|
|
41 |
* Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur
|
|
|
42 |
* des tags séparés par des espaces recherche
|
|
|
43 |
*
|
|
|
44 |
* TODO:
|
|
|
45 |
* -affiner la gestion de passage de mots-clefs dans le masque général.
|
|
|
46 |
* - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image
|
|
|
47 |
* plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN)
|
|
|
48 |
* (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/)
|
|
|
49 |
* - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission
|
|
|
50 |
* (cf VIEW del_image)
|
1492 |
raphael |
51 |
* - poursuivre la réorganisation des méthodes statiques parmis Observation, ListeObservations et ListeImages2
|
1422 |
raphael |
52 |
* - *peut-être*: passer requestFilterParams() en méthode de classe
|
|
|
53 |
*
|
1438 |
raphael |
54 |
*
|
|
|
55 |
* MySQL sux:
|
|
|
56 |
* EXPLAIN SELECT id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1 LIMIT 1);
|
1486 |
raphael |
57 |
* MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
|
1438 |
raphael |
58 |
* EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
|
1486 |
raphael |
59 |
* PRIMARY
|
1438 |
raphael |
60 |
* EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
|
1486 |
raphael |
61 |
* DEPENDENT SUBQUERY ... ... ... mwarf !
|
1438 |
raphael |
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);
|
1486 |
raphael |
63 |
* 5.5: MATERIALIZED del_image_tag ALL ce_image NULL NULL NULL 38276 Using where
|
|
|
64 |
* 5.1: DEPENDENT SUBQUERY del_image_tag index_subquery ce_image ce_image 8 func 1 Using where
|
1438 |
raphael |
65 |
* FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
|
|
|
66 |
*
|
1390 |
raphael |
67 |
*/
|
|
|
68 |
|
1490 |
raphael |
69 |
require_once(dirname(__FILE__) . '/../DelTk.php');
|
1390 |
raphael |
70 |
require_once(dirname(__FILE__) . '/../observations/Observation.php');
|
|
|
71 |
restore_error_handler();
|
|
|
72 |
restore_exception_handler();
|
|
|
73 |
error_reporting(E_ALL);
|
|
|
74 |
|
1422 |
raphael |
75 |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc
|
|
|
76 |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop
|
|
|
77 |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
|
|
|
78 |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
|
|
|
79 |
|
1514 |
aurelien |
80 |
class ListeImages {
|
1390 |
raphael |
81 |
|
1495 |
raphael |
82 |
// TODO: PHP-x.y, ces variables devrait être des "const"
|
|
|
83 |
static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
|
1390 |
raphael |
84 |
|
1564 |
mathias |
85 |
static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags', 'points');
|
1390 |
raphael |
86 |
|
1495 |
raphael |
87 |
// en plus de ceux dans DelTk
|
|
|
88 |
static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
|
1422 |
raphael |
89 |
|
1495 |
raphael |
90 |
static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
|
|
|
91 |
'tri' => 'date_transmission', 'ordre' => 'desc',
|
|
|
92 |
// spécifiques à PictoFlora:
|
|
|
93 |
'format' => 'XL');
|
1422 |
raphael |
94 |
|
1564 |
mathias |
95 |
static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes|points))
|
1422 |
raphael |
96 |
|
1495 |
raphael |
97 |
static $mappings = array(
|
|
|
98 |
'observations' => array( // v_del_image
|
|
|
99 |
"id_observation" => 1,
|
|
|
100 |
"date_observation" => 1,
|
|
|
101 |
"date_transmission" => 1,
|
|
|
102 |
"famille" => "determination.famille",
|
|
|
103 |
"nom_sel" => "determination.ns",
|
|
|
104 |
"nom_sel_nn" => "determination.nn",
|
|
|
105 |
"nom_referentiel" => "determination.referentiel",
|
|
|
106 |
"nt" => "determination.nt",
|
|
|
107 |
"ce_zone_geo" => "id_zone_geo",
|
|
|
108 |
"zone_geo" => 1,
|
|
|
109 |
"lieudit" => 1,
|
|
|
110 |
"station" => 1,
|
|
|
111 |
"milieu" => 1,
|
|
|
112 |
"mots_cles_texte" => "mots_cles_texte",
|
|
|
113 |
"commentaire" => 1,
|
|
|
114 |
"ce_utilisateur" => "auteur.id",
|
|
|
115 |
"nom_utilisateur" => "auteur.nom",
|
|
|
116 |
"prenom_utilisateur" => "auteur.prenom",
|
|
|
117 |
),
|
|
|
118 |
'images' => array( // v_del_image
|
|
|
119 |
'id_image' => 1,
|
|
|
120 |
// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte
|
|
|
121 |
// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex)
|
|
|
122 |
'i_mots_cles_texte' => 1
|
|
|
123 |
));
|
1422 |
raphael |
124 |
|
|
|
125 |
|
1495 |
raphael |
126 |
public function __construct(Conteneur $conteneur = null) {
|
1564 |
mathias |
127 |
$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
|
|
|
128 |
$this->conteneur->chargerConfiguration('config_images.ini');
|
|
|
129 |
$this->gestionBdd = $conteneur->getGestionBdd();
|
|
|
130 |
$this->bdd = $this->gestionBdd->getBdd();
|
1495 |
raphael |
131 |
}
|
1390 |
raphael |
132 |
|
1495 |
raphael |
133 |
public function consulter($ressources, $parametres) {
|
|
|
134 |
/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas
|
|
|
135 |
Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur.
|
|
|
136 |
Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur
|
|
|
137 |
Le propriétaire d'une obs et de l'image associée est *toujours* le même. */
|
|
|
138 |
array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
|
|
|
139 |
array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
|
|
|
140 |
// pour les votes, les mappings de "Observation" nous suffisent
|
|
|
141 |
array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;'));
|
1390 |
raphael |
142 |
|
1495 |
raphael |
143 |
// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité
|
|
|
144 |
// de `ce_image` dans ces tables jointes.
|
|
|
145 |
// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY
|
|
|
146 |
$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array());
|
1422 |
raphael |
147 |
|
|
|
148 |
|
1495 |
raphael |
149 |
$db = $this->bdd;
|
1390 |
raphael |
150 |
|
1495 |
raphael |
151 |
// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés
|
|
|
152 |
// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs.
|
|
|
153 |
// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
|
|
|
154 |
// même si ceux-ci sont improbables (pas d'<input> pour cela).
|
|
|
155 |
$params_ip = DelTk::requestFilterParams($parametres,
|
|
|
156 |
array_diff(DelTk::$parametres_autorises,
|
|
|
157 |
array('masque.type')),
|
|
|
158 |
$this->conteneur);
|
1422 |
raphael |
159 |
|
1495 |
raphael |
160 |
// notre propre filtrage sur l'INPUT
|
|
|
161 |
$params_pf = self::requestFilterParams($parametres,
|
|
|
162 |
array_merge(DelTk::$parametres_autorises,
|
|
|
163 |
self::$parametres_autorises));
|
1390 |
raphael |
164 |
|
1495 |
raphael |
165 |
/* filtrage des tags + sémantique des valeurs multiples:
|
|
|
166 |
Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
|
|
|
167 |
postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
|
|
|
168 |
$params_pf['masque.tag_cel'] = DelTk::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
|
1514 |
aurelien |
169 |
|
|
|
170 |
|
|
|
171 |
if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) {
|
|
|
172 |
$parametres['masque.tag_pictoflora'] = $parametres['masque.tag'];
|
|
|
173 |
}
|
1495 |
raphael |
174 |
$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
|
1390 |
raphael |
175 |
|
1495 |
raphael |
176 |
$params = array_merge(
|
1490 |
raphael |
177 |
DelTk::$default_params, // paramètre par défaut Identiplante
|
|
|
178 |
self::$default_params, // paramètres par défaut PictoFlora
|
|
|
179 |
$params_ip, // les paramètres passés, traités par Identiplante
|