* @author Jean-Pascal MILCENT * @author Aurelien PERONNET * @license GPL v3 * @license CECILL v2 * @copyright 1999-2014 Tela Botanica */ class Sql { const APPLI_IMG = 'IMG'; const APPLI_OBS = 'OBS'; private $conteneur; private $bdd; private $parametres = array(); private $appli; private $requete = array( 'join' => array(), 'where' => array(), 'groupby' => array(), 'orderby' => array()); private $champsPrenom = array('prenom_utilisateur'); private $champsNom = array('nom_utilisateur'); private $champsSousRequeteObs = array('masque.genre', 'masque.famille', 'masque.ns', 'masque.commune', 'masque.milieu', 'masque.pays'); public function __construct(Conteneur $conteneur) { $this->conteneur = $conteneur; $this->bdd = $this->conteneur->getBdd(); } public function setParametres(Array $parametres) { $this->parametres = $parametres; } public function setAppli($appliType) { if ($appliType == 'IMG' || $appliType == 'OBS') { $this->appli = $appliType; } else { throw new Exception("Les types d'appli disponible sont : IMG (pour PictoFlora) et OBS (pour IdentiPlante)"); } } private function getPrefixe() { return $this->appli === 'IMG' ? 'di' : 'do'; } private function etreAppliImg() { return $this->appli === 'IMG'; } private function etreAppliObs() { return $this->appli === 'OBS'; } public function getRequeteSql() { return $this->requete; } private function addJoin($join) { if (!isset($this->requete['join'][$join])) { $this->requete['join'][$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_'])) { if ($this->etreAppliImg() && in_array($idParam, $this->champsSousRequeteObs)) { $this->requete['where']['OR_SOUS_REQUETE'][] = $where; } else { $this->requete['where']['OR'][] = $where; } } else { $this->requete['where']['AND'][] = $where; } } public function getWhere() { // Sous-requete spéciale pour éviter de rechercher dans la table obs jointe à img depuis Pictoflora... if (isset($this->requete['where']['OR_SOUS_REQUETE']) && count($this->requete['where']['OR_SOUS_REQUETE']) > 0) { $clauseWhereSousRequete = implode(' OR ', $this->requete['where']['OR_SOUS_REQUETE']); $sousRequete = 'di.ce_observation IN '. "(SELECT id_observation FROM del_observation AS do WHERE $clauseWhereSousRequete ) "; $this->requete['where']['OR'][] = "( $sousRequete )"; unset($this->requete['join'][$this->getSqlJointureObs()]); } 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'].' '; } /** * @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->ajouterContraintePays(); $this->ajouterContrainteDepartement(); $this->ajouterContrainteIdZoneGeo(); $this->ajouterContrainteGenre(); $this->ajouterContrainteFamille(); $this->ajouterContrainteNs(); $this->ajouterContrainteNn(); $this->ajouterContrainteReferentiel(); $this->ajouterContrainteCommune(); $this->ajouterContraintePnInscrits(); } private function ajouterContrainteAuteur() { if (isset($this->parametres['masque.auteur'])) { $auteur = $this->parametres['masque.auteur']; // id du poster de l'obs $prefixe = $this->getPrefixe(); if (is_numeric($auteur)) { $this->ajouterContrainteAuteurId(); } elseif(preg_match('/@[a-z0-9-]+(?:\.[a-z0-9-]+)*\.[a-z]{2,}$/i', $auteur)) { $this->ajouterContrainteAuteurEmail(); } else { $this->ajouterContrainteAuteurIntitule(); } } } private function ajouterContrainteAuteurId() { $id = $this->parametres['masque.auteur']; $prefixe = $this->getPrefixe(); $sqlTpl = "($prefixe.ce_utilisateur = %1\$d)"; $whereAuteur = sprintf($sqlTpl, $id); $this->addWhere('masque.auteur', $whereAuteur); } private function ajouterContrainteAuteurEmail() { $email = $this->parametres['masque.auteur']; $prefixe = $this->getPrefixe(); $sqlTpl = "($prefixe.courriel_utilisateur 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' */ 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'; $prefixe = $this->getPrefixe(); $champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom, $prefixe); $champsNomSql = self::ajouterIfNullPourConcat($this->champsNom, $prefixe); $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)'; $prefixe = $this->getPrefixe(); $champsPrenomSql = self::ajouterIfNullPourConcat($this->champsPrenom, $prefixe); $champsNomSql = self::ajouterIfNullPourConcat($this->champsNom, $prefixe); $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, $prefixe) { $champsProteges = array(); foreach ($champs as $champ) { if (strstr($champ, '.') === false) { $champ = "$prefixe.$champ"; } $champsProteges[] = "IFNULL($champ, '')"; } return implode(',', $champsProteges); } private function ajouterContrainteDate() { if (isset($this->parametres['masque.date'])) { $date = $this->parametres['masque.date']; if (preg_match('/^\d{4}$/', $date) && $date < 2030 && $date > 1600) { $sqlTpl = "YEAR(do.date_observation) = %d"; $dateWhere = sprintf($sqlTpl, $date); $this->addWhere('masque.date', $dateWhere); } else { $sqlTpl = "do.date_observation = %s"; $dateP = $this->bdd->proteger($date); $dateWhere = sprintf($sqlTpl, $dateP); $this->addWhere('masque.date', $dateWhere); } $this->ajouterJoinObsSiNecessaire(); } } 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', "do.ce_zone_geo LIKE $deptMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContraintePays() { if (isset($this->parametres['masque.pays'])) { // Attention le standard contient parfois FX pour la france métropolitaine // Dans ce cas particulier on cherche donc FR et FX $this->parametres['masque.pays'] = strtoupper($this->parametres['masque.pays']); if(strpos($this->parametres['masque.pays'], 'FR') !== false) { $this->parametres['masque.pays'] = str_replace('FR', 'FR,FX', $this->parametres['masque.pays']); } $pays = explode(',', $this->parametres['masque.pays']); $pays = implode(',', $this->bdd->proteger($pays)); $this->addWhere('masque.pays', "do.pays IN ($pays)"); $this->ajouterJoinObsSiNecessaire(); } } 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', "do.ce_zone_geo = $idZgMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteGenre() { if (isset($this->parametres['masque.genre'])) { $genre = $this->parametres['masque.genre']; $genreMotif = $this->bdd->proteger("$genre%"); $this->addWhere('masque.genre', "do.nom_sel LIKE $genreMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteFamille() { if (isset($this->parametres['masque.famille'])) { $familleMotif = $this->bdd->proteger($this->parametres['masque.famille']); $this->addWhere('masque.famille', "do.famille = $familleMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteNs() { if (isset($this->parametres['masque.ns'])) { $ns = $this->parametres['masque.ns']; $nsMotif = $this->bdd->proteger("$ns%"); $this->addWhere('masque.ns', "do.nom_sel LIKE $nsMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteNn() { if (isset($this->parametres['masque.nn'])) { $sqlTpl = '(do.nom_sel_nn = %1$d OR do.nom_ret_nn = %1$d)'; $nnWhere = sprintf($sqlTpl, $this->parametres['masque.nn']); $this->addWhere('masque.nn', $nnWhere); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteReferentiel() { if (isset($this->parametres['masque.referentiel'])) { $ref = $this->parametres['masque.referentiel']; $refMotif = $this->bdd->proteger("$ref%"); $this->addWhere('masque.referentiel', "do.nom_referentiel LIKE $refMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteCommune() { if (isset($this->parametres['masque.commune'])) { $commune = $this->parametres['masque.commune']; $communeMotif = $this->bdd->proteger("$commune%"); $this->addWhere('masque.commune', "do.zone_geo LIKE $communeMotif"); $this->ajouterJoinObsSiNecessaire(); } } /** * Si masque.pninscritsseulement vaut true, les observations ayant un tag * "plantnet" mais dont l'auteur n'est pas inscrit à TB seront éliminées * (décision FlorisTic 2016-09) */ protected function ajouterContraintePnInscrits() { if (isset($this->parametres['masque.pninscritsseulement'])) { // avec la classe ParametresFiltrage, on ne passe là que si le masque vaut 1 $motifMotClePlantnet = "'%plantnet%'"; $this->addWhere('masque.pninscritsseulement', "((do.mots_cles_texte NOT LIKE $motifMotClePlantnet OR do.mots_cles_texte IS NULL) OR do.ce_utilisateur != 0)"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterJoinObsSiNecessaire() { if ($this->etreAppliImg()) { $this->addJoin($this->getSqlJointureObs()); } } private function getSqlJointureObs() { $typeJointure = !empty($this->parametres['masque']) ? 'LEFT' : 'INNER'; return $typeJointure.' JOIN del_observation AS do ON (di.ce_observation = do.id_observation) '; } // la constrainte de nostre bon roy public function ajouterConstrainteAppliObs() { $this->ajouterContrainteTagCel(); $this->ajouterContrainteType(); // TODO : ATTENTION -> vu que l'on utilise une vue basée sur les images, nous devons grouper par obs $this->addGroupBy('do.id_observation'); } private function ajouterContrainteType() { // Les contraintes régissant les onglets sont issus de la réunion dont le compte rendu // disponible ici : http://tela-botanica.net/intranet/wakka.php?wiki=Octobre2014 // Ce lien est à modifier pour pointer vers toute nouvelle réunion modifiant ce fonctionnement if (isset($this->parametres['masque.type'])) { if (array_key_exists('adeterminer', $this->parametres['masque.type'])) { // A DETERMINER : toutes les observations qui ont 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', '('. 'do.certitude = "aDeterminer" '. 'OR do.certitude = "douteux" '. 'OR do.mots_cles_texte LIKE "%aDeterminer%" '. 'OR do.nom_sel_nn IS NULL '. 'OR do.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'])) { // VALIDEES : toutes les observations ayant un commentaire doté de proposition_retenue = 1 // ou bien possédant une proposition initiale avec un nom valide ayant totalisé un score d'au moins 4 // (ce qui correspond à au moins deux votes positifs dans la plupart des cas, dont un identifié) $sous_requete_score_prop_votees = $this->getSousRequeteSommeVotesPropositions(); $this->addJoin('INNER JOIN del_commentaire AS dc '. 'ON ( '. 'do.id_observation = dc.ce_observation '. 'AND ( '. 'dc.proposition_retenue = 1 OR '. '( '. 'dc.proposition_initiale = 1 '. 'AND dc.nom_sel_nn != 0 '. 'AND dc.nom_sel_nn IS NOT NULL '. ' AND dc.id_commentaire IN ('.$sous_requete_score_prop_votees.' >= 4) '. ') '. ') '. ')' ); } if(array_key_exists('aconfirmer', $this->parametres['masque.type'])) { // A CONFIRMER : toutes les observations moins les validées et à confirmer // i.e. : des observations avec un nom valide, qui ne sont pas à déterminer // (ni certitude "aDeterminer" ou "douteuse", ni mot clé), // ne possédant pas de proposition officiellement retenue // et ayant une proposition initiale totalisant un score de moins de 4 $sous_requete_score_prop_votees = $this->getSousRequeteSommeVotesPropositions(); $this->addWhere('masque.type', '('. 'do.id_observation IN ('. 'SELECT dc.ce_observation FROM del_commentaire dc WHERE dc.proposition_retenue = 0'. ' AND ( '. 'dc.proposition_initiale = 1 '. 'AND dc.nom_sel_nn != 0 '. 'AND dc.nom_sel_nn IS NOT NULL '. 'AND dc.id_commentaire IN ('.$sous_requete_score_prop_votees.' < 4) '. ') '. ') AND do.id_observation NOT IN ('. 'SELECT dc.ce_observation FROM del_commentaire dc '. 'WHERE '. ' dc.proposition_retenue = 1'. ') '. 'AND do.certitude != "douteux" AND do.certitude != "aDeterminer" '. 'AND do.mots_cles_texte NOT LIKE "%aDeterminer%" '. 'AND do.nom_sel_nn != 0 '. 'AND do.nom_sel_nn IS NOT NULL'. ') ' ); } $this->ajouterJoinObsSiNecessaire(); } } private function getSousRequeteSommeVotesPropositions() { // ATTENTION : un vote identifié compte 3 votes anonymes (dans les deux sens) return 'SELECT ce_proposition '. 'FROM del_commentaire_vote dcv '. 'GROUP BY ce_proposition HAVING '. 'SUM(CASE '. ' WHEN valeur = 1 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' != 0 THEN 3 '. ' WHEN valeur = 0 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' != 0 THEN -3 '. ' WHEN valeur = 1 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' = 0 THEN 1 '. ' WHEN valeur = 0 AND dcv.ce_utilisateur REGEXP \'^-?[0-9]+$\' = 0 THEN -1 '. 'END '. ') '; } public function ajouterConstrainteAppliImg() { $this->ajouterContrainteMilieu(); $this->ajouterContrainteTri(); $this->ajouterContrainteTagCel(); $this->ajouterContrainteTagDel(); $this->ajouterContraintePnInscrits(); } private function ajouterContrainteMilieu() { if (isset($this->parametres['masque.milieu'])) { $milieu = $this->parametres['masque.milieu']; $milieuMotif = $this->bdd->proteger("%$milieu%"); $this->addWhere('masque.milieu', "do.milieu LIKE $milieuMotif"); $this->ajouterJoinObsSiNecessaire(); } } private function ajouterContrainteTri() { if (isset($this->parametres['tri'])) { $tri = $this->parametres['tri']; if (isset($this->parametres['protocole']) && ($tri == 'moyenne-arithmetique' || $tri == 'points')) { // $this->parametres['protocole'] *est* défini (cf Outils::filtrerUrlsParams...()) $sqlTpl = 'LEFT JOIN del_image_stat AS dis ON di.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 AS dis ON di.id_image = dis.ce_image"); } } } private function ajouterContrainteTagCel() { if (isset($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%"); if ($this->etreAppliImg()) { $sousRequete = 'SELECT id_observation '. 'FROM del_observation '. "WHERE mots_cles_texte LIKE $tagMotif "; $sql = " (di.mots_cles_texte LIKE $tagMotif OR di.id_image IN ($sousRequete) ) "; } else { // WARNING : la sous-requête est la meilleure solution trouvée pour contrer le fonctionnement // étrange de l'optimiseur de MYSQL 5.6 (à retester avec Mysql 5.7 et suivant). $sousRequete = 'SELECT DISTINCT ce_observation '. 'FROM del_image '. "WHERE mots_cles_texte LIKE $tagMotif ". 'AND ce_observation IS NOT NULL'; $sql = " (do.mots_cles_texte LIKE $tagMotif OR do.id_observation IN ($sousRequete)) "; } $clausesWhere[] = $sql; } $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']; $tagMotif = $this->bdd->proteger(implode('|', $tags)); $sqlTpl = "CONCAT(IFNULL(do.mots_cles_texte,''),IFNULL(di.mots_cles_texte,'')) REGEXP %s"; $tagSql = sprintf($sqlTpl, $tagMotif); $this->addWhere('masque.tag_cel', $tagSql); if ($this->etreAppliObs()) { $this->addJoin('LEFT JOIN del_image AS di ON (di.ce_observation = do.id_observation) '); } } $this->ajouterJoinObsSiNecessaire(); } } /** * Plusieurs solutions sont disponibles dans les anciennes versions (voir DelTk et l'historique SVN de ce fichier). */ private function ajouterContrainteTagDel() { if (isset($this->parametres['masque.tag_del'])) { $nbTags = $this->getNombreDeTags(); if($nbTags > 1) { // sous-requêtes car le GROUP BY avec GROUP_CONCAT est *trop* lent if (isset($this->parametres['masque.tag_del']['AND'])) { foreach ($this->parametres['masque.tag_del']['AND'] as $mc) { $sousRequete = 'SELECT ce_image '. 'FROM del_image_tag '. 'WHERE actif = 1 '. "AND tag_normalise LIKE '%$mc%' "; $this->addWhere('masque.tag_del', "di.id_image IN ($sousRequete)"); } } else if (isset($this->parametres['masque.tag_del']['OR'])) { $tagsMotif = "'(" . implode('|', $this->parametres['masque.tag_del']['OR']) . ")'"; $sousRequete = 'SELECT ce_image '. 'FROM del_image_tag '. 'WHERE actif = 1 '. "AND tag_normalise REGEXP $tagsMotif "; $this->addWhere('masque.tag_del', "di.id_image IN ($sousRequete)"); } } else { // Si un seul tag est demandé il se trouve dans le OR dans le cas de la recherche // spécifique et dans le AND dans le cas de la recherche générale // WTF? $tag = ""; if(isset($this->parametres['masque.tag_del']['OR'][0])) { $tag = $this->parametres['masque.tag_del']['OR'][0]; } else if(isset($this->parametres['masque.tag_del']['AND'][0])) { $tag = $this->parametres['masque.tag_del']['AND'][0]; } $sousRequete = 'SELECT ce_image '. 'FROM del_image_tag '. 'WHERE actif = 1 '. 'AND tag_normalise LIKE '.$this->bdd->proteger($tag.'%'); $this->addWhere('masque.tag_del', "di.id_image IN ($sousRequete)"); } } } private function getNombreDeTags() { $somme = 0; if (isset($this->parametres['masque.tag_del']['AND'])) { $somme = count($this->parametres['masque.tag_del']['AND']); } else { $somme = count($this->parametres['masque.tag_del']['OR']); } return $somme; } /** * Partie spécifique à PictoFlora: * Attention : si le critère de tri n'est pas suffisant, les résultats affichés peuvent varier à chaque appel * de la même page de résultat de PictoFlora. */ public function definirOrdreSqlAppliImg() { $ordre = $this->parametres['ordre']; $tri = isset($this->parametres['tri']) ? $this->parametres['tri'] : ''; switch ($tri) { case 'moyenne-arithmetique' : $this->addOrderBy("dis.moyenne $ordre, dis.nb_votes $ordre, id_image $ordre"); break; case 'points' : $this->addOrderBy("dis.nb_points $ordre, dis.moyenne $ordre, dis.nb_votes $ordre, id_image $ordre"); break; case 'tags' : $this->addOrderBy("dis.nb_tags $ordre, id_image $ordre"); break; case 'date_observation' : $this->addOrderBy("date_observation $ordre, ce_observation $ordre"); break; case 'date_transmission' : default: $this->addOrderBy("di.date_transmission $ordre, ce_observation $ordre"); } } public function definirOrdreSqlAppliObs() { $ordre = $this->parametres['ordre']; // parmi self::$tri_possible $tri = isset($this->parametres['tri']) ? $this->parametres['tri'] : ''; switch ($tri) { case 'date_observation' : $this->addOrderBy("date_observation $ordre, id_observation $ordre"); break; case 'nb_commentaires' : $sql_nb_comms = '(SELECT COUNT(id_commentaire) FROM del_commentaire AS dc WHERE ce_observation = id_observation)'; $this->addOrderBy("$sql_nb_comms $ordre, id_observation $ordre"); break; case 'date_transmission' : default: $this->addOrderBy("do.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)); } 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. */ // TODO : supprimer cette "subtilité" source d'erreurs 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; } } public function getTotalLignesTrouvees() { $resultat = $this->bdd->recuperer('SELECT FOUND_ROWS() AS nbre -- '.__FILE__.':'.__LINE__); return intval($resultat['nbre']); } public function getRequeteIdObsMonactiviteTout($id_utilisateur, $limite = "") { /* Une action c'est : - Quelqu'un commente mon observation - Quelqu'un fait une proposition sur mon observation - Quelqu'un vote pour ma proposition - Quelqu'un commente ma proposition ou mon commentaire - Quelqu'un vote pour une proposition sur mon observation - Quelqu'un commente une proposition ou un commentaire sur mon observation */ // on selectionne aussi la combinaison des champs de date afin que la liste soit triée correctement // pas d'autre meilleure solution mais attention ce comportement dépend entièrement de mysql $requete = "SELECT SQL_CALC_FOUND_ROWS DISTINCT id_observation, ".$this->getCombinaisonChampsDateMax()." as date_max FROM del_observation do ". $this->getJointureMonActivite($id_utilisateur). $this->getConditionMonActivite($id_utilisateur). "ORDER BY date_max DESC ". $limite; return $requete; } public function getRequeteNbEvenementsDepuisDate($id_utilisateur, $date) { $requete = "SELECT COUNT(DISTINCT id_observation) as nb_evenements FROM del_observation do ". $this->getJointureMonActivite($id_utilisateur). $this->getConditionMonActivite($id_utilisateur). "AND ".$this->getCombinaisonChampsDateMax()." > '".$date."' ". "ORDER BY ".$this->getCombinaisonChampsDateMax()." DESC "; return $requete; } public function getEvenementsObs($idsObsConcat, $id_utilisateur) { $sous_champ_date_max = $this->getCombinaisonChampsDateMax()." as date_max"; $sous_champ_date = "dc.date as date_com, dc.nom_sel as nom_sel_com, dcpr.ce_commentaire_parent as parent_com, dcv.date as date_vote, ". "do.date_observation as date_obs, dcp.date_validation as date_validation, dcpr.date as date_com_reponse, ". "dcvp.nom_sel as nom_sel_com_parent"; $sous_champs_utilisateurs = "dc.ce_utilisateur as utilisateur_commentaire, dcp.ce_utilisateur as utilisateur_commentaire_valide, ". "dcv.ce_utilisateur as utilisateur_vote_commentaire, do.ce_utilisateur as utilisateur_observation, ". "dcp.ce_validateur as utilisateur_validation, dcvp.ce_utilisateur as utilisateur_commentaire_vote, ". "dcpr.ce_utilisateur as utilisateur_commentaire_reponse"; $sous_champs_infos = "dc.nom_sel as proposition_commentaire_nom_sel, dc.texte as proposition_commentaire_texte, dcp.nom_sel as proposition_validee_nom_sel, ". "dcvp.nom_sel as proposition_commentaire_nom_sel_votee, dcpr.texte as proposition_commentaire_texte_commente"; $requete = "SELECT DISTINCT id_observation, ".$sous_champs_utilisateurs.", ".$sous_champ_date_max.", ".$sous_champ_date.", ".$sous_champs_infos." ". "FROM del_observation do ". $this->getJointureMonActivite($id_utilisateur). $this->getConditionMonActivite($id_utilisateur). "AND id_observation IN ($idsObsConcat) ORDER BY date_max DESC"; $evenements = $this->bdd->recupererTous($requete); return $evenements; } public function getJointureMonActivite($id_utilisateur) { return // quelqu'un commente mon observation ou fait une proposition "LEFT JOIN del_commentaire dc ON do.id_observation = dc.ce_observation ". " AND do.ce_utilisateur = ".$id_utilisateur." ". " AND dc.ce_utilisateur != ".$id_utilisateur." ". // quelqu'un valide ma proposition (et ce n'est pas moi qui l'ai validée) "LEFT JOIN del_commentaire dcp ON do.id_observation = dcp.ce_observation ". " AND dcp.nom_sel IS NOT NULL AND dcp.ce_validateur != ".$id_utilisateur." ". " AND dcp.ce_validateur != 0 ". " AND dcp.date_validation IS NOT NULL ". " AND dcp.ce_utilisateur = ".$id_utilisateur." ". // quelqu'un vote pour ma proposition ou sur une proposition sur une de mes observations "LEFT JOIN del_commentaire dcvp ON do.id_observation = dcvp.ce_observation ". "LEFT JOIN del_commentaire_vote dcv ON dcv.ce_proposition = dcvp.id_commentaire ". "AND (dcvp.ce_utilisateur = $id_utilisateur OR do.ce_utilisateur = $id_utilisateur) ". "AND dcv.ce_utilisateur != $id_utilisateur ". "AND dcv.ce_utilisateur != dcvp.ce_utilisateur ". // Quelqu'un répond à l'un de mes commentaires ou commente une de mes propositions "LEFT JOIN del_commentaire dcpr ON do.id_observation = dcpr.ce_observation ". "AND dcpr.ce_commentaire_parent = dcvp.id_commentaire AND dcvp.ce_utilisateur = $id_utilisateur "; } public function getConditionMonActivite($id_utilisateur, $type = "autres") { //TODO: gérer les cas suivants : // demander les activités des autres sur mes obs ou propositions (c'est dejà le cas) // demander mes activités // demander toutes les activités (combinaisons des deux cas ci dessus) return // Vérification que l'évènement me concerne (de près ou ou de loin) "WHERE (do.ce_utilisateur = $id_utilisateur OR dc.ce_utilisateur = $id_utilisateur ". "OR dcp.ce_utilisateur = $id_utilisateur OR dcv.ce_utilisateur = $id_utilisateur ". "OR dcvp.ce_utilisateur = $id_utilisateur) AND ". // mais qu'il y a au moins eu une action de la part d'une autre personne "(dc.ce_utilisateur IS NOT NULL OR dcp.ce_utilisateur IS NOT NULL OR dcv.ce_utilisateur IS NOT NULL) "; } private function getCombinaisonChampsDateMax() { return "GREATEST(IFNULL(dc.date,0), IFNULL(dcv.date,0), IFNULL(do.date_observation,0), IFNULL(dcp.date_validation,0), IFNULL(dcpr.date,0))"; } }