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. 'œ' |
$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); |
} |
} |
} |
?> |