$v) if(array_key_exists($k, $GLOBALS)) unset($GLOBALS[$k]); } if(ini_get('register_globals')) { unregister_globals( '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES'); ini_set('register_globals', 0); } # B. removing magic quotes /** * @access private * @param string $array * @return array */ function remove_magic_quotes($array) { foreach ($array as $k => $v) $array[$k] = is_array($v) ? remove_magic_quotes($v) : stripslashes($v); return $array; } if (get_magic_quotes_gpc()) { $_GET = remove_magic_quotes($_GET); $_POST = remove_magic_quotes($_POST); $_COOKIE = remove_magic_quotes($_COOKIE); ini_set('magic_quotes_gpc', 0); } if(function_exists('set_magic_quotes_runtime') && get_magic_quotes_runtime()) set_magic_quotes_runtime(false); # C. Disable error display # by default, no error reporting; it will be switched on later in run(). # ini_set('display_errors', 1); must be called explicitly in app file # if you want to show errors before running app ini_set('display_errors', 0); ## SETTING INTERNAL ROUTES _____________________________________________________ dispatch(array("/_lim_css/*.css", array('_lim_css_filename')), 'render_limonade_css'); /** * Internal controller that responds to route /_lim_css/*.css * * @access private * @return string */ function render_limonade_css() { option('views_dir', file_path(option('limonade_public_dir'), 'css')); $fpath = file_path(params('_lim_css_filename').".css"); return css($fpath, null); // with no layout } dispatch(array("/_lim_public/**", array('_lim_public_file')), 'render_limonade_file'); /** * Internal controller that responds to route /_lim_public/** * * @access private * @return void */ function render_limonade_file() { $fpath = file_path(option('limonade_public_dir'), params('_lim_public_file')); return render_file($fpath, true); } # # # # ============================================================================ # # 1. BASE # # ============================================================================ # ## ABSTRACTS ___________________________________________________________________ # Abstract methods that might be redefined by user: # # - function configure(){} # - function initialize(){} # - function autoload_controller($callback){} # - function before($route){} # - function after($output, $route){} # - function not_found($errno, $errstr, $errfile=null, $errline=null){} # - function server_error($errno, $errstr, $errfile=null, $errline=null){} # - function route_missing($request_method, $request_uri){} # - function before_exit(){} # - function before_render($content_or_func, $layout, $locals, $view_path){} # - function autorender($route){} # - function before_sending_header($header){} # # See abstract.php for more details. ## MAIN PUBLIC FUNCTIONS _______________________________________________________ /** * Set and returns options values * * If multiple values are provided, set $name option with an array of those values. * If there is only one value, set $name option with the provided $values * * @param string $name * @param mixed $values,... * @return mixed option value for $name if $name argument is provided, else return all options */ function option($name = null, $values = null) { static $options = array(); $args = func_get_args(); $name = array_shift($args); if(is_null($name)) return $options; if(!empty($args)) { $options[$name] = count($args) > 1 ? $args : $args[0]; } if(array_key_exists($name, $options)) return $options[$name]; return; } /** * Set and returns params * * Depending on provided arguments: * * * Reset params if first argument is null * * * If first argument is an array, merge it with current params * * * If there is a second argument $value, set param $name (first argument) with $value * * params('name', 'Doe') // set 'name' => 'Doe' * * * If there is more than 2 arguments, set param $name (first argument) value with * an array of next arguments * * params('months', 'jan', 'feb', 'mar') // set 'month' => array('months', 'jan', 'feb', 'mar') * * * @param mixed $name_or_array_or_null could be null || array of params || name of a param (optional) * @param mixed $value,... for the $name param (optional) * @return mixed all params, or one if a first argument $name is provided */ function params($name_or_array_or_null = null, $value = null) { static $params = array(); $args = func_get_args(); if(func_num_args() > 0) { $name = array_shift($args); if(is_null($name)) { # Reset params $params = array(); return $params; } if(is_array($name)) { $params = array_merge($params, $name); return $params; } $nargs = count($args); if($nargs > 0) { $value = $nargs > 1 ? $args : $args[0]; $params[$name] = $value; } return array_key_exists($name,$params) ? $params[$name] : null; } return $params; } /** * Set and returns template variables * * If multiple values are provided, set $name variable with an array of those values. * If there is only one value, set $name variable with the provided $values * * @param string $name * @param mixed $values,... * @return mixed variable value for $name if $name argument is provided, else return all variables */ function set($name = null, $values = null) { static $vars = array(); $args = func_get_args(); $name = array_shift($args); if(is_null($name)) return $vars; if(!empty($args)) { $vars[$name] = count($args) > 1 ? $args : $args[0]; } if(array_key_exists($name, $vars)) return $vars[$name]; return $vars; } /** * Sets a template variable with a value or a default value if value is empty * * @param string $name * @param string $value * @param string $default * @return mixed setted value */ function set_or_default($name, $value, $default) { return set($name, value_or_default($value, $default)); } /** * Running application * * @param string $env * @return void */ function run($env = null) { if(is_null($env)) $env = env(); # 0. Set default configuration $root_dir = dirname(app_file()); $lim_dir = dirname(__FILE__); $base_path = dirname(file_path($env['SERVER']['SCRIPT_NAME'])); $base_file = basename($env['SERVER']['SCRIPT_NAME']); $base_uri = file_path($base_path, (($base_file == 'index.php') ? '?' : $base_file.'?')); option('root_dir', $root_dir); option('limonade_dir', file_path($lim_dir)); option('limonade_views_dir', file_path($lim_dir, 'limonade', 'views')); option('limonade_public_dir',file_path($lim_dir, 'limonade', 'public')); option('public_dir', file_path($root_dir, 'public')); option('views_dir', file_path($root_dir, 'views')); option('controllers_dir', file_path($root_dir, 'controllers')); option('lib_dir', file_path($root_dir, 'lib')); option('error_views_dir', option('limonade_views_dir')); option('base_path', $base_path); option('base_uri', $base_uri); // set it manually if you use url_rewriting option('env', ENV_PRODUCTION); option('debug', true); option('session', LIM_SESSION_NAME); // true, false or the name of your session option('encoding', 'utf-8'); option('signature', LIM_NAME); // X-Limonade header value or false to hide it option('gzip', false); option('x-sendfile', 0); // 0: disabled, // X-SENDFILE: for Apache and Lighttpd v. >= 1.5, // X-LIGHTTPD-SEND-FILE: for Apache and Lighttpd v. < 1.5 # 1. Set handlers # 1.1 Set error handling ini_set('display_errors', 1); set_error_handler('error_handler_dispatcher', E_ALL ^ E_NOTICE); # 1.2 Register shutdown function register_shutdown_function('stop_and_exit'); # 2. Set user configuration call_if_exists('configure'); # 2.1 Set gzip compression if defined if(is_bool(option('gzip')) && option('gzip')) { ini_set('zlib.output_compression', '1'); } # 2.2 Set X-Limonade header if($signature = option('signature')) send_header("X-Limonade: $signature"); # 3. Loading libs require_once_dir(option('lib_dir')); fallbacks_for_not_implemented_functions(); # 4. Starting session if(!defined('SID') && option('session')) { if(!is_bool(option('session'))) session_name(option('session')); if(!session_start()) trigger_error("An error occured while trying to start the session", E_USER_WARNING); } # 5. Set some default methods if needed if(!function_exists('after')) { function after($output) { return $output; } } if(!function_exists('route_missing')) { function route_missing($request_method, $request_uri) { halt(NOT_FOUND, "($request_method) $request_uri"); } } call_if_exists('initialize'); # 6. Check request if($rm = request_method($env)) { if(request_is_head($env)) ob_start(); // then no output if(!request_method_is_allowed($rm)) halt(HTTP_NOT_IMPLEMENTED, "The requested method '$rm' is not implemented"); # 6.1 Check matching route if($route = route_find($rm, request_uri($env))) { params($route['params']); # 6.2 Load controllers dir if(!function_exists('autoload_controller')) { function autoload_controller($callback) { require_once_dir(option('controllers_dir')); } } autoload_controller($route['callback']); if(is_callable($route['callback'])) { # 6.3 Call before function call_if_exists('before', $route); # 6.4 Call matching controller function and output result $output = call_user_func_array($route['callback'], array_values($route['params'])); if(is_null($output)) $output = call_if_exists('autorender', $route); echo after(error_notices_render() . $output, $route); } else halt(SERVER_ERROR, "Routing error: undefined function '{$route['callback']}'", $route); } else route_missing($rm, request_uri($env)); } else halt(HTTP_NOT_IMPLEMENTED, "The requested method '$rm' is not implemented"); } /** * Stop and exit limonade application * * @access private * @param boolean exit or not * @return void */ function stop_and_exit($exit = true) { call_if_exists('before_exit', $exit); $headers = headers_list(); if(request_is_head()) { ob_end_clean(); } else { $flash_sweep = true; foreach($headers as $header) { // If a Content-Type header exists, flash_sweep only if is text/html // Else if there's no Content-Type header, flash_sweep by default if(stripos($header, 'Content-Type:') === 0) { $flash_sweep = stripos($header, 'Content-Type: text/html') === 0; break; } } if($flash_sweep) flash_sweep(); } if(defined('SID')) session_write_close(); if($exit) exit; } /** * Returns limonade environment variables: * * 'SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE', * 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' * * If a null argument is passed, reset and rebuild environment * * @param null @reset reset and rebuild environment * @return array */ function env($reset = null) { static $env = array(); if(func_num_args() > 0) { $args = func_get_args(); if(is_null($args[0])) $env = array(); } if(empty($env)) { if(empty($GLOBALS['_SERVER'])) { // Fixing empty $GLOBALS['_SERVER'] bug // http://sofadesign.lighthouseapp.com/projects/29612-limonade/tickets/29-env-is-empty $GLOBALS['_SERVER'] =& $_SERVER; $GLOBALS['_FILES'] =& $_FILES; $GLOBALS['_REQUEST'] =& $_REQUEST; $GLOBALS['_SESSION'] =& $_SESSION; $GLOBALS['_ENV'] =& $_ENV; $GLOBALS['_COOKIE'] =& $_COOKIE; } $glo_names = array('SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE'); $vars = array_merge($glo_names, request_methods()); foreach($vars as $var) { $varname = "_$var"; if(!array_key_exists($varname, $GLOBALS)) $GLOBALS[$varname] = array(); $env[$var] =& $GLOBALS[$varname]; } $method = request_method($env); $varname = "_$method"; if ((isset($_SERVER['CONTENT_TYPE'])) && (strpos($_SERVER['CONTENT_TYPE'], 'application/json') === 0)) { // handle PUT/POST requests which have JSON in request body $GLOBALS[$varname] = json_decode(file_get_contents('php://input'), true); } elseif($method == 'PUT' || $method == 'DELETE') { if(array_key_exists('_method', $_POST) && $_POST['_method'] == $method) { foreach($_POST as $k => $v) { if($k == "_method") continue; $GLOBALS[$varname][$k] = $v; } } else { parse_str(file_get_contents('php://input'), $GLOBALS[$varname]); } } } return $env; } /** * Returns application root file path * * @return string */ function app_file() { static $file; if(empty($file)) { $debug_backtrace = debug_backtrace(); $stacktrace = array_pop($debug_backtrace); $file = $stacktrace['file']; } return file_path($file); } # # # # ============================================================================ # # 2. ERROR # # ============================================================================ # /** * Associate a function with error code(s) and return all associations * * @param string $errno * @param string $function * @return array */ function error($errno = null, $function = null) { static $errors = array(); if(func_num_args() > 0) { $errors[] = array('errno'=>$errno, 'function'=> $function); } return $errors; } /** * Raise an error, passing a given error number and an optional message, * then exit. * Error number should be a HTTP status code or a php user error (E_USER...) * $errno and $msg arguments can be passsed in any order * If no arguments are passed, default $errno is SERVER_ERROR (500) * * @param int,string $errno Error number or message string * @param string,string $msg Message string or error number * @param mixed $debug_args extra data provided for debugging * @return void */ function halt($errno = SERVER_ERROR, $msg = '', $debug_args = null) { $args = func_get_args(); $error = array_shift($args); # switch $errno and $msg args # TODO cleanup / refactoring if(is_string($errno)) { $msg = $errno; $oldmsg = array_shift($args); $errno = empty($oldmsg) ? SERVER_ERROR : $oldmsg; } else if(!empty($args)) $msg = array_shift($args); if(empty($msg) && $errno == NOT_FOUND) $msg = request_uri(); if(empty($msg)) $msg = ""; if(!empty($args)) $debug_args = $args; set('_lim_err_debug_args', $debug_args); error_handler_dispatcher($errno, $msg, null, null); } /** * Internal error handler dispatcher * Find and call matching error handler and exit * If no match found, call default error handler * * @access private * @param int $errno * @param string $errstr * @param string $errfile * @param string $errline * @return void */ function error_handler_dispatcher($errno, $errstr, $errfile, $errline) { $back_trace = debug_backtrace(); while($trace = array_shift($back_trace)) { if($trace['function'] == 'halt') { $errfile = $trace['file']; $errline = $trace['line']; break; } } # Notices and warning won't halt execution if(error_wont_halt_app($errno)) { error_notice($errno, $errstr, $errfile, $errline); return; } else { # Other errors will stop application static $handlers = array(); if(empty($handlers)) { error(E_LIM_PHP, 'error_default_handler'); $handlers = error(); } $is_http_err = http_response_status_is_valid($errno); while($handler = array_shift($handlers)) { $e = is_array($handler['errno']) ? $handler['errno'] : array($handler['errno']); while($ee = array_shift($e)) { if($ee == $errno || $ee == E_LIM_PHP || ($ee == E_LIM_HTTP && $is_http_err)) { echo call_if_exists($handler['function'], $errno, $errstr, $errfile, $errline); exit; } } } } } /** * Default error handler * * @param string $errno * @param string $errstr * @param string $errfile * @param string $errline * @return string error output */ function error_default_handler($errno, $errstr, $errfile, $errline) { $is_http_err = http_response_status_is_valid($errno); $http_error_code = $is_http_err ? $errno : SERVER_ERROR; status($http_error_code); return $http_error_code == NOT_FOUND ? error_not_found_output($errno, $errstr, $errfile, $errline) : error_server_error_output($errno, $errstr, $errfile, $errline); } /** * Returns not found error output * * @access private * @param string $msg * @return string */ function error_not_found_output($errno, $errstr, $errfile, $errline) { if(!function_exists('not_found')) { /** * Default not found error output * * @param string $errno * @param string $errstr * @param string $errfile * @param string $errline * @return string */ function not_found($errno, $errstr, $errfile=null, $errline=null) { option('views_dir', option('error_views_dir')); $msg = h(rawurldecode($errstr)); return html("

Page not found:

{$msg}

", error_layout()); } } return not_found($errno, $errstr, $errfile, $errline); } /** * Returns server error output * * @access private * @param int $errno * @param string $errstr * @param string $errfile * @param string $errline * @return string */ function error_server_error_output($errno, $errstr, $errfile, $errline) { if(!function_exists('server_error')) { /** * Default server error output * * @param string $errno * @param string $errstr * @param string $errfile * @param string $errline * @return string */ function server_error($errno, $errstr, $errfile=null, $errline=null) { $is_http_error = http_response_status_is_valid($errno); $args = compact('errno', 'errstr', 'errfile', 'errline', 'is_http_error'); option('views_dir', option('limonade_views_dir')); $html = render('error.html.php', null, $args); option('views_dir', option('error_views_dir')); return html($html, error_layout(), $args); } } return server_error($errno, $errstr, $errfile, $errline); } /** * Set and returns error output layout * * @param string $layout * @return string */ function error_layout($layout = false) { static $o_layout = 'default_layout.php'; if($layout !== false) { option('error_views_dir', option('views_dir')); $o_layout = $layout; } return $o_layout; } /** * Set a notice if arguments are provided * Returns all stored notices. * If $errno argument is null, reset the notices array * * @access private * @param string, null $str * @return array */ function error_notice($errno = false, $errstr = null, $errfile = null, $errline = null) { static $notices = array(); if($errno) $notices[] = compact('errno', 'errstr', 'errfile', 'errline'); else if(is_null($errno)) $notices = array(); return $notices; } /** * Returns notices output rendering and reset notices * * @return string */ function error_notices_render() { if(option('debug') && option('env') > ENV_PRODUCTION) { $notices = error_notice(); error_notice(null); // reset notices $c_view_dir = option('views_dir'); // keep for restore after render option('views_dir', option('limonade_views_dir')); $o = render('_notices.html.php', null, array('notices' => $notices)); option('views_dir', $c_view_dir); // restore current views dir return $o; } } /** * Checks if an error is will halt application execution. * Notices and warnings will not. * * @access private * @param string $num error code number * @return boolean */ function error_wont_halt_app($num) { return $num == E_NOTICE || $num == E_WARNING || $num == E_CORE_WARNING || $num == E_COMPILE_WARNING || $num == E_USER_WARNING || $num == E_USER_NOTICE || $num == E_DEPRECATED || $num == E_USER_DEPRECATED || $num == E_LIM_DEPRECATED; } /** * return error code name for a given code num, or return all errors names * * @param string $num * @return mixed */ function error_type($num = null) { $types = array ( E_ERROR => 'ERROR', E_WARNING => 'WARNING', E_PARSE => 'PARSING ERROR', E_NOTICE => 'NOTICE', E_CORE_ERROR => 'CORE ERROR', E_CORE_WARNING => 'CORE WARNING', E_COMPILE_ERROR => 'COMPILE ERROR', E_COMPILE_WARNING => 'COMPILE WARNING', E_USER_ERROR => 'USER ERROR', E_USER_WARNING => 'USER WARNING', E_USER_NOTICE => 'USER NOTICE', E_STRICT => 'STRICT NOTICE', E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR', E_DEPRECATED => 'DEPRECATED WARNING', E_USER_DEPRECATED => 'USER DEPRECATED WARNING', E_LIM_DEPRECATED => 'LIMONADE DEPRECATED WARNING' ); return is_null($num) ? $types : $types[$num]; } /** * Returns http response status for a given error number * * @param string $errno * @return int */ function error_http_status($errno) { $code = http_response_status_is_valid($errno) ? $errno : SERVER_ERROR; return http_response_status($code); } # # # # ============================================================================ # # 3. REQUEST # # ============================================================================ # /** * Returns current request method for a given environment or current one * * @param string $env * @return string */ function request_method($env = null) { if(is_null($env)) $env = env(); $m = array_key_exists('REQUEST_METHOD', $env['SERVER']) ? $env['SERVER']['REQUEST_METHOD'] : null; if($m == "POST" && array_key_exists('_method', $env['POST'])) $m = strtoupper($env['POST']['_method']); if(!in_array(strtoupper($m), request_methods())) { trigger_error("'$m' request method is unknown or unavailable.", E_USER_WARNING); $m = false; } return $m; } /** * Checks if a request method or current one is allowed * * @param string $m * @return bool */ function request_method_is_allowed($m = null) { if(is_null($m)) $m = request_method(); return in_array(strtoupper($m), request_methods()); } /** * Checks if request method is GET * * @param string $env * @return bool */ function request_is_get($env = null) { return request_method($env) == "GET"; } /** * Checks if request method is POST * * @param string $env * @return bool */ function request_is_post($env = null) { return request_method($env) == "POST"; } /** * Checks if request method is PUT * * @param string $env * @return bool */ function request_is_put($env = null) { return request_method($env) == "PUT"; } /** * Checks if request method is DELETE * * @param string $env * @return bool */ function request_is_delete($env = null) { return request_method($env) == "DELETE"; } /** * Checks if request method is HEAD * * @param string $env * @return bool */ function request_is_head($env = null) { return request_method($env) == "HEAD"; } /** * Checks if request method is PATCH * * @param string $env * @return bool */ function request_is_patch($env = null) { return request_method($env) == "PATCH"; } /** * Returns allowed request methods * * @return array */ function request_methods() { return array("GET","POST","PUT","DELETE","HEAD","PATCH"); } /** * Returns current request uri (the path that will be compared with routes) * * (Inspired from codeigniter URI::_fetch_uri_string method) * * @return string */ function request_uri($env = null) { static $uri = null; if(is_null($env)) { if(!is_null($uri)) return $uri; $env = env(); } if(array_key_exists('uri', $env['GET'])) { $uri = $env['GET']['uri']; } else if(array_key_exists('u', $env['GET'])) { $uri = $env['GET']['u']; } // bug: dot are converted to _... so we can't use it... // else if (count($env['GET']) == 1 && trim(key($env['GET']), '/') != '') // { // $uri = key($env['GET']); // } else { $app_file = app_file(); $path_info = isset($env['SERVER']['PATH_INFO']) ? $env['SERVER']['PATH_INFO'] : @getenv('PATH_INFO'); $query_string = isset($env['SERVER']['QUERY_STRING']) ? $env['SERVER']['QUERY_STRING'] : @getenv('QUERY_STRING'); // Is there a PATH_INFO variable? // Note: some servers seem to have trouble with getenv() so we'll test it two ways if (trim($path_info, '/') != '' && $path_info != "/".$app_file) { if(strpos($path_info, '&') !== 0) { # exclude GET params $params = explode('&', $path_info); $path_info = array_shift($params); # populate $_GET foreach($params as $param) { if(strpos($param, '=') > 0) { list($k, $v) = explode('=', $param); $env['GET'][$k] = $v; } } } $uri = $path_info; } // No PATH_INFO?... What about QUERY_STRING? elseif (trim($query_string, '/') != '' && $query_string[0] == '/') { $uri = $query_string; $get = $env['GET']; if(count($get) > 0) { # exclude GET params $keys = array_keys($get); $first = array_shift($keys); if(strpos($query_string, $first) === 0) $uri = $first; } } elseif(array_key_exists('REQUEST_URI', $env['SERVER']) && !empty($env['SERVER']['REQUEST_URI'])) { $request_uri = rtrim($env['SERVER']['REQUEST_URI'], '?/').'/'; $base_path = $env['SERVER']['SCRIPT_NAME']; if($request_uri."index.php" == $base_path) $request_uri .= "index.php"; $uri = str_replace($base_path, '', $request_uri); if(option('base_uri') && strpos($uri, option('base_uri')) === 0) { $uri = substr($uri, strlen(option('base_uri'))); } if(strpos($uri, '?') !== false) { $uri = substr($uri, 0, strpos($uri, '?')) . '/'; } } elseif($env['SERVER']['argc'] > 1 && trim($env['SERVER']['argv'][1], '/') != '') { $uri = $env['SERVER']['argv'][1]; } } $uri = rtrim($uri, "/"); # removes ending / if(empty($uri)) { $uri = '/'; } else if($uri[0] != '/') { $uri = '/' . $uri; # add a leading slash } return rawurldecode($uri); } # # # # ============================================================================ # # 4. ROUTER # # ============================================================================ # /** * An alias of {@link dispatch_get()} * * @return void */ function dispatch($path_or_array, $callback, $options = array()) { dispatch_get($path_or_array, $callback, $options); } /** * Add a GET route. Also automatically defines a HEAD route. * * @param string $path_or_array * @param string $callback * @param array $options (optional). See {@link route()} for available options. * @return void */ function dispatch_get($path_or_array, $callback, $options = array()) { route("GET", $path_or_array, $callback, $options); route("HEAD", $path_or_array, $callback, $options); } /** * Add a POST route * * @param string $path_or_array * @param string $callback * @param array $options (optional). See {@link route()} for available options. * @return void */ function dispatch_post($path_or_array, $callback, $options = array()) { route("POST", $path_or_array, $callback, $options); } /** * Add a PUT route * * @param string $path_or_array * @param string $callback * @param array $options (optional). See {@link route()} for available options. * @return void */ function dispatch_put($path_or_array, $callback, $options = array()) { route("PUT", $path_or_array, $callback, $options); } /** * Add a DELETE route * * @param string $path_or_array * @param string $callback * @param array $options (optional). See {@link route()} for available options. * @return void */ function dispatch_delete($path_or_array, $callback, $options = array()) { route("DELETE", $path_or_array, $callback, $options); } /** * Add a PATCH route * * @param string $path_or_array * @param string $callback * @param array $options (optional). See {@link route()} for available options. * @return void */ function dispatch_patch($path_or_array, $callback, $options = array()) { route("PATCH", $path_or_array, $callback, $options); } /** * Add route if required params are provided. * Delete all routes if null is passed as a unique argument * Return all routes * * @see route_build() * @access private * @param string $method * @param string|array $path_or_array * @param callback $func * @param array $options (optional). Available options: * - 'params' key with an array of parameters: for parametrized routes. * those parameters will be merged with routes parameters. * @return array */ function route() { static $routes = array(); $nargs = func_num_args(); if( $nargs > 0) { $args = func_get_args(); if($nargs === 1 && is_null($args[0])) $routes = array(); else if($nargs < 3) trigger_error("Missing arguments for route()", E_USER_ERROR); else { $method = $args[0]; $path_or_array = $args[1]; $func = $args[2]; $options = $nargs > 3 ? $args[3] : array(); $routes[] = route_build($method, $path_or_array, $func, $options); } } return $routes; } /** * An alias of route(null): reset all routes * * @access private * @return void */ function route_reset() { route(null); } /** * Build a route and return it * * @access private * @param string $method allowed http method (one of those returned by {@link request_methods()}) * @param string|array $path_or_array * @param callback $callback callback called when route is found. It can be * a function, an object method, a static method or a closure. * See {@link http://php.net/manual/en/language.pseudo-types.php#language.types.callback php documentation} * to learn more about callbacks. * @param array $options (optional). Available options: * - 'params' key with an array of parameters: for parametrized routes. * those parameters will be merged with routes parameters. * @return array array with keys "method", "pattern", "names", "callback", "options" */ function route_build($method, $path_or_array, $callback, $options = array()) { $method = strtoupper($method); if(!in_array($method, request_methods())) trigger_error("'$method' request method is unkown or unavailable.", E_USER_WARNING); if(is_array($path_or_array)) { $path = array_shift($path_or_array); $names = $path_or_array[0]; } else { $path = $path_or_array; $names = array(); } $single_asterisk_subpattern = "(?:/([^\/]*))?"; $double_asterisk_subpattern = "(?:/(.*))?"; $optionnal_slash_subpattern = "(?:/*?)"; $no_slash_asterisk_subpattern = "(?:([^\/]*))?"; if($path[0] == "^") { if($path{strlen($path) - 1} != "$") $path .= "$"; $pattern = "#".$path."#i"; } else if(empty($path) || $path == "/") { $pattern = "#^".$optionnal_slash_subpattern."$#"; } else { $parsed = array(); $elts = explode('/', $path); $parameters_count = 0; foreach($elts as $elt) { if(empty($elt)) continue; $name = null; # extracting double asterisk ** if($elt == "**"): $parsed[] = $double_asterisk_subpattern; $name = $parameters_count; # extracting single asterisk * elseif($elt == "*"): $parsed[] = $single_asterisk_subpattern; $name = $parameters_count; # extracting named parameters :my_param elseif($elt[0] == ":"): if(preg_match('/^:([^\:]+)$/', $elt, $matches)) { $parsed[] = $single_asterisk_subpattern; $name = $matches[1]; }; elseif(strpos($elt, '*') !== false): $sub_elts = explode('*', $elt); $parsed_sub = array(); foreach($sub_elts as $sub_elt) { $parsed_sub[] = preg_quote($sub_elt, "#"); $name = $parameters_count; } // $parsed[] = "/".implode($no_slash_asterisk_subpattern, $parsed_sub); else: $parsed[] = "/".preg_quote($elt, "#"); endif; /* set parameters names */ if(is_null($name)) continue; if(!array_key_exists($parameters_count, $names) || is_null($names[$parameters_count])) $names[$parameters_count] = $name; $parameters_count++; } $pattern = "#^".implode('', $parsed).$optionnal_slash_subpattern."?$#i"; } return array( "method" => $method, "pattern" => $pattern, "names" => $names, "callback" => $callback, "options" => $options ); } /** * Find a route and returns it. * Parameters values extracted from the path are added and merged * with the default 'params' option of the route * If not found, returns false. * Routes are checked from first added to last added. * * @access private * @param string $method * @param string $path * @return array,false route array has same keys as route returned by * {@link route_build()} ("method", "pattern", "names", "callback", "options") * + the processed "params" key */ function route_find($method, $path) { $routes = route(); $method = strtoupper($method); foreach($routes as $route) { if($method == $route["method"] && preg_match($route["pattern"], $path, $matches)) { $options = $route["options"]; $params = array_key_exists('params', $options) ? $options["params"] : array(); if(count($matches) > 1) { array_shift($matches); $n_matches = count($matches); $names = array_values($route["names"]); $n_names = count($names); if( $n_matches < $n_names ) { $a = array_fill(0, $n_names - $n_matches, null); $matches = array_merge($matches, $a); } else if( $n_matches > $n_names ) { $names = range($n_names, $n_matches - 1); } $arr_comb = array_combine($names, $matches); $params = array_replace($params, $arr_comb); } $route["params"] = $params; return $route; } } return false; } # ============================================================================ # # 5. OUTPUT AND RENDERING # # ============================================================================ # /** * Returns a string to output * * It might use a template file, a function, or a formatted string (like {@link sprintf()}). * It could be embraced by a layout or not. * Local vars can be passed in addition to variables made available with the {@link set()} * function. * * @param string $content_or_func * @param string $layout * @param string $locals * @return string */ function render($content_or_func, $layout = '', $locals = array()) { $args = func_get_args(); $content_or_func = array_shift($args); $layout = count($args) > 0 ? array_shift($args) : layout(); $view_path = file_path(option('views_dir'),$content_or_func); if(function_exists('before_render')) list($content_or_func, $layout, $locals, $view_path) = before_render($content_or_func, $layout, $locals, $view_path); $vars = array_merge(set(), $locals); $flash = flash_now(); if(array_key_exists('flash', $vars)) trigger_error('A $flash variable is already passed to view. Flash messages will only be accessible through flash_now()', E_USER_NOTICE); else if(!empty($flash)) $vars['flash'] = $flash; $infinite_loop = false; # Avoid infinite loop: this function is in the backtrace ? if(function_exists($content_or_func)) { $back_trace = debug_backtrace(); while($trace = array_shift($back_trace)) { if($trace['function'] == strtolower($content_or_func)) { $infinite_loop = true; break; } } } if(function_exists($content_or_func) && !$infinite_loop) { ob_start(); call_user_func($content_or_func, $vars); $content = ob_get_clean(); } elseif(file_exists($view_path)) { ob_start(); extract($vars); include $view_path; $content = ob_get_clean(); } else { if(substr_count($content_or_func, '%') !== count($vars)) $content = $content_or_func; else $content = vsprintf($content_or_func, $vars); } if(empty($layout)) return $content; return render($layout, null, array('content' => $content)); } /** * Returns a string to output * * Shortcut to render with no layout. * * @param string $content_or_func * @param string $locals * @return string */ function partial($content_or_func, $locals = array()) { return render($content_or_func, null, $locals); } /** * Returns html output with proper http headers * * @param string $content_or_func * @param string $layout * @param string $locals * @return string */ function html($content_or_func, $layout = '', $locals = array()) { send_header('Content-Type: text/html; charset='.strtolower(option('encoding'))); $args = func_get_args(); return call_user_func_array('render', $args); } /** * Set and return current layout * * @param string $function_or_file * @return string */ function layout($function_or_file = null) { static $layout = null; if(func_num_args() > 0) $layout = $function_or_file; return $layout; } /** * Returns xml output with proper http headers * * @param string $content_or_func * @param string $layout * @param string $locals * @return string */ function xml($data) { send_header('Content-Type: text/xml; charset='.strtolower(option('encoding'))); $args = func_get_args(); return call_user_func_array('render', $args); } /** * Returns css output with proper http headers * * @param string $content_or_func * @param string $layout * @param string $locals * @return string */ function css($content_or_func, $layout = '', $locals = array()) { send_header('Content-Type: text/css; charset='.strtolower(option('encoding'))); $args = func_get_args(); return call_user_func_array('render', $args); } /** * Returns javacript output with proper http headers * * @param string $content_or_func * @param string $layout * @param string $locals * @return string */ function js($content_or_func, $layout = '', $locals = array()) { send_header('Content-Type: application/javascript; charset='.strtolower(option('encoding'))); $args = func_get_args(); return call_user_func_array('render', $args); } /** * Returns txt output with proper http headers * * @param string $content_or_func * @param string $layout * @param string $locals * @return string */ function txt($content_or_func, $layout = '', $locals = array()) { send_header('Content-Type: text/plain; charset='.strtolower(option('encoding'))); $args = func_get_args(); return call_user_func_array('render', $args); } /** * Returns json representation of data with proper http headers. * On PHP 5 < PHP 5.2.0, you must provide your own implementation of the * json_encode() function beore using json(). * * @param string $data * @param int $json_option * @return string */ function json($data, $json_option = 0) { send_header('Content-Type: application/json; charset='.strtolower(option('encoding'))); return version_compare(PHP_VERSION, '5.3.0', '>=') ? json_encode($data, $json_option) : json_encode($data); } /** * undocumented function * * @param string $filename * @param string $return * @return mixed number of bytes delivered or file output if $return = true */ function render_file($filename, $return = false) { # TODO implements X-SENDFILE headers // if($x-sendfile = option('x-sendfile')) // { // // add a X-Sendfile header for apache and Lighttpd >= 1.5 // if($x-sendfile > X-SENDFILE) // add a X-LIGHTTPD-send-file header // // } // else // { // // } $filename = str_replace('../', '', $filename); if(file_exists($filename)) { $content_type = mime_type(file_extension($filename)); $header = 'Content-type: '.$content_type; if(file_is_text($filename)) $header .= '; charset='.strtolower(option('encoding')); send_header($header); return file_read($filename, $return); } else halt(NOT_FOUND, "unknown filename $filename"); } /** * Call before_sending_header() if it exists, then send headers * * @param string $header * @return void */ function send_header($header = null, $replace = true, $code = false) { if(!headers_sent()) { call_if_exists('before_sending_header', $header); header($header, $replace, $code); } } # # # # ============================================================================ # # 6. HELPERS # # ============================================================================ # /** * Returns an url composed of params joined with / * A param can be a string or an array. * If param is an array, its members will be added at the end of the return url * as GET parameters "&key=value". * * @param string or array $param1, $param2 ... * @return string */ function url_for($params = null) { $paths = array(); $params = func_get_args(); $GET_params = array(); foreach($params as $param) { if(is_array($param)) { $GET_params = array_merge($GET_params, $param); continue; } if(filter_var_url($param)) { $paths[] = $param; continue; } $p = explode('/',$param); foreach($p as $v) { if($v != "") $paths[] = str_replace('%23', '#', rawurlencode($v)); } } $path = rtrim(implode('/', $paths), '/'); if(!filter_var_url($path)) { # it's a relative URL or an URL without a schema $base_uri = option('base_uri'); $path = file_path($base_uri, $path); } if(!empty($GET_params)) { $is_first_qs_param = true; $path_as_no_question_mark = strpos($path, '?') === false; foreach($GET_params as $k => $v) { $qs_separator = $is_first_qs_param && $path_as_no_question_mark ? '?' : '&'; $path .= $qs_separator . rawurlencode($k) . '=' . rawurlencode($v); $is_first_qs_param = false; } } if(DIRECTORY_SEPARATOR != '/') $path = str_replace(DIRECTORY_SEPARATOR, '/', $path); return $path; } /** * An alias of {@link htmlspecialchars()}. * If no $charset is provided, uses option('encoding') value * * @param string $str * @param string $quote_style * @param string $charset * @return void */ function h($str, $quote_style = ENT_NOQUOTES, $charset = null) { if(is_null($charset)) $charset = strtoupper(option('encoding')); return htmlspecialchars($str, $quote_style, $charset); } /** * Set and returns flash messages that will be available in the next action * via the {@link flash_now()} function or the view variable $flash. * * If multiple values are provided, set $name variable with an array of those values. * If there is only one value, set $name variable with the provided $values * or if it's $name is an array, merge it with current messages. * * @param string, array $name * @param mixed $values,... * @return mixed variable value for $name if $name argument is provided, else return all variables */ function flash($name = null, $value = null) { if(!defined('SID')) trigger_error("Flash messages can't be used because session isn't enabled", E_USER_WARNING); static $messages = array(); $args = func_get_args(); $name = array_shift($args); if(is_null($name)) return $messages; if(is_array($name)) return $messages = array_merge($messages, $name); if(!empty($args)) { $messages[$name] = count($args) > 1 ? $args : $args[0]; } if(!array_key_exists($name, $messages)) return null; else return $messages[$name]; return $messages; } /** * Set and returns flash messages available for the current action, included those * defined in the previous action with {@link flash()} * Those messages will also be passed to the views and made available in the * $flash variable. * * If multiple values are provided, set $name variable with an array of those values. * If there is only one value, set $name variable with the provided $values * or if it's $name is an array, merge it with current messages. * * @param string, array $name * @param mixed $values,... * @return mixed variable value for $name if $name argument is provided, else return all variables */ function flash_now($name = null, $value = null) { static $messages = null; if(is_null($messages)) { $fkey = LIM_SESSION_FLASH_KEY; $messages = array(); if(defined('SID') && array_key_exists($fkey, $_SESSION)) $messages = $_SESSION[$fkey]; } $args = func_get_args(); $name = array_shift($args); if(is_null($name)) return $messages; if(is_array($name)) return $messages = array_merge($messages, $name); if(!empty($args)) { $messages[$name] = count($args) > 1 ? $args : $args[0]; } if(!array_key_exists($name, $messages)) return null; else return $messages[$name]; return $messages; } /** * Delete current flash messages in session, and set new ones stored with * flash function. * Called before application exit. * * @access private * @return void */ function flash_sweep() { if(defined('SID')) { $fkey = LIM_SESSION_FLASH_KEY; $_SESSION[$fkey] = flash(); } } /** * Starts capturing block of text * * Calling without params stops capturing (same as end_content_for()). * After capturing the captured block is put into a variable * named $name for later use in layouts. If second parameter * is supplied, its content will be used instead of capturing * a block of text. * * @param string $name * @param string $content * @return void */ function content_for($name = null, $content = null) { static $_name = null; if(is_null($name) && !is_null($_name)) { set($_name, ob_get_clean()); $_name = null; } elseif(!is_null($name) && !isset($content)) { $_name = $name; ob_start(); } elseif(isset($name, $content)) { set($name, $content); } } /** * Stops capturing block of text * * @return void */ function end_content_for() { content_for(); } /** * Shows current memory and execution time of the application. * Returns only execution time if memory_get_usage() * isn't available. * ( That's the case before PHP5.2.1 if PHP isn't compiled with option * --enable-memory-limit. ) * * @access public * @return array */ function benchmark() { $res = array( 'execution_time' => (microtime(true) - LIM_START_MICROTIME) ); if(defined('LIM_START_MEMORY')) { $current_mem_usage = memory_get_usage(); $res['current_memory'] = $current_mem_usage; $res['start_memory'] = LIM_START_MEMORY; $res['average_memory'] = (LIM_START_MEMORY + $current_mem_usage) / 2; } return $res; } # # # # ============================================================================ # # 7. UTILS # # ============================================================================ # /** * Calls a function if exists * * @param callback $callback a function stored in a string variable, * or an object and the name of a method within the object * See {@link http://php.net/manual/en/language.pseudo-types.php#language.types.callback php documentation} * to learn more about callbacks. * @param mixed $arg,.. (optional) * @return mixed */ function call_if_exists($callback) { $args = func_get_args(); $callback = array_shift($args); if(is_callable($callback)) return call_user_func_array($callback, $args); return; } /** * Define a constant unless it already exists * * @param string $name * @param string $value * @return void */ function define_unless_exists($name, $value) { if(!defined($name)) define($name, $value); } /** * Return a default value if provided value is empty * * @param mixed $value * @param mixed $default default value returned if $value is empty * @return mixed */ function value_or_default($value, $default) { return empty($value) ? $default : $value; } /** * An alias of {@link value_or_default()} * * * @param mixed $value * @param mixed $default * @return mixed */ function v($value, $default) { return value_or_default($value, $default); } /** * Load php files with require_once in a given dir * * @param string $path Path in which are the file to load * @param string $pattern a regexp pattern that filter files to load * @param bool $prevents_output security option that prevents output * @return array paths of loaded files */ function require_once_dir($path, $pattern = "*.php", $prevents_output = true) { if($path[strlen($path) - 1] != "/") $path .= "/"; $filenames = glob($path.$pattern); if(!is_array($filenames)) $filenames = array(); if($prevents_output) ob_start(); foreach($filenames as $filename) require_once $filename; if($prevents_output) ob_end_clean(); return $filenames; } /** * Dumps a variable into inspectable format * * @param anything $var the variable to debug * @param bool $output_as_html sets whether to wrap output in
 tags. default: true
 * @return string the variable with output
 */
function debug($var, $output_as_html = true)
{ 
  if ( is_null($var) ) { return '[NULL]'; };
  $out = '';
  switch ($var) 
  { 
    case empty($var):
      $out = '[empty value]';
      break;
    
    case is_array($var):
      $out = var_export($var, true);
      break;
    
    case is_object($var):
      $out = var_export($var, true);
      break;
      
    case is_string($var):
      $out = $var;
      break;
    
    default:
      $out = var_export($var, true);
      break;
  }
  if ($output_as_html) { $out = "
\n" . h($out) ."
"; } return $out; } ## HTTP utils _________________________________________________________________ ### Constants: HTTP status codes define( 'HTTP_CONTINUE', 100 ); define( 'HTTP_SWITCHING_PROTOCOLS', 101 ); define( 'HTTP_PROCESSING', 102 ); define( 'HTTP_OK', 200 ); define( 'HTTP_CREATED', 201 ); define( 'HTTP_ACCEPTED', 202 ); define( 'HTTP_NON_AUTHORITATIVE', 203 ); define( 'HTTP_NO_CONTENT', 204 ); define( 'HTTP_RESET_CONTENT', 205 ); define( 'HTTP_PARTIAL_CONTENT', 206 ); define( 'HTTP_MULTI_STATUS', 207 ); define( 'HTTP_MULTIPLE_CHOICES', 300 ); define( 'HTTP_MOVED_PERMANENTLY', 301 ); define( 'HTTP_MOVED_TEMPORARILY', 302 ); define( 'HTTP_SEE_OTHER', 303 ); define( 'HTTP_NOT_MODIFIED', 304 ); define( 'HTTP_USE_PROXY', 305 ); define( 'HTTP_TEMPORARY_REDIRECT', 307 ); define( 'HTTP_BAD_REQUEST', 400 ); define( 'HTTP_UNAUTHORIZED', 401 ); define( 'HTTP_PAYMENT_REQUIRED', 402 ); define( 'HTTP_FORBIDDEN', 403 ); define( 'HTTP_NOT_FOUND', 404 ); define( 'HTTP_METHOD_NOT_ALLOWED', 405 ); define( 'HTTP_NOT_ACCEPTABLE', 406 ); define( 'HTTP_PROXY_AUTHENTICATION_REQUIRED', 407 ); define( 'HTTP_REQUEST_TIME_OUT', 408 ); define( 'HTTP_CONFLICT', 409 ); define( 'HTTP_GONE', 410 ); define( 'HTTP_LENGTH_REQUIRED', 411 ); define( 'HTTP_PRECONDITION_FAILED', 412 ); define( 'HTTP_REQUEST_ENTITY_TOO_LARGE', 413 ); define( 'HTTP_REQUEST_URI_TOO_LARGE', 414 ); define( 'HTTP_UNSUPPORTED_MEDIA_TYPE', 415 ); define( 'HTTP_RANGE_NOT_SATISFIABLE', 416 ); define( 'HTTP_EXPECTATION_FAILED', 417 ); define( 'HTTP_UNPROCESSABLE_ENTITY', 422 ); define( 'HTTP_LOCKED', 423 ); define( 'HTTP_FAILED_DEPENDENCY', 424 ); define( 'HTTP_UPGRADE_REQUIRED', 426 ); define( 'HTTP_INTERNAL_SERVER_ERROR', 500 ); define( 'HTTP_NOT_IMPLEMENTED', 501 ); define( 'HTTP_BAD_GATEWAY', 502 ); define( 'HTTP_SERVICE_UNAVAILABLE', 503 ); define( 'HTTP_GATEWAY_TIME_OUT', 504 ); define( 'HTTP_VERSION_NOT_SUPPORTED', 505 ); define( 'HTTP_VARIANT_ALSO_VARIES', 506 ); define( 'HTTP_INSUFFICIENT_STORAGE', 507 ); define( 'HTTP_NOT_EXTENDED', 510 ); /** * Output proper HTTP header for a given HTTP code * * @param string $code * @return void */ function status($code = 500) { if(!headers_sent()) { $str = http_response_status_code($code); send_header($str); } } /** * Http redirection * * Same use as {@link url_for()} * By default HTTP status code is 302, but a different code can be specified * with a status key in array parameter. * * * redirecto('new','url'); # 302 HTTP_MOVED_TEMPORARILY by default * redirecto('new','url', array('status' => HTTP_MOVED_PERMANENTLY)); * * * @param string or array $param1, $param2... * @return void */ function redirect_to($params) { # [NOTE]: (from php.net) HTTP/1.1 requires an absolute URI as argument to » Location: # including the scheme, hostname and absolute path, but some clients accept # relative URIs. You can usually use $_SERVER['HTTP_HOST'], # $_SERVER['PHP_SELF'] and dirname() to make an absolute URI from a relative # one yourself. # TODO make absolute uri if(!headers_sent()) { $status = HTTP_MOVED_TEMPORARILY; # default for a redirection in PHP $params = func_get_args(); $n_params = array(); # extract status param if exists foreach($params as $param) { if(is_array($param)) { if(array_key_exists('status', $param)) { $status = $param['status']; unset($param['status']); } } $n_params[] = $param; } $uri = call_user_func_array('url_for', $n_params); $uri = htmlspecialchars_decode($uri, ENT_NOQUOTES); stop_and_exit(false); send_header('Location: '.$uri, true, $status); exit; } } /** * Http redirection * * @deprecated deprecated since version 0.4. Please use {@link redirect_to()} instead. * @param string $url * @return void */ function redirect($uri) { # halt('redirect() is deprecated. Please use redirect_to() instead.', E_LIM_DEPRECATED); # halt not necesary... it won't be visible because of http redirection... redirect_to($uri); } /** * Returns HTTP response status for a given code. * If no code provided, return an array of all status * * @param string $num * @return string,array */ function http_response_status($num = null) { $status = array( 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Reserved', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 426 => 'Upgrade Required', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 510 => 'Not Extended' ); if(is_null($num)) return $status; return array_key_exists($num, $status) ? $status[$num] : ''; } /** * Checks if an HTTP response code is valid * * @param string $num * @return bool */ function http_response_status_is_valid($num) { $r = http_response_status($num); return !empty($r); } /** * Returns an HTTP response status string for a given code * * @param string $num * @return string */ function http_response_status_code($num) { $protocole = empty($_SERVER["SERVER_PROTOCOL"]) ? "HTTP/1.1" : $_SERVER["SERVER_PROTOCOL"]; if($str = http_response_status($num)) return "$protocole $num $str"; } /** * Check if the _Accept_ header is present, and includes the given `type`. * * When the _Accept_ header is not present `true` is returned. Otherwise * the given `type` is matched by an exact match, and then subtypes. You * may pass the subtype such as "html" which is then converted internally * to "text/html" using the mime lookup table. * * @param string $type * @param string $env * @return bool */ function http_ua_accepts($type, $env = null) { if(is_null($env)) $env = env(); $accept = array_key_exists('HTTP_ACCEPT', $env['SERVER']) ? $env['SERVER']['HTTP_ACCEPT'] : null; if(!$accept || $accept === '*/*') return true; if($type) { // Allow "html" vs "text/html" etc if(!strpos($type, '/')) $type = mime_type($type); // Check if we have a direct match if(strpos($accept, $type) > -1) return true; // Check if we have type/* $type_parts = explode('/', $type); $type = $type_parts[0].'/*'; return (strpos($accept, $type) > -1); } return false; } ## FILE utils _________________________________________________________________ /** * Returns mime type for a given extension or if no extension is provided, * all mime types in an associative array, with extensions as keys. * If extension is unknown, returns null. * (extracted from Orbit source http://orbit.luaforge.net/). * * * @param string $ext * @return string, array, null */ function mime_type($ext = null) { $types = array( 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'asc' => 'text/plain', 'atom' => 'application/atom+xml', 'atom' => 'application/atom+xml', 'au' => 'audio/basic', 'avi' => 'video/x-msvideo', 'bcpio' => 'application/x-bcpio', 'bin' => 'application/octet-stream', 'bmp' => 'image/bmp', 'cdf' => 'application/x-netcdf', 'cgm' => 'image/cgm', 'class' => 'application/octet-stream', 'cpio' => 'application/x-cpio', 'cpt' => 'application/mac-compactpro', 'csh' => 'application/x-csh', 'css' => 'text/css', 'csv' => 'text/csv', 'dcr' => 'application/x-director', 'dir' => 'application/x-director', 'djv' => 'image/vnd.djvu', 'djvu' => 'image/vnd.djvu', 'dll' => 'application/octet-stream', 'dmg' => 'application/octet-stream', 'dms' => 'application/octet-stream', 'doc' => 'application/msword', 'dtd' => 'application/xml-dtd', 'dvi' => 'application/x-dvi', 'dxr' => 'application/x-director', 'eps' => 'application/postscript', 'etx' => 'text/x-setext', 'exe' => 'application/octet-stream', 'ez' => 'application/andrew-inset', 'gif' => 'image/gif', 'gram' => 'application/srgs', 'grxml' => 'application/srgs+xml', 'gtar' => 'application/x-gtar', 'hdf' => 'application/x-hdf', 'hqx' => 'application/mac-binhex40', 'htm' => 'text/html', 'html' => 'text/html', 'ice' => 'x-conference/x-cooltalk', 'ico' => 'image/x-icon', 'ics' => 'text/calendar', 'ief' => 'image/ief', 'ifb' => 'text/calendar', 'iges' => 'model/iges', 'igs' => 'model/iges', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'js' => 'application/x-javascript', 'json' => 'application/json', 'kar' => 'audio/midi', 'latex' => 'application/x-latex', 'lha' => 'application/octet-stream', 'lzh' => 'application/octet-stream', 'm3u' => 'audio/x-mpegurl', 'man' => 'application/x-troff-man', 'mathml' => 'application/mathml+xml', 'me' => 'application/x-troff-me', 'mesh' => 'model/mesh', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mif' => 'application/vnd.mif', 'mov' => 'video/quicktime', 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpga' => 'audio/mpeg', 'ms' => 'application/x-troff-ms', 'msh' => 'model/mesh', 'mxu' => 'video/vnd.mpegurl', 'nc' => 'application/x-netcdf', 'oda' => 'application/oda', 'ogg' => 'application/ogg', 'pbm' => 'image/x-portable-bitmap', 'pdb' => 'chemical/x-pdb', 'pdf' => 'application/pdf', 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'ppm' => 'image/x-portable-pixmap', 'ppt' => 'application/vnd.ms-powerpoint', 'ps' => 'application/postscript', 'qt' => 'video/quicktime', 'ra' => 'audio/x-pn-realaudio', 'ram' => 'audio/x-pn-realaudio', 'ras' => 'image/x-cmu-raster', 'rdf' => 'application/rdf+xml', 'rgb' => 'image/x-rgb', 'rm' => 'application/vnd.rn-realmedia', 'roff' => 'application/x-troff', 'rss' => 'application/rss+xml', 'rtf' => 'text/rtf', 'rtx' => 'text/richtext', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'sh' => 'application/x-sh', 'shar' => 'application/x-shar', 'silo' => 'model/mesh', 'sit' => 'application/x-stuffit', 'skd' => 'application/x-koan', 'skm' => 'application/x-koan', 'skp' => 'application/x-koan', 'skt' => 'application/x-koan', 'smi' => 'application/smil', 'smil' => 'application/smil', 'snd' => 'audio/basic', 'so' => 'application/octet-stream', 'spl' => 'application/x-futuresplash', 'src' => 'application/x-wais-source', 'sv4cpio' => 'application/x-sv4cpio', 'sv4crc' => 'application/x-sv4crc', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'swf' => 'application/x-shockwave-flash', 't' => 'application/x-troff', 'tar' => 'application/x-tar', 'tcl' => 'application/x-tcl', 'tex' => 'application/x-tex', 'texi' => 'application/x-texinfo', 'texinfo' => 'application/x-texinfo', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'tr' => 'application/x-troff', 'tsv' => 'text/tab-separated-values', 'txt' => 'text/plain', 'ustar' => 'application/x-ustar', 'vcd' => 'application/x-cdlink', 'vrml' => 'model/vrml', 'vxml' => 'application/voicexml+xml', 'wav' => 'audio/x-wav', 'wbmp' => 'image/vnd.wap.wbmp', 'wbxml' => 'application/vnd.wap.wbxml', 'wml' => 'text/vnd.wap.wml', 'wmlc' => 'application/vnd.wap.wmlc', 'wmls' => 'text/vnd.wap.wmlscript', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wrl' => 'model/vrml', 'xbm' => 'image/x-xbitmap', 'xht' => 'application/xhtml+xml', 'xhtml' => 'application/xhtml+xml', 'xls' => 'application/vnd.ms-excel', 'xml' => 'application/xml', 'xpm' => 'image/x-xpixmap', 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml', 'xul' => 'application/vnd.mozilla.xul+xml', 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz', 'zip' => 'application/zip' ); if (is_null($ext)) return $types; $lower_ext = strtolower($ext); return isset($types[$lower_ext]) ? $types[$lower_ext] : null; } /** * Detect MIME Content-type for a file * * @param string $filename Path to the tested file. * @return string */ function file_mime_content_type($filename) { $ext = file_extension($filename); /* strtolower isn't necessary */ if($mime = mime_type($ext)) return $mime; elseif (function_exists('finfo_open')) { if($finfo = finfo_open(FILEINFO_MIME)) { if($mime = finfo_file($finfo, $filename)) { finfo_close($finfo); return $mime; } } } return 'application/octet-stream'; } /** * Read and output file content and return filesize in bytes or status after * closing file. * This function is very efficient for outputing large files without timeout * nor too expensive memory use * * @param string $filename * @param string $retbytes * @return bool, int */ function file_read_chunked($filename, $retbytes = true) { $chunksize = 1*(1024*1024); // how many bytes per chunk $buffer = ''; $cnt = 0; $handle = fopen($filename, 'rb'); if ($handle === false) return false; ob_start(); while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; ob_flush(); flush(); if ($retbytes) $cnt += strlen($buffer); set_time_limit(0); } ob_end_flush(); $status = fclose($handle); if ($retbytes && $status) return $cnt; // return num. bytes delivered like readfile() does. return $status; } /** * Create a file path by concatenation of given arguments. * Windows paths with backslash directory separators are normalized in *nix paths. * * @param string $path, ... * @return string normalized path */ function file_path($path) { $args = func_get_args(); $ds = '/'; $win_ds = '\\'; $n_path = count($args) > 1 ? implode($ds, $args) : $path; if(strpos($n_path, $win_ds) !== false) $n_path = str_replace( $win_ds, $ds, $n_path ); $n_path = preg_replace( "#$ds+#", $ds, $n_path); return $n_path; } /** * Returns file extension or false if none * * @param string $filename * @return string, false */ function file_extension($filename) { $pos = strrpos($filename, '.'); if($pos !== false) return substr($filename, $pos + 1); return false; } /** * Checks if $filename is a text file * * @param string $filename * @return bool */ function file_is_text($filename) { if($mime = file_mime_content_type($filename)) return substr($mime,0,5) == "text/"; return null; } /** * Checks if $filename is a binary file * * @param string $filename * @return void */ function file_is_binary($filename) { $is_text = file_is_text($filename); return is_null($is_text) ? null : !$is_text; } /** * Return or output file content * * @return string, int * **/ function file_read($filename, $return = false) { if(!file_exists($filename)) trigger_error("$filename doesn't exists", E_USER_ERROR); if($return) return file_get_contents($filename); return file_read_chunked($filename); } /** * Returns an array of files contained in a directory * * @param string $dir * @return array */ function file_list_dir($dir) { $files = array(); if ($handle = opendir($dir)) { while (false !== ($file = readdir($handle))) { if ($file[0] != "." && $file != "..") $files[] = $file; } closedir($handle); } return $files; } ## Extra utils ________________________________________________________________ if(!function_exists('array_replace')) { /** * For PHP 5 < 5.3.0 (backward compatibility) * (from {@link http://www.php.net/manual/fr/function.array-replace.php#92549 this php doc. note}) * * @see array_replace() * @param string $array * @param string $array1 * @return $array */ function array_replace( array &$array, array &$array1 ) { $args = func_get_args(); $count = func_num_args(); for ($i = 0; $i < $count; ++$i) { if(is_array($args[$i])) { foreach ($args[$i] as $key => $val) $array[$key] = $val; } else { trigger_error( __FUNCTION__ . '(): Argument #' . ($i+1) . ' is not an array', E_USER_WARNING ); return null; } } return $array; } } /** * Check if a string is an url * * This implementation no longer requires * {@link http://www.php.net/manual/en/book.filter.php the filter extenstion}, * so it will improve compatibility with older PHP versions. * * @param string $str * @return false, str the string if true, false instead */ function filter_var_url($str) { $regexp = '@^https?://([-[:alnum:]]+\.)+[a-zA-Z]{2,6}(:[0-9]+)?(.*)?$@'; $options = array( "options" => array("regexp" => $regexp )); return preg_match($regexp, $str) ? $str : false; } /** * For PHP 5 < 5.1.0 (backward compatibility) * (from {@link http://www.php.net/manual/en/function.htmlspecialchars-decode.php#82133}) * * @param string $string * @param string $quote_style, one of: ENT_COMPAT, ENT_QUOTES, ENT_NOQUOTES * @return the decoded string */ function limonade_htmlspecialchars_decode($string, $quote_style = ENT_COMPAT) { $table = array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)); if($quote_style === ENT_QUOTES) $table['''] = $table['''] = '\''; return strtr($string, $table); } if(!function_exists('htmlspecialchars_decode')) { function htmlspecialchars_decode($string, $quote_style = ENT_COMPAT) { return limonade_htmlspecialchars_decode($string, $quote_style); } } /** * Called just after loading libs, it provides fallback for some * functions if they don't exists. * */ function fallbacks_for_not_implemented_functions() { if(!function_exists('json_encode')) { /** * for PHP 5 < PHP 5.2.0 * */ function json_encode() { trigger_error( __FUNCTION__ . '(): no JSON functions available. Please provide your own implementation of ' . __FUNCTION__ . '() in order to use it.', E_USER_WARNING ); } } } # ================================= END ================================== #