5,6 → 5,51 |
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL |
* @license http://www.gnu.org/licenses/gpl.html Licence GNU-GPL |
* @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Images |
* |
* Backend pour PictoFlora (del.html#page_recherche_images) |
* |
* |
* == Notes == |
* |
* tri=votes et tri=tags: affectent le choix des images affichées (donc getIdImages()) |
* Cependant ce total ne nous intéresse même pas (MoyenneVotePresenteur.java s'en occupe). |
* Seul tri=date_transmission nous évite l'AVG() + GROUP BY |
* |
* protocole: il affecte l'affichage des information, mais le JSON contient déjà |
* l'intégralité (chercher les données de vote pour 1 ou plusieurs protocoles) est quasi-identique. |
* Par contre, le tri par moyenne des votes, sous-entend "pour un protocole donné". |
* Dès lors le choix d'un protocole doit avoir été fait afin de régler le JOIN et ainsi l'ORDER BY. |
* (cf requestFilterParams()) |
* |
* Histoire: auparavant (pré-r142x) un AVG + GROUP BY étaient utilisés pour générer on-the-fly les valeurs |
* utilsées ensuite pour l'ORDER BY. La situation à base de del_image_stat |
* est déjà bien meilleur sans être pour autant optimale. cf commentaire de sqlAddConstraint() |
* |
* |
* Tags: |
* Le comportement habituel dans le masque *général*: les mots sont séparés par des espaces, |
* implod()ed par des AND (tous les mots doivent matcher). |
* Et le test effectué doit matcher sur: |
* %(les tags d'observations)% *OU* %(les tags d'images)% *OU* %(les tags publics)% |
* |
* Le comportement habituel dans le masque *tag*: les mots ne sont *pas* splittés (1 seule expression), |
* Et le test effectué doit matcher sur: |
* ^(expression)% *OU* %(expression)% [cf getConditionsImages()] |
* |
* Par défaut les tags sont comma-separated (OU logique). |
* Cependant pour conserver le comportement du masque général qui sous-entend un ET logique sur |
* des tags séparés par des espaces recherche |
* |
* TODO: |
* -affiner la gestion de passage de mots-clefs dans le masque général. |
* - subqueries dans le FROM pour les critère WHERE portant directement sur v_del_image |
* plutôt que dans WHERE (qui nécessite dès lors un FULL-JOIN) |
* (http://www.mysqlperformanceblog.com/2007/04/06/using-delayed-join-to-optimize-count-and-limit-queries/) |
* - éviter de dépendre d'une jointure systématique sur `cel_obs`, uniquement pour `(date_)transmission |
* (cf VIEW del_image) |
* - réorganiser les méthodes statiques parmis Observation, ListeObservations2 et ListImages2 |
* - *peut-être*: passer requestFilterParams() en méthode de classe |
* |
*/ |
|
require_once(dirname(__FILE__) . '/../observations/ListeObservations2.php'); |
13,14 → 58,56 |
restore_exception_handler(); |
error_reporting(E_ALL); |
|
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3 |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop |
|
class ListeImages2 { |
|
static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L'); |
// TODO: INDEX static $tri_possible = array('date_observation','votes','tags'); |
static $tri_possible = array('date_observation'); |
|
static $default_params = array('format' => 'XL'); |
static $tri_possible = array('date_observation', 'votes', 'tags'); |
|
// en plus de ceux dans ListeObservations2 |
static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu'); |
|
static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10, |
'tri' => 'date_transmission', 'ordre' => 'desc', |
// spécifiques à PictoFlora: |
'format' => 'XL'); |
|
static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes)) |
|
static $mappings = array( |
'observations' => array( // v_del_image |
"id_observation" => 1, |
"date_observation" => 1, |
"date_transmission" => 1, |
"famille" => "determination.famille", |
"nom_sel" => "determination.ns", |
"nom_sel_nn" => "determination.nn", |
"nom_referentiel" => "determination.referentiel", |
"nt" => "determination.nt", |
"ce_zone_geo" => "id_zone_geo", |
"zone_geo" => 1, |
"lieudit" => 1, |
"station" => 1, |
"milieu" => 1, |
"mots_cles_texte" => "mots_cles_texte", |
"commentaire" => 1, |
"ce_utilisateur" => "auteur.id", |
"nom_utilisateur" => "auteur.nom", |
"prenom_utilisateur" => "auteur.prenom", |
), |
'images' => array( // v_del_image |
'id_image' => 1, |
// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte |
// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex) |
'i_mots_cles_texte' => 1 |
)); |
|
|
public function __construct(Conteneur $conteneur = null) { |
$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur; |
$this->conteneur->chargerConfiguration('config_images.ini'); |
29,53 → 116,410 |
} |
|
public function consulter($ressources, $parametres) { |
$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'having' => array()); |
// toujours nécessaire puisque nous tapons sur v_del_image qui INNER JOIN cel_images, or nous voulons certes |
// toutes les images, mais nous voulons $limite observations uniques. |
$req['groupby'][] = 'dob.id_observation'; |
/* Certes nous sélectionnons ici (nom|prenom|courriel)_utilisateur de cel_obs, mais il ne nous intéressent pas |
Par contre, ci-dessous nous prenons i_(nom|prenom|courriel)_utilisateur. |
Notons cependant qu'aucun moyen ne devrait permettre que i_*_utilisateur != *_utilisateur |
Le propriétaire d'une obs et de l'image associée est *toujours* le même. */ |
array_walk(self::$mappings['observations'], create_function('&$val, $k', 'if($val==1) $val = $k;')); |
array_walk(self::$mappings['images'], create_function('&$val, $k', 'if($val==1) $val = $k;')); |
// pour les votes, les mappings de "Observation" nous suffisent |
array_walk(Observation::$mappings['votes'], create_function('&$val, $k', 'if($val==1) $val = $k;')); |
|
// la nécessité du 'groupby' dépend des 'join's utilisés (LEFT ou INNER) ainsi que de la cardinalité |
// de `ce_image` dans ces tables jointes. |
// Contrairement à IdentiPlantes, nous n'avons de HAVING pour PictoFlora, mais par contre un ORDER BY |
$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array()); |
|
|
$db = $this->bdd; |
|
// filtrage de l'INPUT général |
$params = ListeObservations2::requestFilterParams($parametres, ListeObservations2::$parametres_autorises, $this->conteneur); |
// filtrage de l'INPUT général, on réutilise 90% de identiplante en terme de paramètres autorisés |
// ($parametres_autorises) sauf... masque.type qui fait des modif' de WHERE sur les mots-clefs. |
// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag |
// même si ceux-ci sont improbables (pas d'<input> pour cela). |
$params_ip = ListeObservations2::requestFilterParams($parametres, |
array_diff(ListeObservations2::$parametres_autorises, |
array('masque.type')), |
$this->conteneur); |
|
// Cette variable est utile au filtrage des tags |
// Elle peut-être redéfinie en interne par sqlAddMasqueConstraint() ci-dessous |
// lorsque celui-ci transforme le masque général en masque par champ |
// et ré-appelle self::requestFilterParams() |
$parametres['tag_explode_char'] = ','; // " " (whitespace) ou "," ou NULL |
// tag_explode_semantic est lui utilisé lors de l'ajout des contraintes sur tags self::sqlAddConstraint() |
$parametres['tag_explode_semantic'] = 'OR'; // "AND" ou "OR" (si 'tag_explode_char' NOT NULL) |
// notre propre filtrage sur l'INPUT |
$params_pf = self::requestFilterParams($parametres, |
array_merge(ListeObservations2::$parametres_autorises, |
self::$parametres_autorises)); |
|
$params = array_merge($params, self::requestFilterParams($parametres, array('format'))); |
|
$this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'), |
"%09d", $params['format'])); |
$params = array_merge(ListeObservations2::$default_params, // paramètre par défaut Identiplante |
self::$default_params, // paramètres par défaut PictoFlora |
$params_ip, // les paramètres passés, traités par Identiplante |
$params_pf); // les paramètres passés, traités par PictoFlora |
|
// création des contraintes (masques) |
// XXX: temp tweak |
/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'), |
"%09d", $params['format']));*/ |
|
// création des contraintes (génériques, de ListeObservations2) |
ListeObservations2::sqlAddConstraint($params, $db, $req, $this->conteneur); |
// création des contraintes spécifiques (sur les tags essentiellement) |
self::sqlAddConstraint($params, $db, $req, $this->conteneur); |
// création des contraintes spécifiques impliquées par le masque général |
self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur); |
// l'ORDER BY s'avére complexe |
self::sqlOrderBy($params, $db, $req); |
|
// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS) |
$idobs_tab = ListeObservations2::getIdObs($params, $req, $db); |
// idobs est une liste (toujours ordonnée) des id d'observations recherchées |
$idobs = array_values(array_map(create_function('$a', 'return $a["id_observation"];'), $idobs_tab)); |
// $idobs_tab = ListeObservations2::getIdObs($params, $req, $db); |
$idobs_tab = self::getIdImages($params, $req, $db); |
|
if($idobs) { |
$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']); |
// XXX |
// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats dans la table del_obs_images |
if(!$idobs_tab) { |
header('HTTP/1.0 404 Not Found'); |
// don't die (phpunit) |
throw(new Exception()); |
} |
|
$images = array(); |
$o = new Observation($this->conteneur); |
foreach($idobs as $i) { |
$images[$i] = $o->consulter(array($i), array('justthrow' => 1)); |
} |
|
// idobs est une liste (toujours ordonnée) des id d'observations recherchées |
$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab)); |
$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']); |
|
$liaisons = self::chargerImages($db, $idobs); |
|
/* |
// Q&D |
$images = array(); |
$o = new Observation($this->conteneur); |
foreach($idobs as $i) { |
$images[$i] = $o->consulter(array($i), array('justthrow' => 1)); |
} |
*/ |
list($images, $images_keyed_by_id_image) = ListeObservations2::reformateImagesDoubleIndex( |
$liaisons, |
$this->conteneur->getParametre('url_images'), |
$params['format']); |
|
|
// on charge les votes pour ces images et pour *tous* les protocoles |
$votes = Observation::chargerVotesImage($db, $liaisons, NULL); |
|
// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour |
// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation. |
// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation) |
// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque |
// c'est encore possible. |
if($votes) Observation::mapVotesToImages($votes, $images_keyed_by_id_image); |
|
$resultat = new ResultatService(); |
$resultat->corps = array('entete' => ListeObservations2::makeJSONHeader($total, $params, Config::get('url_service')), |
'resultats' => $images); |
return $resultat; |
} |
|
/** |
* TODO: partie spécifique liées à la complexité de PictoFlora: |
* génération de la clause ORDER BY (génère la valeur de la clef orderby' de $req) |
* nécessaire ? tableau sprintf(key (tri) => value (ordre), key => value ...). |
* Cependant il est impensable de joindre sur un AVG() des valeurs des votes pour |
* *chaque* couple (id_image, protocole) de la base afin de trouver les images |
* les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT()) |
*/ |
static function sqlOrderBy($p, $db, &$req) { |
// parmi self::$tri_possible |
if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous |
$req['orderby'] = 'dis.moyenne ' . $p['ordre']; |
return; |
} |
|
if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous |
$req['orderby'] = 'dis.nb_tags ' . $p['ordre']; |
return; |
} |
|
// tri == 'date_transmission' |
$req['orderby'] = 'date_transmission ' . $p['ordre']; |
// avant cel:r1860, date_transmission pouvait être NULL |
// or nous voulons de la consistence (notamment pour phpunit) |
$req['orderby'] .= ', id_observation ' . $p['ordre']; |
} |
|
static function requestFilterParams(Array $params) { |
/* |
* in $p: un tableau de paramètres, dont: |
* - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte |
* - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise |
* - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON |
* |
* in/ou: $req: un tableau de structure de requête MySQL |
* |
* Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel |
* ne dépend pas de nous, mais du niveau supérieur de construction de la requête: |
* Soit directement $this->consulter() si des masque.tag* sont passés |
* (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag) |
* Soit via sqlAddMasqueConstraint(): |
* (pas de split, "OR" entre chaque condition) [ comportement historique ] |
* équivalent à: |
* (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag) |
* |
*/ |
static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) { |
// TODO implement dans ListeObservations2 ? |
if(!empty($p['masque.milieu'])) { |
$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%'); |
} |
|
|
/* Pour le tri par AVG() des votes nous avons toujours un protocole donné, |
celui-ci indique sur quels votes porte l'AVG. |
(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */ |
/* TODO: perf problème: |
1) SQL_CALC_FOUND_ROWS: fixable en: |
- dissociant le comptage de la récup d'id + javascript async |
- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination |
(paramètre booléen "with-total" par exemple) |
2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers |
JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission". |
Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse |
jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé |
3) non-problème: l'ordre des joins est forcé par l'usage de la vue: |
(cel_images/cel_obs_images/cel_obs/del_image_stat) |
Cependant c'est à l'optimiseur de définir son ordre préféré. */ |
if($p['tri'] == 'votes') { |
// $p['protocole'] *est* défini (cf requestFilterParams()) |
// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin |
if($p['ordre'] == 'desc') { |
// pas de group by nécessaire pour cette jointure |
// PRIMARY KEY (`ce_image`, `ce_protocole`) |
$req['join'][] = sprintf('INNER JOIN del_image_stat dis'. |
' ON vdi.id_image = dis.ce_image'. |
' AND dis.ce_protocole = %d', |
$p['protocole']); |
} else { |
$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'. |
' ON vdi.id_image = dis.ce_image'. |
' AND dis.ce_protocole = %d', |
$p['protocole']); |
// nécessaire (dup ce_image dans del_image_stat) |
$req['groupby'][] = 'vdi.id_observation'; |
} |
} |
|
if($p['tri'] == 'tags') { |
$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image', |
($p['ordre'] == 'desc') ? 'INNER' : 'LEFT'); |
// nécessaire (dup ce_image dans del_image_stat) |
$req['groupby'][] = 'vdi.id_observation'; |
} |
|
|
// TODO: support du "ET", "OU", "(", ")" |
// http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html |
// http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/ |
if(!empty($p['masque.tag_cel'])) { |
if($p['tag_explode_semantic'] == 'AND') { |
// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ? |
// et auquel cas laisser au client le choix du couteux "%" ? |
$tags = $p['masque.tag_cel']; |
array_walk($tags, create_function('&$val, $k, $db', |
'$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s", |
$db->proteger("%".$val."%"));'), |
$db); |
$req['where'][] = implode(' AND ', $tags); |
} |
else { // OR assumed |
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.mots_cles_texte) REGEXP %s", |
$db->proteger(implode('|', $p['masque.tag_cel']))); |
} |
} |
|
|
// XXX: utiliser tag plutôt que tag_normalise ? |
if(!empty($p['masque.tag_pictoflora'])) { |
// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque |
/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image'; |
$req['where'][] = 'dit.actif = 1'; */ |
|
if($p['tag_explode_semantic'] == 'AND') { |
// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT() |
// donc nous utilisons des ".*" plutôt que de multiples conditions et "|" |
sort($p['masque.tag_pictoflora']); |
$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1". |
" GROUP BY ce_image". |
" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)", |
$db->proteger(implode('.*', $p['masque.tag_pictoflora']))); |
} |
else { // OR assumed |
$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1". |
" GROUP BY ce_image". |
" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)", |
$db->proteger(implode('|', $p['masque.tag_pictoflora']))); |
} |
} |
|
} |
|
static function getIdImages($p, $req, $db) { |
return $db->recupererTous(sprintf( |
'SELECT SQL_CALC_FOUND_ROWS id_image' . |
' FROM v_del_image vdi'. |
' %s' . // LEFT JOIN if any |
' WHERE %s'. // where-clause ou TRUE |
' %s'. // group-by |
' ORDER BY %s'. |
' LIMIT %d, %d -- %s', |
|
$req['join'] ? implode(' ', array_unique($req['join'])) : '', |
$req['where'] ? implode(' AND ', $req['where']) : 'TRUE', |
|
$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '', |
|
$req['orderby'], |
|
$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__)); |
|
} |
|
static function chargerImages($db, $idImg) { |
$obs_fields = Observation::sqlFieldsToAlias(self::$mappings['observations'], NULL); |
$image_fields = Observation::sqlFieldsToAlias(self::$mappings['images'], NULL); |
|
return $db->recupererTous(sprintf('SELECT '. |
' CONCAT(id_image, "-", id_observation) AS jsonindex,'. |
' %1$s, %2$s FROM v_del_image '. |
' WHERE %3$s'. |
' -- %4$s', |
$obs_fields, $image_fields, |
sprintf('id_image IN (%s)', implode(',', $idImg)), |
__FILE__ . ':' . __LINE__)); |
|
} |
|
/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités |
de manière identique à la seule différence que: |
1) ils sont combinés par des "OU" logiques plutôt que des "ET". |
2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique: |
Tous les mots-clefs doivent matcher et sont séparés par des espaces |
(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql) |
Pour plus d'information: ListeObservations2::sqlAddMasqueConstraint() */ |
static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) { |
if(!empty($p['masque'])) { |
$or_params = array('masque.auteur' => $p['masque'], |
'masque.departement' => $p['masque'], |
'masque.commune' => $p['masque'], // TODO/XXX ? |
'masque.id_zone_geo' => $p['masque'], |
|
// tous-deux remplacent masque.tag |
'masque.tag_cel' => $p['masque'], |
'masque.tag_pictoflora' => $p['masque'], |
|
'masque.ns' => $p['masque'], |
'masque.famille' => $p['masque'], |
'masque.date' => $p['masque'], |
'masque.genre' => $p['masque'], |
'masque.milieu' => $p['masque'], |
|
// tri est aussi nécessaire car affecte les contraintes de JOIN |
'tri' => $p['tri'], |
'ordre' => $p['ordre']); |
|
$or_masque = array_merge( |
ListeObservations2::requestFilterParams($or_params, NULL, $c /* pour masque.departement */), |
self::requestFilterParams($or_params), |
array('tag_explode_char' => ' ', 'tag_explode_semantic' => 'AND')); // spéciaux |
|
// pas de select, groupby & co ici |
$or_req = array('join' => array(), 'where' => array()); |
ListeObservations2::sqlAddConstraint($or_masque, $db, $or_req); |
self::sqlAddConstraint($or_masque, $db, $or_req); |
|
if($or_req['where']) { |
$req['where'][] = '(' . implode(' OR ', $or_req['where']) . ')'; |
// utile au cas ou des jointures seraient rajoutées |
$req['join'] = array_unique(array_merge($req['join'], $or_req['join'])); |
} |
} |
} |
|
// complete & override ListeObservations2::requestFilterParams() |
static function requestFilterParams(Array $params, $parametres_autorises = NULL) { |
if($parametres_autorises) { // filtrage de toute clef inconnue |
$params = array_intersect_key($params, array_flip($parametres_autorises)); |
} |
|
$p = array(); |
$p['tri'] = ListeObservations2::unsetIfInvalid($params, 'tri', self::$tri_possible); |
$p['format'] = ListeObservations2::unsetIfInvalid($params, 'format', self::$format_image_possible); |
$p = array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);')); |
$p = array_merge(self::$default_params, $p); |
return $p; |
|
// et pour identiplantes ? |
if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']); |
|
// compatibilité |
if(isset($params['masque.tag'])) { |
$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag']; |
} |
|
/* Nous n'implod()ons pas ici, car la sémantique des éléments multiples est |
variable (tag_explode_semantic) et considérée plus tard, notamment lors de la |
construction du SQL (sqlAddConstraint()). |
Cette sémantique peut varier selon que ces tags proviennent du formulaire de |
recherche avancée ou via le masque général (et l'appel récursif à requestFilterParams() |
qu'il implique) */ |
if(isset($params['masque.tag_cel'])) { |
if(isset($params['tag_explode_char'])) { |
$x = explode($params['tag_explode_char'], $params['masque.tag_cel']); |
$x = array_map('trim', $x); |
$p['masque.tag_cel'] = array_filter($x); |
} else { |
// toujours un tableau |
$p['masque.tag_cel'] = array(trim($params['masque.tag_cel'])); |
} |
|
} |
|
if(isset($params['masque.tag_pictoflora'])) { |
if(isset($params['tag_explode_char'])) { |
$x = explode($params['tag_explode_char'], $params['masque.tag_pictoflora']); |
$x = array_map('trim', $x); |
$p['masque.tag_pictoflora'] = array_filter($x); |
} else { |
// toujours un tableau |
$p['masque.tag_pictoflora'] = array(trim($params['masque.tag_pictoflora'])); |
} |
} |
|
if($p['tri'] == 'votes' || $p['tri'] == 'tags') { |
// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné |
if(!isset($params['protocole']) || !is_numeric($params['protocole'])) |
$p['protocole'] = self::$default_proto; |
else |
$p['protocole'] = intval($params['protocole']); |
} |
|
return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);')); |
} |
|
|
|
// met à jour *toutes* les stats de nombre de tags et de moyenne des votes |
static function _update_statistics($db) { |
$db->requeter("TRUNCATE TABLE del_image_stat"); |
$db->requeter(<<<EOF |
INSERT INTO `del_image_stat` ( |
SELECT id_image, divo.ce_protocole, divo.moyenne, dit.ctags |
FROM `BASECEL`.`cel_images` ci |
LEFT JOIN |
( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne FROM del_image_vote |
GROUP BY ce_image, ce_protocole ) AS divo |
ON ci.id_image = divo.ce_image |
LEFT JOIN |
( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag |
GROUP BY ce_image ) AS dit |
ON ci.id_image = dit.ce_image ) |
EOF |
); |
} |
} |