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; |
} |
} |
} |