diff --git a/README.md b/README.md index 8846c1d..8904a2e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -### xdrm-framework +# xdrm-framework + + + #### Description xdrm-framework is a tool that wraps my framework and all it's component's versions. It allows to build a web framework's base with chosen modules and their versions. diff --git a/exporter/packages.json b/exporter/packages.json index 651d1b0..b3bef6d 100755 --- a/exporter/packages.json +++ b/exporter/packages.json @@ -28,6 +28,14 @@ "http": [ "1.0" ] + }, + "2.3": { + "error": [ + "2.0" + ], + "http": [ + "1.0" + ] } }, "orm": { @@ -63,7 +71,7 @@ } }, "installed": { - "api": "2.2", + "api": "2.3", "error": "2.0", "http": "1.0", "orm": "0.8.2", diff --git a/notice/api/2.0.md b/notice/api/2.0.md new file mode 100644 index 0000000..e60d964 --- /dev/null +++ b/notice/api/2.0.md @@ -0,0 +1,300 @@ +```yaml +module: api +version: 2.2 +requires: + - http: 1.0 + - error: 2.0 +``` + +Links +==== + +[[1] User guide]() +- [1 - Overview]() + - [(1) introduction & features]() + - [(2) basic knowledge]() +- [2 - Usage]() + - [(1) setup]() + - [(2) from php internally]() + - [(3) from HTTP requests]() +- [3 - Configuration]() + - [(1) Basic usage]() + - [(2) Advanced usage]() +- [4 - Implementation]() + - [(1) Permissions / AuthSystem]() + - [(2) Modules & methods]() + - [(3) Automatic type check]() +- [5 - Class documentation]() + - [(1) Request]() + - [(2) Response]() + - [(3) AuthSystem]() + - [(4) Checker]() + - [(4) ModuleFactory]() + +[[2]. Advanced guide]() + +User guide +==== + +1 - Overview +---- + +#### (1) Introduction & features +The `api` package (v2.2) allows you to easily create and manage an API. It could be an HTTP API (REST, or other), or you can use it as an internal core for your system. + +The aim of this package is to make your life easier working with API. The only things you have to do is to implement your processes and edit the configuration, the package will do the rest. + +Things you have to do : +- implement your processes (obviously) +- implement your authentication system (cf. [AuthSystem]()) +- edit the configuration file (cf. [configuration]()) + +Things you **don't have** to do : +- input type check (cf. [Checker]()) +- API multiple permission management +- optional or required inputs +- before and after scripts + + +#### (2) Basic knowledge + +The API is based over a 2-level delegation structure : +1. `module` which is a set of methods +2. `method` which have input, output, permissions, and is bound to a function + + + +2 - Usage +---- +#### (1) Setup +In order to make the API work, you have to : +1. Edit the configuration file according to your needs (cf. [configuration]()) +2. Implement the Authentication System to manage permissions (cf. [AuthSystem]()) + + +#### (2) From php internally + +> ##### 1) include the `autoloader` file +> ```php +> require_once '../autoloader.php'; +> ``` + +> ##### 2) load useful classes +> ```php +> +> ... +> +> // for API use +> use \api\core\Request; +> use \api\core\Response; +> +> // for error handling +> use \error\core\Err; +>``` + +> ##### 3) create a request +> ```php +> +> ... +> +> // creates a request for the module {module} and its method {method} with params +> $request = new Request('{module}/{method}', [ +> 'param1' => 10, +> 'param2' => 'somevalue' +> ]); +> +>``` + +> ##### 4) catch possible errors (optional) +> ```php +> +> ... +> +> // if error is not Err::Success +> if( $request->error->get() !== Err::Success ) +> 'do something'; +> ``` + +> ##### 5) execute the request and catch response +> ```php +> +> ... +> +> $response = $request->dispatch(); +> ``` + +> ##### 6) catch response errors (optional) +> ```php +> +> ... +> +> // if error is not Err::Success +> if( $response->error->get() !== Err::Success ) +> 'do something'; +> ``` + +> ##### 7) catch response output +> ```php +> +> ... +> +> // fetch all outputs +> $output = $response->getAll(); +> +> // fetch specific output +> $specific = $response->get('someOutputName'); +> ``` + + +#### (3) From HTTP requests + +5 - class documentation +---- + +#### (4) Checker + +`Checker` checks the input values according to the type given in the configuration. + +The default types below are available in the default package. +To add a new type, just open the file `/build/api/Checker.php` and add an entry in the `switch` statement. + +##### Default types +|Type|Example|Description| +|---|---|---| +|`mixed`|`[9,"a"]`, `"a"`|Any content (can be simple or complex)| +|`id`|`10`, `"23"`|Positive integer number between `0` and `2147483647`| +|`numeric`|`-10.2`, `"23"`|Any number, `null` and the string `"null"`| +|`text`|`"Hello!"`|String that can be of any length (even empty)| +|`hash`|`"4612473aa81f93a878674f9ebffa8d63a1b51ea28dcdcdb1e89eb512aae9b77e"`|String with a length of 40 or 64, containing only hexadecimal characters| +|`alphanumeric`|`"abc029.-sd9"`|String containing only alphanumeric, ___, _-_, and _._ characters| +|`letters`|`"abc -sd"`|String containing only letters, _-_, and space characters| +|`mail`|`"a.b@c.def"`|Valid email address| +|`number`|`0102030405`|Phone number, following formats allowed : `06`, `+336`, `+33 6`| +|`array`|`[1, 3]`|Non-empty array| +|`object`|_works only within php_|Non-empty object| +|`boolean`|`true`, `false`|Boolean| +|`varchar(a,b)`|`"Hello!"`|String with a length between `a` and `b` (included)| +|`varchar(a,b,c)`|`"abc"`|String with a length between `a` and `b` (included) and matching the `c` type| + +##### Complex type : chainable array + +|Type|Sub-Type|Description| +|---|---|---| +|`array`|`a`|Array containing only entries matching the type `a`| + +> **Note:** It is possible to chain `array` type as many as needed. +**Ex.:** `array>` - Will match array only containing arrays that only contains `id` entries. + + + +## IV. How to use ? + +#### 1. Set up +To make your api work, you have to: +1. edit the configuration (mode details [here](#IV. configuration)) +2. create the modules classes and implement their methods according to your configuration (mode details [here](#V. implementation)) + +#### 2. Internal use in php +You can use your api from php internally without using HTTP request. + +First, you must require the `autoloader` file and load the API. + +```php + 'someusername']; // and the parameters + + // 1. Create the request + $request = new Request("$module/$method", $params); + + // 2. Execute request and catch response + $response = $request->dispatch(); + + // 3. Get response error code + $error_code = $response->error->get(); + + // 4. Get response output + $output = $response->getAll(); + +``` + + +#### 3. HTTP Request use in php +In order to setup an automatic bound from HTTP requests to API directly, you must use a router. Then you have some possibilities : + +**Case 1**: You want an URL like `http://www.host.com/{module}/{method}/` and pass parameters through POST or form-data. In order to set it up, you must catch the url starting at `/{module}/{method}` so you have to truncate the beginning (for instance if you have /api/{module}/..) + +**Case 2**: You want an URL like `http://www.host.com/api/` and pass all data through POST or form-data. + +## V. configuration + +```json +{ + + "{module_name}": { + + "{http_method}::{method_name}": { + "description": "{method_description}", + "permissions": ["{method_perm}"], + "options": { "download": "{is_downloadable}" }, + "parameters": { + "{name_param}": { "description": "{desc_param}", "type": "{type_param}", "optional": "{is_optional}" } + }, + "output": { + "{name_output}": { "description": "{desc_output}", "type": "{type_output}" } + } + } + + } +} +``` + +|variable|description|exemple| +|-------|-------|------| +|`{module_name}`|alphanumeric module name|"publications"| +|`{http_method}`|uppercase HTTP method|"POST"| +|`{method_name}`|alphanumeric method name|"article"| +|`{method_description}`|textual description|"Returns a specific article"| +|`{method_perm}`|permission array|`["poster", "admin", "user"]`| +|`{is_downloadable}`|If you want this method to return a file|`true`, `false`| +|`{name_param}`|Your param's name|"id_article"| +|`{desc_param}`|Your param's description|"Wanted article's id"| +|`{type_param}`|Your param's type (cf. Checker)|"Wanted article's type"| +|`{is_optional}`|Whether to make your param _required_|`true`, `false`| +|`{name_output}`|Your output's name|"article"| +|`{desc_output}`|Your output's description|"Article content"| + +## VI. implementation + +For the implementation let's assume that `/config/modules.json` looks like this +```json +{ + "user": { + "POST::sign_in": { + "" + } + } + +} +``` diff --git a/notice/api/2.2.md b/notice/api/2.2.md index 5a796a1..de13b90 100644 --- a/notice/api/2.2.md +++ b/notice/api/2.2.md @@ -30,6 +30,18 @@ The api works on a 2-level delegation structure : ## III. components and files +The API core is build over 5 classes: +> 1. **Request** - is the core of the package +2. **Response** - renders and manages the responses +3. **Authentification** - manages the authentification part (it has to be over written for each project) +4. **Checker** - manages auto checked types for input parameters +5. **ModuleFactory** - used to instanciate each module, transparent to the user + +The API file structure is composed with : +> 1. the configuration file is located at `/config/modules.php`. +2. the core folder located at `/build/api/core/` +3. the module's implementation folder located at `/build/api/module/` + ## IV. How to use ? #### 1. Set up @@ -86,12 +98,6 @@ In order to setup an automatic bound from HTTP requests to API directly, you mus ## V. configuration - -###### How to use -In order to use the API, you must begin by writting your _"methods"_ in the configuration file located at `/config/modules.json`. -In order to be understood, lets call `module` a set of methods, and `method` a function which outputs data from input parameters. - -**Configuration format** ```json { @@ -111,7 +117,6 @@ In order to be understood, lets call `module` a set of methods, and `method` a f } } - ``` |variable|description|exemple| @@ -129,15 +134,16 @@ In order to be understood, lets call `module` a set of methods, and `method` a f |`{name_output}`|Your output's name|"article"| |`{desc_output}`|Your output's description|"Article content"| - -###### Classes (advanced) -The API is managed through 5 classes : -1. Request - is the core of the module -2. Response - renders and manages the responses -3. Authentification - manages the authentification part (it has to be over written for each case) -4. Checker - manages auto checked parameters' types -5. ModuleFactory - to instanciate each module, transparent to the user - ## VI. implementation -For the implementation let's assume that your module is `xmodulex` and your method +For the implementation let's assume that `/config/modules.json` looks like this +```json +{ + "user": { + "POST::sign_in": { + "" + } + } + +} +``` diff --git a/src/packages/api/2.2/core/Checker.php b/src/packages/api/2.2/core/Checker.php index 7b4f3c0..0aa6056 100644 --- a/src/packages/api/2.2/core/Checker.php +++ b/src/packages/api/2.2/core/Checker.php @@ -81,11 +81,6 @@ return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0; break; - // Code RFID - case 'rfid': - return $checker && is_string($value) && preg_match('/^[\dA-F]{2}(\-[\dA-F]{2}){3,5}$/i', $value); - break; - // String quelconque (peut etre vide) case 'text': return $checker && is_string($value); diff --git a/src/packages/api/2.3/core/AuthSystem.php b/src/packages/api/2.3/core/AuthSystem.php new file mode 100644 index 0000000..33878cc --- /dev/null +++ b/src/packages/api/2.3/core/AuthSystem.php @@ -0,0 +1,20 @@ + Liste des permissions attendues + * + * @return error Erreur associée à la permission (Success/PermissionError/TokenError/etc) + * + */ + public static function permission($expected); + } + +?> diff --git a/src/packages/api/2.3/core/Checker.php b/src/packages/api/2.3/core/Checker.php new file mode 100644 index 0000000..0aa6056 --- /dev/null +++ b/src/packages/api/2.3/core/Checker.php @@ -0,0 +1,145 @@ + Type que l'on veut verifier + * @value Valeur a verifier + * + * @return match Retourne si oui ou non la valeur @value est du bon type @type + * + */ + public static function run($type, $value){ + $checker = true; + + /* [0] On verifie que $value n'est pas nul + =========================================================*/ + if( is_null($value) ) return false; + + + + /* [1] Si de type VARCHAR(min, max, flags) + =========================================================*/ + if( preg_match('/^varchar\((\d+), ?(\d+)((?:, ?\w+)+)?\)$/', $type, $match) ){ + // On recupere la taille min + $min = (int) $match[1]; + // On recupere la taille max + $max = (int) $match[2]; + + // On recupere le sous-type si défini + $flags = isset($match[3]) ? explode(',', substr($match[3], 1)) : null; + + // On effectue la verification de taille + $lenCheck = $checker && is_string($value) && strlen($value) <= $max && strlen($value) >= $min; + + // On vérifie les FLAGS s'il est donné + if( is_array($flags) ) + foreach( $flags as $flag ) + $lenCheck = $lenCheck && self::run($flag, $value); + + return $lenCheck; + } + + + /* [2] Si de type ARRAY(type_elements) + =========================================================*/ + if( preg_match('/^array<(.+)>$/', $type, $match) ){ + + // Si c'est pas un tableau on retourne une erreur + if( !is_array($value) ) + return false; + + + $elements_type = $match[1]; + + // On verifie le type pour chaque element + foreach($value as $element) + // Si erreur dans au moins 1 element, on retourne que c'est incorrect + if( !self::run($elements_type, trim($element) ) ) + return false; + + // Si aucune erreur, on retourne que tout est bon + return true; + } + + + /* [n] Sinon, tous les autres types definis + =========================================================*/ + switch($type){ + // Quoi que ce soit + case 'mixed': + return $checker && !is_null($value); + break; + + // Entier positif (id dans BDD) + case 'id': + return $checker && is_numeric($value) && $value <= 2147483647 && $value >= 0; + break; + + // String quelconque (peut etre vide) + case 'text': + return $checker && is_string($value); + + // Adresse mail (255 caracteres max) + case 'mail': + return $checker && is_string($value) && strlen($value) <= 50 && preg_match('/^[\w\.-]+@[\w\.-]+\.[a-z]{2,4}$/i', $value); + break; + + // Hash sha1/md5 + case 'hash': + return $checker && is_string($value) && preg_match('/^[\da-f]+$/i', $value) && (strlen($value) == 40 || strlen($value) == 64); + break; + + case 'alphanumeric': + return $checker && is_string($value) && preg_match('/^[\w\.-]+$/ui', $value); + break; + + case 'letters': + return $checker && is_string($value) && preg_match('/^[a-z -]+$/i', $value); + break; + + case 'status': + return $checker && is_numeric($value) && floor($value) == $value && $value >= 0 && $value <= 100; + break; + + // Tableau non vide + case 'array': + return $checker && is_array($value) && count($value) > 0; + break; + + // Boolean + case 'boolean': + return $checker && is_bool($value); + break; + + // Objet non vide + case 'object': + return $checker && is_object($value) && count((array) $value) > 0; + break; + + // Chaine JSON (on vérifie via le parser) + case 'json': + return $checker && is_string($value) && json_decode($value, true) !== NULL; + break; + + case 'numeric': + return $checker && (is_numeric($value) || $value == null || $value == 'null'); + break; + + default: + return false; + break; + } + + return $checker; + + } + + + } +?> diff --git a/src/packages/api/2.3/core/ModuleFactory.php b/src/packages/api/2.3/core/ModuleFactory.php new file mode 100644 index 0000000..527d5e9 --- /dev/null +++ b/src/packages/api/2.3/core/ModuleFactory.php @@ -0,0 +1,31 @@ + Nom du module + * @arguments [OPTIONNEL] Arguments à passer au constructeur + * + * @return instance Instance du module en question + * + */ + public static function getModule($module, $arguments=[]){ + /* (1) On gère les arguments */ + $arguments = is_array($arguments) ? $arguments : []; + + /* (1) On vérifie que la classe existe */ + if( !file_exists(__BUILD__."/api/module/$module.php") ) + return false; + + /* (2) On récupère la classe */ + $class_name = "\\api\\module\\$module"; + + /* (3) On retourne une instance */ + return new $class_name($arguments); + } + + } diff --git a/src/packages/api/2.3/core/Request.php b/src/packages/api/2.3/core/Request.php new file mode 100644 index 0000000..8db689c --- /dev/null +++ b/src/packages/api/2.3/core/Request.php @@ -0,0 +1,608 @@ + false + ]; + private static $authsystem = null; + + // Attributs prives utiles (initialisation) + private $path; + private $params; + private $modules; + private $options; + + // Contiendra la reponse a la requete + public $answer; + + // Contiendra l'etat de la requete + public $error; + + + + + + /* CONSTRUCTEUR D'UNE REQUETE DE MODULE + * + * @path Chemin de delegation ("module/methode") + * @param Tableau associatif contenant les parametres utiles au traitement + * + * @return status Retourne si oui ou non tout s'est bien passe + * + */ + public function __construct($path=null, $params=null){ + /* [1] Fetch HttpRequest correct data + =========================================================*/ + /* (1) Parse HttpRequest data because php doesn't parse it for non-POST HTTP method */ + $httprequest = new HttpRequest(); + $_POST = $httprequest->POST(); + + + /* [2] Initialisation + =========================================================*/ + /* (1) Erreur par défaut */ + $this->error = new Error(Err::Success); + + /* (2) Si pas parametre manquant, on quitte */ + if( $path == null ){ + $this->error->set(Err::MissingPath); + return false; + } + + + /* [3] On met a jour la configuration + =========================================================*/ + /* (1) Section Title */ + $this->modules = json_decode( file_get_contents(self::config_path()), true ); + + /* (2) Gestion de l'erreur de parsage */ + if( $this->modules == null ){ + $this->error->set(Err::ParsingFailed, 'json'); + return false; + } + + + + /* [4] Verification des types des parametres + =========================================================*/ + /* (1) Section Title */ + if( !is_string($path) ){ // Si le type est incorrect + $this->error->set(Err::WrongPathModule); + return false; // On retourne FALSE, si erreur + } + + /* (2) Section Title */ + $params = (is_array($params)) ? $params : []; + + /* (3) On définit en constante la méthode HTTP */ + define('__HTTP_METHOD__', strtoupper($_SERVER['REQUEST_METHOD'])); + + + /* [5] Verification du chemin (existence module+methode) + =========================================================*/ + if( !$this->checkPath($path) ) // Verification de la coherence du chemin + attribution + return false; + + + /* [6] Verification des droits + =========================================================*/ + if( !$this->checkPermission() ) // Si on a pas les droits + return false; + + + /* [7] Verification des parametres (si @type est defini) + =========================================================*/ + if( !$this->checkParams($params) ) // Verification de tous les types + return false; + + + /* [8] Récupèration des options + =========================================================*/ + $this->buildOptions(); + + + /* [9] Construction de l'objet + =========================================================*/ + $this->params = $params; + $this->error->set(Err::Success); + + return true; // On retourne que tout s'est bien passe + + } + + + + /* DEFINIT LE SYSTEME D'AUTHENTIFICATION + * + * @instance Instance de type AuthSystem + * + * @return success Whether the AuthSystem is valid or not + * + */ + public static function setAuthSystem($instance=null){ + /* (1) Check instance type */ + if( !($instance instanceof AuthSystem) ) + return false; + + /* (2) Store instance */ + self::$authsystem = $instance; + } + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET REMPLIE LA REPONSE + * + * @return answer Retourne une reponse de type si tout s'est bien passe + * + */ + public function dispatch(){ + /* [0] Si c'est un download, on lance la methode `download()` + =========================================================*/ + if( $this->options['download'] === true ) + return $this->download(); + + /* [1] On verifie qu'aucune erreur n'a ete signalee + =========================================================*/ + if( $this->error->get() !== Err::Success ) // si il y a une erreur + return new Response($this->error); // on la passe a la reponse + + + /* [2] On essaie d'instancier le module + =========================================================*/ + $instance = ModuleFactory::getModule($this->path['module']); + + if( $instance === false ){ + $this->error->set(Err::UncallableModule, $this->path['module']); + return new Response($this->error); + } + + /* [3] On verifie que la methode est amorcable + =========================================================*/ + if( !is_callable([$instance, $this->getModuleMethod()]) ){ + $this->error->set(Err::UncallableMethod, preg_replace('/\w+::/i', '', $this->path['method']) ); + return new Response($this->error); + } + + + /* [4] On amorce la methode + =========================================================*/ + /* (1) On lance la fonction */ + $returned = call_user_func( [$instance, $this->getModuleMethod()], $this->params ); + + /* (2) On appelle le destructeur (si défini) */ + $instance = null; + + + /* [5] Gestion de la reponse + =========================================================*/ + /* (1) On construit la réponse avec l'erreur */ + $response = new Response($this->error); + + /* (2) On ajoute les données */ + $response->appendAll($returned); + + // On retourne la réponse + return $response; + } + + + + /* EXECUTE LE TRAITEMENT ASSOCIE ET RENVOIE UN FICHIER AVEC LE HEADER ET LE BODY SPECIFIE + * + */ + public function download(){ + /* [1] On verifie qu'aucune erreur n'a ete signalee + =========================================================*/ + if( $this->error->get() !== Err::Success ) // si il y a une erreur + return new Response($this->error); // on la passe a la reponse + + + /* [2] On essaie d'instancier le module + =========================================================*/ + $instance = ModuleFactory::getModule($this->path['module']); + + if( $instance === false ){ + $this->error->set(Err::UncallableModule); + return new Response($this->error); + } + + /* [3] On verifie que la methode est amorcable + =========================================================*/ + if( !is_callable([$instance, $this->getModuleMethod()]) ){ + $this->error->set(Err::UncallableMethod); + return new Response($this->error); + } + + + /* [4] On amorce la methode + =========================================================*/ + /* (1) On lance la fonction */ + $returned = call_user_func( [$instance, $this->getModuleMethod()], $this->params ); + + /* (2) On appelle le destructeur (si défini) */ + $instance = null; + + + /* [5] Vérification des erreurs et paramètres + =========================================================*/ + /* (1) Vérification de l'erreur retournée, si pas Success, on retourne l'erreur */ + if( isset($returned['error']) && $returned['error'] instanceof Error && $returned['error']->get() != Err::Success ){ + $this->error = $returned['error']; + return new Response($this->error); + } + + /* (2) Vérification du contenu, si pas défini */ + if( !isset($returned['body']) ){ + $this->error->set(Err::ParamError); + return new Response($this->error); + } + + /* (3) Si @headers n'est pas défini on met par défaut */ + if( !isset($returned['headers']) || !is_array($returned['headers']) ) + $returned['headers'] = []; + + $fromAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + + + /* [6.1] Si requête ajax on crée un fichier temporaire et on renvoie son URL + =========================================================*/ + if( $fromAjax ){ + + + $tmpfname = '/tmp/download_'.uniqid().'.php'; + $bodyfname = __ROOT__.'/tmp/content_'.uniqid().'.php'; + + /* (1) On crée le fichier temporaire */ + $tmpfnameroot = __ROOT__.$tmpfname; + $tmpfile = fopen($tmpfnameroot, 'w'); + + fwrite($tmpfile, '$value) + fwrite($tmpfile, "header(\"$header: $value\");".PHP_EOL); + + /* (3) Script qui écrira le contenu */ + // 1) On écrit le contenu dans un fichier temporaire (et oui encore) + $bodyfile = fopen($bodyfname, 'w'); + fwrite($bodyfile, $returned['body']); + fclose($bodyfile); + chmod($bodyfname, 0775); + + fwrite($tmpfile, "readfile('$bodyfname');".PHP_EOL); + + /* (4) Script qui supprimera les fichiers temporaires */ + fwrite($tmpfile, "unlink('$bodyfname');".PHP_EOL); + fwrite($tmpfile, "unlink(__FILE__);".PHP_EOL); + + fwrite($tmpfile, '?>'.PHP_EOL); + + /* (5) On ferme le fichier */ + fclose($tmpfile); + chmod($tmpfnameroot, 0775); + + $response = new Response($this->error); + $response->append('link', $tmpfname); + + return $response; + + /* [6.2] Gestion du download direct si pas AJAX + =========================================================*/ + }else{ + /* (1) On définit les headers */ + foreach($returned['headers'] as $header=>$value) + header($header.': '.$value); + + /* (2) On affiche le contenu */ + echo $returned['body']; + + return true; + } + } + + + /* DESERIALISATION A PARTIR DE L'URL ET DES DONNEES POST (OPT) + * + * @url Contenu de l'url formatté (commence à "/module/methode") + * @post [opt] Tableau des donnes + * + * @return instance Retourne un objet de type + * + * @note + * 1. `path` peut être dans l'url : /method/module + * `path` peut être dans les données $_POST + * 2. les données peuvent être dans l'url : /module/method/data1/data2/... + * les données peuvent être dans les données $_POST + * + */ + public static function remote($url, $data=null){ + is_null($data) && ($data = []); + + + /* [1] On verifie que le @path est renseigne + =========================================================*/ + /* (1) Si le path est dans @url */ + $pathInUrl = is_string($url) && preg_match('#^/?([\w_-]+/[\w_-]+)(?:/?|/((?:\w+/)*(?:\w+/?)))$#', $url, $urlMatches); + + /* (2) On récupère le @path + les arguments dans l'URL */ + if( $pathInUrl ){ + // {1} On ajoute le @path aux données // + $data['path'] = $urlMatches[1]; + + // {2} On ajoute les arguments d'URL aux données // + if( count($urlMatches) > 2 ){ + + $urlParams = explode('/', trim($urlMatches[2], '/')); + foreach($urlParams as $k=>$v) + $data["URL_$k"] = $v; + + } + + } + + + /* (2) On vérifie dans tous les cas si le path existe */ + if( !isset($data['path']) ) + return new Request(); + + + + /* [3] On met les paramètres en JSON + =========================================================*/ + /* (1) On initialise les paramètres*/ + $params = []; + + /* (2) On met tous les paramètres en json (sauf @path) */ + foreach($data as $name=>$value){ + if( $name === 'path' ) + continue; + + // {1} On met en JSON // + $json = json_decode( $value, true ); + + // {2} Si ok -> on remplace // + if( !is_null($json) ) + $params[$name] = $json; + + // {3} Sinon, on laisse tel quel // + else + $params[$name] = $value; + } + + /* [4] On retourne une instance de + =========================================================*/ + return new Request($data['path'], $params); + } + + + + + + /* VERIFICATION DU FORMAT ET DE LA COHERENCE DU CHEMIN SPECIFIE + * + * @path String correspondant au chemin de delegation ("module/methode") + * + * @return validity Retourne si oui ou non l'objet est correct + * + */ + private function checkPath($path){ + /* [1] Verification format general + =========================================================*/ + if( !preg_match('#^([\w_-]+)/([\w_-]+)$#i', $path, $matches) ){ // Si mauvais format + $this->error->set(Err::WrongPathModule); + return false; + } + + // On recupere les données de la regex + $module = $matches[1]; + $method = __HTTP_METHOD__.'::'.$matches[2]; + + + /* [2] Verification de l'existence du module (conf) + =========================================================*/ + if( !array_key_exists($module, $this->modules) ){ // Si le module n'est pas specifie dans la conf + $this->error->set(Err::UnknownModule, $module); + return false; // On retourne FALSE, si erreur + } + + + /* [3] Verification de l'existence de la methode (conf) + =========================================================*/ + if( array_key_exists($method, $this->modules[$module]) === false ){ // Si la methode n'est pas specifie dans la conf + $this->error->set(Err::UnknownMethod, preg_replace('/\w+::/i', '', $method) ); + return false; // On retourne FALSE, si erreur + } + + + + /* [4] Enregistrement du chemin et renvoi de SUCCESS + =========================================================*/ + $this->path = [ + 'module' => $module, + 'method' => $method + ]; + + return true; + } + + + + + + + /* RETOURNE SI ON A LA PERMISSION D'EXECUTER CETTE METHODE + * + * @return permission Retourne si on a les droits ou pas pour executer cette methode + * + */ + private function checkPermission(){ + /* [1] On recupere les informations utiles + =========================================================*/ + // On recupere le nom de la methode + $method = $this->modules[$this->path['module']][$this->path['method']]; + + // Si aucune permission n'est definie + if( !isset($method['permissions']) || !is_array($method['permissions']) || count($method['permissions']) < 1 ) + return true; + + + + /* [2] Vérification des permissions et de l'authentification + =========================================================*/ + $granted = self::$authsystem::permission( $method['permissions'] ); + + /* (1) On retourne FAUX si aucun droit n'a ete trouve */ + if( $granted->get() !== Err::Success ){ + $this->error = $granted; + return false; + } + + + + + /* On retourne VRAI si la permission est ok */ + return true; + } + + + + + /* VERIFICATION DU TYPE DES PARAMETRES ENVOYES + * + * @params Tableau associatif contenant les parametres + * @params peut se voir rajouter les paramètres optionnels s'ils ne sont pas renseignés (initialisés à NULL) + * + * @return correct Retourne si oui ou non les parametres ont le bon type + * + */ + private function checkParams(&$params){ + /* [1] On verifie qu'il ne manque aucun parametre + =========================================================*/ + // Si @params n'est pas un tableau + if( !is_array($params) ){ + $this->error->set(Err::ConfigError); + return false; + } + + $method = $this->modules[$this->path['module']][$this->path['method']]; + + + /* [2] Si le type est defini, pour chaque param, on teste + =========================================================*/ + foreach($method['parameters'] as $name=>$paramsdata){ + /* (1) On récupère si le paramètre est optionnel ou pas */ + $optional = isset($paramsdata['optional']) && $paramsdata['optional'] === true; + + /* (2) Récupère si le paramètre est un fichier et définit comme de type 'FILE' */ + $isFile = isset($paramsdata['type']) && $paramsdata['type'] == 'FILE' && isset($_FILES[$name]); + + /* (3) Si le paramètre est obligatoire et qu'il n'est pas donné -> erreur */ + if( !isset($params[$name]) && !$optional && !$isFile ){ + $this->error->set(Err::MissingParam, $name); + return false; + } + + /* (4) Si le type n'est pas defini, on a pas besoin de le vérifier */ + if( !isset($paramsdata['type']) ) + continue; + + /* (5) Si le paramètre est optionnel et n'est pas donné */ + if( $isFile || $optional && (!isset($params[$name]) || is_null($params[$name])) ){ + // On le crée le param optionnel avec la valeur NULL + $params[$name] = null; + + // On donne une référence vers le fichier, si c'en est un + if( $isFile ) + $params[$name] = &$_FILES[$name]; + + continue; // On passe au paramètre suivant + + + /* (6) Si le paramètre est renseigné */ + }else + // Si la verification est fausse, on retourne faux + if( !Checker::run($paramsdata['type'], $params[$name]) ){ + $this->error->set(Err::WrongParam, $name, $paramsdata['type']); + return false; + } + + } + + /* [3] Gestion du retour, si tout s'est bien passe + =========================================================*/ + return true; + } + + + + + + /* AJOUT DES OPTIONS A PARTIR DE LA CONFIGURATION + * + */ + private function buildOptions(){ + /* [0] On récupère les options de la méthode en cours + =========================================================*/ + $method = $this->modules[$this->path['module']][$this->path['method']]; + + /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ + if( !isset($method['options']) || !is_array($method['options']) ) + return true; + + /* (2) Par défaut on définit les options par défaut */ + $this->options = self::$default_options; + + + /* (3) On récupère les options données */ + $options = $method['options']; + + + /* [1] Gestion des différentes options + =========================================================*/ + foreach($options as $option=>$value){ + /* (1) On ne prend en compte l'option que si elle est dans les options par défaut */ + if( !isset(self::$default_options[$option]) ) + continue; + + /* (2) Le type de la valeur doit être le même que celui de la valeur par défaut */ + if( gettype($value) != gettype(self::$default_options[$option]) ) + continue; + + /* (3) Si tout est bon, on définit la valeur */ + $this->options[$option] = $value; + } + + return true; + + } + + + + + + /* RENVOI LE CHEMIN D'AMORCAGE DE LA METHODE + * + * @return method Retourne le chemin d'amorcage de la method + * + */ + private function getModuleMethod(){ + /* (1) On essaie de trouver le bon nom */ + return preg_replace('/\w+::/i', '', $this->path['method']); + } + + + } + +?> diff --git a/src/packages/api/2.3/core/Response.php b/src/packages/api/2.3/core/Response.php new file mode 100644 index 0000000..840f91b --- /dev/null +++ b/src/packages/api/2.3/core/Response.php @@ -0,0 +1,122 @@ + Erreur passee par la requete (si existe) + * + */ + public function __construct($error=null){ + if( !( $error instanceof Error ) ) + $error = new Error(Err::Success); + + $this->data = []; + $this->error = $error; + } + /* AJOUTE UNE DONNEE A LA REPONSE + + + * + * @key Le nom de la valeur a ajouter + * @value La valeur a ajouter + * + */ + public function append($key, $value){ + // Ajoute une entree pour la cle @key et de valeur @value + $this->data[$key] = $value; + + return $this; + } + + + /* AJOUTE TOUTES LES DONNEES A LA REPONSE + * + * @dataset Le tableau associatif correspondant a la reponse + * + */ + public function appendAll($dataset){ + // Si ce n'est pas un tableau, on ne fais rien + if( !is_array($dataset) ) + return $this; + + // Si une valeur contient une erreur + if( array_key_exists('error', $dataset) && $dataset['error'] instanceof Error){ + // On definit cette erreur + $this->error = $dataset['error']; + // On enleve cette entree des donnees + unset($dataset['error']); + } + + // Ajoute une entree pour la cle @key et de valeur @value + $this->data = $dataset; + + return $this; + } + /* RECUPERE UNE DONNEE DE LA REPONSE + + + * + * @key Le nom de la valeur a recuperer + * + * @return value La valeur a cette cle + * @return error Retourne NULL si aucune valeur pour cette cle + * + */ + public function get($key){ + // Si la valeur de cle @key n'existe pas, on retourne NULL + if( !isset($this->data[$key]) ) + return null; + + // Sinon, on retourne la valeur associee + return $this->data[$key]; + } + + + /* RECUPERE TOUTES LES DONNEES DE LA REPONSE + * + * @return data Les donnees de la reponse + * + */ + public function getAll(){ + // Sinon, on retourne la valeur associee + return $this->data; + } + + + /* SERIALISATION A PARTIR DES DONNEES + * + * @return json Retourne les donnees serialisees + * + */ + public function serialize(){ + + // Code Http + $this->error->setHttpCode(); + + // Type de contenu + header('Content-Type: application/json; charset=utf-8'); + + // On rajoute l'erreur au message + $returnData = array_merge([ + 'error' => $this->error->get(), + 'ErrorDescription' => $this->error->explicit() + ], + $this->data + ); + + return json_encode($returnData); + + } + } + +?> diff --git a/src/packages/api/2.3/module/RESTexample.php b/src/packages/api/2.3/module/RESTexample.php new file mode 100644 index 0000000..f3d929a --- /dev/null +++ b/src/packages/api/2.3/module/RESTexample.php @@ -0,0 +1,125 @@ + new Error(Err::ModuleError)]; // or other `Err` constant + + return ['created_id' => "Article with title = `$title` and content=`$content`"]; + break; + + case 'GET': + // GET all/an article with the variable: + $URL_0; // id of article ; if not given -> null + + // ... + // process to get articles and get $output_get_articles + $success = true; + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + // optional param not given is set to null + if( is_null($URL_0) ) + return ['articles' => [ "Article number `1`: sometitle / somecontent", "Article number `2`: sometitle / somecontent"] ]; + else + return ['articles' => [ "Article number `$URL_0`: sometitle / somecontent"] ]; + break; + + case 'VIEW': + // VIEW a specific article (download json file) with the variable: + $URL_0; // id of article ; if not given -> null + + // ... + // process to get articles and get $output_get_articles + $success = true; + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + // will download, but if AJAX will give a `link` to the file + return [ + 'headers' => [ + 'Content-Type' => 'application/json; charset=utf-8', + 'Content-Disposition' => 'attachment; filename=export'.date('_d_m_Y', time()).'.json', + 'Pragma' => 'no-cache', + 'Expires' => '0' + ], + 'body' => "Article number `$URL_0`: sometitle / somecontent" + ]; + break; + + case 'PUT': + // UPDATE an article with new content with variables: + $URL_0; // id of article to update + $title; // new article's title + $content; // new article's content + + // ... + // process to get $output_updated_article + $success = true; + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + return ['article' => "Article number `$URL_0`: $title / $content"]; + break; + + case 'DELETE': + // DELETEs an article with the variable: + $URL_0; // id of the article to remove + + // ... + // process to delete article + $success = true; + // ... + + if( !$success ) + return ['error' => new Error(Err::ModuleError)]; // or other `Err` constant + + return ['log' => "Article `$URL_0` successfully deleted"]; // returns success + break; + + // if no match -> error + default: + return ['error' => new Error(Err::UnknownHttpMethod)]; + break; + } + + } + + +}