From 98c518eac64f28af48a5087219a9c3ef2a669fc9 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 12 Sep 2017 18:32:01 +0200 Subject: [PATCH] added version:2.0 + router to manage downloads --- build/router/controller/ics.php | 57 ++++ build/router/controller/page.php | 63 +++++ build/router/controller/redirect.php | 34 +++ build/router/core/ControllerFactory.php | 51 ++++ build/router/core/Route.php | 100 +++++++ build/router/core/Router.php | 333 ++++++++++++++++++++++++ build/service/Updater.php | 2 +- config/routes.json | 32 +++ public_html/index.php | 25 +- 9 files changed, 674 insertions(+), 23 deletions(-) create mode 100644 build/router/controller/ics.php create mode 100644 build/router/controller/page.php create mode 100644 build/router/controller/redirect.php create mode 100644 build/router/core/ControllerFactory.php create mode 100644 build/router/core/Route.php create mode 100644 build/router/core/Router.php create mode 100644 config/routes.json diff --git a/build/router/controller/ics.php b/build/router/controller/ics.php new file mode 100644 index 0000000..757836a --- /dev/null +++ b/build/router/controller/ics.php @@ -0,0 +1,57 @@ + Calling URI + * + */ + public function __construct($url){ + $this->diplome_id = $url['diplome_id']; + } + + + /* CALL + * + */ + public function download(){ + + /* [1] Check .ics file + =========================================================*/ + /* (1) Set file name */ + $file_name = __ROOT__."/tmp/".$this->diplome_id.".ics"; + + /* (2) Check if exists */ + if( !file_exists($file_name) ) + die("An error occured. Please contact the developers.\n"); + + + /* [2] Display file + =========================================================*/ + /* (1) Headers */ + header('Content-Type: text/calendar; charset=utf-8'); + header('Content-Disposition: attachment; filename='.$this->diplome_id); + + /* (2) Body */ + readfile($file_name); + + + } + + /* POST-CALL + * + */ + public function __destruct(){ + + } + + + + } diff --git a/build/router/controller/page.php b/build/router/controller/page.php new file mode 100644 index 0000000..026c9fc --- /dev/null +++ b/build/router/controller/page.php @@ -0,0 +1,63 @@ + Calling URI + * + */ + public function __construct($url){ + $this->pagename = $url['page']; + + } + + + /* CALL + * + */ + public function home(){ + + + /* [1] Fetch configuration + =========================================================*/ + /* (1) Get config */ + try{ + $config = Config::load("http://sciences.univ-pau.fr/edt/_ressource.js", "http://sciences.univ-pau.fr/edt/_periode.js"); + + /* (2) Error management */ + }catch(\Exception $e){ + die("An error occured. Please contact the developers.\n"); + } + + /* (3) Get the diplome list */ + $diplomes = $config->getDiplomes(); + + + + /* [2] Display the links + =========================================================*/ + foreach($diplomes as $id=>$name){ + echo "$name
"; + + } + + } + + /* POST-CALL + * + */ + public function __destruct(){ + + } + + + + } diff --git a/build/router/controller/redirect.php b/build/router/controller/redirect.php new file mode 100644 index 0000000..31c0d26 --- /dev/null +++ b/build/router/controller/redirect.php @@ -0,0 +1,34 @@ + Calling URI + * + */ + public function __construct($url){ + } + + + /* CALL + * + */ + public function homepage(){ + header('Location: /'); + } + + /* POST-CALL + * + */ + public function __destruct(){ + + } + + + + } diff --git a/build/router/core/ControllerFactory.php b/build/router/core/ControllerFactory.php new file mode 100644 index 0000000..ad2d642 --- /dev/null +++ b/build/router/core/ControllerFactory.php @@ -0,0 +1,51 @@ + Nom du controller + * + * @return exists Si oui ou non le controller existe + * + */ + public static function checkController($controller){ + /* (1) Check type + pattern */ + if( !is_string($controller) || !preg_match('/^[A-Za-z_]\w+$/', $controller) ) + return false; + + /* (2) On vérifie que la classe existe */ + if( !file_exists(__BUILD__."/router/controller/$controller.php") ) + return false; + + /* (3) Sinon il existe */ + return true; + } + + + + /* INSTANCIE UN CONTROLLER + * + * @controller Nom du controller + * @arguments [OPTIONNEL] Arguments à passer au constructeur + * + * @return instance Instance du controller en question + * + */ + public static function getController($controller, $arguments=[]){ + /* (1) On vérifie l'existance du controller */ + if( !self::checkController($controller) ) + return false; + + /* (2) On récupère la classe */ + $class_name = "\\router\\controller\\$controller"; + + /* (3) On retourne une instance */ + return new $class_name($arguments); + } + + } diff --git a/build/router/core/Route.php b/build/router/core/Route.php new file mode 100644 index 0000000..83bfd95 --- /dev/null +++ b/build/router/core/Route.php @@ -0,0 +1,100 @@ + Pattern correspondant a la route + * @controller Controller de la route + * @method Methode du controller + * + * @return instance Retour de l'instance courante + * + =========================================================*/ + public function __construct($pattern=null, $controller=null, $method=null){ + // Note: all arguments must be verified by 'Router->add' method + + /* (1) Pattern -> regex format */ + $this->pattern = "/^$pattern$/"; + + /* (2) Controller */ + $this->controller = $controller; + + /* (3) Controller's method */ + $this->method = $method; + + /* (4) Initialize matches */ + $this->matches = []; + + } + + + + /* [3] Checks if route matches URL + * + * @url URL + * + * @return matches If matches URL + * + =========================================================*/ + public function match($url){ + + /* (1) If doesn't matches @url -> false */ + if( !preg_match($this->pattern, $url, $matches) ) + return false; + + /* (2) Return only named matches */ + foreach($matches as $name=>$match) + if( !is_numeric($name) ) + $this->matches[$name] = $match; + + /* (4) Add complete URL */ + $this->matches['__URL__'] = $url; + + /* (5) Return status */ + return true; + } + + + /* [4] Method call + * + * @return response Response + * + =========================================================*/ + public function call(){ + /* (1) Instanciate controller */ + $instance = ControllerFactory::getController($this->controller, $this->matches); + + /* (2) Launch method & catch response */ + $response = call_user_func([$instance, $this->method]); + + /* (3) Call controller's destructor */ + $instance = null; + + /* (4) Return response */ + return $response; + } + + } diff --git a/build/router/core/Router.php b/build/router/core/Router.php new file mode 100644 index 0000000..f2200a7 --- /dev/null +++ b/build/router/core/Router.php @@ -0,0 +1,333 @@ +run(); + + } + + + /* [3] Constructor + * + * @url Current URL + * + * @return instance Instance du routeur + * + =========================================================*/ + public function __construct($url=null){ + /* (1) Checks arguments + ---------------------------------------------------------*/ + /* (1) Default value if incorrect */ + $this->url = is_string($url) ? $url : ''; + + /* (2) Add first '/' if missing */ + if( !preg_match('/^\//', $url) ) + $this->url = '/'.$this->url; + + + /* (2) Loads configuration + ---------------------------------------------------------*/ + /* (1) Tries to load configuration */ + $this->cnf = self::loadConfig(); + + /* (2) If error occurs, throw Exception */ + if( is_null($this->cnf) ) + throw new \Exception("[Router] Configuration file error found"); + + + /* (3) Set allowed HTTP methods + ---------------------------------------------------------*/ + /* (1) If not defined */ + if( !isset($this->cnf['methods']) ) + throw new \Exception('[Router] Configuration file error, \'methods\' clause missing'); + + /* (2) Try to clean methods */ + $this->http_methods = self::cleanMethods($this->cnf['methods']); + + /* (3) Manage error */ + if( is_null($this->http_methods) ) + throw new \Exception('[Router] Configuration file error. \'methods\' must be an array of HTTP methods ["GET", "POST", ...]'); + + + /* (4) Initialize routes + ---------------------------------------------------------*/ + /* (1) Init routes */ + $this->routes = []; + + foreach($this->http_methods as $method) + $this->routes[$method] = []; + + /* (2) Default configuration if missing */ + if( !isset($this->cnf['routes']) || !is_array($this->cnf['routes']) ) + $this->cnf['routes'] = []; + + + /* (5) Loads each route + ---------------------------------------------------------*/ + foreach($this->cnf['routes'] as $pattern=>$route){ + + /* (1) If missing (required) parameters */ + if( !isset($route['controller']) ) + continue; + + /* (2) Default value for 'methods' */ + ( !isset($route['methods']) || !is_array($route['methods']) ) && ($route['methods'] = $this->http_methods); + + /* (3) Default value for 'arguments' */ + ( !isset($route['arguments']) || !is_array($route['arguments']) ) && ($route['arguments'] = []); + + /* (4) Add route */ + $added = $this->add($pattern, $route['controller'], $route['arguments']); + + // if error -> next + if( $added === false ) + continue; + + + /* (5) Add route for each method */ + foreach($route['methods'] as $method) + if( in_array($method, $this->http_methods) ) + $this->routes[$method][] = $added; + + } + + + } + + + + + /* [4] Adds a route + * + * @pattern URL pattern with {somevar} variables within + * @controller Controller name + method "controllername:methodname" + * @arguments List of pattern's arguments and their RegExp composition (default is alphanumeric) + * + * @return route New instance of Route || false on error + * + =========================================================*/ + public function add($pattern=null, $controller=null, $arguments=[]){ + + /* (1) Format and check pattern + ---------------------------------------------------------*/ + /* (1) If not a string */ + if( !is_string($pattern) ) + return false; + + /* (2) Format pattern and check result */ + $pattern = self::formatPattern($pattern, $arguments); + + if( $pattern === false ) + return false; + + + /* (2) Check controller + ---------------------------------------------------------*/ + /* (1) Check default type */ + if( !is_string($controller) || !preg_match('/^([A-Za-z_]\w+):([A-Za-z_]\w+)$/', $controller, $c_matches) ) + return false; + + /* (2) Check existence */ + if( !ControllerFactory::checkController($c_matches[1]) ) + return false; + + + /* (3) Check method + ---------------------------------------------------------*/ + if( !method_exists('\\router\\controller\\'.$c_matches[1], $c_matches[2]) ) + return false; + + + /* (4) Return new route + ---------------------------------------------------------*/ + return new Route($pattern, $c_matches[1], $c_matches[2]); + } + + + + + /* [5] Router launch + * + =========================================================*/ + public function run(){ + /* (1) Manage HTTP method + ---------------------------------------------------------*/ + /* (1) Fetch HTTP method */ + $httpMethod = $_SERVER['REQUEST_METHOD']; + + /* (2) If no route for this -> exit */ + if( !isset($this->routes[$httpMethod]) || count($this->routes[$httpMethod]) <= 0 ) + return false; + + + /* (2) Manage routes (matching) + ---------------------------------------------------------*/ + /* (1) Check for each HTTP method's route */ + foreach($this->routes[$httpMethod] as $route) + + /* (2) First route that matches -> call & return response */ + if( $route->match($this->url) ) + return $route->call(); + + + /* (3) If no route found -> return false + ---------------------------------------------------------*/ + return false; + } + + + /* FORMATS A PATTERN + * + * @pattern Pattern to process on + * @arguments List of used arguments, with regex if given + * @vars [OPT] If variable replacement have to be done + * + * @return formatted Formatted pattern || false on error + * + */ + public static function formatPattern($pattern, $arguments=[]){ + + /* (1) Check arguments + ---------------------------------------------------------*/ + /* (1) Check minimal length */ + if( strlen($pattern) < 1 ) + return false; + + /* (2) Arguments formatting */ + $arguments = !is_array($arguments) ? [] : $arguments; + + + /* (2) Replace special characters + replace vars + ---------------------------------------------------------*/ + /* (1) Check default URL format */ + if( !preg_match('/^(\/[\w\{\}-]*)*\/?$/', $pattern) ) + return false; + + /* (2) Escape special characters */ + $pattern = str_replace('/', '\\/', $pattern); + + /* (3) Add optional ending '/' */ + if( !preg_match('/\/$/', $pattern) ) + $pattern .= '\\/?'; + + /* (4) Replace variable by tagged capturing groups */ + $boundary = self::randBoundary(); + $pattern = preg_replace('/\{([a-z_][a-z0-9_]*)\}/i', '(?P<$1>'.$boundary.'-$1-'.$boundary.')', $pattern); + + + /* (3) Variable replacement + ---------------------------------------------------------*/ + /* (1) List variables */ + $vars = []; + $var_pattern = '/'.$boundary.'\-([A-Za-z_][A-Za-z0-9_]*)\-'.$boundary.'/'; + preg_match_all($var_pattern, $pattern, $matches); + + /* (2) For each matching variable -> replace with associated regex */ + if( is_array($matches) && isset($matches[1]) ){ + + foreach($matches[1] as $m=>$varname){ + + // {3.1.1} Not in @arguments -> default regex // + if( !isset($arguments[$varname]) || !is_string($arguments[$varname]) ){ + $pattern = str_replace($matches[0][$m], '[A-Za-z0-9_]+', $pattern); + continue; + } + + // {3.1.2} If variable in @arguments -> set regex without capturing-groups // + // $without_capg = str_replace('(', '(?:', $arguments[$varname]); + $pattern = str_replace($matches[0][$m], $arguments[$varname], $pattern); + + } + + } + + + /* (4) Return formatted pattern + ---------------------------------------------------------*/ + return $pattern; + } + + + + /* LOADS CONFIGURATION + * + * @return cnf Configuration content || NULL if error + * + */ + private static function loadConfig(){ + /* (1) Set configuration file's path */ + $cnfpath = self::config_path(); + + /* (2) Checks file */ + if( !file_exists($cnfpath) ) + return null; // throw new \Exception("[Router] Configuration file not found"); + + /* (3) Checks format -> null if error */ + return json_decode( file_get_contents($cnfpath), true ); + + } + + + /* CHECKS METHODS AND CLEAN THE LIST IF CORRECT + * + * @wanted List of wanted methods + * + * @return cleaned Cleaned methods || null if error + * + */ + private static function cleanMethods($wanted=[]){ + /* (1) Checks @wanted */ + if( !is_array($wanted) || count($wanted) < 1 ) + return null; // throw new \Exception('[Router] Configuration file error, \'methods\' must be an array containing managed HTTP methods'); + + /* (2) Set methods (uppercase and unique) */ + $cleaned = []; + + foreach($wanted as $method) + if( !in_array(strtoupper($method), $cleaned) ) + $cleaned[] = strtoupper($method); + + /* (3) Return cleaned method list */ + return $cleaned; + } + + } diff --git a/build/service/Updater.php b/build/service/Updater.php index 8101f21..c4367be 100644 --- a/build/service/Updater.php +++ b/build/service/Updater.php @@ -43,7 +43,7 @@ =========================================================*/ foreach($this->config->getDiplomes() as $d_id=>$d_name){ - file_put_contents(__ROOT__."/tmp/$d_id.ics", "BEGIN:VCALENDAR", FILE_APPEND); + file_put_contents(__ROOT__."/tmp/$d_id.ics", "BEGIN:VCALENDAR\nVERSION:2.0\n", FILE_APPEND); /* (1) Browse each date ---------------------------------------------------------*/ diff --git a/config/routes.json b/config/routes.json new file mode 100644 index 0000000..3229c84 --- /dev/null +++ b/config/routes.json @@ -0,0 +1,32 @@ +{ + + "methods": [ "GET" ], + + + "routes": { + + "/": { + "methods": ["GET"], + "controller": "page:home", + "arguments": {} + }, + + "/ics/{diplome_id}": { + "methods": ["GET"], + "controller": "ics:download", + "arguments": { + "diplome_id": "T\\d+" + } + }, + + "/{any}": { + "methods": ["GET"], + "controller": "redirect:homepage", + "arguments": { + "any": ".+" + } + } + + } + +} diff --git a/public_html/index.php b/public_html/index.php index 7a4acf1..86c88fd 100644 --- a/public_html/index.php +++ b/public_html/index.php @@ -1,27 +1,8 @@ run(); - /* (2) Error management */ - }catch(\Exception $e){ - die("An error occured. Please contact the developers.\n"); - } - - /* (3) Get the diplome list */ - $diplomes = $config->getDiplomes(); - - - - /* [2] Display the links - =========================================================*/ - foreach($diplomes as $id=>$name){ - echo "$name
"; - - }