/trunk/services/bibliotheque/Sql.php |
---|
302,7 → 302,42 |
} |
} |
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 |
* - 'masque.tag_del': *tableau* de mots-clefs à chercher parmi del_image_tag.tag_normalise |
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,70 → 434,10 |
} |
/** |
* 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(); |
} |
} |
/** 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() |
491,6 → 466,7 |
$this->addWhere('masque.tag_del', $sql); |
} |
} |
} |
private function recupererSqlContrainteTag($requete) { |
$resultats = $this->bdd->recupererTous($requete); |
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); |
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'])) { |