Subversion Repositories eFlore/Applications.del

Compare Revisions

Ignore whitespace Rev 1429 → Rev 1430

/trunk/services/modules/0.1/images/ListeImages2.php
65,9 → 65,10
 
class ListeImages2 {
 
// 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');
 
static $tri_possible = array('date_observation', 'votes', 'tags');
static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags');
 
// en plus de ceux dans ListeObservations2
static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
142,18 → 143,16
array('masque.type')),
$this->conteneur);
 
// Cette variable est utile au filtrage des tags
// Elle peut-être redéfinie en interne par sqlAddMasqueConstraint() ci-dessous
// lorsque celui-ci transforme le masque général en masque par champ
// et ré-appelle self::requestFilterParams()
$parametres['tag_explode_char'] = ','; // " " (whitespace) ou "," ou NULL
// tag_explode_semantic est lui utilisé lors de l'ajout des contraintes sur tags self::sqlAddConstraint()
$parametres['tag_explode_semantic'] = 'OR'; // "AND" ou "OR" (si 'tag_explode_char' NOT NULL)
// notre propre filtrage sur l'INPUT
$params_pf = self::requestFilterParams($parametres,
array_merge(ListeObservations2::$parametres_autorises,
self::$parametres_autorises));
 
/* 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'] = self::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
$params_pf['masque.tag_pictoflora'] = self::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
 
$params = array_merge(ListeObservations2::$default_params, // paramètre par défaut Identiplante
self::$default_params, // paramètres par défaut PictoFlora
232,7 → 231,7
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'];
$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
return;
}
241,11 → 240,15
return;
}
 
if($p['tri'] == 'date_observation') {
$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
return;
}
 
// tri == 'date_transmission'
$req['orderby'] = 'date_transmission ' . $p['ordre'];
// avant cel:r1860, date_transmission pouvait être NULL
// or nous voulons de la consistence (notamment pour phpunit)
$req['orderby'] .= ', id_observation ' . $p['ordre'];
$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
}
 
/*
315,53 → 318,51
$req['groupby'][] = 'vdi.id_observation';
}
 
 
// TODO: support du "ET", "OU", "(", ")"
// http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html
// http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/
if(!empty($p['masque.tag_cel'])) {
if($p['tag_explode_semantic'] == 'AND') {
// 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'];
$tags = $p['masque.tag_cel']['AND'];
array_walk($tags, create_function('&$val, $k, $db',
'$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
$db->proteger("%".$val."%"));'),
$db);
$req['where'][] = implode(' AND ', $tags);
$req['where'][] = '(' . implode(' AND ', $tags) . ')';
}
else { // OR assumed
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.mots_cles_texte) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_cel'])));
else {
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
}
}
 
 
// XXX: utiliser tag plutôt que tag_normalise ?
if(!empty($p['masque.tag_pictoflora'])) {
if($p['masque.tag_pictoflora']) {
// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
$req['where'][] = 'dit.actif = 1'; */
 
if($p['tag_explode_semantic'] == 'AND') {
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']);
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'])));
$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
}
else { // OR assumed
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'])));
$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
}
}
 
}
 
 
static function getIdImages($p, $req, $db) {
return $db->recupererTous(sprintf(
'SELECT SQL_CALC_FOUND_ROWS id_image' .
412,9 → 413,10
'masque.commune' => $p['masque'], // TODO/XXX ?
'masque.id_zone_geo' => $p['masque'],
 
// tous-deux remplacent masque.tag
'masque.tag_cel' => $p['masque'],
'masque.tag_pictoflora' => $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'],
428,10 → 430,15
 
$or_masque = array_merge(
ListeObservations2::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
self::requestFilterParams($or_params),
array('tag_explode_char' => ' ', 'tag_explode_semantic' => 'AND')); // spéciaux
self::requestFilterParams($or_params));
 
// pas de select, groupby & co ici
/* 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. */
$or_masque['masque.tag_cel'] = self::buildTagsAST($p['masque'], 'AND', ' ');
$or_masque['masque.tag_pictoflora'] = self::buildTagsAST($p['masque'], 'AND', ' ');
 
 
// pas de select, groupby & co ici: uniquement 'join' et 'where'
$or_req = array('join' => array(), 'where' => array());
ListeObservations2::sqlAddConstraint($or_masque, $db, $or_req);
self::sqlAddConstraint($or_masque, $db, $or_req);
444,7 → 451,7
}
}
 
// complete & override ListeObservations2::requestFilterParams()
// complete & override ListeObservations2::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));
454,7 → 461,7
$p['tri'] = ListeObservations2::unsetIfInvalid($params, 'tri', self::$tri_possible);
$p['format'] = ListeObservations2::unsetIfInvalid($params, 'format', self::$format_image_possible);
 
// et pour identiplantes ?
// "milieu" inutile pour IdentiPlantes ?
if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
 
// compatibilité
462,35 → 469,6
$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
}
 
/* Nous n'implod()ons pas ici, car la sémantique des éléments multiples est
variable (tag_explode_semantic) et considérée plus tard, notamment lors de la
construction du SQL (sqlAddConstraint()).
Cette sémantique peut varier selon que ces tags proviennent du formulaire de
recherche avancée ou via le masque général (et l'appel récursif à requestFilterParams()
qu'il implique) */
if(isset($params['masque.tag_cel'])) {
if(isset($params['tag_explode_char'])) {
$x = explode($params['tag_explode_char'], $params['masque.tag_cel']);
$x = array_map('trim', $x);
$p['masque.tag_cel'] = array_filter($x);
} else {
// toujours un tableau
$p['masque.tag_cel'] = array(trim($params['masque.tag_cel']));
}
 
}
 
if(isset($params['masque.tag_pictoflora'])) {
if(isset($params['tag_explode_char'])) {
$x = explode($params['tag_explode_char'], $params['masque.tag_pictoflora']);
$x = array_map('trim', $x);
$p['masque.tag_pictoflora'] = array_filter($x);
} else {
// toujours un tableau
$p['masque.tag_pictoflora'] = array(trim($params['masque.tag_pictoflora']));
}
}
 
if($p['tri'] == 'votes' || $p['tri'] == 'tags') {
// ces critère de tri des image à privilégier ne s'applique qu'à un protocole donné
if(!isset($params['protocole']) || !is_numeric($params['protocole']))
503,23 → 481,95
}
 
 
/* 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 $default_op: "AND" ou "OR"
@param $additional_sep: séparateur de mots:
*/
static function buildTagsAST($str = NULL, $default_op, $additional_sep = ',') {
if(!$str) return;
$words = preg_split('/ (OR|AND|ET|OU) /', $str, -1, PREG_SPLIT_NO_EMPTY);
 
if(preg_match('/\b(ET|AND)\b/', $str)) $op = 'AND';
elseif(preg_match('/\b(OU|OR)\b/', $str)) $op = 'OR';
else $op = $default_op;
 
if($additional_sep) {
array_walk($words,
create_function('&$v, $k, $sep', '$v = preg_split("/".$sep."/", $v, -1, PREG_SPLIT_NO_EMPTY);'),
$additional_sep);
}
$words = self::array_flatten($words);
$words = array_map('trim', $words);
return array($op => array_filter($words));
}
 
 
// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
static function _update_statistics($db) {
$db->requeter("TRUNCATE TABLE del_image_stat");
$db->requeter(<<<EOF
INSERT INTO `del_image_stat` (
SELECT id_image, divo.ce_protocole, divo.moyenne, dit.ctags
FROM `BASECEL`.`cel_images` ci
LEFT JOIN
( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne FROM del_image_vote
GROUP BY ce_image, ce_protocole ) AS divo
ON ci.id_image = divo.ce_image
LEFT JOIN
( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag
GROUP BY ce_image ) AS dit
ON ci.id_image = dit.ce_image )
SELECT id_image, divo.ce_protocole, divo.moyenne, divo.nb_votes, dit.ctags
FROM `tb_cel`.`cel_images` ci
LEFT JOIN
( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes FROM del_image_vote
GROUP BY ce_image, ce_protocole ) AS divo
ON ci.id_image = divo.ce_image
LEFT JOIN
( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag
GROUP BY ce_image ) AS dit
ON ci.id_image = dit.ce_image )
EOF
);
}
}
 
static function revOrderBy($orderby) {
return $orderby == 'asc' ? 'desc' : 'asc';
}
 
static function array_flatten($arr) {
$arr = array_values($arr);
while (list($k,$v)=each($arr)) {
if (is_array($v)) {
array_splice($arr,$k,1,$v);
next($arr);
}
}
return $arr;
}
}