Subversion Repositories eFlore/Applications.del

Compare Revisions

Regard whitespace Rev 1515 → Rev 1516

/branches/v1.0-hydrogene/services/modules/0.1/images/ListeImages.php
1,1291 → 1,647
<?php
// declare(encoding='UTF-8');
/**
* Le web service image récupère toutes les données de la table del_obs_images
* pour retourner une liste d'images associée à une observation
*
* @category php 5.2
* @package del
* @subpackage images
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
* @copyright Copyright (c) 2012, Tela Botanica (accueil@tela-botanica.org)
* @author Raphaël Droz <raphael@tela-botanica.org>
* @copyright Copyright (c) 2013, Tela Botanica (accueil@tela-botanica.org)
* @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
* @license http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
* @version $Id: Bdd.php 403 2012-02-22 14:35:20Z gduche $
* @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Images
* @see http://www.tela-botanica.org/wikini/identiplante/wakka.php?wiki=IdentiPlante_PictoFlora_MoteurRecherche
*
* 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
* utilisées ensuite pour l'ORDER BY. La situation à base de del_image_stat
* est déjà bien meilleure 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)
* - poursuivre la réorganisation des méthodes statiques parmis Observation, ListeObservations et ListeImages2
* - *peut-être*: passer requestFilterParams() en méthode de classe
*
*
* MySQL sux:
* 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);
* MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
* EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
* PRIMARY
* EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
* DEPENDENT SUBQUERY ... ... ... mwarf !
* EXPLAIN SELECT id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1);
* 5.5: MATERIALIZED del_image_tag ALL ce_image NULL NULL NULL 38276 Using where
* 5.1: DEPENDENT SUBQUERY del_image_tag index_subquery ce_image ce_image 8 func 1 Using where
* FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
*
*/
 
require_once(dirname(__FILE__) . '/../DelTk.php');
require_once(dirname(__FILE__) . '/../observations/Observation.php');
restore_error_handler();
restore_exception_handler();
error_reporting(E_ALL);
 
/**
* FONCTION TEMPORAIRE de debug pour afficher le contenu d'une variable en format lisible
* @param $r la variable à afficher
* */
function debug($r) {
echo '<pre>'.print_r($r, true).'</pre>';
}
// 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
 
 
/**
* Le service ListeImages récupère les données des tables observation et images
* et les mets au format JSON pour identiplante / pictoflora
* */
class ListeImages {
// TODO: PHP-x.y, ces variables devrait être des "const"
static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
// Variables :
// Configuration générale du service
private $conteneur;
private $navigation;
private $bdd;
private $gestionBdd;
static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags');
// Parametres
private $ressources = array();
private $parametres = array();
private $masque;
// en plus de ceux dans DelTk
static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
private $tri = 'date_transmission';
private $directionTri = 'desc';
private $formatRetour = 'XL';
static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
'tri' => 'date_transmission', 'ordre' => 'desc',
// spécifiques à PictoFlora:
'format' => 'XL');
private $imageIds = array();
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
));
/**
* Constructeur de l'application
* Initialisation des variables générale de l'application
* @param Conteneu $conteneur le conteneur de classes de l'application
* */
 
public function __construct(Conteneur $conteneur = null) {
$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
$this->conteneur->chargerConfiguration('config_departements_bruts.ini');
$this->conteneur->chargerConfiguration('config_mapping_votes.ini');
$this->conteneur->chargerConfiguration('config_images.ini');
$this->navigation = $conteneur->getNavigation();
$this->masque = $conteneur->getMasque();
$this->gestionBdd = $conteneur->getGestionBdd();
$this->bdd = $this->gestionBdd->getBdd();
}
/**
* Méthode principale de la classe.
* Lance la récupération des images dans la base et les place dans un objet ResultatService
* pour l'afficher.
* @param array $ressources les ressources situées après l'url de base (ex : http://url/ressource1/ressource2)
* @param array $parametres les paramètres situés après le ? dans l'url
* */
public function consulter($ressources, $parametres) {
/* 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;'));
$this->initialiserRessourcesEtParametres($ressources, $parametres);
// 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());
$this->configurer();
$this->verifierConfiguration();
$this->verifierParametresTri();
$this->initialiserTri();
$db = $this->bdd;
$this->verifierParametreFormatRetour();
$this->initialiserFormatRetour();
// 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 = DelTk::requestFilterParams($parametres,
array_diff(DelTk::$parametres_autorises,
array('masque.type')),
$this->conteneur);
$parametres = $this->nettoyerParametres($parametres);
// notre propre filtrage sur l'INPUT
$params_pf = self::requestFilterParams($parametres,
array_merge(DelTk::$parametres_autorises,
self::$parametres_autorises));
// En fonction des paramètres de recherche, on n'effectue
// pas la même requête, pour optimiser les jointures et les
// rapidités d'éxécution.
$type = $this->getTypeRequete($ressources, $parametres);
/* filtrage des tags + sémantique des valeurs multiples:
Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
$params_pf['masque.tag_cel'] = DelTk::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
switch ($type) {
case 'obs' :
$liaisons = $this->chargerLiaisonsObs();
break;
case 'images' :
$liaisons = $this->chargerLiaisonsImages();
break;
case 'obs-images' :
$liaisons = $this->chargerLiaisons();
break;
case 'id':
$liaisons = $this->chargerLiaisonsParId();
break;
default : //case simple !
$liaisons = $this->chargerLiaisonsSimple();
}
$images = array();
if($liaisons) {
// Partie commune à tous les cas : on complète les liaisons avec les informations des votes
// et des images, puis on affiche sous forme de JSON
$images = $this->chargerImage($liaisons);
$images = $this->chargerVotes($images);
if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) {
$parametres['masque.tag_pictoflora'] = $parametres['masque.tag'];
}
$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
 
$resultat = new ResultatService();
if ($type == 'id') {
$clefsImages =array_keys($images);
$resultat->corps = $images[$clefsImages[0]];
} else {
$resultat->corps = array('entete' => $this->conteneur->getEntete(), 'resultats' => $images);
}
$params = array_merge(
DelTk::$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
 
return $resultat;
if(isset($parametres['format'])) {
$params['format'] = $parametres['format'];
}
// création des contraintes (génériques de DelTk)
DelTk::sqlAddConstraint($params, $db, $req);
// 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 = ListeObservations::getIdObs($params, $req, $db);
$idobs_tab = self::getIdImages($params, $req, $db);
/**************************************************************************************
* FONCTION LIEES AUX REQUETES *
**************************************************************************************/
/**
* Charger la clause WHERE en fonction des paramètres de masque
* */
private function chargerClauseWhere() {
 
$where = array();
$tableauMasque = $this->masque->getMasque();
if (!empty($tableauMasque)) {
foreach($tableauMasque as $idMasque => $valeurMasque) {
//TODO: scinder ceci en fonctions réutilisables ?
// voir si c'est interessant par rapport à la recherche générale
$idMasque = str_replace('masque.', '', $idMasque);
switch ($idMasque) {
// nom du masque => nom BDD
case 'auteur' :
$whereAuteur = ' '.$this->creerFiltreAuteur($this->masque->getMasque('auteur'));
if($whereAuteur != '') {
$where[] = $whereAuteur;
// 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) {
$resultat = new ResultatService();
$resultat->corps = array('entete' => DelTk::makeJSONHeader(0, $params, Config::get('url_service')),
'resultats' => array());
return $resultat;
/*
header('HTTP/1.0 404 Not Found');
// don't die (phpunit)
throw(new Exception()); */
}
break;
case 'date' :
$whereDate = ' '.$this->creerFiltreDate($valeurMasque);
if($whereDate != '') {
$where[] = $whereDate;
}
break;
case 'departement' :
$where[] = ' '.$this->creerFiltreIdZoneGeo($valeurMasque);
break;
case 'genre' :
$where[] = ' '.$this->mappingFiltre['ns'].' LIKE '.$this->proteger($valeurMasque.' %');
break;
case 'tag' :
$where[] = ' '.$this->creerFiltreMotsCles($valeurMasque);
break;
case 'referentiel' :
$where[] = ' dob.nom_referentiel LIKE '.$this->proteger($valeurMasque.'%');
break;
case 'ns' :
$where[] = ' nom_sel LIKE '.$this->proteger($valeurMasque.'%');
break;
case 'nn' :
$num_noms = $valeurMasque;
$num_noms = explode(',', $num_noms);
$num_noms = array_map('intval', $num_noms);
$num_noms = array_filter($num_noms);
$num_noms = implode(',', $num_noms);
$where[] = "(nom_sel_nn IN ($num_noms) OR nom_ret_nn IN ($num_noms)) ";
break;
case 'commune' :
$where[] = ' '.$this->mappingFiltre[$idMasque].' LIKE '.$this->proteger(str_replace(array('-',' '), '_', $valeurMasque).'%');
break;
case 'masque' :
$where[] = ' '.$this->creerFiltreMasqueGeneral($valeurMasque);
break;
default:
$where[] = ' '.$this->mappingFiltre[$idMasque].' = '.$this->proteger($valeurMasque);
break;
}
}
}
if (!empty($where)) {
return ' WHERE '.implode('AND', $where);
} else {
return;
}
}
/**
* Créer un masque général lorsque l'on souhaite utiliser le passe partout
* @param la valeur du passe partout
* */
private function creerFiltreMasqueGeneral($valeurMasque) {
//TODO: affecter d'aborder les variables, puis les tester pour les
// ajouter à la chaine
$whereAuteur = $this->creerFiltreAuteur($valeurMasque);
$whereIdZoneGeo = $this->creerFiltreIdZoneGeo($valeurMasque);
// 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']);
$masqueGeneral = '( '.
(($whereAuteur != '') ? $whereAuteur.' OR ' : '' ).
(($whereIdZoneGeo != '') ? $whereIdZoneGeo.' OR ' : '' ).
'zone_geo LIKE '.$this->proteger($this->remplacerParJokerCaractere($valeurMasque).'%').' OR '.
$this->creerFiltreMotsCles($valeurMasque).' OR '.
'nom_sel LIKE '.$this->proteger($valeurMasque.'%').' OR '.
'famille LIKE '.$this->proteger($valeurMasque.'%').' OR '.
'milieu LIKE '.$this->proteger($valeurMasque).' OR '.
$this->mappingFiltre['ns'].' LIKE '.$this->proteger('%'.$valeurMasque.'% %').' OR '.
$this->creerFiltreDate($valeurMasque).
') ';
return $masqueGeneral;
$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) = self::reformateImagesDoubleIndex(
$liaisons,
$this->conteneur->getParametre('images.url_images'),
$params['format']);
/**
* Créer le filtre auteur en recherchant dans son nom, prénom, adresse email en fonction
* de la chaine donnée
* @param la valeur recherchée
* */
private function creerFiltreAuteur($valeurMasque) {
$masque = '';
$auteurId = $valeurMasque;
if (is_numeric($auteurId)) {
$masque = ' ce_utilisateur = '.$auteurId;
} else {
if (strpos($auteurId, '@') === false) {
$tableauNomPrenom = explode(' ',$auteurId, 2);
if(count($tableauNomPrenom) == 2) {
// on teste potentiellement un nom prenom ou bien un prénom nom
$masque = '('.
'(nom LIKE '.$this->proteger($tableauNomPrenom[0].'%').' AND '.
'prenom LIKE '.$this->proteger($tableauNomPrenom[1].'%').') OR '.
'(nom LIKE '.$this->proteger($tableauNomPrenom[1].'%').' AND '.
'prenom LIKE '.$this->proteger($tableauNomPrenom[0].'%').') OR '.
'(dob.nom_utilisateur LIKE '.$this->proteger($tableauNomPrenom[0].'%').' AND '.
'dob.prenom_utilisateur LIKE '.$this->proteger($tableauNomPrenom[1].'%').') OR '.
'(dob.nom_utilisateur LIKE '.$this->proteger($tableauNomPrenom[1].'%').' AND '.
'dob.prenom_utilisateur LIKE '.$this->proteger($tableauNomPrenom[0].'%').') OR '.
'(nom LIKE '.$this->proteger($valeurMasque.'%').') OR '.
'(prenom LIKE '.$this->proteger($valeurMasque.'%').') OR '.
'(dob.nom_utilisateur LIKE '.$this->proteger($valeurMasque.'%').') OR '.
'(dob.prenom_utilisateur LIKE '.$this->proteger($valeurMasque.'%').') '.
')';
} else {
$masque = '(
(nom LIKE '.$this->proteger($auteurId.'%').' OR '.
'prenom LIKE '.$this->proteger($auteurId.'%').' OR '.
'dob.nom_utilisateur LIKE '.$this->proteger($auteurId.'%').' OR '.
'dob.prenom_utilisateur LIKE '.$this->proteger($auteurId.'%').')'.
')';
}
} else {
$masque = " ( courriel LIKE ".$this->proteger($valeurMasque.'%').
" OR dob.courriel_utilisateur LIKE ".$this->proteger($valeurMasque.'%').") ";
}
}
return $masque;
}
// 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);
/**
* Obtenir une chaine de caractère concaténant nom et prénom séparé par une virgule
* @param String $auteurId l'identifiant de l'auteur
* @return String la chaine de concaténation
* */
private function getChaineNomPrenom($auteurId) {
$nomPrenom = explode(' ', $auteurId);
$nomPrenom = $this->proteger($nomPrenom);
$chaineNomPrenom = implode(', ', $nomPrenom);
return $chaineNomPrenom;
// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
$resultat = new ResultatService();
$resultat->corps = array('entete' => DelTk::makeJSONHeader($total, $params_header, Config::get('url_service')),
'resultats' => $images);
return $resultat;
}
/**
* Créer le filtre de recherche par zone géographique en fonction du masque
* @param $valeurMasque le terme de la recherche
* */
private function creerFiltreIdZoneGeo($valeurMasque) {
$masque = '';
$dept = $valeurMasque;
if (is_numeric($dept)) {
$dept = sprintf('%02s', $dept);
$dept = sprintf("%-'_5s", $dept);
$masque = " ce_zone_geo LIKE ".$this->proteger('INSEE-C:'.$dept);
} else {
$deptId = $this->conteneur->getParametre($dept);
if ($deptId != null) {
$masque = " ce_zone_geo LIKE ".$this->proteger('INSEE-C:'.$deptId.'%');
} else {
$id = $this->obtenirIdDepartement($valeurMasque);
$masque = " ce_zone_geo LIKE ".$this->proteger('INSEE-C:'.$id.'%');
* 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'] . ', dis.nb_votes ' . $p['ordre'];
return;
}
}
return $masque;
}
/**
* Générer la chaine de recherche pour la date en fonction du masque
* @param $valeurMasque la date recherchée (AAAA ou JJ/MM/AAAA)
* */
private function creerFiltreDate($valeurMasque) {
//TODO: définir dans le fichier de config un tableau contenant plusieurs format de date
// autorisés pour la recherche, qui seraient ajoutés au OR
$masque = '(';
$masque .= (is_numeric($valeurMasque)) ? ' YEAR(date_observation) = '.$this->proteger($valeurMasque).' OR ' : '';
$masque .= " DATE_FORMAT(date_observation, '%d/%m/%Y') = ".$this->proteger($valeurMasque).' '.
')';
return $masque;
if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
return;
}
/**
* Générer la chaine de recherche dans les mots clés en fonction du masque
* @param $valeurMasque le mot clé recherché
* */
private function creerFiltreMotsCles($valeurMasque) {
$mots_cles = explode(' ', $valeurMasque);
$requeteMotsClesImg = array();
$requeteMotsClesObs = array();
$requeteMotsClesImgPublic = array();
foreach($mots_cles as $mot_cle) {
//TODO: rechercher sur les mots clés normalisés dans tous les cas ?
$requeteMotsCles = $this->proteger('%'.$mot_cle.'%');
$motsCleProtege = $this->proteger($this->normaliserMotCle('%'.$mot_cle.'%'));
$requeteMotsClesImgPublic[] = 'di.id_image IN (SELECT ce_image FROM del_image_tag WHERE tag_normalise LIKE '.$motsCleProtege.' AND actif = 1)';
$requeteMotsClesImg[] = 'di.mots_cles_texte LIKE '.$requeteMotsCles;
$requeteMotsClesObs[] = 'dob.mots_cles_texte LIKE '.$requeteMotsCles;
if($p['tri'] == 'date_observation') {
$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
return;
}
$requeteMotsClesImgPublic = implode(' AND ', $requeteMotsClesImgPublic);
$requeteMotsClesImg = implode(' AND ', $requeteMotsClesImg);
$requeteMotsClesObs = implode(' AND ', $requeteMotsClesObs);
$masque = '('.
'('.$requeteMotsClesImgPublic.') OR '.
'('.$requeteMotsClesImg.') OR '.
'('.$requeteMotsClesObs.') '.
')';
return $masque;
// tri == 'date_transmission'
// avant cel:r1860, date_transmission pouvait être NULL
// or nous voulons de la consistence (notamment pour phpunit)
$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
}
// ??
private function assemblercomptageOccurencesMotsClesCel() {
$chaineMotsClesAffiches = $this->conteneur->getParametre('mots_cles_cel_affiches');
$tabMotsClesAffiches = explode(',',$chaineMotsClesAffiches);
$chaineSql = '';
// Comptage du nombre de mots clés officiels présents dans la chaine mots clés texte
foreach ($tabMotsClesAffiches as $motCle) {
if($chaineSql != '') {
$chaineSql .= ' + ';
}
$chaineSql .= 'IF(FIND_IN_SET('.$this->proteger($motCle).',di.mots_cles_texte) != 0, 1, 0)';
}
return '('.$chaineSql.')';
}
private function getTri() {
$order = '';
if($this->doitJoindreTableVotes()) {
$order = ' GROUP BY dvote.ce_image, dob.id_observation ORDER BY total_votes '.$this->directionTri.', date_transmission desc ';
} else if($this->doitJoindreTableTags()) {
$order = ' GROUP BY doi.id_image ORDER BY total_tags '.$this->directionTri.', date_transmission desc ';
} else {
$order = ' ORDER BY '.$this->tri.' '.$this->directionTri.' ';
}
return $order;
}
/**
* Compter le nombre total d'images dans la base pour affichage dans entete.
* */
private function getFoundRows() {
$requete = 'SELECT FOUND_ROWS() AS nbre ';
$resultats = $this->bdd->recuperer($requete);
return (int) $resultats['nbre'];
}
/**
* En fonction des paramètres, générer les conditions de recherche
* des observations
* */
private function getConditionsObs() {
$conditionsObs = array();
$masques = $this->masque->getMasque();
if (isset($masques['masque'])) {
$passe = $masques['masque'];
// Si on a saisi le masque passe partout, alors on doit chercher dans tous les champs
// de la table observation (OR)
$conditionLibre = array();
if (!isset($masques['masque.ns'])) {
$conditionsLibre[] = "nom_sel LIKE '$passe%'";
}
if (!isset($masques['masque.famille'])) {
$conditionsLibre[] = "famille LIKE '$passe%'";
}
if (!isset($masques['masque.milieu'])) {
$conditionsLibre[] = "nom_sel LIKE '$passe%'";
}
if (!isset($masques['masque.date'])) {
$conditionsLibre[] = $this->creerFiltreDate($passe);
}
if (!isset($masques['masque.auteur'])) {
$conditionsLibre[] = $this->creerFiltreAuteur($passe);
}
/*
* FIXME : remplacer par motcle projet !
* if (!isset($masques['masque.tag'])) {
$conditionsLibre[] = "mots_cles_texte LIKE '%$passe%'";
}*/
$conditionsObs[] = implode(' OR ', $conditionsLibre);
}
 
// referentiel
if (isset($masques['masque.referentiel'])) {
$ref = $masques['masque.referentiel'];
$conditionsObs[] = "dob.nom_referentiel LIKE '$ref%'";
}
// nom sel
if (isset($masques['masque.ns'])) {
$nom_sel = $masques['masque.ns'];
$conditionsObs[] = "nom_sel LIKE '$nom_sel%'";
}
// num nom
if (isset($masques['masque.nn'])) {
$num_noms = $masques['masque.nn'];
$num_noms = explode(',', $num_noms);
$num_noms = array_map('intval', $num_noms);
$num_noms = array_filter($num_noms);
$num_noms = implode(',', $num_noms);
$conditionsObs[] = "(nom_sel_nn IN ($num_noms) OR nom_ret_nn IN ($num_noms)) ";
}
// num taxon
if (isset($masques['masque.nt'])) {
$num_taxon = $masques['masque.nt'];
$conditionsObs[] = 'nt = '.intval($num_taxon);
}
// famille
if (isset($masques['masque.famille'])) {
$famille = $masques['masque.famille'];
$conditionsObs[] = "famille LIKE '$famille%'";
}
// genre
if (isset($masques['masque.genre'])) {
$genre = $masques['masque.genre'];
$conditionsObs[] = "nom_sel LIKE '$genre%'";
}
// milieu
if (isset($masques['masque.milieu'])) {
$milieu = $masques['masque.milieu'];
$conditionsObs[] = "nom_sel LIKE '$milieu%'";
}
// date
if (isset($masques['masque.date'])) {
$date = $masques['masque.date'];
$conditionsObs[] = $this->creerFiltreDate($date);
}
// utilisateur
if (isset($masques['masque.auteur'])) {
$auteur = $masques['masque.auteur'];
$conditionsObs[] = $this->creerFiltreAuteur($auteur);
}
// commune
if (isset($masques['masque.commune'])) {
$commune = $masques['masque.commune'];
$conditionsObs[] = " zone_geo LIKE ".$this->proteger(str_replace(array('-',' '), '_', $commune).'%');
}
// commune
if (isset($masques['masque.departement'])) {
$dept = $masques['masque.departement'];
$conditionsObs[] = $this->creerFiltreIdZoneGeo($dept);
}
 
return $conditionsObs;
}
/**
* Obtenir le tableu de chaines de condition de requete images en fonction des masques
* */
private function getConditionsImages() {
$conditionsImg = array();
$masques = $this->masque->getMasque();
if (isset($masques['masque.tag'])) {
$tag = $masques['masque.tag'];
$conditionsImg[] = " dit.tag_normalise LIKE '$tag%' ";
$conditionsImg[] = " di.mots_cles_texte LIKE '%$tag%' ";
}
return $conditionsImg;
}
/*-------------------------------------------------------------------------------
CHARGEMENT DES IMAGES
--------------------------------------------------------------------------------*/
/**
* Chargement depuis la bdd de toutes les liaisons entre images et observations
* Méthode appelée uniquement lorsque les paramètres sont vides
* */
private function chargerLiaisonsSimple() {
// On récupère d'abord les N images de del_obs_image, éventuellement triées,
// Et on complète avec les informations des observations associées
$requeteImages = ' SELECT *, di.mots_cles_texte as mots_cles_texte_image '.
' FROM del_obs_image doi '.
' INNER JOIN del_image di ON doi.id_image = di.id_image ';
 
// Si le tri se fait par date d'observation, on récupère les identifiants de N observations triées
if (isset($this->parametres['tri']) && $this->parametres['tri'] == 'date_observation') {
$ordre = isset($this->parametres['ordre']) ? $this->parametres['ordre'] : 'DESC';
$requeteIdObs = ' SELECT doi.id_image as id_image '.
' FROM del_obs_image doi '.
' INNER JOIN del_observation dob ON dob.id_observation = doi.id_observation '.
' INNER JOIN del_image di ON doi.id_image = di.id_image '.
' ORDER BY date_observation '.$ordre.', dob.id_observation '.$ordre;
$requeteIdObs .= $this->gestionBdd->getLimitSql();
// Récupérer les N observations triées par date
$observations = $this->bdd->recupererTous($requeteIdObs . ' -- ' . __FILE__ . ':' . __LINE__);
$idsImages = array();
foreach ($observations as $observation) {
$idsImages[] = $observation['id_image'];
}
$chaineIdImages = implode(',', $idsImages);
$requeteImages .= ' WHERE doi.id_image IN ('.$chaineIdImages.') '.
' GROUP BY doi.id_image, doi.id_observation '.
' ORDER BY FIELD(doi.id_image, '.$chaineIdImages.')'.
' LIMIT '.$this->navigation->getLimite(); // On limite sur le nombre car les obs peuvent avoir plusieurs images
} else {
$requeteImages .= ' GROUP BY doi.id_image, doi.id_observation ';
$requeteImages .= ' ORDER BY doi.id_observation DESC';
$requeteImages .= $this->gestionBdd->getLimitSql();
}
$liaisons = $this->bdd->recupererTous($requeteImages . ' -- ' . __FILE__ . ':' . __LINE__);
// 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 (!empty($liaisons)) {
$idsObservations = array();
foreach ($liaisons as $image) {
$idObs = $image['id_observation'];
$idsObservations[$idObs] = $idObs;
}
$chaineIdObs = implode(',', $idsObservations);
// On récupère les observations qui complètent la requête précédente
$requeteObservations = ' SELECT * '.
' FROM del_observation dob '.
' LEFT JOIN del_utilisateur du ON dob.ce_utilisateur = du.id_utilisateur '.
' WHERE id_observation IN ('.$chaineIdObs.')';
$resultatsObservations = $this->bdd->recupererTous($requeteObservations . ' -- ' . __FILE__ . ':' . __LINE__);
// FIXME : Ca ne doit pas arriver, mais que se passe-t-il s'il n'y a pas d'observation pour l'image ?!
// On range les observations dans un tableau pour pouvoir les retrouver par leur id :
$observations = array();
foreach ($resultatsObservations as $id => $observation) {
$idObs = $observation['id_observation'];
$observations[$idObs] = $observation;
}
// Enfin, pour chaque image préalablement récupérées, on complète avec les informations de l'observation
// FIXME : peut-être peut-on utiliser un array_merge ici ?
foreach ($liaisons as $id => $liaison) {
$idObs = $liaison['id_observation'];
$observation = $observations[$idObs];
foreach ($observation as $cle => $valeur) {
$liaisons[$id][$cle] = $valeur;
}
}
// On compte à part les images issues de la jointure de del_obs_image et del_image car la fonction
// SQL_CALC_FOUND_ROWS dans la fonction requete image fait passer le temps d'éxécution de 0.0011 à 15s !
$requeteNbImages = 'SELECT SUM(t.nb) as nb FROM (SELECT count(DISTINCT doi.id_image) as nb '.
'FROM del_obs_image doi '.
'INNER JOIN del_image di '.
'ON di.id_image = doi.id_image '.
'GROUP BY doi.id_image, doi.id_observation) t ';
$resultatNbImages = $this->bdd->recupererTous($requeteNbImages . ' -- ' . __FILE__ . ':' . __LINE__);
 
$total = (int) $resultatNbImages[0]['nb'];
$this->navigation->setTotal($total);
}
 
return $liaisons;
}
 
/**
* Charge les liaisons pour une seule image dont l'id est spécifié dans l'URL
* Copie de chargerLiaisonsObs avec critère sur l'id_image uniquement
* Supporte seulement le masque sur referentiel
* 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)
*
*/
private function chargerLiaisonsParId() {
 
$idImage = $this->ressources[0];
 
// Récupérer les liaisons
$requeteObs = ' SELECT SQL_CALC_FOUND_ROWS dob.id_observation as id_observation, dob.nom_referentiel, nom_sel, nom_sel_nn, nt, famille, ce_zone_geo, zone_geo, lieudit, station, milieu, '.
' date_observation, dob.mots_cles_texte as mots_cles_texte, dob.commentaire as commentaire, di.mots_cles_texte as mots_cles_texte_image , date_transmission, '.
' doi.id_image as id_image, di.ce_utilisateur as ce_utilisateur, prenom, nom, courriel, dob.prenom_utilisateur, dob.nom_utilisateur, dob.courriel_utilisateur, nom_original '.
'FROM del_observation dob '.
' INNER JOIN del_obs_image doi ON dob.id_observation = doi.id_observation '.
' INNER JOIN del_image di ON doi.id_image = di.id_image '.
' LEFT JOIN del_utilisateur du ON dob.ce_utilisateur = du.id_utilisateur '.
' WHERE doi.id_image = '.intval($idImage);
 
if (isset($this->parametres['masque.referentiel'])) {
$requeteObs .= " AND dob.nom_referentiel LIKE '" . $this->parametres['masque.referentiel'] . "%'";
static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
// TODO implement dans DelTk ?
if(!empty($p['masque.milieu'])) {
$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%');
}
 
$observations = $this->bdd->recupererTous($requeteObs . ' -- ' . __FILE__ . ':' . __LINE__);
 
$total = $this->getFoundRows();
$this->navigation->setTotal($total);
 
return $observations;
}
 
/**
* Chargement depuis la bdd de toutes les liaisons entre images et observations
* Méthode appelée uniquement lorsque les paramètres concernent une observation
* */
private function chargerLiaisonsObs() {
// Récupérer les liaisons
$requeteObs = ' SELECT SQL_CALC_FOUND_ROWS dob.id_observation as id_observation, dob.nom_referentiel, nom_sel, nom_sel_nn, nt, famille, ce_zone_geo, zone_geo, lieudit, station, milieu, '.
' date_observation, dob.mots_cles_texte as mots_cles_texte, dob.commentaire as commentaire, di.mots_cles_texte as mots_cles_texte_image , date_transmission, '.
' doi.id_image as id_image, di.ce_utilisateur as ce_utilisateur, prenom, nom, courriel, dob.prenom_utilisateur, dob.nom_utilisateur, dob.courriel_utilisateur, nom_original '.
'FROM del_observation dob '.
' INNER JOIN del_obs_image doi ON dob.id_observation = doi.id_observation '.
' INNER JOIN del_image di ON doi.id_image = di.id_image '.
' LEFT JOIN del_utilisateur du ON dob.ce_utilisateur = du.id_utilisateur ';
// Récupérer les conditions sous forme de tableau
$conditionsObs = $this->getConditionsObs();
if (!empty($conditionsObs)) {
$where = ' WHERE '.implode(' AND ', $conditionsObs);
$requeteObs .= $where;
}
// Gérer le tri (uniquement si c'est date_observation)
if (isset($this->parametres['tri']) && $this->parametres['tri'] == 'date_observation') {
$ordre = isset($this->parametres['ordre']) ? $this->parametres['ordre'] : 'DESC';
$tri = ' ORDER BY '.$this->parametres['tri'].' '.$ordre.', doi.id_observation '.$ordre.' ';
$requeteObs .= $tri;
}
$requeteObs .= $this->gestionBdd->getLimitSql();
$observations = $this->bdd->recupererTous($requeteObs . ' -- ' . __FILE__ . ':' . __LINE__);
 
$total = $this->getFoundRows();
$this->navigation->setTotal($total);
 
return $observations;
}
/**
* Chargement depuis la bdd de toutes les liaisons entre images et observations
* Méthode appelée uniquement lorsque les paramètres concernent les images
* */
private function chargerLiaisonsImages() {
// FIXME : si on faisait une requete à part pour compter, ca irait plus vite
// Récupérer tous les ID d'image en fonction des paramètres de recherche
$requeteImages = ' SELECT SQL_CALC_FOUND_ROWS '.
' doi.id_image as id_image, dob.id_observation as id_observation, dob.nom_referentiel, nom_sel, nom_sel_nn, nt, famille, ce_zone_geo, zone_geo, lieudit, station, milieu, '.
' date_observation, dob.mots_cles_texte as mots_cles_texte, dob.commentaire as commentaire, di.mots_cles_texte as mots_cles_texte_image , date_transmission, '.
' di.ce_utilisateur as ce_utilisateur, prenom, nom, courriel, dob.prenom_utilisateur, dob.nom_utilisateur, dob.courriel_utilisateur, nom_original '.
' FROM del_obs_image doi '.
' INNER JOIN del_image di ON doi.id_image = di.id_image '.
' INNER JOIN del_observation dob ON dob.id_observation = doi.id_observation '.
' LEFT JOIN del_image_tag dit ON dit.ce_image = di.id_image '.
' LEFT JOIN del_utilisateur du ON du.id_utilisateur = di.ce_utilisateur ';
 
$conditionsImg = $this->getConditionsImages();
if (!empty($conditionsImg)) {
$where = ' WHERE ('.implode(' OR ', $conditionsImg).') ';
$where .= ' AND dit.actif = 1 ';
$requeteImages .= $where;
}
// Gérer le tri, sur les votes ou sur les tags
if (isset($this->parametres['tri'])) {
$chaineTri = '';
$chaineOrdre = '';
if ($this->parametres['tri'] == 'votes') {
$protocole = isset($this->parametres['protocole']) ? $this->parametres['protocole'] : 1;
$requeteVotes = ' SELECT doi.id_image as id_image, IF(divo.ce_protocole = '.$protocole.', AVG(divo.valeur), 0) as total_votes '.
' FROM del_obs_image doi '.
' INNER JOIN del_image di ON doi.id_image = di.id_image '.
' INNER JOIN del_observation dob ON dob.id_observation = doi.id_observation '.
' LEFT JOIN del_image_vote divo ON doi.id_image = divo.ce_image '.
' AND ce_protocole = '.$protocole.' ';
// Et si on a cherché par tag ?
if (isset($this->parametres['masque.tag'])) {
$tag = $this->parametres['masque.tag'];
$requeteVotes .= ' LEFT JOIN del_image_tag dit ON dit.ce_image = di.id_image ';
$requeteVotes .= " WHERE (dit.tag_normalise LIKE '$tag%' OR di.mots_cles_texte LIKE '%$tag%') AND dit.actif = 1 ";
}
$requeteVotes .= ' GROUP BY doi.id_image, doi.id_observation '.
' ORDER by total_votes '.$this->directionTri .', doi.id_observation '.$this->directionTri.' '.
$this->gestionBdd->getLimitSql();
 
$resultatsVotes = $this->bdd->recupererTous($requeteVotes . ' -- ' . __FILE__ . ':' . __LINE__);
$tabVotes = array();
foreach ($resultatsVotes as $vote) {
$tabVotes[] = $vote['id_image'];
}
$strVotes = empty($tabVotes) ? "''" : implode(',', $tabVotes);
// Et si on a cherché par tag ?
if (isset($this->parametres['masque.tag'])) {
$chaineTri .= ' AND ';
/* 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 {
$chaineTri .= ' WHERE ';
$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';
}
$chaineTri .= ' doi.id_image IN ('.$strVotes.') ';
$chaineOrdre = ' ORDER BY FIELD(doi.id_image, '.$strVotes.') ';
}
if ($this->parametres['tri'] == 'tags') {
$requetetags = ' SELECT SQL_CALC_FOUND_ROWS doi.id_image, COUNT(id_tag) as total_tags '.
' FROM del_obs_image doi LEFT JOIN del_image_tag dit ON dit.ce_image = doi.id_image AND dit.actif = 1 '.
' INNER JOIN del_image di ON doi.id_image = di.id_image '.
' INNER JOIN del_observation dob ON dob.id_observation = doi.id_observation ';
 
if (isset($this->parametres['masque.tag'])) {
// Et si on a cherché par tag ?
$tag = $this->parametres['masque.tag'];
$requetetags .= " WHERE (dit.tag_normalise LIKE '$tag%' OR di.mots_cles_texte LIKE '%$tag%') ";
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';
}
$requetetags .= ' GROUP BY doi.id_image, doi.id_observation '.
' ORDER by total_tags '.$this->directionTri.', doi.id_observation '.$this->directionTri.
$this->gestionBdd->getLimitSql();
 
$resultatstags = $this->bdd->recupererTous($requetetags . ' -- ' . __FILE__ . ':' . __LINE__);
$tabtags = array();
foreach ($resultatstags as $tag) {
$tabtags[] = $tag['id_image'];
// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
// sont toujours présentes; bien que parfois NULL.
if($p['masque.tag_cel']) {
if(isset($p['masque.tag_cel']['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']['AND'];
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) . ')';
}
$strtags = empty($tabtags) ? "''" : implode(',', $tabtags);
// Et si on a cherché par tag ?
if (isset($this->parametres['masque.tag'])) {
$chaineTri .= ' AND ';
} else {
$chaineTri .= ' WHERE ';
else {
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
}
$chaineTri .= ' doi.id_image IN ('.$strtags.') ';
$chaineOrdre = ' ORDER BY FIELD(doi.id_image, '.$strtags.') ';
}
$requeteImages .= $chaineTri.' GROUP BY doi.id_image, doi.id_observation '.$chaineOrdre;
} else {
$requeteImages .= ' GROUP BY doi.id_image, doi.id_observation'; // des fois, on a plusieurs observations pour la même image ...
$requeteImages .= $this->gestionBdd->getLimitSql();
}
if($p['masque.tag_pictoflora']) {
// inutilisable pour l'instant
// self::sqlAddPictoFloraTagConstraint1($p, $db, $req);
 
$retour = $this->bdd->recupererTous($requeteImages . ' -- ' . __FILE__ . ':' . __LINE__);
$total = $this->getFoundRows();
$this->navigation->setTotal($total);
// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery)
// self::sqlAddPictoFloraTagConstraint2($p, $db, $req);
return $retour;
// approche fiable mais sous-optimale
self::sqlAddPictoFloraTagConstraint3($p, $db, $req);
}
/**
* Chargement depuis la bdd de toutes les liaisons entre images et observations
* */
private function chargerLiaisons() {
$champs = array('dob.id_observation as id_observation', 'nom_sel', 'nom_sel_nn', 'nt', 'famille', 'dob.nom_referentiel', 'ce_zone_geo', 'zone_geo',
'lieudit', 'station', 'milieu', 'date_observation', 'dob.mots_cles_texte as mots_cles_texte', 'dob.commentaire as commentaire',
'di.mots_cles_texte as mots_cles_texte_image ', 'date_transmission', 'di.id_image as id_image', 'di.ce_utilisateur as ce_utilisateur',
'prenom', 'nom', 'courriel', 'dob.prenom_utilisateur', 'dob.nom_utilisateur', 'dob.courriel_utilisateur', 'nom_original');
// Attention le LEFT JOIN est indispensable pour ramener les images n'ayant pas de votes
// en cas de tri par votes
$requeteLiaisons = 'SELECT DISTINCT SQL_CALC_FOUND_ROWS '.implode(', ',$champs).' '.
($this->doitJoindreTableVotes() ?
', IF(dvote.ce_protocole = '.$this->parametres['protocole'].', AVG(dvote.valeur), 0) as total_votes ' :
''
).
($this->doitJoindreTableTags() ?
// attention le DISTINCT est indispensable !
', (COUNT(DISTINCT dtag.id_tag) + '.$this->assemblercomptageOccurencesMotsClesCel().') as total_tags ' :
''
).
'FROM '.$this->gestionBdd->formaterTable('del_obs_image', 'doi').
'INNER JOIN del_image di '.
'ON doi.id_image = di.id_image '.
'INNER JOIN del_observation dob '.
'ON doi.id_observation = dob.id_observation '.
'LEFT JOIN del_utilisateur du '.
'ON du.id_utilisateur = di.ce_utilisateur '.
($this->doitJoindreTableTags() ?
'LEFT JOIN del_image_tag dtag '.
'ON doi.id_image = dtag.ce_image AND dtag.actif = 1 ' :
''
).
($this->doitJoindreTableVotes() ?
'LEFT JOIN del_image_vote dvote '.
'ON doi.id_image = dvote.ce_image AND dvote.ce_protocole = '.$this->parametres['protocole'] :
''
);
$requeteLiaisons .= $this->chargerClauseWhere();
$requeteLiaisons .= $this->getTri();
$requeteLiaisons .= $this->gestionBdd->getLimitSql();
 
$retour = $this->bdd->recupererTous($requeteLiaisons . ' -- ' . __FILE__ . ':' . __LINE__);
$total = $this->getFoundRows();
$this->navigation->setTotal($total);
return $retour;
}
/**
* Retourner un tableau d'images formaté en fonction des liaisons trouvées
* @param $liaisons les liaisons de la table del_obs_images
* */
private function chargerImage($liaisons) {
$images = array();
foreach ($liaisons as $liaison) {
$idImage = $liaison['id_image'];
if($liaison['ce_utilisateur'] == 0) {
$liaison['prenom'] = $liaison['prenom_utilisateur'];
$liaison['nom'] = $liaison['nom_utilisateur'];
/* approche intéressante si les deux problèmes suivants peuvent être résolu:
- LEFT JOIN => dup => *gestion de multiples GROUP BY* (car in-fine un LIMIT est utilisé)
- dans le cas d'un ET logique, comment chercher les observations correspondantes ? */
static function sqlAddPictoFloraTagConstraint1($p, $db, &$req) {
// XXX: utiliser tag plutôt que tag_normalise ?
$req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
$req['where'][] = 'dit.actif = 1';
$req['groupby'][] = 'vdi.id_image'; // TODO: nécessaire (car dup') mais risque de conflict en cas de tri (multiple GROUP BY)
// XXX: en cas de ET, possibilité du GROUP_CONCAT(), mais probablement sans grand intérêt, d'où une boucle
if(isset($p['masque.tag_pictoflora']['AND'])) {
// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ?
// ... le LEFT-JOIN n'y semble pas adapté
}
// On enregistre l'ID de l'image pour n'effectuer qu'une seule requête par la suite
$this->imageIds[] = $idImage;
$index = $liaison['id_image'].'-'.$liaison['id_observation'];
$images[$index] = array('id_image' => $idImage, 'binaire.href' => $this->formaterLienImage($idImage),
'protocoles_votes' => array(),
'mots_cles_texte' => $liaison['mots_cles_texte_image'], 'observation' => $this->formaterObservation($liaison));
else {
$protected_tags = array();
foreach($p['masque.tag_pictoflora']['OR'] as $tag) $protected_tags[] = $db->proteger(strtolower($tag));
$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags));
}
return $images;
}
/**
* Charger les votes pour chaque image
* */
private function chargerVotes($images) {
$requeteVotes = 'SELECT v.*, p.* FROM '.
$this->gestionBdd->formaterTable('del_image_vote', 'v').
' INNER JOIN del_image_protocole p '.
'ON v.ce_protocole = p.id_protocole '.
$this->chargerClauseWhereVotes();
$resultatsVotes = $this->bdd->recupererTous($requeteVotes . ' -- ' . __FILE__ . ':' . __LINE__);
//TODO : faire une méthode formater vote
$votes = $this->formaterVotes($resultatsVotes);
foreach ($images as $id => $image) {
if (isset($votes[$image['id_image']])) {
$images[$id]['protocoles_votes'] = $votes[$image['id_image']];
// inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro)
static function sqlAddPictoFloraTagConstraint2($p, $db, &$req) {
// Note à propos des 4 "@ instruction" ci-dessous (notamment sur recupererTous())
// REGEXP permet un puissant mécanisme de sélection des obs/image à qui sait
// l'utiliser, mais peut sortir une erreur en cas de REGEXP invalide
// ex: REGEX "^(".
// Pour l'heure nous ignorons ce type d'erreur car aucun de nos champ de recherche
// ne peuvent (ou ne devrait) comporter des meta-caractères
// ([])?*+\\
if(isset($p['masque.tag_pictoflora']['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']['AND']);
$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']['AND'])));
}
else {
$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']['OR'])));
}
return $images;
}
private function chargerClauseWhereVotes() {
if (sizeof($this->imageIds) > 0) {
$chaineImageIds = implode(',', $this->imageIds);
$where[] = 'v.ce_image IN ('.$chaineImageIds.')';
}
if (isset($this->parametres['protocole'])) {
$where[] = 'v.ce_protocole = '.$this->proteger($this->parametres['protocole']);
}
// si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction
static function sqlAddPictoFloraTagConstraint3($p, $db, &$req) {
if(isset($p['masque.tag_pictoflora']['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']['AND']);
$where = (!empty($where)) ? 'WHERE '.implode(' AND ', $where) : '';
return $where;
}
// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
$ids = @$db->recupererTous(sprintf(
"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']['AND']))));
/**************************************************************************************
* FONCTION DE CONFIGURATION ET UTILITAIRES *
***************************************************************************************/
/**
* Enregistrer dans les variables de classe les paramètres et ressources
* @param $ressources
* @param $parametres de recherche
* */
private function initialiserRessourcesEtParametres($ressources, $parametres) {
$this->ressources = $ressources;
$this->parametres = $parametres;
}
// puis:
$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag WHERE false';
$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
/**
* Configuration du service en fonction du fichier de config config_del.ini
* */
public function configurer() {
$this->mappingFiltre = $this->conteneur->getParametre('mapping_masque');
$this->mappingObservation = $this->conteneur->getParametre('mapping_observation');
$this->mappingVotes = $this->conteneur->getParametre('mapping_votes');
}
else {
$ids = @$db->recupererTous(sprintf(
"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']['OR']))));
/**
* Vérifier que le service est bien configuré
* */
public function verifierConfiguration() {
$erreurs = array();
$tableauImages = $this->conteneur->getParametre('images');
if (empty($tableauImages)) {
$erreurs[] = '- le fichier de configuration ne contient pas le tableau [images] ou celui-ci est vide ;';
} else {
if ($this->conteneur->getParametre('url_service') == null) {
$erreurs[] = '- paramètre "url_service" manquant ;';
$ids = @array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
$ids = !empty($ids) ? implode(',', $ids) : 'SELECT ce_image FROM del_image_tag WHERE false';
$req['where'][] = sprintf("vdi.id_image IN (%s)", $ids);
}
if ($this->conteneur->getParametre('url_images') == null) {
$erreurs[] = '- paramètre "url_images" manquant ;';
}
}
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',
if (empty($this->mappingObservation)) {
$erreurs[] = '- le fichier de configuration ne contient pas le tableau [mapping_observation] ou celui-ci est vide ;';
} else {
$champsMappingObs = array('id_observation', 'date_observation', 'date_transmission', 'famille', 'nom_sel', 'nom_sel_nn', 'nt',
'ce_zone_geo', 'lieudit', 'station', 'milieu', 'ce_utilisateur', 'nom', 'prenom');
foreach ($champsMappingObs as $champ) {
if (!isset($this->mappingObservation[$champ])) {
$erreurs[] = '- le mapping du champ "'.$champ.'" pour l\'observation est manquant ;';
}
}
}
$req['join'] ? implode(' ', array_unique($req['join'])) : '',
$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
if (empty($this->mappingFiltre)) {
$erreurs[] = '- le fichier de configuration ne contient pas le tableau [mapping_masque] ou celui-ci est vide ;';
} else {
$champsMappingFiltre = array('famille', 'ns', 'nn', 'date', 'tag', 'commune');
foreach ($champsMappingFiltre as $champ) {
if (!isset($this->mappingFiltre[$champ])) {
$erreurs[] = '- le mapping du champ "'.$champ.'" pour l\'observation est manquant ;';
}
}
}
$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
$tris_possibles = $this->conteneur->getParametre('tris_possibles');
if (empty($tris_possibles)) {
$erreurs[] = '- le fichier de configuration ne contient pas le parametre tris_possibles ou celui-ci est vide ;';
}
$req['orderby'],
if (!empty($erreurs)) {
$e = 'Erreur lors de la configuration : '."\n";
$e .= implode("\n", $erreurs);
throw new Exception($e, RestServeur::HTTP_CODE_ERREUR);
$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__));
}
}
/**
* Verifier que les paramètres de tri sont bien autorisés et qu'ils sont au bon format.
*/
private function verifierParametresTri() {
static function chargerImages($db, $idImg) {
$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL);
$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL);
$erreurs = array();
$tris_possibles = $this->conteneur->getParametre('tris_possibles');
$tris_possibles_tableau = explode(',', $tris_possibles);
if(isset($this->parametres['tri']) && !in_array($this->parametres['tri'], $tris_possibles_tableau)) {
$erreurs[] = '- le type de tri demandé est incorrect, les valeurs possibles sont '.$tris_possibles.' ;';
}
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__));
if(isset($this->parametres['tri']) && $this->parametres['tri'] == "votes") {
if(!isset($this->parametres['protocole']) || !is_numeric($this->parametres['protocole'])) {
$erreurs[] = '- Le paramètre protocole est obligatoire en cas de tri par vote et doit être un entier ;';
}
}
$directions_tri = array('asc', 'desc');
if(isset($this->parametres['ordre']) && !in_array($this->parametres['ordre'], $directions_tri)) {
$erreurs[] = '- la direction du tri demandé est incorrecte, les valeurs supportées sont asc ou desc ;';
}
/* "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: (ListeObservations|DelTk)::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'],
if (!empty($erreurs)) {
$e = 'Erreur lors de l\'analyse des parametres du tri : '."\n";
$e .= implode("\n", $erreurs);
throw new Exception($e, RestServeur::HTTP_CODE_ERREUR);
}
}
/* tous-deux remplacent masque.tag
mais sont traité séparément des requestFilterParams() */
// 'masque.tag_cel' => $p['masque'],
// 'masque.tag_pictoflora' => $p['masque'],
private function verifierParametreFormatRetour() {
$erreurs = array();
$formats_possibles_str = $this->conteneur->getParametre('formats_possibles');
$formats_possibles = explode(',',$formats_possibles_str);
'masque.ns' => $p['masque'],
'masque.famille' => $p['masque'],
'masque.date' => $p['masque'],
'masque.genre' => $p['masque'],
'masque.milieu' => $p['masque'],
 
if(isset($this->parametres['format']) && !in_array($this->parametres['format'], $formats_possibles)) {
$erreurs[] = "- le format d'image demandé n'est pas supporté ; Les format supportés sont : ".$formats_possibles_str;
}
// tri est aussi nécessaire car affecte les contraintes de JOIN
'tri' => $p['tri'],
'ordre' => $p['ordre']);
if (!empty($erreurs)) {
$e = 'Erreur lors de l\'analyse du format de retour demandé : '."\n";
$e .= implode("\n", $erreurs);
throw new Exception($e, RestServeur::HTTP_CODE_ERREUR);
}
}
/* Cependant les champs spécifiques ont priorité sur le masque général.
Pour cette raison nous supprimons la génération de SQL du masque général sur les
champ spécifiques qui feront l'objet d'un traitement avec une valeur propre. */
if(isset($p['masque.auteur'])) unset($or_params['masque.auteur']);
if(isset($p['masque.departement'])) unset($or_params['masque.departement']);
if(isset($p['masque.commune'])) unset($or_params['masque.commune']);
if(isset($p['masque.id_zone_geo'])) unset($or_params['masque.id_zone_geo']);
if(isset($p['masque.ns'])) unset($or_params['masque.ns']);
if(isset($p['masque.famille'])) unset($or_params['masque.famille']);
if(isset($p['masque.date'])) unset($or_params['masque.date']);
if(isset($p['masque.genre'])) unset($or_params['masque.genre']);
if(isset($p['masque.milieu'])) unset($or_params['masque.milieu']);
if(isset($p['masque.tag_cel'])) unset($or_params['masque.tag_cel']);
if(isset($p['masque.tag_pictoflora'])) unset($or_params['masque.tag_pictoflora']);
private function initialiserFormatRetour() {
$this->formatRetour = isset($this->parametres['format']) ? $this->parametres['format'] : $this->formatRetour;
}
$or_masque = array_merge(
DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
self::requestFilterParams($or_params));
 
/**
* Initialiser les variables de tri depuis les paramètres
* */
private function initialiserTri() {
$this->tri = isset($this->parametres['tri']) ? $this->parametres['tri'] : $this->tri;
$this->directionTri = isset($this->parametres['ordre']) ? $this->parametres['ordre'] : $this->directionTri;
}
/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
postulés comme séparés par des espaces, et doivent être tous matchés. */
if(isset($or_params['masque.tag_cel']))
$or_masque['masque.tag_cel'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
if(isset($or_params['masque.tag_pictoflora']))
$or_masque['masque.tag_pictoflora'] = DelTk::buildTagsAST($p['masque'], 'AND', ' ');
/** Pour eviter les requêtes trop gourmandes, on supprime les caractères passe-partout
* @param les paramètres de l'application
* */
public function nettoyerParametres($parametres) {
$parametresRetour = array();
foreach ($parametres as $cle => $valeur) {
$valSansPourcent = trim($valeur, "% ");
if ($valSansPourcent != '') {
$parametresRetour[$cle] = $valeur;
}
}
return $parametresRetour;
}
// pas de select, groupby & co ici: uniquement 'join' et 'where'
$or_req = array('join' => array(), 'where' => array());
DelTk::sqlAddConstraint($or_masque, $db, $or_req);
self::sqlAddConstraint($or_masque, $db, $or_req);
/**
* Nettoyer les jokers
* @param la valeur du masque
* */
private function remplacerParJokerCaractere($valeurMasque) {
return str_replace(array('-',' '), '_', $valeurMasque);
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']));
}
 
//TODO: déplacer les fonctions ci dessus et dessous dans une classe
// utilitaire
/**
* Supprimer les accents des chaines de caractères
* */
function supprimerAccents($str, $charset='utf-8')
{
$str = htmlentities($str, ENT_NOQUOTES, $charset);
$str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str);
$str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); // pour les ligatures e.g. '&oelig;'
$str = preg_replace('#&[^;]+;#', '', $str); // supprime les autres caractères
return $str;
}
/**
* Normaliser en supprimant les accents et en mettant en minuscule
* @param $mot_cle le mot recherché
* */
private function normaliserMotCle($mot_cle) {
return mb_strtolower($this->supprimerAccents(trim($mot_cle)));
}
/**
* Récupérer le numéro du département d'un fichier de configuration
* */
private function obtenirIdDepartement($nomDpt) {
$nomDpt = $this->supprimerAccents($nomDpt);
$nomDpt = strtolower(str_replace(' ','-',$nomDpt));
// cf Observation::reformateObservationSimpleIndex() et ListeObservations::reformateObservation()
// (trop de variétés de formatage, à unifier côté client pour unifier côté backend ...)
static function reformateImagesDoubleIndex($obs, $url_pattern = '', $image_format = 'XL') {
// XXX: cf Observation.php::consulter(), nous pourriouns ici
// conserver les valeurs vides (pour les phptests notamment, ou non)
// $obs = array_map('array_filter', $obs);
$obs_merged = $obs_keyed_by_id_image = array();
foreach($obs as $o) {
// ceci nous complique la tâche pour le reste du processing...
$id = $o['jsonindex'];
// ainsi nous utilisons deux tableaux: le final, indexé par couple d'id(image-obs)
// et celui indexé par simple id_image qui est fort utile pour mapVotesToImages()
// mais tout deux partage leur référence à "protocole"
$image = array(
'id_image' => $o['id_image'],
'binaire.href' => sprintf($url_pattern, $o['id_image'], $image_format),
'mots_cles_texte' => @$o['i_mots_cles_texte'], // @, peut avoir été filtré par array_map() ci-dessus
);
unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']);
if(!isset($obs_merged[$id])) $obs_merged[$id] = $image;
$obs_merged[$id]['observation'] = $o;
$obs_merged[$id]['protocoles_votes'] = array();
$idDpt = $this->conteneur->getParametre($nomDpt);
if($idDpt == null || $idDpt == ' ') {
$idDpt = ' ';
$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] = &$obs_merged[$id]['protocoles_votes'];
}
return $idDpt;
}
/**
* Obtenir le type de requête à exécuter en fonction des paramètres de recherche
* @param $parametres les paramètres de l'application
* */
private function getTypeRequete($ressources, $parametres) {
 
// une image par id
if ((count($ressources) == 1) && is_numeric($ressources[0])) {
return 'id';
return array($obs_merged,$obs_keyed_by_id_image);
}
 
$typeRequete = 'simple';
// Dans ce cas précis, les informations concernant le depart, la limite ou l'ordre ne
// rentre pas en compte dans le type de requête ; ce ne sont que des compléments.
unset($parametres['navigation.depart']);
unset($parametres['navigation.limite']);
unset($parametres['ordre']);
// En revanche, chaque masque est associé à un type de requête particulier.
$masquesObservation = array('masque', 'masque.departement', 'masque.ns', 'masque.genre', 'masque.date', 'masque.commune', 'masque.famille', 'masque.auteur', 'masque.nn', 'masque.referentiel');
$masquesImage = array('masque', 'masque.tag');
// Le type de requête est défini par les tables qu'il doit inclure (observation, image, ou les deux)
$requeteSimple = false;
$pourObservation = false;
$pourImage = false;
// S'il n'y a aucun paramètre, on lance une requête simple
if (empty($parametres)) {
$requeteSimple = true;
// complete & override DelTk::requestFilterParams() (même usage)
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));
}
// Si l'un des masques demandé concerne l'observation
foreach ($masquesObservation as $masque) {
if (isset($parametres[$masque])) {
$pourObservation = true;
break;
}
}
$p = array();
$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
// Si l'un des masques demandé concerne les images
foreach ($masquesImage as $masque) {
if (isset($parametres[$masque])) {
$pourImage = true;
break;
}
}
// "milieu" inutile pour IdentiPlantes ?
if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
// Selon les tri
if (isset($parametres['tri'])) {
switch ($parametres['tri']) {
case 'votes' :
case 'tags' :
$pourImage = true;
break;
default : //case 'date_observation' :
if (sizeof($parametres) > 1) {
$pourObservation = true;
// compatibilité
if(isset($params['masque.tag'])) {
$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
}
}
}
// Vérifier la combinaison des booléens pour en déduire le type de requête
if ($pourObservation && $pourImage) {
$typeRequete = 'obs-images';
} else {
if ($pourImage) {
$typeRequete = 'images';
} else if ($pourObservation) {
$typeRequete = 'obs';
} else { // if ($requeteSimple)
$typeRequete = 'simple';
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 $typeRequete;
return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
}
private function doitJoindreTableVotes() {
return ($this->tri == 'votes');
}
private function doitJoindreTableTags() {
return ($this->tri == 'tags');
// 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, divo.nb_votes, dit.ctags
FROM `tb_cel`.`cel_images` ci
LEFT JOIN
( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes 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
);
}
 
/*-------------------------------------------------------------------------------
FORMATER ET METTRE EN FORME
--------------------------------------------------------------------------------*/
/**
* Formater une observation depuis une ligne liaison
* @param $liaison liaison issue de la recherche
* @return $observation l'observation mise en forme
* */
private function formaterObservation($liaison) {
$observation = array();
foreach ($this->mappingObservation as $nomOriginal => $nomFinal) {
$observation[$nomFinal] = $liaison[$nomOriginal];
static function revOrderBy($orderby) {
return $orderby == 'asc' ? 'desc' : 'asc';
}
 
return $observation;
}
/**
* Formater une observation depuis une ligne liaison
* @param $liaison liaison issue de la recherche
* @return $observation l'observation mise en forme
* */
private function formaterVotes($votes) {
$retour = array();
foreach ($votes as $vote) {
$retour_vote = array();
foreach ($vote as $param=>$valeur) {
if (strpos($this->mappingVotes[$param], 'protocole.') === 0) {
$retour_protocole[$this->mappingVotes[$param]] = $valeur;
} else {
$retour_vote[$this->mappingVotes[$param]] = $valeur;
}
}
if (!isset($retour[$vote['ce_image']][$vote['ce_protocole']])) {
$retour[$vote['ce_image']][$vote['ce_protocole']] = $retour_protocole;
}
$retour[$vote['ce_image']][$vote['ce_protocole']]['votes'][$vote['id_vote']] = $retour_vote;
}
return $retour;
}
/**
* Formater le lien de l'image en fonction du fichier de config et de l'identifiant de l'image
* */
private function formaterLienImage($idImage) {
$idImage = sprintf('%09s', $idImage);
$url = $this->conteneur->getParametre('url_images');
$urlImage = sprintf($url, $idImage, $this->formatRetour);
return $urlImage;
}
private function proteger($valeur) {
if (is_array($valeur)) {
return $this->bdd->protegerTableau($valeur);
} else {
return $this->bdd->proteger($valeur);
}
}
}
?>