* @author Jean-Pascal MILCENT * @author Aurelien PERONNET * @license GPL v3 * @license CECILL v2 * @copyright 1999-2014 Tela Botanica */ class GestionMotsClesChemin { private $config; private $mode; private $table_liaison; private $table_mots_cles; private $id_mot_cle; private $id_element_lie; //TODO: trigger pour les tables liaisons public function __construct($config, $mode = 'obs') { $this->config = $config; //TODO: switch suivant mode $this->mode = $mode; list($this->table_liaison, $this->table_mots_cles, $this->id_mot_cle, $this->id_element_lie) = self::getTablesMotsClesEtLiaisons($mode); } public function obtenirIdsMotsClesParIdParent($user_id, $id_mot_cle) { $idMotCleP = Cel::db()->proteger($id_mot_cle); $idUtilisateurP = Cel::db()->proteger($user_id); $sousRequete = "SELECT path FROM {$this->table_mots_cles} WHERE $this->id_mot_cle = $idMotCleP "; $requete = "SELECT {$this->id_mot_cle} ". "FROM {$this->table_mots_cles} ". "WHERE path LIKE CONCAT(($sousRequete), '%') ". "AND user_id = $idUtilisateurP ". ' -- '.__FILE__.':'.__LINE__; return Cel::db()->requeter($requete); } /** * Retourne la liste des mots clés pour l'élément lié fourni. Ne tient pas compte de l'utilisateur * car seul le propriétaire d'un élément lié peut y lier des mots clés */ public function obtenirIdsMotsClesParIdElementLie($id_element_lie) { $idElementLieP = Cel::db()->proteger($id_element_lie); $requete = "SELECT {$this->id_mot_cle} FROM {$this->table_liaison} ". "WHERE {$this->id_element_lie} = $idElementLieP ". ' -- '.__FILE__.':'.__LINE__; return Cel::db()->requeter($requete); } public function insererParCheminSiInexistant($mot_cle, $chemin_parent, $user_id) { $mot_cle = self::simplifier($mot_cle); $motCleP = Cel::db()->proteger($mot_cle); $idUtilisateurP = Cel::db()->proteger($user_id); $requete = "SELECT id ". "FROM {$this->table_mots_cles} ". "WHERE name = $motCleP". "AND user_id = $idUtilisateurP ". ' -- '.__FILE__.':'.__LINE__; $infosMotCle = Cel::db()->requeter($requete); if (!empty($infosMotCle) && $infosMotCle !== false) { $idMotCle = $infosMotCle[0]['id']; } else { $idMotCle = $this->insererParChemin($mot_cle, $chemin_parent, $user_id); } return $idMotCle; } public function insererParChemin($mot_cle, $chemin_parent, $user_id) { $mot_cle = self::simplifier($mot_cle); $cheminMotCle = self::getCheminHarmonise($chemin_parent, $mot_cle); $cheminMotCleP = Cel::db()->proteger($cheminMotCle); $idUtilisateurP = Cel::db()->proteger($user_id); $motCleP = Cel::db()->proteger($mot_cle); $requete = "INSERT INTO {$this->table_mots_cles} ". '(path, user_id, name) '. "VALUES ($cheminMotCleP, $idUtilisateurP, $motCleP ) ". ' -- '.__FILE__.':'.__LINE__; $insertion = Cel::db()->executer($requete); $resultat = false; if ($insertion !== false) { $resultat = Cel::db()->obtenirDernierId(); } return $resultat; } public function insererParIdParent($mot_cle, $id_parent, $user_id) { $motCleSimple = self::simplifier($mot_cle); $motCleSimpleP = Cel::db()->proteger(strtolower(self::supprimerAccents($mot_cle))); $idParentP = Cel::db()->proteger($id_parent); $racineP = Cel::db()->proteger('/'); $idUtilisateurP = Cel::db()->proteger($user_id); $motCleP = Cel::db()->proteger($mot_cle); $sousRequete = $racineP; if ($id_parent != '') { $sousRequete = '(SELECT path '. "FROM {$this->table_mots_cles} AS ctp ". "WHERE ctp.id = $idParentP) "; } $requete = "INSERT INTO {$this->table_mots_cles} (path, user_id, name) ". "VALUES (CONCAT($sousRequete, $motCleSimpleP, '/'), $idUtilisateurP, $motCleP ) ". ' -- '.__FILE__.':'.__LINE__; $insertion = Cel::db()->executer($requete); if ($insertion !== false) { $resultat = Cel::db()->obtenirDernierId(); } return $resultat; } public function lierParTableaux($ids_mots_cles, $ids_elements_lies) { $combinaisons = array(); foreach ($ids_mots_cles as $id_mot_cle) { $idMotCleP = Cel::db()->proteger($id_mot_cle); foreach ($ids_elements_lies as $id_element_lie) { $idElementLieP = Cel::db()->proteger($id_element_lie); $combinaisons[] = "($idElementLieP, $idMotCleP)"; } } $valeursGroupees = implode(', ', $combinaisons); $requete = "INSERT INTO {$this->table_liaison} ({$this->id_element_lie}, {$this->id_mot_cle}) ". "VALUES $valeursGroupees ". ' -- '.__FILE__.':'.__LINE__; return Cel::db()->executer($requete); } public function supprimerLiaisonsMotsCles($ids_mots_cles, $ids_elements_lies, $user_id) { $combinaisons = array(); foreach ($ids_mots_cles as $id_mot_cle) { $idMotCleP = Cel::db()->proteger($id_mot_cle); foreach ($ids_elements_lies as $id_element_lie) { $idElementLieP = Cel::db()->proteger($id_element_lie); $combinaisons[] = "({$this->id_element_lie} = $idElementLieP AND {$this->id_mot_cle} = $idMotCleP)"; } } $clauseWhere = implode(' OR ', $combinaisons); $requete = "DELETE FROM {$this->table_liaison} ". "WHERE $clauseWhere ". ' -- '.__FILE__.':'.__LINE__; return Cel::db()->executer($requete); } public function supprimerToutesLiaisonsPourIdsElementsLies($ids_elements_lies) { $idsElementsLiesP = Cel::db()->proteger($ids_elements_lies); $listeIds = implode(',', $idsElementsLiesP); $requete = "DELETE FROM {$this->table_liaison} ". "WHERE {$this->id_element_lie} IN ($listeIds) ". ' -- '.__FILE__.':'.__LINE__; $suppression = Cel::db()->executer($requete); $suppression = ($suppression !== false) ? true : false; return $suppression; } public function supprimerToutesLiaisonsIdsMotsCles($ids_mots_cles, $user_id) { $suppression = true; if (!empty($ids_mots_cles)) { $idsMotsClesP = Cel::db()->proteger($ids_mots_cles); $listeIds = implode(',', $idsMotsClesP); $requete = "DELETE FROM {$this->table_liaison} ". "WHERE {$this->id_mot_cle} IN ($listeIds) ". ' -- '.__FILE__.':'.__LINE__; $suppression = Cel::db()->executer($requete); $suppression = ($suppression !== false) ? true : false; } return $suppression; } /** * Supprime toutes les laisons pour un utilisateur et un mot clé (au sens textuel) donnés. * */ public function supprimerLiaisonPourMotCleEtIdElementLie($mot_cle, $id_element_lie, $user_id) { $mot_cle = self::simplifier($mot_cle); $idElementLieP = Cel::db()->proteger($id_element_lie); $motCleP = Cel::db()->proteger($mot_cle); $idUtilisateurP = Cel::db()->proteger($user_id); $sousRequete = "SELECT id FROM {$this->table_mots_cles} ". "WHERE name = $motCleP ". "AND user_id = $idUtilisateurP "; $requete = "DELETE FROM {$this->table_liaison} ". "WHERE {$this->id_element_lie} = $idElementLieP ". "AND {$this->id_mot_cle} IN ($sousRequete) ". ' -- '.__FILE__.':'.__LINE__; $suppression_liaison = Cel::db()->executer($requete); $suppression_liaison = ($suppression_liaison !== false); return $suppression_liaison; } public function supprimerChemin($chemin, $user_id) { $idUtilisateurP = Cel::db()->proteger($user_id); $cheminP = Cel::db()->proteger($chemin.'%'); // TODO : triggers pour les tables liées ? $requete = "DELETE FROM {$this->$table_mots_cles} ". "WHERE path LIKE $cheminP ". "AND user_id = $idUtilisateurP ". ' -- '.__FILE__.':'.__LINE__; return Cel::db()->executer($requete); } /** * suppression des associations du mots clé aux images ou obs, mais aussi des associations de ses enfants * (car ceux-ci seront supprimés aussi dans le processus) * même s'il n'a pas d'enfants, le tableau contient au moins l'id du mot clé lui même */ public function supprimerMotCleParId($id_mot_cle, $user_id) { //TODO: simplifier cette fonction $ids_mot_cle_et_enfants = $this->obtenirIdsMotsClesParIdParent($user_id, $id_mot_cle); // obtention des ids des éléments liés au mot clé ainsi qu'à ces enfants (afin de pouvoir // régénérer les index texte de mots clés sur les éléments liés) $ids_a_delier = array(); foreach ($ids_mot_cle_et_enfants as $id) { $ids_a_delier[] = $id['id_mot_cle']; } $ids_elements_lies = $this->obtenirIdElementsLiesPourIds($ids_a_delier); $suppression_liaison = $this->supprimerToutesLiaisonsIdsMotsCles($ids_a_delier, $user_id); // suppression du mot clé proprement dit ainsi que de ses enfants $suppression = $this->supprimerMotCleEtEnfantsParId($id_mot_cle, $user_id); return $suppression && $suppression_liaison; } public function supprimerMotCleEtEnfantsParId($id_mot_cle, $user_id) { $idMotCleP = Cel::db()->proteger($id_mot_cle); $requete = 'SELECT path '. "FROM {$this->table_mots_cles} ". "WHERE id = $idMotCleP ". ' -- '.__FILE__.':'.__LINE__; $chemin = Cel::db()->requeter($requete); $suppression = true; // vérification pour empecher la suppression accidentelle de tout l'arbre, // cas qui ne devrait jamais arriver normalement if (!empty($chemin) && $chemin != '/') { $chemin = $chemin[0]['chemin']; $cheminP = Cel::db()->proteger($chemin.'%'); $idUtilisateurP = Cel::db()->proteger($user_id); $requete = "DELETE FROM {$this->table_mots_cles} ". "WHERE path LIKE $cheminP ". "AND user_id = $idUtilisateurP ". ' -- '.__FILE__.':'.__LINE__;; $suppression = Cel::db()->executer($requete); } return ($suppression !== false); } public function obtenirIdsMotClesPourMotsCles($mots_cles, $user_id) { $motsClesP = array(); foreach ($mots_cles as $mot_cle) { $motsClesP[] = Cel::db()->proteger(self::simplifier($mot_cle)); } $listeMotsClesP = implode(',', $motsClesP); $idUtilisateurP = Cel::db()->proteger($user_id); $requete = 'SELECT id as id_mot_cle, name as mot_cle '. "FROM {$this->table_mots_cles} ". "WHERE mot_cle IN ($listeMotsClesP) ". "AND user_id = $idUtilisateurP ". ' -- '.__FILE__.':'.__LINE__; $resultat = Cel::db()->executerRequete($requete); return $resultat; } public function obtenirIdElementsLiesPourIds($ids_mots_cles) { $idsElementsLies = array(); if (!empty($ids_mots_cles)) { $idsMotsClesP = Cel::db()->proteger($ids_mots_cles); $listeIdsMotsCles = implode(',', $idsMotsClesP); $requete = "SELECT {$this->id_element_lie} as id_element_lie ". "FROM {$this->table_liaison} ". "WHERE {$this->id_mot_cle} IN ($listeIdsMotsCles) ". ' -- '.__FILE__.':'.__LINE__; $idsElementsLies = Cel::db()->requeter($requete); } return $idsElementsLies; } /** * * Fonctions statiques utilitaires * (Dans l'idéal toute la classe pourrait être statique car elle n'a * pas d'état (mais il faudrait passer $mode à toutes les fonctions) * */ public static function getTablesMotsClesEtLiaisons($mode) { if ($mode == 'obs') { $table_liaison = 'occurrence_user_occurrence_tag'; $table_mots_cles = 'user_occurrence_tag'; $id_mot_cle = "user_occurrence_tag_id"; $id_element_lie = "occurrence_id"; } else { $table_liaison = 'photo_tag_photo'; $table_mots_cles = 'photo_tag'; $id_mot_cle = "photo_tag_id"; $id_element_lie = "photo_id"; } return array($table_liaison, $table_mots_cles, $id_mot_cle, $id_element_lie); } /** * Renvoie un template de recherche sur les ids de mots clés utilisables avec sprintf. */ public static function obtenirTemplateRequeteMotsClesIds($mode) { list($table_liaison, $table_mots_cles, $id_mot_cle, $id_element_lie) = self::getTablesMotsClesEtLiaisons($mode); $requeteTpl = "SELECT $id_element_lie FROM $table_liaison WHERE $id_mot_cle IN (%s) "; return $requeteTpl; } private static function getNomTablesEtChampsElementsLies($mode) { $tables = array(); if ($mode == 'obs') { $tables = array('occurrence', 'id'); } else { $tables = array('photo', 'id'); } return $tables; } /** * Renvoie un template de requete pour selectionner la concatenation de mots clé * pour un element donné (utilisable avec sprintf) */ public static function obtenirTemplateRequeteMotsClesTexte($mode) { list($table_liaison, $table_mots_cles, $id_mot_cle, $id_element_lie) = self::getTablesMotsClesEtLiaisons($mode); $requeteTpl = 'SELECT GROUP_CONCAT(name) '. "FROM $table_mots_cles AS cm ". "INNER JOIN $table_liaison AS cml ON cml.$id_mot_cle = cm.$id_mot_cle ". "AND cml.$id_element_lie = %s "; return $requeteTpl; } // Méthodes utilitaires /** * La profondeur d'un noeud est déterminée par le nombre de slashs * qu'il contient (étant donné que ceux ci sont interdits dans le texte du mot clé. */ static public function comparerProfNoeuds($a, $b) { $nb_slashs_a = substr_count($a['path'], '/'); $nb_slashs_b = substr_count($a['path'], '/'); $cmp = 0; if ($nb_slashs_a == $nb_slashs_b) { $cmp = strcasecmp($a['path'], $b['path']); } else { $cmp = ($a['path'] > $b['path']) ? +1 : -1; } return $cmp; } static public function getCheminHarmonise($chemin_parent, $mot_cle) { return self::harmoniserChemin($chemin_parent.'/'); } static public function harmoniserChemin($chemin) { $chemin = self::startsWith($chemin,'/') ? $chemin : '/'.$chemin; $chemin = self::endsWith($chemin,'/') ? $chemin : $chemin.'/'; $chemin = str_replace('//', '/', $chemin); // mise en minuscule du chemin afin d'éviter des cas où l'on aurait // des même mots clés avec minuscule et majuscule $chemin = strtolower($chemin); $chemin = self::supprimerAccents($chemin); return $chemin; } static function supprimerAccents($str, $charset='utf-8') { $str = htmlentities($str, ENT_NOQUOTES, $charset); $str = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $str); $str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); // pour les ligatures e.g. 'œ' $str = preg_replace('#&[^;]+;#', '', $str); // supprime les autres caractères return $str; } /** * Fonction de slugification du mot clé * * Ni slashes ou antislashes ou virgules (ce qui fausserait l'arbre ou bien les mots * clés texte dans les tables obs ou image) */ static public function simplifier($text) { $caracteresASupprimer = array('\\','/', ','); $text = str_replace($caracteresASupprimer, '', $text); $text = trim($text); return $text; } /** * Gardée pour compatibilité ancienne version (mais devrait être supprimée * dans le futur */ static function nettoyerMotsClesAvantSuppression($chaine) { $valeur = str_replace('null', '', $chaine); $valeur = trim($valeur, ';;'); return $valeur; } public static function startsWith($haystack, $needle) { return $needle === '' || strpos($haystack, $needle) === 0; } public static function endsWith($haystack, $needle) { return $needle === '' || substr($haystack, -strlen($needle)) === $needle; } /** * Fonction utilisée pour importer les anciens mots clés saisis dans les widget dans un compte identifié * Dans ce cas là, le widget remplit la case user_id par le mail indiqué lors de la saisie * @param string $mail_utilisateur * @param string $user_id */ public static function migrerMotsClesMailVersId($mail_utilisateur, $infos_utilisateur) { return self::migrerLiaisonEtMotsCles($mail_utilisateur, $infos_utilisateur, 'obs') && self::migrerLiaisonEtMotsCles($mail_utilisateur, $infos_utilisateur, 'images'); } /** * ATTENTION : cette fonction suppose que l'utilisateur n'ai pas déjà de mots clés dans le CEL * avec l'identifiant $user_id ce qui est normalement le cas * ça devrait normalement marcher correctement même s'il en a déjà mais ça n'a pas été testé */ private static function migrerLiaisonEtMotsCles($email_utilisateur, $infos_utilisateur, $mode) { list($table_liaisons, $table_mots_cles, $id_mot_cle, $id_element_lie) = self::getTablesMotsClesEtLiaisons($mode); $idUtilisateurP = Cel::db()->proteger($infos_utilisateur['user_id']); $emailUtilisateurP = Cel::db()->proteger($email_utilisateur); $requete_migration_mc = "UPDATE {$table_mots_cles} ". "SET user_id = $idUtilisateurP ". "WHERE user_id = $emailUtilisateurP "; $migration = Cel::db()->executer($requete_migration_mc); $migration = ($migration !== false) ? true : false; return $migration; } }