[a-zA-Z0-9]+)$|' => array ( * 'handler' => 'get', * 'authenticated' => true, * 'api_mode' => false, * 'methods' => array('GET'), * ), * '|get/all$|' => => array ( * 'handler' => 'get_all', * 'authenticated' => true, * 'api_mode' => false, * 'methods' => array('GET', 'POST'), * ), * ) * */ $url_patterns =array(); /** * Add an URL pattern * * @param string|array $pattern The URL pattern or an array of patterns (required) * @param callable $handler The URL pattern handler (must be callable, required) * @param boolean $authenticated Permit to define if this URL is accessible only for * authenticated users (optional, default: true if the * special force_authentication function is defined, * false otherwise) * @param boolean $override Allow override if a command already exists with the * same name (optional, default: false) * @param boolean $api_mode Enable API mode (optional, default: false) * @param array|string|null $methods HTTP method (optional, default: array('GET', 'POST')) **/ function add_url_handler($pattern, $handler=null, $authenticated=null, $override=true, $api_mode=false, $methods=null) { $authenticated = ( is_null($authenticated)? function_exists('force_authentication'): (bool)$authenticated ); if (is_null($methods)) $methods = array('GET', 'POST'); elseif (!is_array($methods)) $methods = array($methods); global $url_patterns; if (is_array($pattern)) { if (is_null($handler)) foreach($pattern as $p => $h) add_url_handler($p, $h, $authenticated, $override, $api_mode, $methods); else foreach($pattern as $p) add_url_handler($p, $handler, $authenticated, $override, $api_mode, $methods); } else { if (!isset($url_patterns[$pattern])) { $url_patterns[$pattern] = array( 'handler' => $handler, 'authenticated' => $authenticated, 'api_mode' => $api_mode, 'methods' => $methods, ); } elseif ($override) { Log :: debug( "URL : override pattern '%s' with handler '%s' (old handler = '%s')". $pattern, format_callable($handler), vardump($url_patterns[$pattern]) ); $url_patterns[$pattern] = array( 'handler' => $handler, 'authenticated' => $authenticated, 'api_mode' => $api_mode, 'methods' => $methods, ); } else { Log :: debug("URL : pattern '$pattern' already defined : do not override."); } } } /** * Show error page * * @param $request UrlRequest|null The request (optional, default: null) * @param $error_code int|null The HTTP error code (optional, default: 400) * * @return void **/ function error_page($request=null, $error_code=null) { global $smarty; $http_errors = array( 400 => array( 'pagetitle' => _("Bad request"), 'message' => _("Invalid request."), ), 401 => array( 'pagetitle' => _("Authentication required"), 'message' => _("You have to be authenticated to access to this page."), ), 403 => array( 'pagetitle' => _("Access denied"), 'message' => _("You do not have access to this application. If you think this is an error, please contact support."), ), 404 => array( 'pagetitle' => _("Whoops ! Page not found"), 'message' => _("The requested page can not be found."), ), ); $error_code = ($error_code?intval($error_code):400); if (array_key_exists($error_code, $http_errors)) $error = $http_errors[intval($error_code)]; else $error = array( 'pagetitle' => _('Error'), 'message' => _('An unknown error occurred. If problem persist, please contact support.'), ); http_response_code($error_code); $smarty -> assign('message', $error['message']); display_template('error_page.tpl', $error['pagetitle']); exit(); } /* * Error 404 page */ /** * Error 404 handler * * @param UrlRequest|null $request The request (optional, default: null) * * @return void **/ function error_404($request=null) { error_page($request, 404); } $_404_url_handler = 'error_404'; function set_404_url_handler($handler=null) { global $_404_url_handler; $_404_url_handler = $handler; } /** * Interprets the requested URL and return the corresponding UrlRequest object * * @param $default_url string|null The default URL if current one does not * match with any configured pattern. * * @return UrlRequest The UrlRequest object corresponding to the the requested URL. */ function get_request($default_url=null) { global $url_patterns, $_404_url_handler; $current_url = get_current_url(); if ($current_url === false) { Log :: fatal( _('Unable to determine the requested page. '. 'If the problem persists, please contact support.') ); exit(); } if (!is_array($url_patterns)) { Log :: fatal('URL : No URL patterns configured !'); exit(); } Log :: debug("URL : current url = '$current_url'"); Log :: trace( "URL : check current url with the following URL patterns :\n - ". implode("\n - ", array_keys($url_patterns)) ); foreach ($url_patterns as $pattern => $handler_infos) { $m = url_match($pattern, $current_url, $handler_infos['methods']); if (is_array($m)) { $request = new UrlRequest($current_url, $handler_infos, $m); // Reset last redirect if (isset($_SESSION['last_redirect'])) unset($_SESSION['last_redirect']); Log :: trace("URL : result :\n".vardump($request)); return $request; } } if ($default_url !== false) { Log :: debug("Current url match with no pattern. Redirect to default url ('$default_url')"); redirect($default_url); exit(); } // Error 404 $api_mode = (strpos($current_url, 'api/') === 0); Log :: debug( "Current URL match with no pattern. Use error 404 handler (API mode=$api_mode)."); return new UrlRequest( $current_url, array( 'handler' => $_404_url_handler, 'authenticated' => false, 'api_mode' => $api_mode, ) ); } /** * Check if the current requested URL match with a specific pattern * * @param string $pattern The URL pattern * @param string|false $current_url The current URL (optional) * @param array|null $methods HTTP method (optional, default: no check) * * @return array|false The URL info if pattern matched, false otherwise. **/ function url_match($pattern, $current_url=false, $methods=null) { if ($methods && !in_array($_SERVER['REQUEST_METHOD'], $methods)) return false; if ($current_url === false) { $current_url = get_current_url(); if (!$current_url) return False; } if (preg_match($pattern, $current_url, $m)) { Log :: debug( "URL : Match found with pattern '$pattern' :\n\t". str_replace("\n", "\n\t", print_r($m, true))); return $m; } return False; } /** * Retreive current requested URL and return it * * @return string|false The current request URL or false if fail **/ function get_current_url() { Log :: trace("URL : request URI = '".$_SERVER['REQUEST_URI']."'"); $base = get_rewrite_base(); Log :: trace("URL : rewrite base = '$base'"); if ($_SERVER['REQUEST_URI'] == $base) return ''; if (substr($_SERVER['REQUEST_URI'], 0, strlen($base)) != $base) { Log :: error( "URL : request URI (".$_SERVER['REQUEST_URI'].") does not start with rewrite base ($base)"); return False; } $current_url = substr($_SERVER['REQUEST_URI'], strlen($base)); // URL contain params ? $params_start = strpos($current_url, '?'); if ($params_start !== false) // Params detected, remove it // No url / currrent url start by '?' ? if ($params_start == 0) return ''; else return substr($current_url, 0, $params_start); return $current_url; } /** * Try to detect rewrite base from public root URL * * @return string The detected rewrite base **/ function get_rewrite_base() { global $public_root_url; if (preg_match('|^https?://[^/]+/(.*)$|', $public_root_url, $m)) return '/'.remove_trailing_slash($m[1]).'/'; elseif (preg_match('|^/(.*)$|', $public_root_url, $m)) return '/'.remove_trailing_slash($m[1]).'/'; return '/'; } /** * Trigger redirect to specified URL (or homepage if omited) * * @param string|false $go The destination URL * * @return void **/ function redirect($go=false) { global $public_root_url; if ($go===false) $go = ""; // If more than one argument passed, format URL using sprintf & urlencode parameters elseif (func_num_args() > 1) $go = call_user_func_array( 'sprintf', array_merge( array($go), array_map('urlencode', array_slice(func_get_args(), 1)) ) ); if (is_absolute_url($go)) $url = $go; elseif (isset($public_root_url) && $public_root_url) { // Check $public_root_url end if (substr($public_root_url, -1)=='/') { $public_root_url=substr($public_root_url, 0, -1); } $url="$public_root_url/$go"; } else $url="/$go"; // Prevent loop if (isset($_SESSION['last_redirect']) && $_SESSION['last_redirect'] == $url) Log :: fatal( _('Unable to determine the requested page (loop detected). '. 'If the problem persists, please contact support.')); else $_SESSION['last_redirect'] = $url; Log :: debug("redirect($go) => Redirect to : <$url>"); header("Location: $url"); exit(); } /** * Handle the current requested URL * * Note: if the route required that user is authenticated, this method will * invoke the force_authentication() special function (or trigger a fatal error * if it's not defined). * * @param string|null $default_url The default URL if current one does not * match with any configured pattern. * * @return void **/ function handle_request($default_url=null) { global $smarty, $api_mode; $sentry_span = new SentrySpan('http.handle_request', 'Handle the HTTP request'); $request = get_request($default_url); if (!is_callable($request -> handler)) { Log :: error( "URL handler function %s does not exists !", format_callable($request -> handler)); Log :: fatal(_("This request cannot be processed.")); } if ($request -> api_mode) $api_mode = true; if (isset($smarty) && $smarty) $smarty -> assign('request', $request); // Check authentication (if need) if($request -> authenticated) if (function_exists('force_authentication')) force_authentication(); else Log :: fatal(_("Authentication required but force_authentication function is not defined.")); try { call_user_func($request -> handler, $request); } catch (Exception $e) { Log :: exception( $e, "An exception occured running URL handler function ".$request -> handler."()"); Log :: fatal(_("This request could not be processed correctly.")); } $sentry_span->finish(); } /** * Remove trailing slash in specified URL * * @param string $url The URL * * @return string The specified URL without trailing slash **/ function remove_trailing_slash($url) { if ($url == '/') return $url; elseif (substr($url, -1) == '/') return substr($url, 0, -1); return $url; } /** * Check an AJAX request and trigger a fatal error on fail * * Check if session key is present and valid and set AJAX * mode. * * @param string|null $session_key string The current session key (optional) * * @return void **/ function check_ajax_request($session_key=null) { global $ajax, $debug_ajax; $ajax = true; if (check_session_key($session_key)) fatal_error('Invalid request'); if ($debug_ajax) Log :: debug("Ajax Request : ".vardump($_REQUEST)); } /** * Get the public absolute URL * * @param string|null $relative_url Relative URL to convert (Default: current URL) * * @return string The public absolute URL **/ function get_absolute_url($relative_url=null) { global $public_root_url; if (!is_string($relative_url)) $relative_url = get_current_url(); if ($public_root_url[0] == '/') { Log :: debug( "URL :: get_absolute_url($relative_url): configured public root URL is relative ". "($public_root_url) => try to detect it from current request infos."); $public_root_url = ( 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'?'s':'').'://'. $_SERVER['HTTP_HOST'].$public_root_url); Log :: debug( "URL :: get_absolute_url($relative_url): detected public_root_url: $public_root_url"); } if (substr($relative_url, 0, 1) == '/') $relative_url = substr($relative_url, 1); $url = remove_trailing_slash($public_root_url)."/$relative_url"; Log :: debug("URL :: get_absolute_url($relative_url): result = $url"); return $url; } /** * Check if specified URL is absolute * * @param string $url The URL to check * * @return boolean True if specified URL is absolute, False otherwise **/ function is_absolute_url($url) { return boolval(preg_match('#^https?://#', $url)); } /** * Add parameter in specified URL * * @param string &$url The reference of the URL * @param string $param The parameter name * @param string $value The parameter value * @param boolean $encode Set if parameter value must be URL encoded (optional, default: true) * * @return string The completed URL */ function add_url_parameter(&$url, $param, $value, $encode=true) { if (strpos($url, '?') === false) $url .= '?'; else $url .= '&'; $url .= "$param=".($encode?urlencode($value):$value); return $url; } /** * URL request abstraction * * @author Benjamin Renard */ class UrlRequest { // The URL requested handler private $current_url = null; // The URL requested handler private $handler = null; // Request need authentication ? private $authenticated = true; // API mode enabled ? private $api_mode = false; // Parameters detected on requested URL private $url_params = array(); public function __construct($current_url, $handler_infos, $url_params=array()) { $this -> current_url = $current_url; $this -> handler = $handler_infos['handler']; $this -> authenticated = ( isset($handler_infos['authenticated'])? boolval($handler_infos['authenticated']):true); $this -> api_mode = ( isset($handler_infos['api_mode'])? boolval($handler_infos['api_mode']):false); $this -> url_params = $url_params; } /** * Get request info * * @param string $key The name of the info * * @return mixed The value **/ public function __get($key) { if ($key == 'current_url') return $this -> current_url; if ($key == 'handler') return $this -> handler; if ($key == 'authenticated') return $this -> authenticated; if ($key == 'api_mode') return $this -> api_mode; if ($key == 'referer') return $this -> get_referer(); if ($key == 'http_method') return $_SERVER['REQUEST_METHOD']; if (array_key_exists($key, $this->url_params)) { return urldecode($this->url_params[$key]); } // Unknown key, log warning Log :: warning( "__get($key): invalid property requested\n%s", Log :: get_debug_backtrace_context()); } /** * Set request info * * @param string $key The name of the info * @param mixed $value The value of the info * * @return void **/ public function __set($key, $value) { if ($key == 'referer') $_SERVER['HTTP_REFERER'] = $value; elseif ($key == 'http_method') $_SERVER['REQUEST_METHOD'] = $value; else $this->url_params[$key] = $value; } /** * Check is request info is set * * @param string $key The name of the info * * @return bool True is info is set, False otherwise **/ public function __isset($key) { if ( in_array( $key, array('current_url', 'handler', 'authenticated', 'api_mode', 'referer', 'http_method') ) ) return True; return array_key_exists($key, $this->url_params); } /** * Get request parameter * * @param string $parameter The name of the parameter * @param bool $decode If true, the parameter value will be urldecoded * (optional, default: true) * * @return mixed The value or false if parameter does not exists **/ public function get_param($parameter, $decode=true) { if (array_key_exists($parameter, $this->url_params)) { if ($decode) return urldecode($this->url_params[$parameter]); return $this->url_params[$parameter]; } return false; } /** * Get request referer (if known) * * @return string|null The request referer URL if known, null otherwise */ public function get_referer() { if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER']) return $_SERVER['HTTP_REFERER']; return null; } } # vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab