Subversion Repositories eFlore/Applications.del

Compare Revisions

Ignore whitespace Rev 1843 → Rev 1845

/trunk/services/modules/0.1/images/ListeImages.php
57,7 → 57,7
$this->sql->definirOrdreSqlAppliImg();
 
$idImgs = $this->getIdImages();
$this->navigation->setTotal($this->getTotal());
$this->navigation->setTotal($this->sql->getTotalLignesTrouvees());
 
// Ce n'est pas la peine de continuer s'il n'y a pas eu de résultats
$resultat = new ResultatService();
100,11 → 100,6
return $idImgs;
}
 
private function getTotal() {
$resultat = $this->bdd->recuperer('SELECT FOUND_ROWS() AS nbre');
return intval($resultat['nbre']);
}
 
private function getInfosImages($idImgs) {
$obsChamps = $this->sql->getAliasDesChamps($this->mappings['observations']);
$imgChamps = $this->sql->getAliasDesChamps($this->mappings['images']);
136,7 → 131,7
'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($infos['id_image'], $infos['mots_cles_texte_img'], $infos['jsonindex']);
if (!isset($obs_merged[$id])) {
$obs_merged[$id] = $image;
/trunk/services/modules/0.1/observations/ObservationDetails.php
1,25 → 1,20
<?php
// declare(encoding='UTF-8');
/**
* 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
* @category DEL
* @package Services
* @subpackage Observations
* @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>
*/
 
// 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;
29,7 → 24,7
private $protocole;
private $observation;
private $mappings = array();
 
public function __construct(Conteneur $conteneur) {
$this->conteneur = $conteneur;
$this->bdd = $this->conteneur->getBdd();
110,7 → 105,9
$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;
if (!isset($this->observation)) {
$this->observation = $info;
}
$this->observation['images'][$image['id_image']] = $image;
}
}
/trunk/services/modules/0.1/observations/ListeObservationsWidget.php
380,5 → 380,4
return $this->bdd->proteger($valeur);
}
}
}
?>
}
/trunk/services/modules/0.1/observations/VoteObservation.php
1,17 → 1,18
<?php
// declare(encoding='UTF-8');
/**
* Les web service permet d'ajouter ou de modifier les votes associés aux propositions d'une observation
* Web service permetant d'ajouter ou de modifier les votes associés aux propositions d'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)
* @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
* @category DEL
* @package Services
* @subpackage Observations
* @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 VoteObservation extends RestService {
279,5 → 280,4
return $this->bdd->proteger($valeur);
}
}
}
?>
}
/trunk/services/modules/0.1/observations/ListeObservations.php
1,162 → 1,89
<?php
// declare(encoding='UTF-8');
/**
* Le web service observations récupère toutes les observations et, pour chacune d'elle, les
* images qui lui sont associées.
* Basée sur la classe antérieure dans ListeObservations.php de
* Grégoire Duché et Aurélien Peronnet
* (formaterVote, formaterDeterminations, chargerNombreCommentaire, chargerVotes, chargerDeterminations)
* Web service récupèrant toutes les observations et, pour chacune d'elle, les images qui lui sont associées.
*
* @category php 5.2
* @package del
* @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=ApiIdentiplante01Observations
* ATTENTION : le web service commence par récupérer seulement les id des obs (1er requete SQL), puis dans une
* deuxième requête SQL récupère les informations complémentaires. Il s'avère qu'en procédant ainsi le web service
* est 3 fois plus rapide !
*
* TODO:
* PDO::prepare()
* Sphinx pour auteur, genre, ns, commune, tag et masque-général
* @category DEL
* @package Services
* @subpackage Observations
* @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');
/*
restore_error_handler();
restore_exception_handler();
error_reporting(E_ALL);
*/
 
class ListeObservations {
 
private $conteneur;
private $bdd;
private $navigation;
private $filtrage;
private $sql;
private $parametres = array();
private $ressources = array();
private $mappings = array();
private $paramsFiltres = array();
 
static $tris_possibles = array('date_observation');
// paramètres autorisés
 
static $sql_fields_liaisons = array(
'dob' => array('id_observation', 'nom_sel AS `determination.ns`', 'nt AS `determination.nt`',
'nom_sel_nn AS `determination.nn`', 'famille AS `determination.famille`',
'nom_referentiel AS `determination.referentiel`',
'ce_zone_geo AS id_zone_geo', 'zone_geo', 'lieudit',
'station', 'milieu', 'date_observation', 'mots_cles_texte', 'date_transmission',
'ce_utilisateur AS `auteur.id`', 'prenom_utilisateur AS `auteur.prenom`',
'nom_utilisateur AS `auteur.nom`', 'courriel_utilisateur AS `auteur.courriel` ',
'commentaire'),
'di' => array('id_image', 'date_prise_de_vue AS `date`', 'hauteur',/* 'largeur','nom_original' // apparemment inutilisés */),
'du' => array('prenom', 'nom', 'courriel'),
'dc' => array('commentaire')
);
public function __construct(Conteneur $conteneur) {
$this->conteneur = $conteneur;
$this->conteneur->chargerConfiguration('config_departements_bruts.ini');
 
public function __construct(Conteneur $conteneur = null) {
$this->conteneur = $conteneur == null ? new Conteneur() : $conteneur;
$this->conteneur->chargerConfiguration('config_departements_bruts.ini');
$this->conteneur->chargerConfiguration('config_observations.ini');
$this->conteneur->chargerConfiguration('config_mapping_votes.ini');
//$this->conteneur->chargerConfiguration('config_mapping_commentaires.ini');
$this->navigation = $conteneur->getNavigation();
$this->bdd = $this->conteneur->getBdd();
}
$this->filtrage = $this->conteneur->getParametresFiltrage();
$this->sql = $this->conteneur->getSql();
$this->navigation = $this->conteneur->getNavigation();
 
static function reformateObservation($obs, $url_pattern = '') {
$obs = array_map('array_filter', $obs);
$obs_merged = array();
foreach ($obs as $o) {
$id = $o['id_observation'];
$this->mappings['votes'] = $this->conteneur->getParametreTableau('votes.mapping');
$this->mappings['commentaires'] = $this->conteneur->getParametreTableau('commentaires.mapping');
}
 
// car auteur.id peut être un email, un hash, ou un annuaire_tela.U_ID
// mais dans les deux premiers cas SELECT courriel AS observateur fait déjà l'affaire
if (!isset($o['auteur.id']) || !is_numeric($o['auteur.id'])) $o['auteur.id'] = "0";
if (!isset($o['auteur.nom'])) $o['auteur.nom'] = '[inconnu]';
 
$image = array_intersect_key($o, array_flip(array('id_image', 'date', 'hauteur' , 'largeur', 'nom_original')));
$image['binaire.href'] = sprintf($url_pattern, $image['id_image']);
unset($o['id_image'], $o['date'], $o['hauteur'], $o['largeur'], $o['nom_original']);
if (!isset($obs_merged['"' . $id . '"'])) $obs_merged['"' . $id . '"'] = $o;
$obs_merged['"' . $id . '"']['images'][] = $image;
}
return $obs_merged;
}
 
/**
* 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) {
// SELECT, à terme, pourrait affecter getInfos(), mais en aucune manière getIdObs()
$req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'having' => array());
$this->ressources = $ressources;
$this->parametres = $parametres;
 
// toujours nécessaire puisque nous tapons sur v_del_image qui INNER JOIN cel_images, or nous voulons certes
// toutes les images, mais nous voulons $limite observations uniques.
$req['groupby'][] = 'vdi.id_observation';
$this->paramsFiltres = $this->filtrage->filtrerUrlParamsAppliObs();
$this->sql->setParametres($this->paramsFiltres);
$this->sql->ajouterContraintes();
$this->sql->ajouterConstrainteAppliObs();
$this->sql->definirOrdreSqlAppliObs();
 
$db = $this->bdd;
$idObs = $this->getIdObs();
$this->navigation->setTotal($this->sql->getTotalLignesTrouvees());
 
// filtrage de l'INPUT
$params = DelTk::requestFilterParams($parametres, DelTk::$parametres_autorises, $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($idObs) > 0) {
 
$params['masque.tag'] = DelTk::buildTagsAST(@$parametres['masque.tag'], 'OR', ',');
 
// ... et paramètres par défaut
$params = array_merge(DelTk::$default_params, $params);
 
// création des contraintes (masques)
DelTk::sqlAddConstraint($params, $db, $req);
self::sqlAddConstraint($params, $db, $req, $this->conteneur);
self::sqlAddMasqueConstraint($params, $db, $req, $this->conteneur);
 
// 1) grunt-work: *la* requête de récupération des id valides (+ SQL_CALC_FOUND_ROWS)
$idobs_tab = self::getIdObs($params, $req, $db);
// idobs est une liste (toujours ordonnée) des id d'observations recherchées
$idobs = array_values(array_map(create_function('$a', 'return $a["id_observation"];'), $idobs_tab));
 
if ($idobs) {
$total = $db->recuperer('SELECT FOUND_ROWS() AS c'); $total = intval($total['c']);
 
// 2) récupération des données nécessaires pour ces observations (obs + images)
// ici les champs récupérés sont issus de self::$sql_fields_liaisons mais sans préfixes
// car tout provient de v_del_image
$obs_unfmt = self::getInfos($idobs, $db);
$obs_unfmt = $this->getInfos($idObs);
 
// 3) suppression, merge des données en tableau assez représentatif du futur JSON en output
$observations = self::reformateObservation($obs_unfmt, $this->conteneur->getParametre('url_images'));
$observations = $this->formaterObservations($obs_unfmt);
 
// 4) récupération des données nécessaires pour ces observations (commentaires + votes)
// modifie $observations
$this->configurer();
$this->chargerDeterminations($observations);
$this->chargerDeterminations($observations, $idObs);
 
// 5) restauration de l'ordre souhaité initialement
$observations = self::sortArrayByArray($observations, $idobs);
} else {
$observations = array();
$total = 0;
$observations = $this->ordonnerObservations($observations, $idObs);
 
$resultat->corps = array(
'entete' => $this->navigation->getEntete(),
'resultats' => $observations);
}
 
// 6) JSON output
$resultat = new ResultatService();
$resultat->corps = array('entete' => DelTk::makeJSONHeader($total, $params, Config::get('url_service')),
'resultats' => $observations);
 
return $resultat;
}
 
static function sortArrayByArray($array, $orderArray) {
$ordered = array();
foreach ($orderArray as $key) {
if (array_key_exists('"' . $key . '"', $array)) {
$ordered['"' . $key . '"'] = $array['"' . $key . '"'];
unset($array['"' . $key . '"']);
}
}
return $ordered + $array;
}
 
// SQL helpers
/*
* Retourne une liste ordonnée d'id d'observation correspondant aux critères
166,249 → 93,147
* @param req: le tableau représentant les composants de la requete SQL
* @param db: l'instance de db
*/
static function getIdObs($p, $req, $db) {
$req_s = sprintf('SELECT SQL_CALC_FOUND_ROWS id_observation' .
' FROM v_del_image vdi'.
' %s' . // LEFT JOIN if any
' WHERE %s'. // where-clause ou TRUE
' %s'. // group-by
' %s'. // having (si commentaires)
' ORDER BY %s %s %s'.
' LIMIT %d, %d -- %s',
private function getIdObs() {
$requete = 'SELECT SQL_CALC_FOUND_ROWS id_observation '.
'FROM v_del_image AS vdi '.
$this->sql->getJoin().
'WHERE '.$this->sql->getWhere().
$this->sql->getGroupBy().
$this->sql->getOrderBy().
$this->sql->getLimit().
' -- '.__FILE__.':'.__LINE__;
 
$req['join'] ? implode(' ', $req['join']) : '',
$req['where'] ? implode(' AND ', $req['where']) : 'TRUE',
$resultats = $this->bdd->recupererTous($requete);
 
$req['groupby'] ? ('GROUP BY ' . implode(', ', array_unique($req['groupby']))) : '',
$req['having'] ? ('HAVING ' . implode(' AND ', $req['having'])) : '',
 
$p['tri'], strtoupper($p['ordre']),
// date_transmission peut-être NULL et nous voulons de la consistence
// (sauf après r1860 de Cel)
$p['tri'] == 'date_transmission' ? ', id_observation' : '',
 
$p['navigation.depart'], $p['navigation.limite'], __FILE__ . ':' . __LINE__);
 
$res = $db->recupererTous($req_s);
$err = mysql_error();
if (!$res && $err) {
// http_response_code(400);
// if(defined('DEBUG') && DEBUG) header("X-Debug: $req_s");
throw new Exception('not found', 400);
$idObs = array();
if ($resultats !== false ) {
foreach ($resultats as $resultat) {
$idObs[] = $resultat['id_observation'];
}
}
// ordre préservé, à partir d'ici c'est important.
return $res;
return $idObs;
}
 
/**
* Champs récupérés:
* Pour del_images, la vue retourne déjà ce que nous recherchons de cel_obs et cel_images
* (cel_obs.* et cel_[obs_]images.{id_observation, id_image, date_prise_de_vue AS date, hauteur, largeur})
* Pour del_commentaires: nous voulons *
* Reste ensuite à formatter.
* Note: le préfixe de table utilisé ici (vdi) n'impacte *aucune* autre partie du code car rien
* n'en dépend pour l'heure. (inutilisation de $req['select'])
* Après avoir récupérer seulement les ids dans une première requête, nous récupérons maintenant les infos.
* Le web service est ainsi 3 fois plus rapide.
*/
static function getInfos($idobs, $db) {
/*$select_fields = implode(',', array_merge(
array_map(create_function('$a', 'return "vdi.".$a;'), self::$sql_fields_liaisons['dob']),
array_map(create_function('$a', 'return "vdi.".$a;'), self::$sql_fields_liaisons['di']),
array_map(create_function('$a', 'return "du.".$a;'), self::$sql_fields_liaisons['du'])));*/
$select_fields = array_merge(self::$sql_fields_liaisons['dob'], self::$sql_fields_liaisons['di']);
$req_s = sprintf('SELECT %s FROM v_del_image vdi'.
// ' LEFT JOIN del_commentaire AS dc ON di.id_observation = dc.ce_observation AND dc.nom_sel IS NOT NULL'.
' WHERE id_observation IN (%s)',
implode(',', $select_fields),
implode(',', $idobs));
return $db->recupererTous($req_s);
private function getInfos($idObs) {
$idsObsConcat = implode(',', $idObs);
$requete = "SELECT id_observation, nom_sel AS `determination.ns`, nt AS `determination.nt`, ".
'nom_sel_nn AS `determination.nn`, famille AS `determination.famille`, '.
'nom_referentiel AS `determination.referentiel`, ce_zone_geo AS id_zone_geo, '.
'zone_geo, lieudit, station, milieu, date_observation, mots_cles_texte, '.
'date_transmission, commentaire, '.
'ce_utilisateur AS `auteur.id`, prenom_utilisateur AS `auteur.prenom`, '.
'nom_utilisateur AS `auteur.nom`, courriel_utilisateur AS `auteur.courriel`, '.
'id_image, date_prise_de_vue AS `date`, hauteur, largeur, nom_original '.
'FROM v_del_image AS vdi '.
"WHERE id_observation IN ($idsObsConcat) ".
' -- '.__FILE__.':'.__LINE__;
return $this->bdd->recupererTous($requete);
}
 
/**
* Complément à DelTk::sqlAddConstraint()
*
* @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
* @param $c conteneur, utilisé soit pour l'appel récursif à requestFilterParams() en cas de param "masque"
* soit pour la définition du type (qui utilise la variable nb_commentaires_discussion)
* Les informations étant extraites d'une vue dont les infos des obs sont dupliquées pour chaque image,
* il nous faut maintenant récupérer qu'une seule fois les données d'observations et y intégrer les données
* des images.
*/
static function sqlAddConstraint($p, $db, &$req, Conteneur $c = NULL) {
if (!empty($p['masque.tag'])) {
// TODO: remove LOWER() lorsqu'on est sur que les tags sont uniformés en minuscule
// i_mots_cles_texte provient de la VIEW v_del_image
if (isset($p['masque.tag']['AND'])) {
/* Lorsque nous interprêtons la chaîne provenant du masque général (cf: buildTagsAST($p['masque'], 'OR', ' ') dans sqlAddMasqueConstraint()),
nous sommes splittés par espace. Cependant, assurons que si une virgule à été saisie, nous n'aurons pas le motif
" AND CONCAT(mots_cles_texte, i_mots_cles_texte) REGEXP ',' " dans notre requête.
XXX: Au 12/11/2013, une recherche sur tag depuis le masque général implique un OU, donc le problème ne se pose pas ici */
$subwhere = array();
foreach ($p['masque.tag']['AND'] as $tag) {
if (trim($tag) == ',') continue;
private function formaterObservations($observations) {
$observations = array_map('array_filter', $observations);
$obsFormatees = array();
foreach ($observations as $obs) {
$this->nettoyerAuteur($obs);
$image = $this->extraireInfosImage($obs);
 
$subwhere[] = sprintf('LOWER(CONCAT(%s)) REGEXP %s',
DelTk::sqlAddIfNullPourConcat(array('vdi.mots_cles_texte', 'vdi.i_mots_cles_texte')),
$db->proteger(strtolower($tag)));
}
$req['where'][] = '(' . implode(' AND ', $subwhere) . ')';
} else {
$req['where'][] = sprintf('LOWER(CONCAT(%s)) REGEXP %s',
DelTk::sqlAddIfNullPourConcat(array('vdi.mots_cles_texte', 'vdi.i_mots_cles_texte')),
$db->proteger(strtolower(implode('|', $p['masque.tag']['OR']))));
$id = 'idx-'.$obs['id_observation'];
if (!isset($obsFormatees[$id])) {
$obsFormatees[$id] = $obs;
}
$obsFormatees[$id]['images'][] = $image;
}
return $obsFormatees;
}
 
if (!empty($p['masque.type'])) {
self::addTypeConstraints($p['masque.type'], $db, $req, $c);
private function nettoyerAuteur(&$obs) {
// car auteur.id peut être un email, un hash, ou un annuaire_tela.U_ID
// mais dans les deux premiers cas SELECT courriel AS observateur fait déjà l'affaire
if (!isset($obs['auteur.id']) || !is_numeric($obs['auteur.id'])) {
$obs['auteur.id'] = "0";
}
if (!isset($obs['auteur.nom'])) {
$obs['auteur.nom'] = '[inconnu]';
}
}
 
/** Le masque fait une recherche générique parmi de nombreux champs ci-dessus.
* Nous initialisons donc ces paramètres (excepté masque biensur), et nous rappelons
* récursivement. À la seule différence que nous n'utiliserons que $or_req['where']
* imploded par des " OR ".
*/
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.id_zone_geo' => $p['masque'],
'masque.tag' => $p['masque'],
'masque.ns' => $p['masque'],
'masque.famille' => $p['masque'],
'masque.date' => $p['masque'],
'masque.genre' => $p['masque'],
/* milieu: TODO ? */ );
/* 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.id_zone_geo'])) unset($or_params['masque.id_zone_geo']);
if (isset($p['masque.tag'])) unset($or_params['masque.tag']);
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']);
private function extraireInfosImage(&$obs) {
$champsImageAffichables = array('id_image', 'date', 'hauteur' , 'largeur', 'nom_original');
$image = array_intersect_key($obs, array_flip($champsImageAffichables));
 
$urlImgTpl = $this->conteneur->getParametre('cel_img_url_tpl');
$image['binaire.href'] = sprintf($urlImgTpl, $image['id_image'], 'XL');
 
$or_masque = DelTk::requestFilterParams($or_params, array_keys($or_params), $c);
if (isset($or_params['masque.tag'])) {
$or_masque['masque.tag'] = DelTk::buildTagsAST($p['masque'], 'OR', ' ');
}
unset($obs['id_image'], $obs['date'], $obs['hauteur'], $obs['largeur'], $obs['nom_original']);
return $image;
}
 
// $or_req = array('select' => array(), 'join' => array(), 'where' => array(), 'groupby' => array(), 'having' => array());
$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']));
private function ordonnerObservations($observations, $ordreDesObs) {
$obsOrdonnees = array();
foreach ($ordreDesObs as $id) {
if (array_key_exists("idx-$id", $observations)) {
$obsOrdonnees["idx-$id"] = $observations["idx-$id"];
unset($observations["idx-$id"]);
}
}
return $obsOrdonnees + $observations;
}
 
private function configurer() {
$this->mappingVotes = $this->conteneur->getParametre('mapping_votes');
$this->mappingCommentaire = $this->conteneur->getParametreTableau('commentaires.mapping');
}
 
/**
* @param $req: la représentation de la requête MySQL complète, à amender.
*/
static function addTypeConstraints($val, $db, &$req, Conteneur $c) {
if (array_key_exists('adeterminer', $val)) {
// On récupère toutes les observations qui on le tag "aDeterminer" *ou* qui n'ont pas de nom d'espèce
// *ou* qui ont la "certitude" à ("aDeterminer" *ou* "douteux")
$req['where'][] = '(' . implode(' OR ', array(
'vdi.certitude = "aDeterminer"',
'vdi.certitude = "douteux"',
'vdi.mots_cles_texte LIKE "%aDeterminer%"',
'vdi.nom_sel_nn IS NULL',
'vdi.nom_sel_nn = 0', // il ne DEVRAIT pas y avoir d'entrées à 0, mais il y en a quand-même !!
)) . ')';
}
if (array_key_exists('validees', $val)) {
//On récupère toutes les observations ayant un commentaire doté de proposition_retenue = 1
$req['join'][] = 'INNER JOIN del_commentaire AS dc ON vdi.id_observation = dc.ce_observation AND dc.proposition_retenue = 1';
}
 
// solution n°1: impraticable
if (false && array_key_exists('endiscussion', $val)) {
//Si on veut les observations en discussion,
// on va récupérer les ids des observations dont le nombre de commentaire est supérieur à N
$req['select'][] = 'COUNT(dc.id_commentaire) AS comm_count';
$req['join'][] = 'INNER JOIN del_commentaire AS dc ON vdi.id_observation = dc.ce_observation';
$req['groupby'][] = 'vdi.id_observation';
$req['having'][] = "COUNT(id_commentaire) > " . $c->getParametre('nb_commentaires_discussion');
}
 
if (array_key_exists('endiscussion', $val)) {
$req['where'][] = '(SELECT COUNT(id_commentaire) FROM del_commentaire AS dc'.
' WHERE ce_observation = id_observation) > ' . intval($c->getParametre('nb_commentaires_discussion'));
}
}
 
/**
* Récupérer toutes les déterminations et le nombre de commentaire au total
* @param array $observations la liste des observations à mettre à jour
*/
private function chargerDeterminations(&$observations) {
$idObs = array_values(array_map(create_function('$a', 'return $a["id_observation"];'), $observations));
$r = sprintf('SELECT * FROM del_commentaire AS dc WHERE dc.nom_sel IS NOT NULL AND ce_observation IN (%s) -- %s',
implode(',',$idObs),
__FILE__ . ':' . __LINE__);
$propositions = $this->bdd->recupererTous($r);
if (!$propositions) return;
foreach ($propositions as $proposition) {
$idObs = $proposition['ce_observation'];
$idComment = $proposition['id_commentaire'];
$comment = $this->formaterDetermination($idComment, $proposition);
if ($comment) $observations['"' . $idObs . '"']['commentaires'][$idComment] = $comment;
private function chargerDeterminations(&$observations, $idObs) {
$idObsConcat = implode(',', $idObs);
$requete = 'SELECT * '.
'FROM del_commentaire AS dc '.
'WHERE dc.nom_sel IS NOT NULL '.
"AND ce_observation IN ($idObsConcat) ".
'-- '.__FILE__.':'.__LINE__;
 
$propositions = $this->bdd->recupererTous($requete);
if ($propositions) {
foreach ($propositions as $proposition) {
$idObs = $proposition['ce_observation'];
$idComment = $proposition['id_commentaire'];
$comment = $this->formaterDetermination($idComment, $proposition);
if ($comment) {
$observations["idx-$idObs"]['commentaires'][$idComment] = $comment;
}
}
}
}
 
private function formaterDetermination($commentId, $proposition) {
if (!$proposition) return NULL;
private function formaterDetermination($propositionId, $propositionInfos) {
if (!$propositionInfos) return NULL;
 
$proposition_formatee = array('nb_commentaires' => '0');
foreach ($this->mappingCommentaire as $nomOriginal => $nomFinal) {
if (isset($proposition[$nomOriginal])) {
$proposition_formatee[$nomFinal] = $proposition[$nomOriginal];
$propositionFormatee = array();
foreach ($this->mappings['commentaires'] as $nomChamp => $nomAttributJson) {
if (isset($propositionInfos[$nomChamp])) {
$propositionFormatee[$nomAttributJson] = $propositionInfos[$nomChamp];
}
}
 
// Charger les votes sur les déterminations
$resultatsVotes = $this->bdd->recupererTous(
sprintf('SELECT * FROM del_commentaire_vote WHERE ce_proposition = %d', $commentId));
 
$requete = "SELECT * FROM del_commentaire_vote WHERE ce_proposition = $propositionId".
'-- '.__FILE__.':'.__LINE__;
$resultatsVotes = $this->bdd->recupererTous($requete);
foreach ($resultatsVotes as $vote) {
$proposition_formatee['votes'][$vote['id_vote']] = $this->formaterVote($vote);
$propositionFormatee['votes'][$vote['id_vote']] = $this->formaterVote($vote);
}
 
// chargerNombreCommentaire()
// Charger le nombre de commentaires (sans détermination) associé à l'observation
$listeCommentaires = $this->bdd->recupererTous(sprintf(
'SELECT ce_commentaire_parent, ce_proposition, COUNT( id_commentaire ) AS nb '.
'FROM del_commentaire WHERE ce_proposition = %d GROUP BY ce_proposition -- %s',
$commentId, __FILE__ . ':' . __LINE__));
foreach ($listeCommentaires as $ligneProposition) {
// ce test sert à exclure les proposition de 1er niveau qui sont elles aussi des commentaires
if ($ligneProposition['ce_commentaire_parent']) {
// TODO/debug: id_commentaire_parent != $commentId ??
// reprendre la "logique" du code... moins de boucles, moins de requêtes, ...
if ($ligneProposition['ce_commentaire_parent'] != $commentId) {
// restore_error_handler();
error_log(sprintf("possible error: nb_commentaires = %s: comment = %d, parent = %d, %s",
$ligneProposition['nb'], $commentId, $ligneProposition['ce_commentaire_parent'], __FILE__));
}
$proposition_formatee['nb_commentaires'] = $ligneProposition['nb'];
} else {
$proposition_formatee['observation']['nb_commentaires'] = $ligneProposition['nb'];
}
}
$propositionFormatee['nb_commentaires'] = $this->chargerNombreCommentaire($propositionId);
 
return $proposition_formatee;
return $propositionFormatee;
}
 
/**
416,10 → 241,20
* @param $votes array()
*/
private function formaterVote($vote) {
$retour = array();
foreach ($vote as $param=>$valeur) {
$retour[$this->mappingVotes[$param]] = $valeur;
$voteFormate = array();
foreach ($vote as $nomChamp => $valeur) {
$voteFormate[$this->mappings['votes'][$nomChamp]] = $valeur;
}
return $retour;
return $voteFormate;
}
 
private function chargerNombreCommentaire($propositionId) {
$requete = 'SELECT COUNT( id_commentaire ) AS nb '.
'FROM del_commentaire '.
"WHERE ce_proposition = $propositionId ".
'GROUP BY ce_proposition '.
'-- '.__FILE__.':'.__LINE__;
$commentaires = $this->bdd->recuperer($requete);
return $commentaires ? $commentaires['nb'] : 0;
}
}
/trunk/services/bibliotheque/Sql.php
302,6 → 302,41
}
}
 
public function ajouterConstrainteAppliObs() {
$this->ajouterContrainteTagCel();
$this->ajouterContrainteType();
// TODO : ATTENTION -> vue que l'on utilise une vue basée sur les images, nous devons grouper par obs
$this->addGroupBy('vdi.id_observation');
}
 
/**
* @param $req: la représentation de la requête MySQL complète, à amender.
*/
private function ajouterContrainteType() {
if (array_key_exists('adeterminer', $this->parametres['masque.type'])) {
// Récupèration de toutes les observations qui on le tag "aDeterminer" *ou* qui n'ont pas de nom d'espèce
// *ou* qui ont la "certitude" à ("aDeterminer" *ou* "douteux")
$this->addWhere('masque.type', '('.
'vdi.certitude = "aDeterminer" '.
'OR vdi.certitude = "douteux" '.
'OR vdi.mots_cles_texte LIKE "%aDeterminer%" '.
'OR vdi.nom_sel_nn IS NULL '.
'OR vdi.nom_sel_nn = 0 '.// il ne DEVRAIT pas y avoir d'entrées à 0, mais il y en a quand-même !!
')');
}
if (array_key_exists('validees', $this->parametres['masque.type'])) {
// Récupèration de toutes les observations ayant un commentaire doté de proposition_retenue = 1
$this->addJoin('INNER JOIN del_commentaire AS dc '.
'ON (vdi.id_observation = dc.ce_observation AND dc.proposition_retenue = 1) ');
}
 
if (array_key_exists('endiscussion', $this->parametres['masque.type'])) {
$nbreCommentaire =(int) ($this->conteneur->getParametre('observations.nb_commentaires_discussion'));
$this->addWhere('masque.type', '(SELECT COUNT(id_commentaire) FROM del_commentaire AS dc '.
"WHERE ce_observation = id_observation) > $nbreCommentaire ");
}
}
 
/**
* in $p: un tableau de paramètres, dont:
* - 'masque.tag_cel': *tableau* de mots-clefs à chercher parmi cel_image.mots_clefs_texte
377,7 → 412,7
*/
// 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'])) {
if (isset($this->parametres['masque.tag_cel']['AND'])) {
$tags = $this->parametres['masque.tag_cel']['AND'];
$clausesWhere = array();
399,99 → 434,40
}
 
/**
* 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();
* Plusieurs solutions sont disponibles dans les anciennes versions (voir DelTk).
*/
private function ajouterContrainteTagDel() {
if (isset($this->parametres['masque.tag_del'])) {
$this->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);
 
/** 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));
} 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);
}
$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();
537,6 → 513,19
}
}
 
public function definirOrdreSqlAppliObs() {
$ordre = $this->parametres['ordre'];
 
// parmi self::$tri_possible
switch ($this->parametres['tri']) {
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);
584,7 → 573,7
*/
public function ajouterInfosVotesProtocoles($votes, &$images) {
if (!$votes) return;
 
$mappingVotes = $this->conteneur->getParametreTableau('votes.mapping');
$mappingProtocoles = $this->conteneur->getParametreTableau('protocoles.mapping');
 
606,4 → 595,9
$images[$imgId]['protocoles_votes'][$protoId]['votes'][$vote['vote.id']] = $vote;
}
}
 
public function getTotalLignesTrouvees() {
$resultat = $this->bdd->recuperer('SELECT FOUND_ROWS() AS nbre');
return intval($resultat['nbre']);
}
}
/trunk/services/bibliotheque/ParametresFiltrage.php
35,74 → 35,6
$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();
 
139,7 → 71,7
 
$this->filtrerUrlParamsGeneraux();
 
$trisPossibles = $this->conteneur->getParametre('appli_obs.tris_possibles');
$trisPossibles = $this->conteneur->getParametreTableau('appli_obs.tris_possibles');
$this->detruireParametreInvalide('tri', $trisPossibles);
 
$this->supprimerParametresFiltresInvalides();
419,6 → 351,74
}
}
 
 
/**
* 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);
}
 
private 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;
}
 
// masque.type: ['adeterminer', 'aconfirmer', 'endiscussion', 'validees']
private function filtrerType() {
if(isset($this->parametres['masque.type'])) {