Subversion Repositories eFlore/Projets.eflore-projets

Rev

Rev 847 | Blame | Last modification | View Log | RSS feed

<?php
/**
* Classe Commun.php est une classe abstraite qui contient les méthodes de base communes à tous les
* sous-services des projets.
*
* Encodage en entrée : utf8
* Encodage en sortie : utf8
* @package eflore-projets
* @author Jennifer DHÉ <jennifer.dhe@tela-botanica.org>
* @author Delphine CAUQUIL <delphine@tela-botanica.org>
* @author Jean-Pascal MILCENT <jpm@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>
* @version 1.0
* @copyright 1999-2011 Tela Botanica (accueil@tela-botanica.org)
*/

abstract class Commun {

        /** Objet Bdd. */
        private $Bdd = null;
        /** Objet Rest Client. */
        private $RestClient = null;

        /** Contients les paramètres. Doit remplacer table_param. */
        protected $parametres = array();
        /** Contients les ressources. Doit remplacer table_ressources. */
        protected $ressources = array();
        /** Le nom du service courrant. */
        protected $serviceNom = null;

        //Classe commune à tous les services web d'un projet. Contient par exemple les fonctions permettant de
        //renvoyer la réponse http...
        protected $entete_http; // Entete de la réponse correspondant au code de réponse de la requete http */
        protected $corps_http; // Tableau de résultat à retourner au format json ou la description de l'erreur si elle existe */
        protected $service; // Nom du service appelé
        /** Stocke la version du projet demandée dans la requete
         *  - "*" : (/#projet/* /meta-donnees) Renvoi les meta-données de toutes les versions du projet
         *  - "numero de la version" : (/#projet/2.00/meta-donnees) Renvoi les meta-données de la version 2.00 du projet */
        protected $version_projet = '+';
        protected $table_version; //Stocke les noms des tables de toutes les versions du projet disponibles
        /** tableau contenant tous les champs d'une table (est rempli par la fonction Commun::recupererNomChamp($table)) */
        protected $champs_table = array();
        private static $tri_multi_dimension = array();
        private static $tri_type = '';

        public function __construct($config = null) {
                $this->config = is_null($config) ? Config::get($this->serviceNom) : $config;
        }

        public function consulter($ressources, $parametres) {
                $this->ressources = $ressources;
                $this->parametres = $parametres;
                $this->chargerNomDuService();

                $this->traiterParametres();
                $this->traiterVersionProjet();

                $resultats = '';
                foreach ($this->table_version as $version) {
                        $this->table = $version; //on stocke le nom de la table correspondant à la version du projet en cours
                        $this->recupererNomChamp($this->table); //on récupère les noms des champs disponibles (Ds Commun.php)
                        $this->traiterRessources(); //dans CommunNomsTaxons.php
                        $requete = $this->assemblerLaRequete();// dans Noms ou Taxons...
                        $resultat = $this->getBdd()->recupererTous($requete . ' -- ' . __FILE__ . ':' . __LINE__ . ' (' .$this->table . ')');
                        $versionResultat = $this->traiterResultat($resultat, $version, $requete);
                        if (count($this->table_version) > 1) {
                                $resultats[$version] = $versionResultat;
                        } else {
                                $resultats = $versionResultat;
                        }
                }

                return $resultats;
        }

        private function chargerNomDuService() {
                $this->serviceNom = get_class($this);
        }

        public function traiterResultat($resultat, $version) {
                $versionResultat = null;
                if ($resultat == '') {
                        //cas ou la requete comporte des erreurs
                        $message = 'La requête SQL formée comporte une erreur!';
                        $code = RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE;
                        throw new Exception($message, $code);
                } elseif ($resultat) {
                        $versionResultat = $this->retournerResultatFormate($resultat, $version);
                } else {
                        $message = 'Les données recherchées sont introuvables.';
                        $code = RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE;
                        throw new Exception($message, $code);
                }
                return $versionResultat;
        }

        //+------------------------------------------------------------------------------------------------------+
        // Méthodes concernant les paramètres
        /**
         *  Permet de récupérer une liste des noms des champs de la table passée en paramètre
         *  @param $table : Nom de la table dont on souhaite connaitre les champs
         */
        public function recupererNomChamp($table) {
                $requete = 'SHOW FIELDS FROM '.$table;
                $resultat = $this->getBdd()->recupererTous($requete);
                if ($resultat == '') {
                        $e = 'La requête SQL formée comporte une erreur!';
                        $this->renvoyerErreur(RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE, $e);
                } elseif ($resultat) {
                        foreach ($resultat as $info) {
                                $this->champs_table[] = $info['Field'];
                        }
                } else {
                        $m = "La table recherchée n'existe pas";
                        $this->renvoyerErreur(RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE, $m);
                }
        }

        /**
         * Fonction permettant de creer la table dont le nom est passé en paramètre (champs_api, champs_bdtfx,
         * correspondance_champs...). Les données de chaque table sont présentes dans le fichier de configuration config.ini
         * @param String $table : Peut contenir plusieurs nom de table dont on souhaite récupérer les données : table,table,table.
         *      Ex : recupererTableConfig('champs_api,champs_bdtfx')
         */
        public function recupererTableConfig($table) {
                $tables = explode(',', $table);
                foreach ($tables as $tab) {
                        $tableau = explode(',', Config::get($tab));
                        $tableau = array_map('trim', $tableau);
                        foreach ($tableau as $champ) {
                                list($code, $rang) = explode('=', $champ);
                                $tab_tampon[$code] = $rang;
                        }
                        $this->$tab = $tab_tampon;
                        $tab_tampon = array();
                }
        }

        public function renvoyerErreur($entete, $message) {
                throw new Exception($message, $entete);
        }

        /**
         * Permet de remplir la variable version_projet et de retirer cette donnée du tableau des ressources
         * @param $ressources
         */
        public function traiterVersionProjet() {
                if (isset($this->parametres['version.projet'])) {
                        if (preg_match('/^[0-9]+(?:[._][0-9]+|)$/', $this->parametres['version.projet'])) {
                                $this->version_projet = $this->parametres['version.projet'];
                                $this->version_projet = 'v'.str_replace('.', '_', $this->version_projet);
                        } else  {
                                $this->version_projet = $this->parametres['version.projet'];
                        }
                }
                //si la liste des noms est demandée pr toutes les versions, on affiche seulement la dernière version :
                if ($this->version_projet == '*' && $this->ressources == array()) {
                        $message = "L'affichage de plusieurs versions ne fonctionne que pour les ressources de type /ressources/#id";
                        $code = RestServeur::HTTP_CODE_MAUVAISE_REQUETE;
                        throw new Exception($message, $code);
                }
                //on recupère les versions du projet disponible dans la table des meta-donnees (utilisation service MetaDonnees)
                $table_num_version = $this->recupererVersionDisponible();
                //on recupere la liste des noms des tables de la bdd correspondant aux differentes versions du projet en fct de la ou les versions demandées
                $this->recupererListeNomTablePrChaqueVersion($table_num_version);
        }

        /**
         * Recupération des versions disponibles par appel du service metaDonnees
         * Verification de l'existance du service recherché dans la requete (si précisé : hors *)
         * @return array  : tableau contenant le numéro de chaque version disponible
         */
        public function recupererVersionDisponible() {
                $versions_dispo = '';
                $req_version = 'SELECT version FROM '.Config::get('bdd_table_meta');
                $res_version = $this->getBdd()->recupererTous($req_version);
                if ($res_version == '') { //cas ou la requete comporte des erreurs
                        $e = "La requête SQL de versionnage formée comporte une erreur : $req_version";
                        $this->renvoyerErreur(RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE, $e);
                } elseif ($res_version) {
                        foreach ($res_version as $version) {
                                $versions_dispo[] = $version['version'];
                        }
                } else {
                        $m = 'Versions introuvables dans la table des méta-données';
                        $this->renvoyerErreur(RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE, $m);
                }
                return $versions_dispo;
        }

        public function recupererListeNomTablePrChaqueVersion($table_num_version) {
                switch ($this->serviceNom) {
                        case 'Ontologies' :
                                $prefixe_table = 'bdd_table_ontologies';
                                break;
                        default:
                                $prefixe_table = 'bdd_table';
                }
                switch ($this->version_projet) {
                        case '+' :
                                $derniere_version = $table_num_version[count($table_num_version) - 1];
                                $this->table_version[] = Config::get($prefixe_table).'_v'.str_replace('.', '_', $derniere_version);
                                break;
                        case '*' :
                                foreach ($table_num_version as $num_version) {
                                        $this->table_version[] = Config::get($prefixe_table).'_v'.str_replace('.', '_', $num_version);
                                }
                                break;
                        default  :
                                $this->table_version[] = Config::get($prefixe_table).'_'.$this->version_projet;
                                break;
                }
        }

        //valeur * signifie pas de limites
        public function definirNavigationLimite($valeur){
                if (isset($this->parametres['navigation.limite'])  ) {
                        if ((preg_match('/^([0-9]+)$/', $valeur) && $valeur != 0 ) || $valeur == '*' ){
                                $this->limite_requete['limite'] = $valeur;
                        } else {
                                $e = "Erreur : valeur erronnée pour le paramètre navigation.limite.";
                                throw new Exception($e, RestServeur::HTTP_CODE_MAUVAISE_REQUETE);
                        }
                }
        }

        public function definirNavigationDepart($valeur){
                if (isset($this->parametres['navigation.depart'])) {
                        if(preg_match('/^([0-9]+)$/', $valeur)){
                                $this->limite_requete['depart'] = $valeur;
                        } else {
                                $e = "Erreur : valeur erronnée pour le paramètre navigation.depart.";
                                throw new Exception($e, RestServeur::HTTP_CODE_MAUVAISE_REQUETE);
                        }
                } else {
                        $e = "indiquez également la valeur pour le paramètre navigation.limite.";
                        throw new Exception($e, RestServeur::HTTP_CODE_MAUVAISE_REQUETE);
                }
        }
        /**
         * Est appelée pour former l'url complete des resultats precedants ou suivants.
         * @param int : Permet de connaitre le nombre de noms obtenus par la requete
         * @return string Retourne l'url complete des resultats precedents ou suivant sous la forme d'un tableau
         */
        public function formulerUrl($nb_resultat, $id = null) {
                $url = array();
                $debut_url = Config::get('url_service').$id.'?';
                //on recréé l'url sans les parametres de navigation qui seront rajoutés ci-apres. On les enlève dc de la table des parametres
                foreach($this->parametres as $cle => $val) {
                        $param_url[str_replace('_', '.', $cle)] = $val;
                }

                $this->recupererLesLimitesSuivantes($nb_resultat, $param_url);
                if (isset($param_url['navigation.depart']) && isset($param_url['navigation.limite'])) {
                        $url['suivant'] = $debut_url.http_build_query($param_url);
                }

                $this->recupererLesLimitesPrecedentes($param_url);
                if (isset($param_url['navigation.depart']) && isset($param_url['navigation.limite'])) {
                        $url['precedent'] = $debut_url.http_build_query($param_url);
                }
                return $url;
        }

        public function supprimerNavigation(&$param_url) {
                unset($param_url['navigation.depart']);
                unset($param_url['navigation.limite']);
        }

        /**
         * Description :
         * Permet de former les limites de la requete retournant les résultats suivants.
         * Cette url sera afficher dans l'entete de la reponse retournée en format JSON (retour.format=defaut).
         * @param int : $nb_resultat : Permet de connaitre le nombre de résultats obtenus par la requete
         * @return string : la fin de l'url decrivant les limites des resultats suivants. Si aucun résultats ne suient,
         * une chaine de caractère vide est retournée
         */
        public function recupererLesLimitesSuivantes($nb_resultat, &$param_url_suiv) {
                $this->supprimerNavigation($param_url);
                $depart = $this->limite_requete['depart'];
                $limite = $this->limite_requete['limite'];
                $depart_suivant = $depart + $limite;
                $limite_suivant = $limite;
                if ($nb_resultat > $depart_suivant) {
                        $param_url_suiv['navigation.depart'] = $depart_suivant;
                        $param_url_suiv['navigation.limite'] = $limite_suivant;
                } else {
                        $param_url_suiv['navigation.depart'] = null;
                        $param_url_suiv['navigation.limite'] = null;
                }
        }

        /**
         * Description :
         * Permet de former les limites de la requete retournant les résultats precedents.
         * Cette url sera afficher dans l'entete de la taxons/105reponse retournée en format JSON (retour.format=defaut)
         * @return string : la fin de l'url decrivant les limites des resultats precedents.
         * Si aucun résultats ne precedent, une chaine de caractère vide est retournée
         */
        public function recupererLesLimitesPrecedentes(&$param_url) {
                $this->supprimerNavigation($param_url);
                $depart = $this->limite_requete['depart'];
                $limite = $this->limite_requete['limite'];
                if ($depart == 0) {
                        $url_precedente = '';
                } else {
                        if (($depart - $limite) < 0) {
                                $depart_precedent = 0;
                        } else {
                                $depart_precedent = $depart - $limite;
                        }
                        $param_url['navigation.depart'] = $depart_precedent;
                        $param_url['navigation.limite'] = $limite;
                }
        }

        static function getDureeCache() {
                $dureecache = 0;
                $dureecache = Config::get('dureecache');
                if ($dureecache == null || !is_numeric($dureecache) || $dureecache < 0) {
                        $dureecache = 0;
                }
                return (int) $dureecache;
        }

        public function ajouterHref($service, $val) {
                // http://tela-botanica.org/service:eflore:0.1/[projet]/[version_projet]/[service]/[ressource]:[valeur]
                if ($this->version_projet == '+') {
                        $url = Config::get('url_service_base').Config::get('nom_projet').'/'.$service.'/'.$val;
                } else {
                        $url = Config::get('url_service_base').Config::get('nom_projet').'/'.$service.'/'.$val.'?version.projet='.ltrim($this->version_projet, 'v');
                }
                return $url;
        }

        public function ajouterHrefAutreProjet($service, $ressource, $valeur, $projet = null, $param = null) {
                //on enleve les GA et Co, les meta ou les "_"
                $this->transliterer($service, $valeur);
                //on définit les nom des projets, des services et des ressources de l'url (dans les méta-donnees)
                $tab = array(
                        'langue'                          => array('service' => 'langues', 'projet' => 'iso-639-1', 'ressource' => ''),
                        'couverture_spatiale' => array('service' => 'zone-geo', 'projet' => 'iso-3166-1', 'ressource' => ''),
                        'type'                            => array('service' => 'ontologies', 'projet' => 'eflore', 'ressource' => 'contactType:'),
                        'datum'                           => array('service' => 'ontologies', 'projet' => 'eflore', 'ressource' => 'datum:')
                );
                if (array_key_exists($service, $tab)) {
                        extract($tab[$service]);
                } else {
                        if (strpos(Config::get('nom_projet'), 'bd') === 0 && $projet == null) {
                                $projet                 = 'bdnt';
                                $service                = 'ontologies';
                                $ressource              = '';
                        }
                }
                $param = ($param) ? "?".$param : "";
                $url = Config::get('url_service_base').$projet.'/'.$service.'/'.$ressource.$valeur.$param;
                return $url;
        }

        /**Permet de consulter une url et retourne le résultat ou une erreur
         * @param $url   */
        public function consulterHref($url) {
                $res = $this->getRestClient()->consulter($url);
                $entete = $this->getRestClient()->getReponseEntetes();
                //Si le service meta-donnees fonctionne correctement, l'entete comprend la clé wrapper_data
                if (isset($entete['wrapper_data'])) {
                        $res = json_decode($res);
                        return $res;
                } else {
                        $u = 'L\'url <a href="'.$url.'">'.$url.'</a> lancée via RestClient renvoie une erreur';
                        $this->renvoyerErreur(RestServeur::HTTP_CODE_RESSOURCE_INTROUVABLE, $u);
                }
        }

        public function transliterer(&$service, &$val) {
                if (preg_match('/^.+:(.+)$/', $val, $match)) {
                        $val = $match[1];
                }
                $service = str_replace(array('_Ga','_Co','_meta'), '', $service);
                if ($service == 'rang') {
                        $ressource = 'rangTaxo';
                } elseif (preg_match('/^(statut)(?:_|-)([^_-]+)$/', $service, $match)) {
                        $service = $match[1].ucfirst($match[2]);
                } elseif (strrpos($service, 'datum') !== false) {
                        $service = 'datum';
                }
        }

        // prend en arguments la valeur de la recherche, les résultats approchés, le paramétre recherche
        // retourne le tableau trié en fonction de la ressemblance entre le résultat approché et la valeur recherchée
        public function trierRechercheFloue($nom_demande, $tab_approchee, $nom) {
                $trie = '';
                $resultat = array();
                foreach ($tab_approchee as $id => $tab) {
                        $nom_demande_ss = strtolower(Chaine::supprimerAccents($nom_demande));
                        $nom_flou_ss = strtolower(Chaine::supprimerAccents($tab[$nom]));
                        $stat = array();
                        // Prime pour la ressemblance globale :
                        $score = 500 - levenshtein($nom_flou_ss, $nom_demande_ss);
                        // On affine
                        $score = $score + (similar_text($nom_demande_ss, $nom_flou_ss) * 3);
                        $stat['score'] = $score;
                        foreach ($tab as $key => $valeur) {
                                $stat[$key] = $valeur;
                        }
                        $resultat[] = $stat;
                }


                // Vérification que nous avons bien trouvé un nom approché
                if (count($resultat) > 0) {
                        $trie = Tableau::trierMD($resultat, array('score' => SORT_DESC));
                }
                return $trie;
        }

        protected function recupererTableauConfig($param) {
                $tableau = array();
                $tableauPartiel = explode(',', Config::get($param));
                $tableauPartiel = array_map('trim', $tableauPartiel);
                foreach ($tableauPartiel as $champ) {
                        if (strpos($champ, '=') === false) {
                                $tableau[] = $champ;
                        } else {
                                list($cle, $val) = explode('=', $champ);
                                $tableau[$cle] = $val;
                        }
                }
                return $tableau;
        }

        //+------------------------------------------------------------------------------------------------------+
        // Méthodes d'accès aux objets du Framework
        /**
         * Méthode de connection à la base de données sur demande.
         * Tous les services web n'ont pas besoin de s'y connecter.
         */
        protected function getBdd() {
                if (! isset($this->Bdd)) {
                        $this->Bdd = new Bdd();
                }
                return $this->Bdd;
        }

        /**
         * Méthode permettant de faire appel à un client REST en fonction des besoins du service.
         */
        protected function getRestClient() {
                if (! isset($this->RestClient)) {
                        $this->RestClient = new RestClient();
                }
                return $this->RestClient;
        }


        /**
         * Génération de fichiers pour les cartes
         */
        static function convertirEnPNGAvecRsvg($idFichier, $chemin, $svg) {
                // test répertoire de cache
                if(!is_dir($chemin)) {
                        mkdir($chemin, 0777, true);
                }
                if(!is_dir($chemin)) {
                        error_log(__FILE__ . ": can't create cache {$chemin}");
                        return NULL;
                }

                // test présence du binaire de conversion (rsvg)
                /*
                  // `which` no possible using safe-mode...
                $i = $s = NULL;
                exec('which rsvg-convert', $s, $i);
                if($i != 0) {
                        error_log(__FILE__ . ": no rsvg-convert binary");
                        return NULL;
                }
                */
                // conversion svg => png
                // troncage du nom de fichier si celui-ci est trop long
                // (passé 255 caractères, le risque de collision est très faible)
                $cheminReduit = substr($chemin.$idFichier, 0, 240);
                
                $fichierPng = $cheminReduit.'.png';
                $fichierSvg = $cheminReduit.'.svg';
                
                
                file_put_contents($fichierSvg, $svg);
                $i = $s = NULL;
                $rsvg = exec("rsvg-convert $fichierSvg -d 75 -p 75 -o $fichierPng", $s, $i);
                if($i != 0) {
                        error_log(__FILE__ . ": `rsvg-convert $fichierSvg -o $fichierPng` returned $i: " . implode(', ', $s));
                        return NULL;
                }

                self::indexerFichierPng($fichierPng);
                return file_get_contents($fichierPng);
        }

        static function indexerFichierPng($fichierPng) {
                $img = imagecreatefrompng($fichierPng);
                imagetruecolortopalette($img, false, 32);
                $blanc = imagecolorallocate($img, 255, 255, 255);
                imagefill($img, 0, 0, $blanc);
                imagepng($img, $fichierPng, 9, PNG_ALL_FILTERS);
        }
}
?>