/trunk/services/modules/0.1/observations/Observation.php |
---|
File deleted |
\ No newline at end of file |
/trunk/services/modules/0.1/observations/ObservationDetails.php |
---|
New file |
0,0 → 1,211 |
<?php |
/** |
* Web service retournant toutes les infos d'une observation donnée : |
* images, votes sur image et protocole, commentaires, votes sur commentaires, ... |
* |
* @category DEL |
* @package Observations |
* @version 0.1 |
* @author Raphaël Droz <raphael@tela-botanica.org> |
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org> |
* @copyright Copyright (c) 2013, Tela Botanica (accueil@tela-botanica.org) |
* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt> |
* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt> |
* @see http://www.tela-botanica.org/wikini/eflore/wakka.php?wiki=ApiIdentiplante01Observations |
* |
* @config-depend: "url_image" (dans configurations/config_observations.ini) ex: http://www.tela-botanica.org/appli:cel-img:%09dXL.jpg |
*/ |
// http://localhost/del/services/0.1/observations/#id => une observation donnée et ses images, SANS LES propositions & nombre de commentaire |
require_once(dirname(__FILE__) . '/../DelTk.php'); |
class ObservationDetails { |
private $conteneur; |
private $bdd; |
private $sql; |
private $idObs; |
private $protocole; |
private $observation; |
private $mappings = array(); |
public function __construct(Conteneur $conteneur) { |
$this->conteneur = $conteneur; |
$this->bdd = $this->conteneur->getBdd(); |
$this->sql = $this->conteneur->getSql(); |
$this->mappings['observations'] = $this->conteneur->getParametreTableau('observations.mapping'); |
$this->mappings['images'] = $this->conteneur->getParametreTableau('images.mapping'); |
$this->mappings['votes'] = $this->conteneur->getParametreTableau('votes.mapping'); |
$this->mappings['commentaires'] = $this->conteneur->getParametreTableau('commentaires.mapping'); |
// les deux alias suivants sont particuliers afin d'éviter un conflit d'alias lors des jointures avec del_commentaire_vote |
$this->mappings['commentaires']['ce_utilisateur'] = '__auteur_com'; |
$this->mappings['commentaires']['date'] = '__date_com'; |
} |
public function consulter($ressources, $parametres) { |
$this->idObs = $ressources[0]; |
$this->protocole = isset($parametres['protocole']) && is_numeric($parametres['protocole']) ? intval($parametres['protocole']) : null; |
// 1) récupération de l'observation (et de ses images (v_del_image est une vue utilisant des INNER JOIN)) |
$infos = $this->getInfosObservationEtImages(); |
if (! $infos) { |
$message = "Aucune observation ne possède d'identifiant '{$this->idObs}'."; |
throw new Exception($message, RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE); |
} |
$this->formaterObservation($infos); |
//var_dump($this->observation); |
// 3) charge les données de votes et protocoles associés aux images |
if ($this->observation['images']) { |
$idsImages = array_keys($this->observation['images']); |
$votes = $this->sql->getVotesDesImages($idsImages, $this->protocole); |
$this->sql->ajouterInfosVotesProtocoles($votes, $this->observation['images']); |
} |
// 4) charge les commentaires et les votes associés -> modifie/créé $observation['commentaires'] |
$commentaires = $this->getCommentaires(); |
$this->ajouterCommentaires($commentaires); |
// désindexe le tableau (tel qu'apparement attendu par les applis), c'est une exception |
// TODO : corriger l'appli cliente pour utiliser les index puis supprimer cette ligne |
$this->observation['images'] = array_values($this->observation['images']); |
// autre élément de post-processing: le ce_utilisateur de l'observation non-numeric... |
$this->nettoyerAuteur(); |
// Mettre en forme le résultat et l'envoyer pour affichage |
$resultat = new ResultatService(); |
$resultat->corps = $this->observation; |
return $resultat; |
} |
private function getInfosObservationEtImages() { |
$obsChamps = $this->sql->getAliasDesChamps($this->mappings['observations'], null, 'dob'); |
$imgChamps = $this->sql->getAliasDesChamps($this->mappings['images'], null, 'dob'); |
// champs de l'annuaire (del_utilisateur): id_utilisateur prenom, nom, courriel |
$annuaireChamps = implode(', ', array( |
"IFNULL(du.prenom, prenom_utilisateur) AS `auteur.prenom`", |
"IFNULL(du.nom, nom_utilisateur) AS `auteur.nom`", |
"IFNULL(du.courriel, courriel_utilisateur) AS `auteur.courriel`")); |
$requete = "SELECT $obsChamps, $imgChamps, $annuaireChamps ". |
"FROM v_del_image as dob ". |
"LEFT JOIN del_utilisateur AS du ". |
" ON CAST(du.id_utilisateur AS CHAR) = CAST(dob.ce_utilisateur AS CHAR) ". |
"WHERE dob.id_observation = {$this->idObs} ". |
'-- '.__FILE__.':'.__LINE__; |
//var_dump($requete); |
return $this->bdd->recupererTous($requete); |
} |
private function formaterObservation($infos) { |
$urlImgTpl = $this->conteneur->getParametre('cel_img_url_tpl'); |
$imageFormat = 'XL'; |
$infos = array_map('array_filter', $infos); |
foreach ($infos as $info) { |
$image = array_intersect_key($info, array_flip(array('id_image', 'date', 'hauteur' , 'largeur', 'nom_original'))); |
$image['binaire.href'] = sprintf($urlImgTpl, $image['id_image'], $imageFormat); |
unset($info['id_image'], $info['date'], $info['hauteur'], $info['largeur'], $info['nom_original']); |
if (!isset($this->observation)) $this->observation = $info; |
$this->observation['images'][$image['id_image']] = $image; |
} |
} |
private function getCommentaires() { |
$selectVotes = array('id_vote', 'ce_proposition', 'ce_utilisateur', 'valeur', 'date'); |
$selectCommentaires = array('id_commentaire', 'ce_observation', 'ce_proposition', 'ce_commentaire_parent', 'texte', |
'ce_utilisateur', 'utilisateur_prenom', 'utilisateur_nom', 'utilisateur_courriel', |
'nom_sel', 'nom_sel_nn', 'nom_ret', 'nom_ret_nn', 'nt', 'famille', 'nom_referentiel', 'date', |
'proposition_initiale'); |
$voteChamps = $this->sql->getAliasDesChamps($this->mappings['votes'], $selectVotes, 'cv'); |
$commentaireChamps = $this->sql->getAliasDesChamps($this->mappings['commentaires'], $selectCommentaires, 'dc'); |
// LEFT JOIN optionnel, mais explicatif : récupèration des infos de vote que pour les commentaires comportant un nom_sel "valide" |
$requete = "SELECT $commentaireChamps, $voteChamps ". |
"FROM del_commentaire AS dc ". |
" LEFT JOIN del_commentaire_vote AS cv ". |
" ON (cv.ce_proposition = dc.id_commentaire AND dc.nom_sel != '' AND dc.nom_sel IS NOT NULL) ". |
"WHERE ce_observation = {$this->idObs} ". |
'-- '.__FILE__.':'.__LINE__; |
$commentaires = $this->bdd->recupererTous($requete); |
return $commentaires; |
} |
private function ajouterCommentaires($commentaires) { |
if (!$commentaires) return; |
$ret = array(); |
foreach ($commentaires as $comment) { |
$commentId = $comment['id_commentaire']; |
$voteId = $comment['vote.id']; |
if (!array_key_exists($commentId, $ret)) { |
$comment_extract = array_intersect_key($comment, array_flip($this->mappings['commentaires'])); |
// cas particulier: conflit d'aliases avec del_commentaire_vote |
$comment_extract['auteur.id'] = $comment_extract['__auteur_com']; |
$comment_extract['date'] = $comment_extract['__date_com']; |
unset($comment_extract['__auteur_com'], $comment_extract['__date_com']); |
// toujours un éléments "votes", quand bien même il n'y en aurait pas |
$comment_extract['votes'] = array(); |
$ret[$commentId] = $comment_extract; |
} |
if (!$comment['nom_sel'] || ! $voteId) continue; |
$vote = array_intersect_key($comment, array_flip($this->mappings['votes'])); |
$ret[$commentId]['votes'][$voteId] = $vote; |
} |
$this->observation['commentaires'] = $ret; |
} |
private function nettoyerAuteur() { |
if (!is_numeric($this->observation['auteur.id'])) { |
$this->observation['auteur.id'] = '0'; |
} |
if (!isset($this->observation['auteur.nom'])) { |
$this->observation['auteur.nom'] = '[inconnu]'; |
} |
} |
/** |
* Modifie une observation directement dans le CEL en faisant un appel à un web service du CEL. |
* Utilisé uniquement par les admins. |
* Permet de dépublier une observation. |
* |
* @param array $ressources tableau des informations contenues dans l'url après le nom du service |
* @param array $parametres contenu du post |
* @return mixed Chaine "OK" (en majuscule) en cas de succès, booléen "false" en cas d'échec |
*/ |
public function modifier($ressources, $parametres) { |
$controlAcces = $this->conteneur->getControleAcces(); |
$controlAcces->etreUtilisateurAvecDroitAdmin(); |
$retour = false; |
if (isset($parametres['transmission'])) { |
$idObs = $ressources[0]; |
$clientRest = $this->conteneur->getRestClient(); |
$urlTpl = $this->conteneur->getParametre('urlServiceCelObs'); |
$url = $urlTpl.$idObs; |
$retourCel = $clientRest->modifier($url, $parametres); |
$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false; |
if ($retour === false) { |
$message = "Erreur du web service CEL : ".$retourCel; |
$code = RestServeur::HTTP_CODE_MAUVAISE_REQUETE; |
throw new Exception($message, $code); |
} |
} else { |
$message = "Ce web service doit contenir un paramètre 'transmission'."; |
$code = RestServeur::HTTP_CODE_MAUVAISE_REQUETE; |
throw new Exception($message, $code); |
} |
return $retour; |
} |
} |
/trunk/services/modules/0.1/Observations.php |
---|
39,6 → 39,7 |
* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt> |
* @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org> |
*/ |
class Observations extends RestService { |
private $parametres = array(); |
95,21 → 96,11 |
} |
private function traiterRessources() { |
$this->chargerConfigService(); |
$this->analyserRessources(); |
$retour = $this->initialiserService(); |
return $retour; |
} |
private function chargerConfigService() { |
$chemin = Config::get('chemin_configurations')."config_{$this->serviceNom}.ini"; |
Config::charger($chemin); |
} |
/*------------------------------------------------------------------------------------------------------------------ |
CONFIGURATION DU SERVICE |
------------------------------------------------------------------------------------------------------------------*/ |
private function analyserRessources() { |
if ($this->methode == 'consulter') { |
$this->analyserRessoucesConsultation(); |
130,7 → 121,7 |
} else if (count($this->ressources) == 1) { |
if ($this->etreRessourceIdentifiant(0)) { |
// http://localhost/service:del:0.1/observations/#idObs |
$this->sousServiceNom = 'observation'; |
$this->sousServiceNom = 'observation-details'; |
} |
} else if (count($this->ressources) == 3) { |
if ($this->etreRessourceIdentifiant(0) && $this->etreRessourceIdentifiant(1) && $this->verifierRessourceValeur(2, 'vote')) { |
207,7 → 198,6 |
$service = null; |
foreach ($chemins as $chemin) { |
if (file_exists($chemin)) { |
$this->conteneur->chargerConfiguration('config_'.$this->serviceNom.'.ini'); |
require_once $chemin; |
$service = new $classe($this->conteneur); |
if ($this->methode == 'consulter') { |
/trunk/services/modules/0.1/images/ListeImages.php |
---|
1,634 → 1,174 |
<?php |
// declare(encoding='UTF-8'); |
/** |
* @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 |
* @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 |
* Listes des images avec leurs infos liées. |
* |
* Backend pour PictoFlora (del.html#page_recherche_images) |
* 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 |
* |
* |
* == 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 |
* |
* @category DEL |
* @package Services |
* @subpackage Images |
* @version 0.1 |
* @author Mathias CHOUET <mathias@tela-botanica.org> |
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org> |
* @author Aurelien PERONNET <aurelien@tela-botanica.org> |
* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt> |
* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt> |
* @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org> |
*/ |
require_once(dirname(__FILE__) . '/../DelTk.php'); |
require_once(dirname(__FILE__) . '/../observations/Observation.php'); |
restore_error_handler(); |
restore_exception_handler(); |
error_reporting(E_ALL); |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&masque=plop |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3 |
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop |
//restore_error_handler(); |
//restore_exception_handler(); |
//error_reporting(E_ALL); |
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'); |
private $ressources = array(); |
private $parametres = array(); |
private $conteneur; |
private $bdd; |
private $filtrage; |
private $sql; |
private $navigation; |
private $paramsFiltres = array(); |
private $mappings = array(); |
static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags', 'points'); |
// en plus de ceux dans DelTk |
static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu'); |
static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10, |
'tri' => 'date_transmission', 'ordre' => 'desc', |
// spécifiques à PictoFlora: |
'format' => 'XL'); |
static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes|points)) |
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", |
"courriel_utilisateur" => "auteur.courriel",), |
'images' => array( // v_del_image |
'id_image' => 1, |
// l'alias suivant est particulier: in-fine il doit s'appeler mots_cles_texte |
// mais nous afin d'éviter un conflit d'alias nous le renommons plus tard (reformateImagesDoubleIndex) |
'i_mots_cles_texte' => 1) |
); |
public function __construct(Conteneur $conteneur) { |
$this->conteneur = $conteneur; |
$this->bdd = $this->conteneur->getBdd(); |
$this->filtrage = $this->conteneur->getParametresFiltrage(); |
$this->sql = $this->conteneur->getSql(); |
$this->navigation = $this->conteneur->getNavigation(); |
$this->mappings['observations'] = $this->conteneur->getParametreTableau('observations.mapping'); |
$this->mappings['images'] = $this->conteneur->getParametreTableau('images.mapping'); |
} |
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->ressources = $ressources; |
$this->parametres = $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->paramsFiltres = $this->filtrage->filtrerUrlParamsAppliImg(); |
$this->sql->setParametres($this->paramsFiltres); |
$this->sql->ajouterContraintes(); |
$this->sql->ajouterConstrainteAppliImg(); |
$this->sql->definirOrdreSqlAppliImg(); |
$db = $this->bdd; |
$idImgs = $this->getIdImages(); |
$this->navigation->setTotal($this->getTotal()); |
// 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); |
// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats |
$resultat = new ResultatService(); |
$resultat->corps = array('entete' => $this->navigation->getEntete(), 'resultats' => array()); |
if (count($idImgs) > 0) { |
$liaisons = $this->getInfosImages($idImgs); |
list($images, $images_indexe_par_id_image) = $this->reformaterImagesDoubleIndex($liaisons); |
// notre propre filtrage sur l'INPUT |
$params_pf = self::requestFilterParams($parametres, |
array_merge(DelTk::$parametres_autorises, self::$parametres_autorises)); |
// Chargement des votes pour ces images et pour *tous* les protocoles |
$votes = $this->sql->getVotesDesImages($idImgs); |
if ($votes) { |
// ATTENTION : $images_indexe_par_id_image est lié par référence à $images ! |
$this->sql->ajouterInfosVotesProtocoles($votes, $images_indexe_par_id_image); |
} |
/* 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', ','); |
if(!isset($parametres['masque.tag_pictoflora']) && isset($parametres['masque.tag'])) { |
$parametres['masque.tag_pictoflora'] = $parametres['masque.tag']; |
$resultat->corps = array( |
'entete' => $this->navigation->getEntete(), |
'resultats' => $images); |
} |
$params_pf['masque.tag_pictoflora'] = DelTk::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ','); |
$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 |
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); |
// 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; |
} |
// idobs est une liste (toujours ordonnée) des id d'observations recherchées |
$idobs = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $idobs_tab)); |
$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']); |
$liaisons = self::chargerImages($db, $idobs); |
list($images, $images_keyed_by_id_image) = self::reformateImagesDoubleIndex( |
$liaisons, |
$this->conteneur->getParametre('images.url_images'), |
$params['format']); |
// on charge les votes pour ces images et pour *tous* les protocoles |
$votes = Observation::chargerVotesImage($db, $liaisons, NULL); |
// subtilité, nous passons ici le tableau d'images indexé par id_image qui est bien plus pratique pour |
// associer les vote à un tableau, puisque nous ne connaissons pas les id d'observation. |
// Mais magiquement (par référence), cela va remplir notre tableau indexé par couple d'id (id_image, id_observation) |
// cf reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions simultanément lorsque |
// c'est encore possible. |
if ($votes) { |
Observation::mapVotesToImages($votes, $images_keyed_by_id_image); |
} |
// 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; |
} |
private function getIdImages() { |
$requete = 'SELECT SQL_CALC_FOUND_ROWS id_image '. |
'FROM v_del_image AS vdi '. |
$this->sql->getJoin(). |
'WHERE '.$this->sql->getWhere(). |
$this->sql->getGroupBy(). |
$this->sql->getOrderBy(). |
$this->sql->getLimit(). |
' -- '.__FILE__.':'.__LINE__; |
/** |
* Supprime une image directement dans le CEL en faisant un appel à un web service du CEL. |
* Utilisé uniquement par les admins. |
* |
* @param array $ressources tableau des informations contenues dans l'url après le nom du service |
* @param array $parametres contenu du post |
* @return mixed Chaine "OK" (en majuscule) en cas de succès, booléen "false" en cas d'échec |
*/ |
public function supprimer($ressources) { |
$controlAcces = $this->conteneur->getControleAcces(); |
$controlAcces->etreUtilisateurAvecDroitAdmin(); |
$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage'); |
$idImage = $ressources[0]; |
$url = $urlServiceBase.$idImage; |
$clientHttp = $this->conteneur->getRestClient(); |
$retourCel = $clientHttp->supprimer($url); |
$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false; |
return $retour; |
} |
/** |
* 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; |
} |
if ($p['tri'] == 'points') { // LEFT JOIN sur "dis" ci-dessous |
$req['orderby'] = 'dis.nb_points ' . $p['ordre'] . ', dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre']; |
return; |
} |
if ($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous |
$req['orderby'] = 'dis.nb_tags ' . $p['ordre']; |
return; |
} |
if ($p['tri'] == 'date_observation') { |
$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre']; |
return; |
} |
// tri == 'date_transmission' |
// avant cel:r1860, date_transmission pouvait être NULL |
// or nous voulons de la cohérence (notamment pour phpunit) |
$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre']; |
} |
/* |
* in $p: un tableau de paramètres, dont: |
* - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte |
* - 'masque.tag_pictoflora': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise |
* - 'tag_explode_semantic': défini si les éléments sont tous recherchés ou NON |
* |
* in/ou: $req: un tableau de structure de requête MySQL |
* |
* Attention, le fait que nous cherchions masque.tag_cel OU/ET masque.tag_cel |
* ne dépend pas de nous, mais du niveau supérieur de construction de la requête: |
* Soit directement $this->consulter() si des masque.tag* sont passés |
* (split sur ",", "AND" entre chaque condition, "OR" pour chaque valeur de tag) |
* Soit via sqlAddMasqueConstraint(): |
* (pas de split, "OR" entre chaque condition) [ comportement historique ] |
* équivalent à: |
* (split sur " ", "OR" entre chaque condition, "AND" pour chaque valeur de tag) |
* |
*/ |
static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) { |
// TODO implement dans DelTk ? |
if (!empty($p['masque.milieu'])) { |
$req['where'][] = 'vdi.milieu LIKE '.$db->proteger('%' . $p['masque.milieu'].'%'); |
} |
/* Pour le tri par AVG() des votes nous avons toujours un protocole donné, |
celui-ci indique sur quels votes porte l'AVG. |
(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */ |
/* TODO: perf problème: |
1) SQL_CALC_FOUND_ROWS: fixable en: |
- dissociant le comptage de la récup d'id + javascript async |
- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination |
(paramètre booléen "with-total" par exemple) |
2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers |
JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission". |
Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse |
jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé |
3) non-problème: l'ordre des joins est forcé par l'usage de la vue: |
(cel_images/cel_obs_images/cel_obs/del_image_stat) |
Cependant c'est à l'optimiseur de définir son ordre préféré. */ |
if ($p['tri'] == 'votes' || $p['tri'] == 'points') { |
// $p['protocole'] *est* défini (cf requestFilterParams()) |
$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'. |
' ON vdi.id_image = dis.ce_image'. |
' AND dis.ce_protocole = %d', |
$p['protocole']); |
} |
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'; |
} |
// 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(IFNULL(vdi.mots_cles_texte,\'\'),IFNULL(vdi.i_mots_cles_texte,\'\')) LIKE %s", |
$db->proteger("%".$val."%"));'), |
$db); |
$req['where'][] = '(' . implode(' AND ', $tags) . ')'; |
} else { |
$req['where'][] = sprintf("CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s", |
$db->proteger(implode('|', $p['masque.tag_cel']['OR']))); |
$resultats = $this->bdd->recupererTous($requete); |
$idImgs = array(); |
if ($resultats !== false ) { |
foreach ($resultats as $resultat) { |
$idImgs[] = $resultat['id_image']; |
} |
} |
if ($p['masque.tag_pictoflora']) { |
// inutilisable pour l'instant |
// self::sqlAddPictoFloraTagConstraint1($p, $db, $req); |
// intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery) |
// self::sqlAddPictoFloraTagConstraint2($p, $db, $req); |
// approche fiable mais sous-optimale |
self::sqlAddPictoFloraTagConstraint3($p, $db, $req); |
} |
return $idImgs; |
} |
/* 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é |
} 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)); |
} |
private function getTotal() { |
$resultat = $this->bdd->recuperer('SELECT FOUND_ROWS() AS nbre'); |
return intval($resultat['nbre']); |
} |
// 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']))); |
} |
} |
private function getInfosImages($idImgs) { |
$obsChamps = $this->sql->getAliasDesChamps($this->mappings['observations']); |
$imgChamps = $this->sql->getAliasDesChamps($this->mappings['images']); |
$idImgsConcat = implode(',', $idImgs); |
// 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']); |
$requete = "SELECT CONCAT(id_image, '-', id_observation) AS jsonindex, $obsChamps, $imgChamps ". |
'FROM v_del_image '. |
"WHERE id_image IN ($idImgsConcat) ". |
"ORDER BY FIELD(id_image, $idImgsConcat) ". // important car MySQL ne conserve par l'ordre du IN() |
'-- '.__FILE__.':'.__LINE__; |
// 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'])))); |
// 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); |
} 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'])))); |
$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); |
} |
return $this->bdd->recupererTous($requete); |
} |
static function getIdImages($p, $req, $db) { |
$req = sprintf( |
'SELECT SQL_CALC_FOUND_ROWS id_image' . |
//', dis.moyenne, dis.nb_points, dis.nb_votes' . // debug |
' FROM v_del_image vdi'. |
' %s' . // LEFT JOIN if any |
' WHERE %s'. // where-clause ou TRUE |
' %s'. // group-by |
' ORDER BY %s'. |
' LIMIT %d, %d -- %s', |
$req['join'] ? implode(' ', array_unique($req['join'])) : '', |
$req['where'] ? implode(' AND ', $req['where']) : 'TRUE', |
$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '', |
$req['orderby'], |
$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__); |
return $db->recupererTous($req); |
} |
static function chargerImages($db, $idImg) { |
$obs_fields = DelTk::sqlFieldsToAlias(self::$mappings['observations'], NULL); |
$image_fields = DelTk::sqlFieldsToAlias(self::$mappings['images'], NULL); |
return $db->recupererTous(sprintf('SELECT '. |
' CONCAT(id_image, "-", id_observation) AS jsonindex,'. |
' %1$s, %2$s FROM v_del_image '. |
' WHERE %3$s'. |
' ORDER BY %4$s'. // important car MySQL ne conserve par l'ordre du IN() |
' -- %5$s', |
$obs_fields, $image_fields, |
sprintf('id_image IN (%s)', implode(',', $idImg)), |
sprintf('FIELD(id_image, %s)', implode(',', $idImg)), |
__FILE__ . ':' . __LINE__)); |
} |
/* "masque" ne fait jamais que faire une requête sur la plupart des champs, (presque) tous traités |
de manière identique à la seule différence que: |
1) ils sont combinés par des "OU" logiques plutôt que des "ET". |
2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique: |
Tous les mots-clefs doivent matcher et sont séparés par des espaces |
(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql) |
Pour plus d'information: (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'], |
/* tous-deux remplacent masque.tag |
mais sont traité séparément des requestFilterParams() */ |
// 'masque.tag_cel' => $p['masque'], |
// 'masque.tag_pictoflora' => $p['masque'], |
'masque.ns' => $p['masque'], |
'masque.famille' => $p['masque'], |
'masque.date' => $p['masque'], |
'masque.genre' => $p['masque'], |
'masque.milieu' => $p['masque'], |
'masque.tag_cel' => $p['masque'], |
'masque.tag_pictoflora' => $p['masque'], |
// tri est aussi nécessaire car affecte les contraintes de JOIN |
'tri' => $p['tri'], |
'ordre' => $p['ordre']); |
if (array_key_exists('protocole', $p)) { |
$or_params['protocole'] = $p['protocole']; |
} |
/* 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']); |
$or_masque = array_merge( |
DelTk::requestFilterParams($or_params, NULL, $c /* pour masque.departement */), |
self::requestFilterParams($or_params) |
); |
/* 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', ' '); |
} |
// 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); |
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'])); |
} |
} |
} |
// 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); |
private function reformaterImagesDoubleIndex($imagesInfos) { |
$urlImgTpl = $this->conteneur->getParametre('cel_img_url_tpl'); |
$imageFormat = isset($this->paramsFiltres['format']) ? $this->paramsFiltres['format'] : 'XL'; |
$obs_merged = $obs_keyed_by_id_image = array(); |
foreach ($obs as $o) { |
foreach ($imagesInfos as $infos) { |
// ceci nous complique la tâche pour le reste du processing... |
$id = $o['jsonindex']; |
$id = $infos['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 |
'id_image' => $infos['id_image'], |
'binaire.href' => sprintf($urlImgTpl, $infos['id_image'], $imageFormat), |
'mots_cles_texte' => @$infos['mots_cles_texte_img'], // @, peut avoir été filtré par array_map() ci-dessus |
); |
unset($o['id_image'], $o['i_mots_cles_texte'], $o['jsonindex']); |
unset($infos['id_image'], $infos['mots_cles_texte_img'], $infos['jsonindex']); |
if (!isset($obs_merged[$id])) { |
$obs_merged[$id] = $image; |
} |
$obs_merged[$id]['observation'] = $o; |
$obs_merged[$id]['observation'] = $infos; |
$obs_merged[$id]['protocoles_votes'] = array(); |
$obs_keyed_by_id_image[$image['id_image']]['protocoles_votes'] =& $obs_merged[$id]['protocoles_votes']; |
} |
return array($obs_merged,$obs_keyed_by_id_image); |
return array($obs_merged, $obs_keyed_by_id_image); |
} |
// 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)); |
} |
/** |
* Supprime une image directement dans le CEL en faisant un appel à un web service du CEL. |
* Utilisé uniquement par les admins. |
* |
* @param array $ressources tableau des informations contenues dans l'url après le nom du service |
* @param array $parametres contenu du post |
* @return mixed Chaine "OK" (en majuscule) en cas de succès, booléen "false" en cas d'échec |
*/ |
public function supprimer($ressources) { |
$controlAcces = $this->conteneur->getControleAcces(); |
$controlAcces->etreUtilisateurAvecDroitAdmin(); |
$p = array(); |
$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible); |
$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible); |
$urlServiceBase = $this->conteneur->getParametre('urlServiceCelImage'); |
$idImage = $ressources[0]; |
$url = $urlServiceBase.$idImage; |
// "milieu" inutile pour IdentiPlantes ? |
if (isset($params['masque.milieu'])) { |
$p['masque.milieu'] = trim($params['masque.milieu']); |
} |
// compatibilité |
if (isset($params['masque.tag'])) { |
$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag']; |
} |
if ($p['tri'] == 'votes' || $p['tri'] == 'tags' || $p['tri'] == 'points') { |
// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné |
if(!isset($params['protocole']) || !is_numeric($params['protocole'])) { |
$p['protocole'] = self::$default_proto; |
} else { |
$p['protocole'] = intval($params['protocole']); |
} |
} |
return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);')); |
$clientHttp = $this->conteneur->getRestClient(); |
$retourCel = $clientHttp->supprimer($url); |
$retour = preg_match('/^OK$/i', $retourCel) ? 'OK' : false; |
return $retour; |
} |
} |
/trunk/services/bibliotheque/Outils.php |
---|
File deleted |
\ No newline at end of file |
/trunk/services/bibliotheque/Navigation.php |
---|
155,8 → 155,8 |
$entete['total'] = $this->getTotal(); |
if ($this->sansLimite == false) { |
$entete['depart'] = $this->getDepart(); |
$entete['limite'] = $this->getLimite(); |
$entete['depart'] = (int) $this->getDepart(); |
$entete['limite'] = (int) $this->getLimite(); |
$lienPrecedent = $this->recupererHrefPrecedent(); |
if ($lienPrecedent != null) { |
/trunk/services/bibliotheque/Sql.php |
---|
New file |
0,0 → 1,609 |
<?php |
// declare(encoding='UTF-8'); |
/** |
* Classe contenant des méthodes permettant de construire les requêtes SQL complexe concernant les images et obs. |
* |
* @category DEL |
* @package Services |
* @package Bibliotheque |
* @version 0.1 |
* @author Mathias CHOUET <mathias@tela-botanica.org> |
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org> |
* @author Aurelien PERONNET <aurelien@tela-botanica.org> |
* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt> |
* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt> |
* @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org> |
*/ |
class Sql { |
private $conteneur; |
private $bdd; |
private $parametres = array(); |
private $requete = array( |
'select' => array(), |
'join' => array(), |
'where' => array(), |
'groupby' => array(), |
'orderby' => array()); |
private $champsPrenom = array('du.prenom', 'vdi.prenom_utilisateur'); |
private $champsNom = array('du.nom', 'vdi.nom_utilisateur'); |
public function __construct(Conteneur $conteneur) { |
$this->conteneur = $conteneur; |
$this->bdd = $this->conteneur->getBdd(); |
} |
public function setParametres(Array $parametres) { |
$this->parametres = $parametres; |
} |
public function getRequeteSql() { |
return $this->requete; |
} |
private function addJoin($join) { |
$this->requete['join'][] = $join; |
} |
public function getJoin() { |
return ($this->requete['join'] ? implode(' ', array_unique($this->requete['join'])).' ' : ''); |
} |
private function addJoinDis($join) { |
$this->requete['join']['dis'] = $join; |
} |
private function addWhere($idParam, $where) { |
if (isset($this->parametres['_parametres_condition_or_']) |
&& in_array($idParam, $this->parametres['_parametres_condition_or_'])) { |
$this->requete['where']['OR'][] = $where; |
} else { |
$this->requete['where']['AND'][] = $where; |
} |
} |
public function getWhere() { |
if (isset($this->requete['where']['OR']) && count($this->requete['where']['OR']) > 0) { |
$this->requete['where']['AND'][] = '('.implode(' OR ', $this->requete['where']['OR']).')'; |
} |
$where = ' TRUE '; |
if (isset($this->requete['where']['AND']) && count($this->requete['where']['AND']) > 0) { |
$where = implode(' AND ', $this->requete['where']['AND']).' '; |
} |
return $where; |
} |
private function addGroupBy($groupBy) { |
$this->requete['groupby'][] = $groupBy; |
} |
public function getGroupBy() { |
$groupby = ''; |
if (isset($this->requete['groupby']) && count($this->requete['groupby']) > 0) { |
$groupby = 'GROUP BY '.implode(', ', array_unique($this->requete['groupby'])).' '; |
} |
return $groupby; |
} |
private function addOrderBy($orderby) { |
$this->requete['orderby'][] = $orderby; |
} |
public function getOrderBy() { |
$orderby = ''; |
if (isset($this->requete['orderby']) && count($this->requete['orderby']) > 0) { |
$orderby = 'ORDER BY '.implode(', ', array_unique($this->requete['orderby'])).' '; |
} |
return $orderby; |
} |
public function getLimit() { |
return 'LIMIT '.$this->parametres['navigation.depart'].','.$this->parametres['navigation.limite'].' '; |
} |
/** |
* - Rempli le tableau des contraintes "where" et "join" nécessaire |
* à la *recherche* des observations demandées ($req) utilisées par self::getIdObs() |
* |
* Attention, cela signifie que toutes les tables ne sont pas *forcément* |
* join'ées, par exemple si aucune contrainte ne le nécessite. |
* $req tel qu'il est rempli ici est utile pour récupéré la seule liste des |
* id d'observation qui match. |
* Pour la récupération effective de "toutes" les données correspondante, il faut |
* réinitialiser $req["join"] afin d'y ajouter toutes les autres tables. |
* |
* Note: toujours rajouter les préfixes de table (vdi,du,doi ou di), en fonction de ce que défini |
* les JOIN qui sont utilisés. |
* le préfix de v_del_image est "vdi" (cf: "FROM" de self::getIdObs()) |
* le préfix de del_utilisateur sur id_utilisateur = vdi.ce_utilisateur est "du" |
* |
* @param $p les paramètres (notamment de masque) passés par l'URL et déjà traités/filtrés (sauf quotes) |
* @param $req le tableau, passé par référence représentant les composants de la requête à bâtir |
*/ |
public function ajouterContraintes() { |
$this->ajouterContrainteAuteur(); |
$this->ajouterContrainteDate(); |
$this->ajouterContrainteDepartement(); |
$this->ajouterContrainteIdZoneGeo(); |
$this->ajouterContrainteGenre(); |
$this->ajouterContrainteFamille(); |
$this->ajouterContrainteNs(); |
$this->ajouterContrainteNn(); |
$this->ajouterContrainteReferentiel(); |
$this->ajouterContrainteCommune(); |
} |
private function ajouterContrainteAuteur() { |
if (isset($this->parametres['masque.auteur'])) { |
$auteur = $this->parametres['masque.auteur']; |
// id du poster de l'obs |
$this->addJoin('LEFT JOIN del_utilisateur AS du ON (du.id_utilisateur = vdi.ce_utilisateur) '); |
// id du poster de l'image... NON, c'est le même que le posteur de l'obs |
// Cette jointure de table est ignoré ci-dessous pour les recherches d'auteurs |
// $req['join'][] = 'LEFT JOIN del_utilisateur AS dui ON dui.id_utilisateur = vdi.i_ce_utilisateur'; |
if (is_numeric($auteur)) { |
$this->ajouterContrainteAuteurId(); |
} elseif(preg_match('/^.{5,}@[a-z0-9-.]{5,}$/i', $auteur)) { |
$this->ajouterContrainteAuteurEmail(); |
} else { |
$this->ajouterContrainteAuteurIntitule(); |
} |
} |
} |
private function ajouterContrainteAuteurId() { |
$id = $this->parametres['masque.auteur']; |
$sqlTpl = '(du.id_utilisateur = %1$d OR vdi.ce_utilisateur = %1$d)'; |
$whereAuteur = sprintf($sqlTpl, $id); |
$this->addWhere('masque.auteur', $whereAuteur); |
} |
private function ajouterContrainteAuteurEmail() { |
$email = $this->parametres['masque.auteur']; |
$sqlTpl = '(du.courriel LIKE %1$s OR vdi.courriel LIKE %1$s )'; |
$emailP = $this->bdd->proteger("$email%"); |
$whereAuteur = sprintf($sqlTpl, $emailP); |
$this->addWhere('masque.auteur', $whereAuteur); |
} |
/** |
* Retourne une clause where du style: |
* CONCAT(IF(du.prenom IS NULL, "", du.prenom), [...] vdi.i_nomutilisateur) REGEXP 'xxx' |
* Note; i_(nom|prenom_utilisateur), alias pour cel_images.(nom|prenom), n'est pas traité |
* car cette information est redondante dans cel_image et devrait être supprimée. |
*/ |
private function ajouterContrainteAuteurIntitule() { |
$auteurExplode = explode(' ', $this->parametres['masque.auteur']); |
$nbreMots = count($auteurExplode); |
if ($nbreMots == 1) { |
$this->ajouterContrainteAuteurPrenomOuNom(); |
} else if ($nbreMots == 2) { |
$this->ajouterContrainteAuteurPrenomEtNom(); |
} |
} |
private function ajouterContrainteAuteurPrenomOuNom() { |
$prenomOuNom = $this->parametres['masque.auteur']; |
$sqlTpl = 'CONCAT(%s,%s) LIKE %s'; |
$champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom); |
$champsNomSql = self::ajouterIfNullPourConcat($this->champsNom); |
$auteurMotif = $this->bdd->proteger("%$prenomOuNom%"); |
$auteurWhere = sprintf($sqlTpl, $champsPrenomSql, $champsNomSql, $auteurMotif); |
$this->addWhere('masque.auteur', $auteurWhere); |
} |
private function ajouterContrainteAuteurPrenomEtNom() { |
list($prenom, $nom) = explode(' ', $this->parametres['masque.auteur']); |
$sqlTpl = '(CONCAT(%1$s,%2$s) LIKE %3$s AND CONCAT(%1$s,%2$s) LIKE %4$s)'; |
$champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom); |
$champsNomSql = self::ajouterIfNullPourConcat($this->champsNom); |
$prenomMotif = $this->bdd->proteger("%$prenom%"); |
$nomMotif = $this->bdd->proteger("%$nom%"); |
$auteurWhere = sprintf($sqlTpl, $champsPrenomSql, $champsNomSql, $prenomMotif, $nomMotif); |
$this->addWhere('masque.auteur', $auteurWhere); |
} |
/** |
* Lorsque l'on concatène des champs, un seul NULL prend le dessus, |
* Il faut donc utiliser la syntaxe IFNULL(%s, ""). |
* (Cette fonction effectue aussi l'implode() "final" |
*/ |
private static function ajouterIfNullPourConcat($champs) { |
$champsProteges = array(); |
foreach ($champs as $champ) { |
$champsProteges[] = "IFNULL($champ, '')"; |
} |
return implode(',', $champsProteges); |
} |
private function ajouterContrainteDate() { |
if (isset($this->parametres['masque.date'])) { |
$date = $this->parametres['masque.date']; |
if (is_integer($date) && $date < 2030 && $date > 1600) { |
$sqlTpl = "YEAR(vdi.date_observation) = %d"; |
$dateWhere = sprintf($sqlTpl, $date); |
$this->addWhere('masque.date', $dateWhere); |
} else { |
$sqlTpl = "DATE_FORMAT(vdi.date_observation, '%%Y-%%m-%%d') = %s"; |
$dateP = $this->bdd->proteger(strftime('%Y-%m-%d', $date)); |
$dateWhere = sprintf($sqlTpl, $dateP); |
$this->addWhere('masque.date', $dateWhere); |
} |
} |
} |
private function ajouterContrainteDepartement() { |
if (isset($this->parametres['masque.departement'])) { |
$dept = $this->parametres['masque.departement']; |
$deptMotif = $this->bdd->proteger("INSEE-C:$dept"); |
$this->addWhere('masque.departement', "vdi.ce_zone_geo LIKE $deptMotif"); |
} |
} |
private function ajouterContrainteIdZoneGeo() { |
if (isset($this->parametres['masque.id_zone_geo'])) { |
$idZgMotif = $this->bdd->proteger($this->parametres['masque.id_zone_geo']); |
$this->addWhere('masque.id_zone_geo', "vdi.ce_zone_geo = $idZgMotif"); |
} |
} |
private function ajouterContrainteGenre() { |
if (isset($this->parametres['masque.genre'])) { |
$genre = $this->parametres['masque.genre']; |
$genreMotif = $this->bdd->proteger("%$genre% %"); |
$this->addWhere('masque.genre', "vdi.nom_sel LIKE $genreMotif"); |
} |
} |
private function ajouterContrainteFamille() { |
if (isset($this->parametres['masque.famille'])) { |
$familleMotif = $this->bdd->proteger($this->parametres['masque.famille']); |
$this->addWhere('masque.famille', "vdi.famille = $familleMotif"); |
} |
} |
private function ajouterContrainteNs() { |
if (isset($this->parametres['masque.ns'])) { |
$ns = $this->parametres['masque.ns']; |
$nsMotif = $this->bdd->proteger("$ns%"); |
$this->addWhere('masque.ns', "vdi.nom_sel LIKE $nsMotif"); |
} |
} |
private function ajouterContrainteNn() { |
if (isset($this->parametres['masque.nn'])) { |
$sqlTpl = '(vdi.nom_sel_nn = %1$d OR vdi.nom_ret_nn = %1$d)'; |
$nnWhere = sprintf($sqlTpl, $this->parametres['masque.nn']); |
$this->addWhere('masque.nn', $nnWhere); |
} |
} |
private function ajouterContrainteReferentiel() { |
if (isset($this->parametres['masque.referentiel'])) { |
$ref = $this->parametres['masque.referentiel']; |
$refMotif = $this->bdd->proteger("$ref%"); |
$this->addWhere('masque.referentiel', "vdi.nom_referentiel LIKE $refMotif"); |
} |
} |
private function ajouterContrainteCommune() { |
if (isset($this->parametres['masque.commune'])) { |
$commune = $this->parametres['masque.commune']; |
$communeMotif = $this->bdd->proteger("$commune%"); |
$this->addWhere('masque.commune', "vdi.zone_geo LIKE $communeMotif"); |
} |
} |
/** |
* in $p: un tableau de paramètres, dont: |
* - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte |
* - 'masque.tag_del': *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) |
* |
*/ |
public function ajouterConstrainteAppliImg() { |
$this->ajouterContrainteMilieu(); |
$this->ajouterContrainteTri(); |
$this->ajouterContrainteTagCel(); |
$this->ajouterContrainteTagDel(); |
} |
private function ajouterContrainteMilieu() { |
if (isset($this->parametres['masque.milieu'])) { |
$milieu = $this->parametres['masque.milieu']; |
$milieuMotif = $this->bdd->proteger("%$milieu%"); |
$this->addWhere('masque.milieu', "vdi.milieu LIKE $milieuMotif"); |
} |
} |
/** 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é. |
*/ |
private function ajouterContrainteTri() { |
if (isset($this->parametres['tri'])) { |
$tri = $this->parametres['tri']; |
if (isset($this->parametres['protocole']) && ($tri == 'votes' || $tri == 'points')) { |
// $this->parametres['protocole'] *est* défini (cf Outils::filtrerUrlsParams...()) |
$sqlTpl = 'LEFT JOIN del_image_stat dis ON vdi.id_image = dis.ce_image AND dis.ce_protocole = %d'; |
$triSql = sprintf($sqlTpl, $this->parametres['protocole']); |
$this->addJoinDis($triSql); |
} |
if (isset($this->parametres['ordre']) && $tri == 'tags') { |
$typeJointure = ($this->parametres['ordre'] == 'desc') ? 'INNER' : 'LEFT'; |
$this->addJoin("$typeJointure JOIN del_image_stat dis ON vdi.id_image = dis.ce_image"); |
// nécessaire (dup ce_image dans del_image_stat) |
$this->addGroupBy('vdi.id_observation'); |
} |
} |
} |
/** |
* 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. |
*/ |
// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ? et auquel cas laisser au client le choix du couteux "%" ? |
private function ajouterContrainteTagCel() { |
if ($this->parametres['masque.tag_cel']) { |
if (isset($this->parametres['masque.tag_cel']['AND'])) { |
$tags = $this->parametres['masque.tag_cel']['AND']; |
$clausesWhere = array(); |
foreach ($tags as $tag) { |
$tagMotif = $this->bdd->proteger("%$tag%"); |
$sqlTpl = "CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) LIKE %s"; |
$clausesWhere[] = sprintf($sqlTpl, $tagMotif); |
} |
$whereTags = implode(' AND ', $clausesWhere); |
$this->addWhere('masque.tag_cel', "($whereTags)"); |
} else if (isset($this->parametres['masque.tag_cel']['OR'])) { |
$tags = $this->parametres['masque.tag_cel']['OR']; |
$sqlTpl = "CONCAT(IFNULL(vdi.mots_cles_texte,''),IFNULL(vdi.i_mots_cles_texte,'')) REGEXP %s"; |
$tagMotif = $this->bdd->proteger(implode('|', $tags)); |
$tagSql = sprintf($sqlTpl, $tagMotif); |
$this->addWhere('masque.tag_cel', $tagSql); |
} |
} |
} |
/** |
* Plusieurs solutions disponibles pour la gestion des contraintes des tags DEL : |
* - inutilisable pour l'instant : ajouterContrainteTagDelSolution1(); |
* - intéressante, mais problème d'optimiseur MySQL 5.5 (dependant subquery) : ajouterContrainteTagDelSolution2(); |
* - approche fiable mais sous-optimale : ajouterContrainteTagDelSolution3(); |
*/ |
private function ajouterContrainteTagDel() { |
if (isset($this->parametres['masque.tag_del'])) { |
$this->ajouterContrainteTagDelSolution3(); |
} |
} |
/** 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 ? |
*/ |
private function ajouterContrainteTagDelSolution1() { |
// 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_del']['AND'])) { |
// TODO/XXX : comment matcher les observations ayant tous les mots-clef passés ? |
// ... le LEFT-JOIN n'y semble pas adapté |
} else { |
$protected_tags = array(); |
foreach ($p['masque.tag_del']['OR'] as $tag) { |
$protected_tags[] = $db->proteger(strtolower($tag)); |
} |
$req['where'][] = sprintf('tag_normalise IN (%s)', implode(',', $protected_tags)); |
} |
} |
/** |
* Inutilisé pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro) |
*/ |
private function ajouterContrainteTagDelSolution2() { |
// 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_del']['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_del']['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_del']['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_del']['OR']))); |
} |
} |
/** |
* Si l'on est bassiné par les "DEPENDENT SUBQUERY", nous la faisons donc indépendemment via cette fonction |
*/ |
private function ajouterContrainteTagDelSolution3() { |
if (isset($this->parametres['masque.tag_del']['AND'])) { |
$tags = $this->parametres['masque.tag_del']['AND']; |
// optimisation: en cas de "AND" on sort() l'input et le GROUP_CONCAT() |
// donc nous utilisons des ".*" plutôt que de multiples conditions et "|" |
sort($tags); |
$tagsMotif = $this->bdd->proteger(implode('.*', $tags)); |
$requete = 'SELECT ce_image '. |
'FROM del_image_tag '. |
'WHERE actif = 1 '. |
'GROUP BY ce_image '. |
"HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP $tagsMotif ". |
' -- '.__FILE__.' : '.__LINE__; |
$sql = $this->recupererSqlContrainteTag($requete); |
$this->addWhere('masque.tag_del', $sql); |
} else if (isset($this->parametres['masque.tag_del']['OR'])) { |
$tags = $this->parametres['masque.tag_del']['OR']; |
$tagsMotif = $this->bdd->proteger(implode('|', $tags)); |
$requete = 'SELECT ce_image '. |
'FROM del_image_tag '. |
'WHERE actif = 1 '. |
'GROUP BY ce_image '. |
"HAVING GROUP_CONCAT(tag_normalise) REGEXP $tagsMotif ". |
' -- '.__FILE__.' : '.__LINE__; |
$sql = $this->recupererSqlContrainteTag($requete); |
$this->addWhere('masque.tag_del', $sql); |
} |
} |
private function recupererSqlContrainteTag($requete) { |
$resultats = $this->bdd->recupererTous($requete); |
$ids = array(); |
foreach ($resultats as $resultat) { |
$ids[] = $resultat['ce_image']; |
} |
if (!empty($ids)) { |
$clauseIn = implode(',', $ids); |
} else { |
$clauseIn = 'SELECT ce_image FROM del_image_tag WHERE false'; |
} |
return "vdi.id_image IN ($clauseIn)"; |
} |
/** |
* Partie spécifique à 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()) |
*/ |
public function definirOrdreSqlAppliImg() { |
$ordre = $this->parametres['ordre']; |
// parmi self::$tri_possible |
switch ($this->parametres['tri']) { |
case 'votes' : |
$this->addOrderBy("dis.moyenne $ordre, dis.nb_votes $ordre"); |
break; |
case 'points' : |
$this->addOrderBy("dis.nb_points $ordre, dis.moyenne $ordre, dis.nb_votes $ordre"); |
break; |
case 'tags' : |
$this->addOrderBy("dis.nb_tags $ordre"); |
break; |
case 'date_observation' : |
$this->addOrderBy("date_observation $ordre, id_observation $ordre"); |
break; |
default: |
$this->addOrderBy("date_transmission $ordre, id_observation $ordre"); |
} |
} |
public function getAliasDesChamps($champsEtAlias, $select = null, $prefix = null) { |
$arr = ($select) ? array_intersect_key($champsEtAlias, array_flip($select)) : $champsEtAlias; |
$keys = array_keys($arr); |
if ($prefix) { |
array_walk($keys, create_function('&$val, $k, $prefix', '$val = sprintf("%s.`%s`", $prefix, $val);'), $prefix); |
} else { |
array_walk($keys, create_function('&$val, $k', '$val = sprintf("`%s`", $val);')); |
} |
return implode(', ', array_map(create_function('$v, $k', 'return sprintf("%s AS `%s`", $k, $v);'), $arr, $keys)); |
} |
// Charger les images et leurs votes associés |
public function getVotesDesImages($idsImages, $protocole = null) { |
if (!$idsImages) return; |
$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping'); |
$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping'); |
$selectVotes = array('id_vote', 'ce_image', 'ce_protocole', 'ce_utilisateur', 'valeur', 'date'); |
$selectProtocole = array('id_protocole', 'intitule', 'descriptif', 'tag'); |
$voteChamps = $this->getAliasDesChamps($mappingVotes, $selectVotes, 'v'); // "v": cf alias dans la requête |
$protoChamps = $this->getAliasDesChamps($mappingProtocoles, $selectProtocole, 'p'); |
$idImgsConcat = implode(',', $idsImages); |
$requete = "SELECT $voteChamps, $protoChamps ". |
'FROM del_image_vote AS v '. |
' INNER JOIN del_image_protocole AS p ON (v.ce_protocole = p.id_protocole) '. |
"WHERE v.ce_image IN ($idImgsConcat) ". |
($protocole ? " AND v.ce_protocole = $protocole " : ''). |
"ORDER BY FIELD(v.ce_image, $idImgsConcat) ". |
'-- '.__FILE__.':'.__LINE__; |
return $this->bdd->recupererTous($requete); |
} |
/** |
* Ajoute les informations sur le protocole et les votes aux images. |
* |
* ATTENTION : 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 ListeImages::reformateImagesDoubleIndex() à qui revient la tâche de créer ces deux versions |
* simultanément lorsque c'est encore possible. |
*/ |
public function ajouterInfosVotesProtocoles($votes, &$images) { |
if (!$votes) return; |
$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping'); |
$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping'); |
// pour chaque vote |
foreach ($votes as $vote) { |
$imgId = $vote['image.id']; |
$protoId = $vote['protocole.id']; |
if (!array_key_exists('protocoles_votes', $images[$imgId]) || |
!array_key_exists($protoId, $images[$imgId]['protocoles_votes'])) { |
// extrait les champs spécifique au protocole (le LEFT JOIN de chargerVotesImage les ramène en doublons |
$protocole = array_intersect_key($vote, array_flip($mappingProtocoles)); |
$images[$imgId]['protocoles_votes'][$protoId] = $protocole; |
} |
$chpsVotes = array('id_vote', 'ce_image', 'ce_utilisateur', 'valeur', 'date'); |
$voteSelection = array_intersect_key($mappingVotes, array_flip($chpsVotes)); |
$vote = array_intersect_key($vote, array_flip($voteSelection)); |
$images[$imgId]['protocoles_votes'][$protoId]['votes'][$vote['vote.id']] = $vote; |
} |
} |
} |
/trunk/services/bibliotheque/ParametresFiltrage.php |
---|
New file |
0,0 → 1,453 |
<?php |
// declare(encoding='UTF-8'); |
/** |
* Classe contenant des méthodes de filtrage/formatage des paramètres de recherche passés dans l'URL. |
* |
* Cette classe filtre et formate les parametres passées dans l'URL et construit un tableau associatif contenant |
* le résultat des filtrages/formatages et les infos nécessaires à la construction d'une requête SQL. |
* |
* @category DEL |
* @package Services |
* @package Bibliotheque |
* @version 0.1 |
* @author Mathias CHOUET <mathias@tela-botanica.org> |
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org> |
* @author Aurelien PERONNET <aurelien@tela-botanica.org> |
* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt> |
* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt> |
* @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org> |
*/ |
class ParametresFiltrage { |
const LISTE_OBS_MAX_RESULT_LIMIT = 1000; |
const LISTE_OBS_MAX_ID_OBS = 10e7; |
const LISTE_OBS_MAX_BDTFX_NT = 1000000; // SELECT MAX(num_taxonomique) FROM bdtfx_v2_00; // 44378 + 1000 |
const LISTE_OBS_MAX_BDTFX_NN = 1000000; // SELECT MAX(num_nom) FROM bdtfx_v2_00;// 120816 + 10000 |
private $conteneur; |
private $contexte; |
private $parametres = array(); |
private $parametresFiltres = array(); |
public function __construct($conteneur) { |
$this->conteneur = $conteneur; |
$this->contexte = $this->conteneur->getContexte(); |
$this->parametres = $this->contexte->getQS(); |
} |
/** |
* Construit un (vulgaire) abstract syntax tree: |
* "AND" => [ "tag1", "tag2" ] |
* Idéalement (avec un parser simple comme proposé par http://hoa-project.net/Literature/Hack/Compiler.html#Langage_PP) |
* nous aurions: |
* "AND" => [ "tag1", "tag2", "OR" => [ "tag3", "tag4" ] ] |
* |
* Ici nous devons traiter les cas suivants: |
* tags séparés par des "ET/AND OU/OR", séparés par des espaces ou des virgules. |
* Mais la chaîne peut aussi avoir été issue du "masque général" (la barre de recherche générique). |
* ce qui implique des comportement par défaut différents afin de préserver la compatibilité. |
* |
* Théorie: |
* 1) tags passés par "champ tag": |
* - support du ET/OU, et explode par virgule. |
* - si pas d'opérande détectée: "OU" |
* |
* 2) tags passés par "recherche générale": |
* - support du ET/OU, et explode par whitespace. |
* - si pas d'opérande détectée: "ET" |
* |
* La présence de $additional_sep s'explique car ET/OU sous-entendent une séparation par des espaces. |
* Mais ce n'est pas toujours pertinent car: 1) la compatibilité suggère de considérer parfois |
* la virgule comme séparateur et 2) les tags *peuvent* contenir des espaces. Par conséquent: |
* * a,b,c => "a" $default_op "b" $default_op "c" |
* * a,b AND c => "a" AND "b" AND "c" |
* * a OR b AND c,d => "a" AND "b" AND "c" AND "d" |
* C'est à dire par ordre décroissant de priorité: |
* 1) opérande contenu dans la chaîne |
* 2) opérande par défaut |
* 3) les séparateurs présents sont substitués par l'opérande déterminée par 1) ou 2) |
* |
* // TODO: support des parenthèses, imbrications & co: "(", ")" |
* // http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html |
* // http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/ |
* |
* @param $str: la chaîne à "parser" |
* @param $operateur_par_defaut: "AND" ou "OR" |
* @param $separateur_additionnel: séparateur de mots: |
*/ |
public function construireTableauTags($str = NULL, $operateur_par_defaut, $separateur_additionnel = ',') { |
if (!$str) return; |
$op = $this->definirOperateurParDefaut($str, $operateur_par_defaut); |
$mots = preg_split('/ (OR|AND|ET|OU) /', $str, -1, PREG_SPLIT_NO_EMPTY); |
if ($separateur_additionnel) { |
foreach ($mots as $index => $mot) { |
$mot = trim($mot); |
$mots_separes = preg_split("/$separateur_additionnel/", $mot, -1, PREG_SPLIT_NO_EMPTY); |
$mots[$index] = array_shift($mots_separes); |
$mots = array_merge($mots, $mots_separes); |
} |
} |
$mots = array_filter($mots); |
return array($op => $mots); |
} |
public function definirOperateurParDefaut($str, $operateur_par_defaut) { |
$op = $operateur_par_defaut; |
if (preg_match('/\b(ET|AND)\b/', $str)) { |
$op = 'AND'; |
} else if(preg_match('/\b(OU|OR)\b/', $str)) { |
$op = 'OR'; |
} |
return $op; |
} |
public function filtrerUrlParamsAppliImg() { |
$this->maintenirCompatibilitesParametres(); |
$parametresAutorises = $this->conteneur->getParametreTableau('images.masques_possibles'); |
$this->eliminerParametresInconnus($parametresAutorises); |
$this->repartirMasqueGeneral(); |
$paramsParDefaut = $this->conteneur->getParametreTableau('images.parametres_valeurs_defaut'); |
$this->definirParametresDefauts($paramsParDefaut); |
$this->filtrerUrlParamsGeneraux(); |
$trisPossibles = $this->conteneur->getParametreTableau('appli_img.tris_possibles'); |
$this->detruireParametreInvalide('tri', $trisPossibles); |
$formatsImgPossibles = $this->conteneur->getParametreTableau('appli_img.img_formats_possibles'); |
$this->detruireParametreInvalide('format', $formatsImgPossibles); |
$this->filtrerProtocole(); |
$this->supprimerParametresFiltresInvalides(); |
return $this->parametresFiltres; |
} |
public function filtrerUrlParamsAppliObs() { |
$this->maintenirCompatibilitesParametres(); |
$parametresAutorises = $this->conteneur->getParametreTableau(('observations.masques_possibles')); |
$this->eliminerParametresInconnus($parametresAutorises); |
$this->repartirMasqueGeneral(); |
$paramsParDefaut = $this->conteneur->getParametreTableau('observations.parametres_valeurs_defaut'); |
$this->definirParametresDefauts($paramsParDefaut); |
$this->filtrerUrlParamsGeneraux(); |
$trisPossibles = $this->conteneur->getParametre('appli_obs.tris_possibles'); |
$this->detruireParametreInvalide('tri', $trisPossibles); |
$this->supprimerParametresFiltresInvalides(); |
return $this->parametresFiltres; |
} |
private function maintenirCompatibilitesParametres() { |
$this->renommerParametres(); |
if (!isset($this->parametres['masque.tag_del']) && isset($this->parametres['masque.tag'])) { |
$this->parametres['masque.tag_del'] = $this->parametres['masque.tag']; |
} |
} |
private function renommerParametres() { |
$renomages = array('masque.tag_pictoflora' => 'masque.tag_del'); |
foreach ($renomages as $ancienNom => $nouveauNom) { |
if (isset($this->parametres[$ancienNom])) { |
$this->parametres[$nouveauNom] = $this->parametres[$ancienNom]; |
unset($this->parametres[$ancienNom]); |
} |
} |
} |
/** |
* Suppression de toutes les clefs NON présentes dans le paramètre de config : images|observations.masques_possibles |
* @param array $parametresAutorises tableau des paramètres pouvant être utilisé dans l'url. |
*/ |
private function eliminerParametresInconnus(Array $parametresAutorises = null) { |
if ($parametresAutorises) { |
$this->parametres = array_intersect_key($this->parametres, array_flip($parametresAutorises)); |
} |
} |
/** |
* Les paramètres par défaut sont écrasés par ceux passés dans l'url. |
* |
* @param array $paramsParDefaut tableau associatif des paramètres d'url par défaut |
*/ |
private function definirParametresDefauts(Array $paramsParDefaut) { |
$this->parametres = array_merge($paramsParDefaut, $this->parametres); |
} |
/** |
* "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. |
*/ |
private function repartirMasqueGeneral() { |
if (isset($this->parametres['masque']) && !empty(trim($this->parametres['masque']))) { |
$masqueGeneral = trim($this->parametres['masque']); |
$masquesDetailCles = array('masque.auteur', 'masque.departement', 'masque.commune', 'masque.id_zone_geo', |
'masque.ns', 'masque.famille', 'masque.date', 'masque.genre', 'masque.milieu'); |
// Suppression de la génération de SQL du masque général sur les champ spécifiques qui sont traités avec leur valeur propre. |
foreach ($masquesDetailCles as $cle) { |
if (isset($this->parametres[$cle]) === false) { |
$this->parametres[$cle] = $masqueGeneral; |
$this->parametresFiltres['_parametres_condition_or_'][] = $cle; |
} |
} |
} |
} |
/** |
* Filtre et valide les paramètres reconnus. Effectue *toute* la sanitization *sauf* l'escape-string |
* Cette fonction est appelée: |
* - une fois sur les champs de recherche avancées |
* - une fois sur le masque général si celui-ci à été spécifié. Dans ce cas, |
* la chaîne générale saisie est utilisée comme valeur pour chacun des champs particuliers |
* avec les traitements particuliers qui s'imposent |
* Par exemple: si l'on cherche "Languedoc", cela impliquera: |
* WHERE (nom_sel like "Languedoc" OR nom_ret ... OR ...) mais pas masque.date ou masque.departement |
* qui s'assure d'un pattern particulier |
* |
* masque.genre est un alias pour masque.ns (nom_sel), mais permet de rajouter une clause supplémentaire |
* sur nom_sel. Précédemment: WHERE nom_sel LIKE '%<masque.genre>% %'. |
* Désormais masque.genre doit être intégralement spécifié, les caractères '%' et '_' seront interprétés. |
* Attention toutefois car la table del_observation intègre des nom_sel contenant '_' |
*/ |
// TODO: ajouter un filtre sur le masque (général) |
private function filtrerUrlParamsGeneraux() { |
$this->detruireParametreInvalide('ordre', $this->conteneur->getParametreTableau('valeurs_ordre')); |
$this->detruireParametreInvalide('masque.referentiel', $this->conteneur->getParametreTableau('valeurs_referentiel')); |
$this->filtrerNavigationLimite(); |
$this->filtrerNavigationDepart(); |
$this->filtrerDepartement(); |
$this->filtrerDate(); |
$this->filtrerNn(); |
$this->filtrerNt(); |
$parametresATrimer = array('masque', 'masque.ns', 'masque.genre', 'masque.espece', 'masque.auteur', 'masque.milieu'); |
$this->supprimerCaracteresInvisibles($parametresATrimer); |
$this->filtrerFamille(); |
$this->filtrerIdZoneGeo(); |
$this->filtrerCommune(); |
$this->filtrerType(); |
$this->filtrerTag(); |
$this->filtrerTagCel(); |
$this->filtrerTagDel(); |
} |
/** |
* Supprime l'index du tableau des paramètres si sa valeur ne correspond pas |
* au spectre passé par $values. |
*/ |
private function detruireParametreInvalide($index, Array $valeursAutorisees) { |
if (array_key_exists($index, $this->parametres)) { |
if (!in_array($this->parametres[$index], $valeursAutorisees)) { |
unset($this->parametres[$index]); |
} else { |
$this->parametresFiltres[$index] = $this->parametres[$index]; |
} |
} |
} |
private function filtrerNavigationLimite() { |
if (isset($this->parametres['navigation.limite'])) { |
$options = array( |
'options' => array( |
'default' => null, |
'min_range' => 1, |
'max_range' => self::LISTE_OBS_MAX_RESULT_LIMIT)); |
$paramFiltre = filter_var($this->parametres['navigation.limite'], FILTER_VALIDATE_INT, $options); |
$this->parametresFiltres['navigation.limite'] = $paramFiltre; |
} |
} |
private function filtrerNavigationDepart() { |
if (isset($this->parametres['navigation.depart'])) { |
$options = array( |
'options' => array( |
'default' => null, |
'min_range' => 0, |
'max_range' => self::LISTE_OBS_MAX_ID_OBS)); |
$paramFiltre = filter_var($this->parametres['navigation.depart'], FILTER_VALIDATE_INT, $options); |
$this->parametresFiltres['navigation.depart'] = $paramFiltre; |
} |
} |
/** |
* STRING: 0 -> 95, 971 -> 976, 2A + 2B (./services/configurations/config_departements_bruts.ini) |
* accept leading 0 ? |
* TODO; filter patterns like 555. |
* |
* @return type |
*/ |
private function filtrerDepartement() { |
if (isset($this->parametres['masque.departement'])) { |
$dept = $this->parametres['masque.departement']; |
$paramFiltre = null; |
if (preg_match('/^(\d{2}|\d{3}|2a|2b)$/i', $dept) != 0) { |
$paramFiltre = is_numeric($dept) ? str_pad($dept, 5, '_') : $dept; |
} else { |
$dept_translit = iconv('UTF-8', 'ASCII//TRANSLIT', $dept); |
$dpt_chaine = strtolower(str_replace(' ', '-', $dept_translit)); |
$this->conteneur->chargerConfiguration('config_departements_bruts.ini'); |
$dpt_numero = $this->conteneur->getParametre($dpt_chaine); |
if (!empty($dpt_numero)) { |
$paramFiltre = str_pad($dpt_numero, 5, '_'); |
} |
} |
$this->parametresFiltres['masque.departement'] = $paramFiltre; |
} |
} |
private function filtrerDate() { |
if (isset($this->parametres['masque.date'])) { |
$date = $this->parametres['masque.date']; |
// une année, TODO: masque.annee |
$paramFiltre = null; |
if (is_numeric($date)) { |
$paramFiltre = $date; |
} elseif(strpos($date, '/' !== false) && ($x = strtotime(str_replace('/', '-', $date)))) { |
$paramFiltre = $x; |
} elseif(strpos($date, '-' !== false) && ($x = strtotime($date)) ) { |
$paramFiltre = $x; |
} |
$this->parametresFiltres['masque.date'] = $paramFiltre; |
} |
} |
private function filtrerNn() { |
if (isset($this->parametres['masque.nn'])) { |
$options = array( |
'options' => array( |
'default' => null, |
'min_range' => 0, |
'max_range' => self::LISTE_OBS_MAX_BDTFX_NN)); |
$paramFiltre = filter_var($this->parametres['masque.nn'], FILTER_VALIDATE_INT, $options); |
$this->parametresFiltres['masque.nn'] = $paramFiltre; |
} |
} |
private function filtrerNt() { |
if (isset($this->parametres['masque.nt'])) { |
$options = array( |
'options' => array( |
'default' => null, |
'min_range' => 0, |
'max_range' => self::LISTE_OBS_MAX_BDTFX_NT)); |
$paramFiltre = filter_var($this->parametres['masque.nt'], FILTER_VALIDATE_INT, $options); |
$this->parametresFiltres['masque.nt'] = $paramFiltre; |
} |
} |
private function supprimerCaracteresInvisibles(Array $liste_params) { |
foreach ($liste_params as $param) { |
if (isset($this->parametres[$param])) { |
$this->parametresFiltres[$param] = trim($this->parametres[$param]); |
} |
} |
} |
private function filtrerFamille() { |
if (isset($this->parametres['masque.famille'])) { |
// mysql -N<<<"SELECT DISTINCT famille FROM bdtfx_v1_02;"|sed -r "s/(.)/\1\n/g"|sort -u|tr -d "\n" |
$familleTranslit = iconv('UTF-8', 'ASCII//TRANSLIT',$this->parametres['masque.famille']); |
$paramFiltre = preg_replace('/[^a-zA-Z %_]/', '', $familleTranslit); |
$this->parametresFiltres['masque.famille'] = $paramFiltre; |
} |
} |
// Idem pour id_zone_geo qui mappait à ce_zone_geo: |
private function filtrerIdZoneGeo() { |
if (isset($this->parametres['masque.id_zone_geo'])) { |
if (preg_match('/^(INSEE-C:\d{5}|\d{2})$/', $this->parametres['masque.id_zone_geo'])) { |
$paramFiltre = $this->parametres['masque.id_zone_geo']; |
$this->parametresFiltres['masque.id_zone_geo'] = $paramFiltre; |
} |
} |
} |
/** masque.commune (zone_geo) |
* TODO: que faire avec des '%' en INPUT ? |
* Le masque doit *permettre* une regexp et non l'imposer. Charge au client de faire son travail. |
*/ |
private function filtrerCommune() { |
if (isset($this->parametres['masque.commune'])) { |
$paramFiltre = str_replace(array('-',' '), '_', $this->parametres['masque.commune']); |
$this->parametresFiltres['masque.commune'] = $paramFiltre; |
} |
} |
// masque.tag, idem que pour masque.genre et masque.commune |
private function filtrerTag() { |
if (isset($this->parametres['masque.tag'])) { |
$tagsArray = explode(',', $this->parametres['masque.tag']); |
$tagsTrimes = array_map('trim', $tagsArray); |
$tagsFiltres = array_filter($tagsTrimes); |
$paramFiltre = implode('|', $tagsFiltres); |
$this->parametresFiltres['masque.tag'] = $paramFiltre; |
} |
} |
private function filtrerTagCel() { |
if (isset($this->parametres['masque.tag_cel'])) { |
$this->parametresFiltres['masque.tag_cel'] = $this->construireTableauTags($this->parametres['masque.tag_cel'], 'OR', ','); |
} else if (isset($this->parametres['masque'])) { |
$this->parametresFiltres['masque.tag_cel'] = $this->construireTableauTags($this->parametres['masque'], 'AND', ' '); |
$this->parametresFiltres['_parametres_condition_or_'][] = 'masque.tag_cel'; |
} |
} |
private function filtrerTagDel() { |
if (isset($this->parametres['masque.tag_del'])) { |
$this->parametresFiltres['masque.tag_del'] = $this->construireTableauTags($this->parametres['masque.tag_del'], 'OR', ','); |
} else if (isset($this->parametres['masque'])) { |
$this->parametresFiltres['masque.tag_del'] = $this->construireTableauTags($this->parametres['masque'], 'AND', ' '); |
$this->parametresFiltres['_parametres_condition_or_'][] = 'masque.tag_del'; |
} |
} |
// masque.type: ['adeterminer', 'aconfirmer', 'endiscussion', 'validees'] |
private function filtrerType() { |
if(isset($this->parametres['masque.type'])) { |
$typesArray = explode(';', $this->parametres['masque.type']); |
$typesFiltres = array_filter($typesArray); |
$typesAutorises = array('adeterminer', 'aconfirmer', 'endiscussion', 'validees'); |
$typesValides = array_intersect($typesFiltres, $typesAutorises); |
$paramFiltre = array_flip($typesValides); |
$this->parametresFiltres['masque.type'] = $paramFiltre; |
} |
} |
private function filtrerProtocole() { |
// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné |
if (!isset($this->parametres['protocole']) || !is_numeric($this->parametres['protocole'])) { |
$this->parametresFiltres['protocole'] = $this->conteneur->getParametre('appli_img.protocole_defaut'); |
} else { |
$this->parametresFiltres['protocole'] = intval($this->parametres['protocole']); |
} |
} |
private function supprimerParametresFiltresInvalides() { |
// Suppression des NULL, FALSE et '', mais pas des 0, d'où l'utilisation de 'strlen'. |
// La fonction 'strlen' permet de supprimer les NULL, FALSE et chaines vides mais gardent les valeurs 0 (zéro). |
// Les valeurs spéciales contenant des tableaux (tag, _parametres_condition_or_) ne sont pas prise en compte |
foreach ($this->parametresFiltres as $cle => $valeur) { |
if (is_array($valeur) || strlen($valeur) !== 0) { |
$this->parametresFiltres[$cle] = $valeur; |
} |
} |
} |
} |
/trunk/services/bibliotheque/Conteneur.php |
---|
148,4 → 148,18 |
} |
return $this->partages['syndicationOutils']; |
} |
public function getParametresFiltrage() { |
if (!isset($this->partages['parametresFiltrage'])) { |
$this->partages['parametresFiltrage'] = new ParametresFiltrage($this); |
} |
return $this->partages['parametresFiltrage']; |
} |
public function getSql() { |
if (!isset($this->partages['sql'])) { |
$this->partages['sql'] = new Sql($this); |
} |
return $this->partages['sql']; |
} |
} |
/trunk/services/bibliotheque/SyndicationOutils.php |
---|
58,8 → 58,7 |
public function getUrlImage($id, $format = 'L') { |
$url_tpl = $this->conteneur->getParametre('cel_img_url_tpl'); |
$id = sprintf('%09s', $id).$format; |
$url = sprintf($url_tpl, $id); |
$url = sprintf($url_tpl, $id, $format); |
return $url; |
} |