Subversion Repositories eFlore/Applications.del

Compare Revisions

Ignore whitespace Rev 1583 → Rev 1584

/trunk/services/modules/0.1/images/ListeImages.php
312,83 → 312,83
*
*/
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())
// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
if($p['ordre'] == 'desc') {
// pas de group by nécessaire pour cette jointure
// PRIMARY KEY (`ce_image`, `ce_protocole`)
$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
} else {
$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
// nécessaire (dup ce_image dans del_image_stat)
$req['groupby'][] = 'vdi.id_observation';
}
}
 
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(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
$db->proteger("%".$val."%"));'),
$db);
$req['where'][] = '(' . implode(' AND ', $tags) . ')';
}
else {
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
}
}
 
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);
}
// 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())
// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
if($p['ordre'] == 'desc') {
// pas de group by nécessaire pour cette jointure
// PRIMARY KEY (`ce_image`, `ce_protocole`)
$req['join']['dis'] = sprintf('INNER JOIN del_image_stat dis'.
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
} else {
$req['join']['dis'] = sprintf('LEFT JOIN del_image_stat dis'.
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
// nécessaire (dup ce_image dans del_image_stat)
$req['groupby'][] = 'vdi.id_observation';
}
}
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(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
$db->proteger("%".$val."%"));'),
$db);
$req['where'][] = '(' . implode(' AND ', $tags) . ')';
}
else {
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
}
}
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);
}
}
 
/* approche intéressante si les deux problèmes suivants peuvent être résolu:
515,67 → 515,72
(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
Pour plus d'information: (ListeObservations|DelTk)::sqlAddMasqueConstraint() */
static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
if(!empty($p['masque'])) {
$or_params = array('masque.auteur' => $p['masque'],
'masque.departement' => $p['masque'],
'masque.commune' => $p['masque'], // TODO/XXX ?
'masque.id_zone_geo' => $p['masque'],
if(!empty($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']);
 
/* 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']);
 
/* 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']));
}
}
$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']));
}
}
}
 
 
613,31 → 618,32
 
// 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));
}
 
$p = array();
$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
 
// "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);'));
if($parametres_autorises) { // filtrage de toute clef inconnue
$params = array_intersect_key($params, array_flip($parametres_autorises));
}
$p = array();
$p['tri'] = DelTk::unsetIfInvalid($params, 'tri', self::$tri_possible);
$p['format'] = DelTk::unsetIfInvalid($params, 'format', self::$format_image_possible);
// "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);'));
}
 
 
/trunk/services/modules/0.1/observations/Observation.php
225,20 → 225,30
$proto_fields = DelTk::sqlFieldsToAlias(self::$mappings['protocoles'], $select['protocole'], 'p');
$where = array();
$idsImages = array_values(array_map(create_function('$a', 'return $a["id_image"];'), $images));
$where[] = sprintf('v.ce_image IN (%s)',
implode(',', array_values(array_map(create_function('$a', 'return $a["id_image"];'), $images))));
implode(',', $idsImages));
 
if ($protocole) {
$where[] = "v.ce_protocole = $protocole";
}
 
return $db->recupererTous(sprintf(
// pour une fois, on conserve l'ordre en ne luttant pas contre les IDs reçus
$ordreDesIdsRecus = sprintf('FIELD(v.ce_image, %s)', implode(',', $idsImages));
$req = sprintf(
'SELECT %s, %s FROM del_image_vote AS v'.
' INNER JOIN del_image_protocole p ON v.ce_protocole = p.id_protocole'.
' WHERE %s -- %s',
$vote_fields, $proto_fields,
$where ? implode(' AND ', $where) : 1,
__FILE__ . ':' . __LINE__));
' WHERE %s'.
' ORDER BY %s -- %s',
$vote_fields,
$proto_fields,
($where ? implode(' AND ', $where) : 1),
$ordreDesIdsRecus,
__FILE__ . ':' . __LINE__);
//echo "REQUETE: $req"; exit;
 
return $db->recupererTous($req);
}
/**