* @author Jean-Pascal MILCENT * @author Aurelien PERONNET * @license GPL v3 * @license CECILL v2 * @copyright 1999-2014 Tela Botanica */ // TODO : il faudrait déplacer les méthodes des sections de cette classe dans des classes séparées chargées via un Conteneur. abstract class Cel { const ARRET_SERVICE = false; const TYPE_OBS = 'observation'; const TYPE_IMG = 'image'; // TODO: delete wrappers const SQL_MODE_ASSOC = Bdd::SQL_MODE_ASSOC; const SQL_MODE_OBJET = Bdd::SQL_MODE_OBJET; const SQL_RETOUR_COMPLET = Bdd::SQL_RETOUR_COMPLET; const SQL_RETOUR_LIGNE = Bdd::SQL_RETOUR_LIGNE; const SQL_RETOUR_COLONNE = Bdd::SQL_RETOUR_COLONNE; const SQL_RETOUR_BRUT = Bdd::SQL_RETOUR_BRUT; public $config; private $ressources; protected $parametres = array(); public static $bdd = null; protected $messages = array(); protected $debug = array(); protected $start; protected $limit; static $referentiels_valides = array(); static $default_referentiel = null; static $fallback_referentiel = 'autre'; public function __construct($config) { @session_start(); // Tableau contenant la config de Jrest $this->config = $config; // Réglages de PHP setlocale(LC_ALL, $this->config['settings']['locale']); date_default_timezone_set($this->config['settings']['fuseauHoraire']); // Chargement des référentiels disponibles self::$referentiels_valides = array_keys($this->config['referentiels']); self::$default_referentiel = $this->config['cel']['referentiel_defaut']; // Connection à la base de données if (self::$bdd === null) { // singleton à l'arrache self::$bdd = new Bdd($this->config, 'database_cel'); } // Nettoyage du _GET (sécurité) $this->collecterParametres();// Récupération de tous les parametres de _GET, nettoyage et mise dans $this->parametres $this->recupererParametresUrl();// Vidage de _GET et création d'attribut de la classe $this->definirParametresUrlParDefaut(); // Définition de variable générale dans la config $this->config['settings']['baseURLAbsoluDyn'] = 'http://'.$_SERVER['SERVER_NAME'].$this->config['settings']['baseURL'].'%s'; } //+----------------------------------------------------------------------------------------------------------------+ protected function connecterPDO($config, $base = 'database_cel') { return new Bdd($config, $base); } public static function db() { if (! self::$bdd) { die('ERREUR: aucune base de données de disponible.'); } return self::$bdd; } // TODO: delete wrappers, en attendant que $this->bdd soit remplacé par Cel::db() partout. public function __get($prop) { $retour = $this->$prop; if ($prop == 'bdd') { $retour = self::$bdd; } return $retour; } protected function protegerTableau(Array $tableau) { foreach ($tableau as $id => $val) { if ($val === null) { $tableau[$id] = 'NULL'; } else { $tableau[$id] = Cel::db()->proteger($val); } } return $tableau; } //+----------------------------------------------------------------------------------------------------------------+ // TRAITEMENT des URLs et des PARAMÊTRES private function collecterParametres() { foreach ($_GET as $cle => $valeur) { $p = $this->verifierSecuriteParametreUrl($valeur); $this->parametres[$cle] = is_string($p) ? rawurldecode($p) : $p; } } private function recupererParametresUrl() { $get_params = array('orderby', 'distinct', 'start', 'limit', 'formatRetour'); foreach ($get_params as $get) { if (!isset($_GET[$get])) continue; $_GET[$get] = $this->verifierSecuriteParametreUrl($_GET[$get]); if (empty($_GET[$get])) { $_GET[$get] = null; continue; } if (!isset($this->$get)) { $this->$get = $_GET[$get]; } else { $e = "Impossible d'ajouter l'attribut $get à la classe du service car elle possède déjà un attribut nommé : $get"; trigger_error($e, E_USER_WARNING); } } } protected function verifierSecuriteParametreUrl($param) { //$verifier = array('NULL', "\n", "\r", "\\", "'", '"', "\x00", "\x1a", ';'); return is_string($param) ? strip_tags($param) : $param; } private function definirParametresUrlParDefaut() { if (!isset($this->start)) { $this->start = 0; } if (!isset($this->limit)) { $this->limit = 150; } } protected function traiterParametres($params_attendu, $params, $pourBDD = true) { $sortie = array(); foreach ($params_attendu as $num => $nom) { if (isset($params[$num]) && $params[$num] != '*') { if ($pourBDD) { $params[$num] = self::$bdd->quote($params[$num]); } $sortie[$nom] = $params[$num]; } } return $sortie; } protected function traiterNomMethodeGet($nom) { $methode = 'get'; $methode .= str_replace(' ', '', ucwords(str_replace('-', ' ', strtolower($nom)))); return $methode; } //+----------------------------------------------------------------------------------------------------------------+ // GESTION de l'ENVOI au NAVIGATEUR protected function envoyerMessageErreur($code, $msg) { http_response_code($code); header("Content-Type: text/plain; charset=utf-8"); die($msg); } protected function envoyerJson($donnees, $encodage = 'utf-8') { $encodage_json = true; $this->envoyer($donnees, 'application/json', $encodage, $encodage_json); } protected function envoyerJsonVar($variable, $donnees = null, $encodage = 'utf-8') { $contenu = "var $variable = ".json_encode($donnees); $this->envoyer($contenu, 'text/html', $encodage); } protected function envoyerJsonp($donnees = null, $encodage = 'utf-8') { $contenu = $this->parametres['callback'].'('.json_encode($donnees).');'; $this->envoyer($contenu, 'text/html', $encodage); } protected function envoyer($donnees = null, $mime = 'text/html', $encodage = 'utf-8', $json = false) { // Traitements des messages d'erreurs et données $sortie = $donnees; if (count($this->messages) != 0) { $code_http = 500; // Internal Server Error $mime = 'application/json'; $json = true; if (is_object($donnees)) $donnees->cause = $this->messages; } else { $code_http = 200; // OK if (is_null($donnees)) { $sortie = 'OK'; } } // Gestion de l'envoie du déboguage $this->envoyerDebogage(); // Encodage au format et JSON et envoie sur la sortie standard $contenu = $json ? json_encode($sortie) : $sortie; $this->envoyerContenu($encodage, $mime, $contenu, $code_http); } private function envoyerDebogage() { if (!is_array($this->debug)) { $this->debug[] = $this->debug; } if (count($this->debug) != 0) { foreach ($this->debug as $cle => $val) { if (is_array($val)) { $this->debug[$cle] = print_r($val, true); } } header('X-DebugJrest-Data:'.json_encode($this->debug)); } } private function envoyerContenu($encodage, $mime, $contenu, $code_http=200) { if (!is_null($mime) && !is_null($encodage)) { header("Content-Type: $mime; charset=$encodage"); } else if (!is_null($mime) && is_null($encodage)) { header("Content-Type: $mime"); } http_response_code($code_http); print $contenu; } static function envoyerAuth($message_accueil, $message_echec) { http_response_code(401);// Unauthorized header('WWW-Authenticate: Basic realm="'.mb_convert_encoding($message_accueil, 'ISO-8859-1', 'UTF-8').'"'); header('Content-type: text/plain; charset=UTF-8'); print $message_echec; exit; } //+----------------------------------------------------------------------------------------------------------------+ // GESTION DES CLASSES CHARGÉES À LA DEMANDE protected function getRestClient() { if (!isset($this->restClient)) { $this->restClient = new CelRestClient(); } return $this->restClient; } //+----------------------------------------------------------------------------------------------------------------+ // GESTION DE L'IDENTIFICATION static function getAuthIdentifiant() { return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null; } static function getAuthMotDePasse() { return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null; } public function authentifierAdmin() { $message_accueil = "Veuillez vous identifier avec votre compte Tela Botanica."; $message_echec = "Accès limité aux administrateurs du CEL.\n". "Votre tentative d'identification a échoué.\n". "Actualiser la page pour essayer à nouveau si vous êtes bien inscrit comme administrateur."; return $this->authentifier($message_accueil, $message_echec, 'Admin'); } public function authentifierUtilisateur() { $message_accueil = "Veuillez vous identifier avec votre compte Tela Botanica."; $message_echec = "Accès limité aux utilisateur du CEL.\n". "Inscrivez vous http://www.tela-botanica.org/page:inscription pour le devenir.\n". "Votre tentative d'identification a échoué.\n". "Actualiser la page pour essayer à nouveau si vous êtes déjà inscrit ou contacter 'accueil@tela-botanica.org'."; return $this->authentifier($message_accueil, $message_echec, 'Utilisateur'); } public function controleUtilisateur($id) { if (@array_key_exists('id_utilisateur', $_SESSION['user']) && empty($_SESSION['user']['id_utilisateur'])) { //cas de la session temporaire, on ne fait rien de particulier } else { if (isset($_SESSION['user']) && isset($_SESSION['user']['id_utilisateur']) && !$this->etreAdminCelParId($_SESSION['user']['id_utilisateur']) && $_SESSION['user']['id_utilisateur'] != $id) { // cas d'usurpation d'identité print 'Accès interdit'; exit(); } } } public function controleAppelIpAutorisee() { $ipsAutorisees = explode(',', $this->config['jrest_admin']['ip_autorisees']); $remoteIp = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP); $serverIp = filter_input(INPUT_SERVER, 'SERVER_ADDR', FILTER_VALIDATE_IP); if (in_array($remoteIp, $ipsAutorisees) == false) { if ($remoteIp != $serverIp) {// ATTENTION : maintenir ce test à l'intérieur du précédent $msg = "Accès interdit. \n". "Vous n'êtes pas autorisé à accéder à ce service depuis '$remoteIp' !\n"; $this->envoyerMessageErreur(401, $msg); } } return true; } public function logger($index,$chaine = 'err') { if (!class_exists('Log')) { Log::getInstance(); } Log::setCheminLog($this->config['log']['cheminlog']); Log::setTimeZone($this->config['log']['timezone']); Log::setTailleMax($this->config['log']['taillemax']); Log::ajouterEntree($index,$chaine); } private function authentifier($message_accueil, $message_echec, $type) { if (!isset($_SERVER['PHP_AUTH_USER'])) { self::envoyerAuth($message_accueil, $message_echec); // exit } if ($type == 'Utilisateur' && self::getAuthMotDePasse() == 'debug') { $autorisation = true; } else { $methodeAutorisation = "etre{$type}Autorise"; $autorisation = $this->$methodeAutorisation(); } if ($autorisation == false) { self::envoyerAuth($message_accueil, $message_echec); } return true; } public function etreUtilisateurAutorise() { $identifiant = self::getAuthIdentifiant(); $mdp = md5(self::getAuthMotDePasse()); $service = "TestLoginMdp/$identifiant/$mdp"; $url = sprintf($this->config['settings']['baseURLServicesAnnuaireTpl'], $service); $json = $this->getRestClient()->consulter($url); $existe = json_decode($json); $autorisation = (isset($existe) && $existe) ? true :false; return $autorisation; } public function etreAdminAutorise() { $identifiant = self::getAuthIdentifiant(); $autorisation = ($this->etreUtilisateurAutorise() && $this->etreAdminCel($identifiant)) ? true : false; return $autorisation; } public function etreAdminCelParId($id) { // si l'utilisateur s'est déjà identifié, alors les infos // sur le fait qu'il est admin ou non sont déjà disponibles if(isset($_SESSION['user']) && isset($_SESSION['user']['admin']) && $_SESSION['user']['id_utilisateur'] == $id) { $admin = $_SESSION['user']['admin']; } else { $requete = "SELECT admin FROM cel_utilisateurs_infos WHERE id_utilisateur = ".Cel::db()->proteger($id); $resultat = Cel::db()->requeter($requete); $admin = false; if ($resultat && count($resultat) > 0) { $admin = ($resultat[0]['admin'] == 1); } } return $admin; } public function etreAdminCel($courriel) { // si l'utilisateur s'est déjà identifié, alors les infos // sur le fait qu'il est admin ou non sont déjà disponibles if(isset($_SESSION['user']) && isset($_SESSION['user']['admin']) && $_SESSION['user']['courriel'] == $courriel) { $admin = $_SESSION['user']['admin']; } else { $requete = "SELECT admin FROM cel_utilisateurs_infos WHERE courriel = ".Cel::db()->proteger($courriel); $resultat = Cel::db()->requeter($requete); $admin = false; if ($resultat && count($resultat) > 0) { $admin = ($resultat[0]['admin'] == 1); } } return $admin; } public function getInfosComplementairesUtilisateur($id_utilisateur) { $infos = array('prenom' => '', 'nom' => '', 'courriel' => ''); if (is_numeric($id_utilisateur)) { $idUtilisateurP = Cel::db()->proteger($id_utilisateur); $requete = 'SELECT prenom, nom, courriel '. 'FROM cel_utilisateurs_infos '. "WHERE id_utilisateur = $idUtilisateurP ". ' -- ' . __FILE__ . ':' . __LINE__; $resultat = Cel::db()->requeter($requete); if ($resultat && count($resultat)) { $infos = $resultat[0]; } } return $infos; } public function getInfosComplementairesUtilisateurPourMail($mail_utilisateur) { $infos = array('prenom' => '', 'nom' => '', 'courriel' => $mail_utilisateur); $mailUtilisateurP = Cel::db()->proteger($mail_utilisateur); $requete = 'SELECT id_utilisateur as id, prenom, nom '. 'FROM cel_utilisateurs_infos '. "WHERE courriel = $mailUtilisateurP ". ' -- ' . __FILE__ . ':' . __LINE__; $resultat = Cel::db()->requeter($requete); if ($resultat && is_array($resultat) && count($resultat) > 0) { $infos = $resultat; } return $infos; } protected function controlerAccessibiliteWs() { if (self::ARRET_SERVICE) { $msg = "Les services du CEL sont temporairement désactivées."; $this->envoyerMessageErreur(503, $msg); } return true; } //+----------------------------------------------------------------------------------------------------------------+ // GESTION DE MÉTHODES COMMUNES ENTRE LES SERVICES protected function denullifierTableauValeurCel(&$tableau) { foreach ($tableau as $k => $v) { if (($v == 'null') || ($v == '000null')) { $row[$k] = ''; } else { $row[$k] = utf8_decode($v); } } return $tableau; } protected function getUrlImage($id, $format = 'L') { $url_tpl = $this->config['settings']['celImgUrlTpl']; $id = sprintf('%09s', $id).$format; $url = sprintf($url_tpl, $id); return $url; } /** * Prend en paramêtre un tableau de courriels et retourne après avoir interrogé un service de l'annuaire * une tableau avec en clé le courriel et en valeur l'intitulé de la personne à afficher. * * @param array $courriels un tableau de courriels pour lesquels il faut rechercher les infos d'identité */ protected function creerAuteurs(Array $courriels) { $auteurs = array(); $identites = $this->recupererUtilisateursIdentite($courriels); if ($identites) { foreach ($identites as $courriel => $infos) { $auteurs[$courriel] = $infos['intitule']; } } return $auteurs; } protected function recupererUtilisateursIdentite(Array $courriels) { // Récupération des données au format Json $service = "utilisateur/identite-par-courriel/".implode(',', $courriels); $url = sprintf($this->config['settings']['baseURLServicesAnnuaireTpl'], $service); $json = file_get_contents($url); $utilisateurs = json_decode($json, true); $noms = array(); foreach ($courriels as $courriel) { $courriel = strtolower($courriel); $info = array('id' => null, 'intitule' => ''); if (isset($utilisateurs[$courriel])) { $info['intitule'] = $utilisateurs[$courriel]['intitule']; $info['id'] = $utilisateurs[$courriel]['id']; } else { $info['intitule'] = $this->tronquerCourriel($courriel); } $noms[$courriel] = $info; } return $noms; } protected function tronquerCourriel($courriel) { return preg_replace('/[^@]+$/i', '...', $courriel); } protected function nettoyerTableau(Array $tableau) { foreach ($tableau as $cle => $valeur) { if (is_array($valeur)) { $valeur = $this->nettoyerTableau($valeur); } else { $valeur = $this->nettoyerTexte($valeur); } $tableau[$cle] = $valeur; } return $tableau; } /** * Fonction nettoyant les caractères spéciaux (&,<) et les valeurs nulles du CEL dans un texte comprenant du HTML. */ protected function nettoyerTexte($txt) { $txt = preg_replace('/&(?!([a-z]+|#[0-9]+|#x[0-9][a-f]+);)/i', '&', $txt); // TODO : trouver une regexp qui permet de remplacer les symboles < et > isolés //$txt = preg_replace('/<(?!([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>|\/\s*([a-z][a-z0-9]*)\s*>)/i', '<', $txt); //$txt = preg_replace('/(?!<([a-z][a-z0-9]*)\b[^>]*)>(?!(.*?)<\/\1>)/i', '>', $txt); $txt = preg_replace('/(?:000null|null)/i', '', $txt); return $txt; } /** * Fonction nettoyant les caractères spéciaux HTML pour les champs de saisie libre du CEL. */ static function protegerCaracteresHtmlDansChamps($donnees) { $champs = array('mots_cles_texte', 'commentaire', 'zone_geo', 'lieudit', 'station', 'milieu', 'commentaire', 'nom_sel'); foreach ($champs as $champ) { if (isset($donnees[$champ])) { $donnees[$champ] = htmlspecialchars($donnees[$champ]); } } return $donnees; } protected function convertirDateHeureMysqlEnTimestamp($date_heure_mysql){ $val = explode(' ', $date_heure_mysql); $date = explode('-', $val[0]); $heure = explode(':', $val[1]); return mktime((int) $heure[0], (int) $heure[1], (int) $heure[2], (int) $date[1], (int) $date[2], (int) $date[0]); } protected function etreNull($valeur) { $etre_null = false; if ($valeur == '' || $valeur == null || $valeur == '000null' || $valeur == 'null' || $valeur == '*') { $etre_null = true; } return $etre_null; } protected function formaterDate($date_heure_mysql, $format = '%A %d %B %Y à %H:%M') { $date_formatee = ''; if (!$this->etreNull($date_heure_mysql)) { $timestamp = $this->convertirDateHeureMysqlEnTimestamp($date_heure_mysql); $date_formatee = strftime($format, $timestamp); } return $date_formatee; } protected function convertirCodeZoneGeoVersDepartement($code_zone_geo) { $code_departement = ''; if (self::estUnCodeInseeDepartement($code_zone_geo)) { $code_departement = substr(ltrim($code_zone_geo,'INSEE-C:'),0,2); } return $code_departement; } protected function estUnCodeInseeDepartement($code_a_tester) { return preg_match('/^INSEE-C:(([0-9][0-9])|2A|2B)[0-9]{3}/i',$code_a_tester); } protected function convertirCodeZoneGeoVersCodeInsee($code_zone_geo) { $code_departement = ''; if (self::estUnCodeInseeDepartement($code_zone_geo)) { $code_departement = ltrim($code_zone_geo,'INSEE-C:'); } return $code_departement; } static function obtenirCodeInseeCommunePourNomEtDepartement($nom_commune, $code_insee) { $nomCommuneP = Cel::db()->proteger($nom_commune); $codeInseeP = Cel::db()->proteger("INSEE-C:$code_insee%"); $requete = 'SELECT id_zone_geo '. 'FROM cel_zones_geo '. "WHERE nom LIKE $nomCommuneP AND id_zone_geo LIKE $codeInseeP ". ' -- ' . __FILE__ . ':' . __LINE__; $resultat = Cel::db()->requeter($requete); $infos = $code_insee; // Par défaut retourne l'original if ($resultat && count($resultat)) { $infos = $resultat[0]['id_zone_geo']; } return $infos; } public function formaterZoneGeoEtCodePourAffichage($obs) { $code_lieu = self::convertirCodeZoneGeoVersCodeInsee($obs['ce_zone_geo']); if(empty($code_lieu) && !empty($obs['pays'])) { $code_lieu = $obs['pays']; } if(!empty(trim($obs['zone_geo'])) && !empty(trim($code_lieu))) { $code_lieu = ' ('.$code_lieu.') '; } return $obs['zone_geo'].$code_lieu; } protected function encoderMotCle($mot_cle) { return md5(mb_strtolower(trim($mot_cle))); } private function protegerMotsCles($mots_cles, $type) { $separateur = ($type == self::TYPE_IMG) ? ',' : ';' ; $mots_cles = $this->traiterValeursMultiples($mots_cles, $separateur); return $mots_cles; } protected function traiterValeursMultiples($valeurs, $separateur_entree = ',' , $separateur_sortie = ',') { if (! $this->etreNull($valeurs)) { $valeurs_a_proteger = explode($separateur_entree,trim(trim($valeurs), $separateur_entree)); foreach ($valeurs_a_proteger as $valeur) { $valeurs_protegees[] = self::$bdd->quote($valeur); } $valeurs = implode($separateur_sortie, $valeurs_protegees); } return ($this->etreNull($valeurs)) ? null : $valeurs; } protected function obtenirSousTaxonsPourNt($referentiel, $nt) { $sous_taxons = array(); // Certains référentiels n'ont pas de nt if(!empty($nt) && is_numeric($nt) && $nt > 0) { $sous_taxons = $this->obtenirSousTaxons($referentiel, 'nt:'.$nt); } return $sous_taxons; } protected function obtenirSousTaxonsPourNn($referentiel, $nn) { return $this->obtenirSousTaxons($referentiel, $nn); } private function obtenirSousTaxons($referentiel, $requete) { $sous_taxons = array(); $url_service_taxon = str_replace('{referentiel}', $referentiel, $this->config['eflore']['url_service_taxon']); $url = $url_service_taxon.'/'.$requete.'/relations/hierarchie'; $res = json_decode(file_get_contents($url),true); if($res && !empty($res)) { $sous_taxons = array_pop($res); } return $sous_taxons; } protected function supprimerVersionDuReferentiel($referentiel) { $referentiel_parties = explode(':', $referentiel); $referentiel_parties = explode('_', $referentiel_parties[0]); return $referentiel_parties[0]; } protected function getUrlEflore($referentiel, $nn) { $urlEflore = null; if (! $this->etreNull($nn)) { $code_referentiel = $this->supprimerVersionDuReferentiel($referentiel); if($code_referentiel == '') { $code_referentiel = 'bdtfx'; } $urlEflore = sprintf($this->config['settings']['efloreUrlTpl'], $code_referentiel, $nn, 'illustration'); } return $urlEflore; } protected function nePasInterpreterXml($txt) { return ''; } //+----------------------------------------------------------------------------------------------------------------+ // GESTION DES SQUELETTES PHP /** * Méthode prenant en paramètre un chemin de fichier squelette et un tableau associatif de données, * en extrait les variables, charge le squelette et retourne le résultat des deux combinés. * * @param String $fichier le chemin du fichier du squelette * @param Array $donnees un tableau associatif contenant les variables a injecter dans le squelette. * * @return boolean false si le squelette n'existe pas, sinon la chaine résultat. */ public static function traiterSquelettePhp($fichier, Array $donnees = array()) { $sortie = false; if (file_exists($fichier)) { // Extraction des variables du tableau de données extract($donnees); // Démarage de la bufferisation de sortie ob_start(); // Si les tags courts sont activés if ((bool) @ini_get('short_open_tag') === true) { // Simple inclusion du squelette include $fichier; } else { // Sinon, remplacement des tags courts par la syntaxe classique avec echo $html_et_code_php = self::traiterTagsCourts($fichier); // Pour évaluer du php mélangé dans du html il est nécessaire de fermer la balise php ouverte par eval $html_et_code_php = '?>'.$html_et_code_php; // Interprétation du html et du php dans le buffer echo eval($html_et_code_php); } // Récupèration du contenu du buffer $sortie = ob_get_contents(); // Suppression du buffer @ob_end_clean(); } else { $msg = "Le fichier du squelette '$fichier' n'existe pas."; trigger_error($msg, E_USER_WARNING); } // Retourne le contenu return $sortie; } /** * Fonction chargeant le contenu du squelette et remplaçant les tags court php (/", "; ?>", $contenu);*/ // return $contenu; } } ?>