Subversion Repositories Applications.framework

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

<?php
/**
 * Classe principale gérant les services.
 * Paramètres liés dans config.ini :
 *  - serveur.baseURL
 * 
 * Encodage en entrée : utf8
 * Encodage en sortie : utf8
 * 
 * @category    Php 5.2
 * @package             JRest
 * @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>
 * @copyright   2010 Tela-Botanica
 * @version             $Id$
 */
// TODO : gerer les retours : dans ce controleur : code retour et envoi ...
class Serveur {
        
        /** Nom de la méthode appelée dans un service pour éxécuter une requête de type GET. */
        const METHODE_GET = 'getElement';
        
        /** Nom de la méthode appelée dans un service pour éxécuter une requête de type POST. */
        const METHODE_POST = 'getElement';
        
        /** Nom de la méthode appelée dans un service pour éxécuter une requête de type DELETE. */
        const METHODE_DELETE = 'getElement';
        
        /** Nom de la méthode appelée dans un service pour éxécuter une requête de type PUT. */
        const METHODE_PUT = 'getElement';
        
        /** Les paramètres de configuration dynamiques internes au serveur. */
        private static $config;
        
        /** La méthode de la requête HTTP utilisée. */
        private $methode = 'GET';

        /** Le contenu de la requête HTTP (s'il y en a). */
        private $requeteDonnees = null;//requestData

        /** Version de l'API demandée.
         * Généralement deux nombres séparés par un point. Ex. : 1.0 
         * Ex. http://www.mondomaine.org/services/[apiVersion]/monService/
         */
        private $apiVersion = null;

        /** Nom du service demandé. 
         * Ex. http://www.mondomaine.org/services/apiVersion/[monService]/ 
         */
        private $service = null;// $ressource
        
        /** Morceaux de l'url servant à préciser la ressource concerné pour service demandé.
         * Ex. http://www.mondomaine.org/services/apiVersion/monService/[maRessource/maSousResource...]
         */
        private $ressources = array();// $uid
        
        /** Partie de l'url servant à paramétrer le service demandé.
         * Ex. http://www.mondomaine.org/services/apiVersion/monService?monParametre1=maValeur1&monParametre2=maValeur2
         */
        private $parametres = array();
        
        /** Codes HTTP. */
        private static $http10 = array(
                '201' => 'Created',
                '204' => 'No Content',
                '400' => 'Bad Request',
                '401' => 'Unauthorized',
                '404' => 'Not Found',
                '405' => 'Method Not Allowed',
                '406' => 'Not Acceptable',
                '411' => 'Length Required',
                '500' => 'Internal Server Error'
        );

        /**
         * Analyse les données envoyées au serveur, enregistre la méthode HTTP utilisée pour appeler le serveur et parse
         * l'url appelée pour trouver le service demandé.
         */
        public function __construct() {
                if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD']) && isset($_SERVER['QUERY_STRING'])) {
                        if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {
                                $this->requeteDonnees = '';
                                $httpContent = fopen('php://input', 'r');
                                while ($data = fread($httpContent, 1024)) {
                                        $this->requeteDonnees .= $data;
                                }
                                fclose($httpContent);
                        }
                        if (strlen($_SERVER['QUERY_STRING']) == 0) {
                                $tailleURL = strlen($_SERVER['REQUEST_URI']);
                        } else {
                                $tailleURL = -(strlen($_SERVER['QUERY_STRING']) + 1);
                        }
                        $urlString = substr($_SERVER['REQUEST_URI'], strlen(Config::get('serveur.baseURL')), $tailleURL);

                        $urlParts = explode('/', $urlString);
                        if (isset($urlParts[0]) && !empty($urlParts[0])) {
                                $this->apiVersion = $urlParts[0];
                                self::$config['chemins']['api'] = Config::get('chemin_modules').$this->apiVersion.DS;
                        } else {
                                $e = "Aucune version d'API n'a été spécifié dans l'url qui doit avoir la forme suivante http://www.mondomaine.org/services/apiVersion/monService/";
                                trigger_error($e, E_USER_ERROR);
                        }
                        
                        if (isset($urlParts[1]) && !empty($urlParts[1])) {
                                $this->service = $this->traiterNomService($urlParts[1]);
                        } else {
                                $e = "Aucun service n'a été spécifié dans l'url qui doit avoir la forme suivante http://www.mondomaine.org/services/apiVersion/monService/";
                                trigger_error($e, E_USER_ERROR);
                        }
                        
                        if (count($urlParts) > 2 && $urlParts[2] != '') {
                                array_shift($urlParts);
                                array_shift($urlParts);
                                foreach ($urlParts as $ressource) {
                                        if ($ressource != '') {
                                                $this->ressources[] = urldecode($ressource);
                                        }
                                }
                        }
                        
                        $this->nettoyerGet();
                        $this->parametres = $_GET;

                        $this->methode = $_SERVER['REQUEST_METHOD'];
                        
                        // Enregistrement en première position des autoload de la méthode gérant les classes des services 
                        spl_autoload_register(array(get_class(), 'chargerClasse'));
                } else {
                        $e = "La classe Serveur du TBFRamework nécessite, pour fonctionner, l'accès aux variables serveurs REQUEST_URI, REQUEST_METHOD et QUERY_STRING.";
                        trigger_error($e, E_USER_ERROR);
                }
        }
        
        private function traiterNomService($serviceNom) {
                return str_replace(' ', '', ucwords(str_replace('-', ' ', strtolower($serviceNom))));
        }
        
        private function nettoyerGet() {
                if (isset($_GET) && count($_GET) > 0) {
                        foreach ($_GET as $cle => $valeur) {
                                $verifier = array('NULL', "\n", "\r", "\\", "'", '"', "\x00", "\x1a", ';');
                                $_GET[$cle] = strip_tags(str_replace($verifier, '', $valeur));
                        }
                }
        }
        
        /**
        * La méthode __autoload() charge dynamiquement les classes trouvées dans le code.
        * Cette fonction est appelée par php5 quand il trouve une instanciation de classe dans le code.
        *
        *@param string le nom de la classe appelée.
        *@return void le fichier contenant la classe doit être inclu par la fonction.
        */
        public static function chargerClasse($classe) {
                if (class_exists($classe)) {
                        return null;
                }

                $chemins = array('', self::$config['chemins']['api']);
                foreach ($chemins as $chemin) {
                        $chemin = $chemin.$classe.'.php';
                        if (file_exists($chemin)) {
                                require_once $chemin;
                        }
                }
        }
        
        /**
         * Execute la requête.
         */
        function executer() {
                switch ($this->methode) {
                        case 'GET':
                                $this->get();
                                break;
                        case 'POST':
                                $this->post();
                                break;
                        case 'DELETE':
                                $this->delete();
                                break;
                        case 'PUT':
                                $this->add();
                                break;
                }
                //      Affichage des exceptions et erreurs générées par les services
                echo GestionnaireException::getExceptions();
        }

        /**
         * Execute a GET request. A GET request fetches a list of resource when no resource name is given, a list of element
         * when a resource name is given, or a resource element when a resource and resource unique identifier are given. It does not change the
         * database contents.
         */
        private function get() {
                Debug::printr($this);
                $Service = new $this->service(self::$config);
                if (method_exists($Service, self::METHODE_GET)) {
                        $methodeGet = self::METHODE_GET;
                        $Service->$methodeGet($this->ressources, $this->parametres);
                } else {
                        $e = "La classe '{$this->service}' ne contient pas de méthode '".self::METHODE_GET."'.";
                        trigger_error($e, E_USER_ERROR);
                }
        }

        private function post() {
                $pairs = array();
                // Récupération des paramètres passés dans le contenu de la requête HTTP (= POST)
                if ($this->requestData) {
                        $pairs = $this->parserDonneesRequete();
                }

                // Ajout des informations concernant l'upload de fichier passées dans la variable $_FILE
                if(isset($_FILES)) {
                        foreach ($_FILES as $v) {
                                $pairs[$v['name']] = $v;
                        }

                        // Ne pas effacer cette ligne ! Elle est indispensable pour les services du Carnet en ligne
                        // qui n'utilisent que le tableau pairs dans les posts
                        $pairs = array_merge($pairs, $_POST);
                }

                // Gestion du contenu du post
                if(isset($_POST))
                {
                        // Safari ne sait pas envoyer des DELETE avec gwt...
                        // Nous utilisons le parametre "action" passé dans le POST qui doit contenir DELETE pour lancer la supression
                        if ($pairs['action'] == 'DELETE') {
                                $this->delete();
                                return;
                        }

                        if (count($pairs) != 0) {
                                if ($this->uid) { // get a resource element
                                        $resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';
                                        $resource_class = $this->ressource;
                                        if (file_exists($resource_file)) {
                                                include_once $resource_file;
                                                if (class_exists($resource_class)) {
                                                        $service = new $resource_class(self::$config);
                                                        if (method_exists($service,'updateElement')) { // Update element
                                                                // TODO : a voir le retour ...
                                                                if ($service->updateElement($this->uid, $pairs)) {
                                                                        $this->envoyerEnteteHttp(201);// Created
                                                                }
                                                        }
                                                }
                                        }
                                } else { // get all elements of a ressource
                                        $this->add($pairs);
                                }
                        } else {
                                $this->envoyerEnteteHttp(411);// Length required
                        }
                }
        }

        private function delete() {
                $resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';
                $resource_class = $this->ressource;
                if (file_exists($resource_file)) {
                        include_once $resource_file;
                        if (class_exists($resource_class)) {
                                $service = new $resource_class(self::$config);
                                if ($this->uid) { // get a resource element
                                        if (method_exists($service, 'deleteElement')) { // Delete element
                                                if ($service->deleteElement($this->uid)) {
                                                        $this->envoyerEnteteHttp(204);// No Content
                                                }
                                        }
                                }
                        }
                }
        }

        private function add($pairs = null) {
                if (is_null($pairs)) {
                        $pairs = array();
                        // Récupération des paramètres passés dans le contenu de la requête HTTP (= POST)
                        // FIXME : vérifier que l'on récupère bien les données passées par PUT
                        if ($this->requestData) {
                                $pairs = $this->parserDonneesRequete();
                        }
                }

                if (count($pairs) != 0) {
                        $resource_file = self::$config['settings']['servicesDir'].$this->ressource.'.php';
                        $resource_class = $this->ressource;
                        if (file_exists($resource_file)) {
                                include_once $resource_file;
                                if (class_exists($resource_class)) {
                                        $service = new $resource_class(self::$config);
                                        if (method_exists($service,'createElement')) { // Create a new element
                                                if ($service->createElement($pairs)) {
                                                        $this->envoyerEnteteHttp(201);// Created
                                                }
                                        }
                                }
                        }
                } else {
                        $this->envoyerEnteteHttp(411);// Length required
                }
        }

        /**
         * Parse les données de la requête HTTP.
         * @return str[] Array of name value pairs
         */
        private function parserDonneesRequete() {
                $values = array();
                $pairs = explode('&', $this->requeteDonnees);
                foreach ($pairs as $pair) {
                        $parts = explode('=', $pair);
                        if (isset($parts[0]) && isset($parts[1])) {
                                $parts[1] = rtrim(urldecode($parts[1]));
                                $values[$parts[0]] = $parts[1];
                        }
                }
                return $values;
        }
        
        /**
         * Envoyer un entête HTTP.
         * @param int $code entier indiquant le code de l'entête http à envoyer 
         */
        public static function envoyerEnteteHttp($code) {
                if (isset(self::$http10[$code])) {
                        $txt = self::$http[$code];
                        header("HTTP/1.0 $code $txt");
                }
        }
}
?>