From 1ff2d5408d5086b782f6fa023ed1c6f8b5083231 Mon Sep 17 00:00:00 2001 From: kematzy Date: Thu, 23 Sep 2010 22:45:18 +0800 Subject: [PATCH 1/6] Added debug() function. Sometimes you just need to know what's going on inside a variable and this helps. Instead of writing , you can now write just and get a more sophisticated output. Concept taken from RubyOnRails by DHH. --- lib/limonade.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lib/limonade.php b/lib/limonade.php index 4689ea8..dbe5d57 100644 --- a/lib/limonade.php +++ b/lib/limonade.php @@ -1891,6 +1891,43 @@ function require_once_dir($path, $pattern = "*.php", $prevents_output = true) 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 = h($out);  }
+  return "
\n" . $out ."
"; +} + ## HTTP utils _________________________________________________________________ From 02ad17260912757b73ff809acad8e970efafc4b4 Mon Sep 17 00:00:00 2001 From: kematzy Date: Thu, 23 Sep 2010 22:55:16 +0800 Subject: [PATCH 2/6] Added support for displaying current settings. When working with an app, it can sometimes be handy to see all the various application settings at the bottom of the page, so that you can quickly spot any issues. This addition makes this very easy. Design liberally borrowed from Sinatra (www.sinatrarb.com) --- lib/limonade.php | 26 +++ lib/limonade/views/_settings.html.php | 318 ++++++++++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 lib/limonade/views/_settings.html.php diff --git a/lib/limonade.php b/lib/limonade.php index dbe5d57..b482489 100644 --- a/lib/limonade.php +++ b/lib/limonade.php @@ -1929,6 +1929,32 @@ function debug($var, $output_as_html = true) } +/** + * Provides debugging output of all system settings, enabling easy inspection of + * all current values and settings. + * + * Depends on option('show_settings') being set and true, else outputs simple HTML comment. + * + * @return string the html output displaying all the settings. + */ + +function show_settings_output() +{ + if ( option('show_settings') ) + { + $c_view_dir = option('views_dir'); // keep for restore after render + option('views_dir', option('limonade_views_dir')); + $o = render('_settings.html.php', null); + option('views_dir', $c_view_dir); // restore current views dir + return $o; + } + else + { + return ""; + } +} + + ## HTTP utils _________________________________________________________________ diff --git a/lib/limonade/views/_settings.html.php b/lib/limonade/views/_settings.html.php new file mode 100644 index 0000000..816cc9c --- /dev/null +++ b/lib/limonade/views/_settings.html.php @@ -0,0 +1,318 @@ +
+ +

LimonadePHP v - APPLICATION SETTINGS

+ +
+ +
+

OPTIONS (Limonade)

+ + + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No Options set.

+ +
+
+ + +
+

SERVER ($_SERVER)

+ + + + + + + $val): + ?> + + + + + +
VariableValue
+ +

No SERVER data.

+ +
+
+ +
+

GET ($_GET)

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No GET data.

+ +
+
+ +
+

POST ($_POST)

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No POST data.

+ +
+
+ +
+

SESSIONS ($_SESSION)

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No SESSION data.

+ +
+
+ + +
+ + + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No cookie data.

+ +
+
+ +
+

ENV ($_ENV)

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No ENV data.

+ +
+
+ +
+

FILES ($_FILES)

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No FILES data.

+ +
+
+ + +
+

REQUEST ($_REQUEST)

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No REQUEST data.

+ +
+
+ +
+

PUT (env()['PUT'])

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No PUT data.

+ +
+
+ +
+

DELETE (env()['DELETE'])

+ + + + + + + $val): ?> + + + + + +
VariableValue
+ +

No DELETE data.

+ +
+
+ + + + +

You're seeing this output because you have enabled option('show_settings').

+ +
+ \ No newline at end of file From c41384d22427a090918c2ad1d862c19936888b62 Mon Sep 17 00:00:00 2001 From: kematzy Date: Thu, 23 Sep 2010 22:55:50 +0800 Subject: [PATCH 3/6] Turn off show_settings by default. --- lib/limonade.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/limonade.php b/lib/limonade.php index b482489..6cb973b 100644 --- a/lib/limonade.php +++ b/lib/limonade.php @@ -333,6 +333,7 @@ function run($env = null) option('error_views_dir', option('limonade_views_dir')); option('env', ENV_PRODUCTION); option('debug', true); + option('show_settings', false); 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 From 440155c93b2b6d97f10d496067213980aa1454cb Mon Sep 17 00:00:00 2001 From: kematzy Date: Wed, 21 Dec 2011 14:35:58 +0800 Subject: [PATCH 4/6] Amended private version with some extra goodies thrown in --- README.mkd | 42 +- lib/limonade.php | 2254 +++++++++++++++---------- lib/limonade/assertions.php | 45 +- lib/limonade/public/css/screen.css | 220 +-- lib/limonade/public/img/404.png | Bin 0 -> 23305 bytes lib/limonade/public/img/500.png | Bin 0 -> 31056 bytes lib/limonade/tests.php | 109 +- lib/limonade/views/_debug.html.php | 12 +- lib/limonade/views/default_layout.php | 38 +- 9 files changed, 1573 insertions(+), 1147 deletions(-) create mode 100644 lib/limonade/public/img/404.png create mode 100644 lib/limonade/public/img/500.png diff --git a/README.mkd b/README.mkd index ba25b07..36d326f 100644 --- a/README.mkd +++ b/README.mkd @@ -2,7 +2,7 @@ Limonade is a PHP micro framework for rapid web development and prototyping. -It's inspired by frameworks like [Sinatra](http://www.sinatrarb.com/) or [Camping](http://github.com/camping/camping) in Ruby, or [Orbit](http://orbit.luaforge.net/) in Lua. It aims to be simple, lightweight and extremly flexible. +It's inspired by frameworks like [Sinatra](http://www.sinatrarb.com/) or [Camping](http://github.com/camping/camping) in Ruby, or [Orbit](http://orbit.luaforge.net/) in Lua. It aims to be simple, lightweight and extremely flexible. Limonade provides functions that complete the PHP basic set, while keeping consistency with native functions and sitting up on them. @@ -252,9 +252,9 @@ You can access your site with urls like `http://your.new-website.com/my/limonade ## Views and templates ## Template files are located by default in `views/` folder. -Views folder location can be set with the `views_dir` option. +Views folder location can be set with the `dir.views` option. - option('views_dir', dirname(__FILE__).'/other/dir/for/views'); + option('dir.views', dirname(__FILE__).'/other/dir/for/views'); To pass variables to templates, we use the function `set ()` @@ -484,7 +484,7 @@ The first three parameters are the same as those passed to the `render` function * `$layout`: current layout path * `$locals`: variables passed directly to the `render` function -Last parameter, `$view_path` is by default `file_path(option('views_dir'), $content_or_func);` +Last parameter, `$view_path` is by default `file_path(option('dir.views'), $content_or_func);` function before_render($content_or_func, $layout, $locals, $view_path) { @@ -585,17 +585,32 @@ You can use it to manage Limonade options and your own custom options in your ap Default Limonade options have the following values: - option('root_dir', $root_dir); // this folder contains your main application file + option('dir.root', $root_dir); // this folder contains your main application file option('base_path', $base_path); option('base_uri', $base_uri); // set it manually if you use url_rewriting - option('limonade_dir', dirname(__FILE__).'/'); // this fiolder contains the limonade.php main file - option('limonade_views_dir', dirname(__FILE__).'/limonade/views/'); - option('limonade_public_dir',dirname(__FILE__).'/limonade/public/'); - option('public_dir', $root_dir.'/public/'); - option('views_dir', $root_dir.'/views/'); - option('controllers_dir', $root_dir.'/controllers/'); - option('lib_dir', $root_dir.'/lib/'); - option('error_views_dir', option('limonade_views_dir')); + option('dir.limonade', dirname(__FILE__).'/'); // this fiolder contains the limonade.php main file + option('dir.limonade.views', dirname(__FILE__).'/limonade/views/'); + option('dir.limonade.public',dirname(__FILE__).'/limonade/public/'); + option('dir.public', $root_dir.'/public/'); + option('dir.views', $root_dir.'/views/'); + option('dir.controllers', $root_dir.'/controllers/'); + option('dir.lib', $root_dir.'/lib/'); + option('dir.views.errors', option('dir.limonade.views')); + + # custom dirs + option('dir.lib.cores', file_path($lim_dir, 'core')); + option('dir.lib.core', file_path($lim_dir, 'core')); + option('dir.lib.helpers', file_path($lim_dir, 'helpers')); + option('dir.lib.lexts', file_path($lim_dir, 'lexts')); + + option('dir.db', file_path(dirname($root_dir), 'db')); + option('dir.app', file_path(dirname($lim_dir))); + option('dir.app.assets', file_path(dirname($lim_dir), 'assets')); + option('dir.app.conf', file_path(dirname($lim_dir), 'conf')); + option('dir.app.lib', file_path($lim_dir)); + option('dir.app.models', file_path(dirname($lim_dir), 'models')); + option('dir.app.views', file_path(dirname($lim_dir), 'views')); + option('env', ENV_PRODUCTION); option('debug', true); option('session', LIM_SESSION_NAME); // true, false or the name of your session @@ -604,6 +619,7 @@ Default Limonade options have the following values: // X-SENDFILE: for Apache and Lighttpd v. >= 1.5, // X-LIGHTTPD-SEND-FILE: for Apache and Lighttpd v. < 1.5 + ## Sessions ## Session starts automatically by default. Then you can access session variables like you used to do, with `$_SESSION` array. diff --git a/lib/limonade.php b/lib/limonade.php index a9f55cf..4b3d2a6 100644 --- a/lib/limonade.php +++ b/lib/limonade.php @@ -1,67 +1,65 @@ $v) - if(array_key_exists($k, $GLOBALS)) unset($GLOBALS[$k]); + { + 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 - */ +* 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); +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 +/** +* 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 _____________________________________________________ +// ----- +// #### 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 - */ + * 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')); + option('dir.views', file_path(option('dir.limonade.public'), 'css')); $fpath = file_path(params('_lim_css_filename').".css"); - return css($fpath, null); // with no layout + 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 - */ + /** + * 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')); + $fpath = file_path(option('dir.limonade.public'), params('_lim_public_file')); return render_file($fpath, true); } - # # # - - - -# ============================================================================ # -# 1. BASE # -# ============================================================================ # +// ----- +// ### 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 - */ + +// ----- +// #### 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(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]; + 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 - */ +* 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(); @@ -251,7 +268,7 @@ function params($name_or_array_or_null = null, $value = null) $name = array_shift($args); if(is_null($name)) { - # Reset params + // Reset params $params = array(); return $params; } @@ -273,119 +290,147 @@ function params($name_or_array_or_null = null, $value = null) } /** - * 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 - */ +* 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(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]; + 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 - */ +* 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 set value +* +*/ function set_or_default($name, $value, $default) { return set($name, value_or_default($value, $default)); } /** - * Running application - * - * @param string $env - * @return void - */ +* Running application +* +* @param string $env +* +* @return void +* +*/ function run($env = null) { if(is_null($env)) $env = env(); - # 0. Set default configuration + // 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')); + $lim_dir = dirname(__FILE__); + option('dir.root', $root_dir); option('base_path', $base_path); option('base_uri', $base_uri); // set it manually if you use url_rewriting + option('dir.limonade', file_path($lim_dir)); + option('dir.limonade.views', file_path($lim_dir, 'limonade', 'views')); + option('dir.limonade.public',file_path($lim_dir, 'limonade', 'public')); + option('dir.public', file_path($root_dir, 'public')); + option('dir.views', file_path($root_dir, 'views')); + option('dir.controllers', file_path($root_dir, 'controllers')); + option('dir.lib', file_path($root_dir, 'lib')); + option('dir.views.errors', option('dir.limonade.views')); + # custom dirs + option('dir.lib.cores', file_path($lim_dir, 'core')); + option('dir.lib.core', file_path($lim_dir, 'core')); + option('dir.lib.helpers', file_path($lim_dir, 'helpers')); + option('dir.lib.lexts', file_path($lim_dir, 'lexts')); + + option('dir.db', file_path(dirname($root_dir), 'db')); + option('dir.app', file_path(dirname($lim_dir))); + option('dir.app.assets', file_path(dirname($lim_dir), 'assets')); + option('dir.app.conf', file_path(dirname($lim_dir), 'conf')); + option('dir.app.lib', file_path($lim_dir)); + option('dir.app.models', file_path(dirname($lim_dir), 'models')); + option('dir.app.views', file_path(dirname($lim_dir), 'views')); + option('env', ENV_PRODUCTION); option('debug', true); - option('session', LIM_SESSION_NAME); // true, false or the name of your session + 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('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 - + 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 + // 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 + // 1.2 Register shutdown function register_shutdown_function('stop_and_exit'); - # 2. Set user configuration + # 1.3 Load extras + # extras(true); + + // 2. Set user configuration + event('configure.before', true, array($env)); call_if_exists('configure'); + event('configure.after', true, array($env)); - # 2.1 Set gzip compression if defined + // 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"); + // 2.2 Set X-Limonade header + if($signature = option('signature')) { header("X-Limonade: $signature"); } - # 3. Loading libs - require_once_dir(option('lib_dir')); + // 3. Loading libs + event('libs.before', true, array($env)); + require_once_dir(option('dir.lib')); + event('libs.after', true, array($env)); + fallbacks_for_not_implemented_functions(); - # 4. Starting session + // 4. Starting session + event('session.before', true, array($env)); + 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); + if(!is_bool(option('session'))) { session_name(option('session')); } + if(!session_start()) { trigger_error("An error occurred while trying to start the session", E_USER_WARNING); } } - # 5. Set some default methods if needed + event('session.after', true, array($env)); + + // 5. Set some default methods if needed if(!function_exists('after')) { - function after($output) - { - return $output; - } + function after($output) { return $output; } } if(!function_exists('route_missing')) { @@ -395,56 +440,93 @@ function route_missing($request_method, $request_uri) } } + event('initialize.before', true, array($env)); + call_if_exists('initialize'); - # 6. Check request + event('initialize.after', true, array($env)); + + + // 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 + // 6.1 Check matching route if($route = route_find($rm, request_uri($env))) { params($route['params']); - # 6.2 Load controllers dir + // 6.2 Load controllers dir if(!function_exists('autoload_controller')) { function autoload_controller($callback) { - require_once_dir(option('controllers_dir')); + require_once_dir(option('dir.controllers')); } } + + event('controller_autoload.before', true, array($env, $route)); + autoload_controller($route['callback']); + event('controller_autoload.after', true, array($env, $route)); + if(is_callable($route['callback'])) { - # 6.3 Call before function + // 6.3 Call before function + event('before.before', true, array($env, $route)); + call_if_exists('before', $route); - # 6.4 Call matching controller function and output result + event('before.after', true, array($env, $route)); + + // 6.4 Call matching controller function and output result + event('render.before', true, array($env, $route)); + $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); + + event('render.after', true, array($env, $route, &$output)); + + event('after.before', true, array($env, $route, &$output)); + $output = after(error_notices_render() . $output, $route); + event('after.after', true, array($env, $route, &$output)); + echo $output; + + # echo after(error_notices_render() . $output, $route); + } + else + { + halt(SERVER_ERROR, "Routing error: undefined function '{$route['callback']}'", $route); } - else halt(SERVER_ERROR, "Routing error: undefined function '{$route['callback']}'", $route); } - else route_missing($rm, request_uri($env)); - + else + { + route_missing($rm, request_uri($env)); + } + } + else + { + halt(HTTP_NOT_IMPLEMENTED, "The requested method '$rm' is not implemented"); } - 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 - */ +* 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); @@ -452,12 +534,14 @@ function stop_and_exit($exit = true) if(request_is_head()) { ob_end_clean(); - } else { + } + 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 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; @@ -471,16 +555,18 @@ function stop_and_exit($exit = true) } /** - * Returns limonade environment variables: - * - * 'SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE', - * 'GET', 'POST', 'PUT', 'DELETE' - * - * If a null argument is passed, reset and rebuild environment - * - * @param null @reset reset and rebuild environment - * @return array - */ +* Returns limonade environment variables: +* +* 'SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE', +* 'GET', 'POST', 'PUT', 'DELETE' +* +* 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(); @@ -494,8 +580,8 @@ function env($reset = null) { if(empty($GLOBALS['_SERVER'])) { - // Fixing empty $GLOBALS['_SERVER'] bug - // http://sofadesign.lighthouseapp.com/projects/29612-limonade/tickets/29-env-is-empty + # 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; @@ -536,10 +622,11 @@ function env($reset = null) } /** - * Returns application root file path - * - * @return string - */ +* Returns application root file path +* +* @return string +* +*/ function app_file() { static $file; @@ -551,26 +638,170 @@ function app_file() } return file_path($file); } - - - - - # # # - - - - -# ============================================================================ # -# 2. ERROR # -# ============================================================================ # /** - * Associate a function with error code(s) and return all associations + * Simplistic event handler. + * + * Usage: + * # Adds a callback to an event. + * event($event, string $callback); + * + * # Adds multiple callbacks to an event. + * event(array('event', array('callback_1, callback_2'))); + * + * # Adds callbacks to multiple events. + * event(array('event_1' => 'callback', 'event_2' => array('callback', 'callback_2'))); + * + * # Fires an event with arguments arg_1, arg2, ..., arg_n. + * event('event_name', true, array(arg_1, arg_2, ..., arg_n)); + * + * # Clears an event queue. + * event('event_name', false); + * * - * @param string $errno - * @param string $function - * @return array + * @param mixed $event The event name or an array of type event => callback(s). + * @param mixed $callback optional, the callback name, set to false to clear the event, set true to fire the event + * + * @return void */ +function event($event = null, $callback = null, $args = array()) +{ + static $events = array(); + if ( is_null($event)) + { + return $events; + } + if(is_array($event)) + { + foreach($event as $e => $callback) + { + if(is_string($callback)) { event($e, $callback); } + } + return; + } + + if(!isset($events[$event])) { $events[$event] = array(); } + + if(true === $callback) + { + foreach($events[$event] as $callback => $_) + { + if(function_exists($callback)) { call_user_func_array($callback, array_force($args)); } + } + } + elseif(false === $callback) + { + $events[$event] = array(); + } + else + { + foreach(array_force($callback) as $c) + { + # add callback as array key to allow easier overrides. + if(is_string($c)) { $events[$event][$c] = null; } + } + } +} + +# TODO:: Work on this implementation +# /** +# * Extras manager. +# * +# * @todo fix this mechanism so it can handle extras requiring other extras. +# * @todo handle cyclic references. +# * +# * @access private +# * +# * @param mixed $name set to true to load all extras, array for multiple extras +# * @return array $extras the list of extras to load +# */ +# function extras($extra) +# { +# static $extras = array(); +# static $loaded = array(); +# +# if(is_array($extra)) +# { +# foreach($extra as $e){ extras((string)$e); } +# } +# elseif(true === $extra) +# { +# foreach(array_diff_assoc($extras, $loaded) as $e => $ignore) +# { +# $file = file_path(option('dir.limonade.extras'), 'limonade.'.$e.'.php'); +# if(file_exists($file)) +# { +# require_once($file); +# if(function_exists($e.'_init')) +# { +# call_user_func($e.'_init'); +# } +# $loaded[$e] = null; +# } +# else +# { +# error_notice(E_WARNING, sprintf('Requested extras file "%s" could not be found', $file)); +# } +# } +# } +# else +# { +# # protect against invalid extras name. +# if(preg_match('/^(\w+)/', $extra, $matches)) +# { +# $extra = $matches[1]; +# $extras[$extra] = null; +# } +# } +# return $extras; +# } +# +# /** +# * Shortcut function for @see{extras()} for naming consistency. +# * +# * @param string $name the name of the extra to use +# */ +# function use_extra($name) { extras((string) $name); } +# +# function use_helper($name) +# { +# $file = file_path(option('dir.lib.helpers'), $name.'.helper.php'); +# if(file_exists($file)) +# { +# require_once($file); +# } +# else +# { +# error_notice( E_WARNING, sprintf('Helper file "%s" not found.', $file )); +# } +# } +# +# function use_model($name) +# { +# $file = file_path(option('dir.app.models'), $name.'.model.php'); +# if(file_exists($file)) +# { +# require_once($file); +# } +# else +# { +# error_notice( E_WARNING, sprintf('Model file "%s" not found.', $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(); @@ -582,24 +813,29 @@ function error($errno = null, $function = null) } /** - * 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 - */ +* 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 + // ### TODO cleanup / refactoring if(is_string($errno)) { $msg = $errno; @@ -614,21 +850,24 @@ function halt($errno = SERVER_ERROR, $msg = '', $debug_args = null) 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 - */ +* 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(); @@ -646,7 +885,7 @@ function error_handler_dispatcher($errno, $errstr, $errfile, $errline) if(error_wont_halt_app($errno)) { error_notice($errno, $errstr, $errfile, $errline); - return; + return; } else { @@ -676,14 +915,19 @@ function error_handler_dispatcher($errno, $errstr, $errfile, $errline) /** - * Default error handler - * - * @param string $errno - * @param string $errstr - * @param string $errfile - * @param string $errline - * @return string error output - */ +* 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); @@ -697,28 +941,33 @@ function error_default_handler($errno, $errstr, $errfile, $errline) } /** - * Returns not found error output - * - * @access private - * @param string $msg - * @return string - */ +* 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 - */ + * 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')); + option('dir.views', option('dir.views.errors')); $msg = h(rawurldecode($errstr)); return html("

Page not found:

{$msg}

", error_layout()); } @@ -727,35 +976,40 @@ function not_found($errno, $errstr, $errfile=null, $errline=null) } /** - * Returns server error output - * - * @access private - * @param int $errno - * @param string $errstr - * @param string $errfile - * @param string $errline - * @return string - */ +* 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 - */ + * 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')); + option('dir.views', option('dir.limonade.views')); $html = render('error.html.php', null, $args); - option('views_dir', option('error_views_dir')); + option('dir.views', option('dir.views.errors')); return html($html, error_layout(), $args); } } @@ -763,17 +1017,19 @@ function server_error($errno, $errstr, $errfile=null, $errline=null) } /** - * Set and returns error output layout - * - * @param string $layout - * @return string - */ +* 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')); + option('dir.views.errors', option('dir.views')); $o_layout = $layout; } return $o_layout; @@ -781,50 +1037,62 @@ function error_layout($layout = false) /** - * 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 - */ +* 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(); + 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 - */ +* 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')); + $c_view_dir = option('dir.views'); // keep for restore after render + option('dir.views', option('dir.limonade.views')); $o = render('_notices.html.php', null, array('notices' => $notices)); - option('views_dir', $c_view_dir); // restore current views dir - + option('dir.views', $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 - */ +* 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 || @@ -841,11 +1109,13 @@ function error_wont_halt_app($num) /** - * return error code name for a given code num, or return all errors names - * - * @param string $num - * @return mixed - */ +* 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 ( @@ -866,42 +1136,46 @@ function error_type($num = null) E_USER_DEPRECATED => 'USER DEPRECATED WARNING', E_LIM_DEPRECATED => 'LIMONADE DEPRECATED WARNING' ); - return is_null($num) ? $types : $types[$num]; + return is_null($num) ? $types : @$types[$num]; } /** - * Returns http response status for a given error number - * - * @param string $errno - * @return int - */ +* 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 -# ============================================================================ # -# 3. REQUEST # -# ============================================================================ # /** - * Returns current request method for a given environment or current one - * - * @param string $env - * @return string - */ +* 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); @@ -911,11 +1185,13 @@ function request_method($env = null) } /** - * Checks if a request method or current one is allowed - * - * @param string $m - * @return bool - */ +* 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(); @@ -923,77 +1199,90 @@ function request_method_is_allowed($m = null) } /** - * Checks if request method is GET - * - * @param string $env - * @return bool - */ +* 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 - */ +* 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 - */ +* 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 - */ +* 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 - */ +* Checks if request method is HEAD +* +* @param string $env +* +* +* @return bool +* +*/ function request_is_head($env = null) { return request_method($env) == "HEAD"; } /** - * Returns allowed request methods - * - * @return array - */ +* Returns allowed request methods +* +* @return array +* +*/ function request_methods() { return array("GET","POST","PUT","DELETE", "HEAD"); } /** - * Returns current request uri (the path that will be compared with routes) - * - * (Inspired from codeigniter URI::_fetch_uri_string method) - * - * @return string - */ +* 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; @@ -1011,19 +1300,19 @@ function request_uri($env = null) { $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']); - // } + # 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 + # 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) @@ -1043,7 +1332,7 @@ function request_uri($env = null) } $uri = $path_info; } - // No PATH_INFO?... What about QUERY_STRING? + # No PATH_INFO?... What about QUERY_STRING? elseif (trim($query_string, '/') != '') { $uri = $query_string; @@ -1084,34 +1373,32 @@ function request_uri($env = null) - - # # # +// ----- +// ### 4. ROUTER - - -# ============================================================================ # -# 4. ROUTER # -# ============================================================================ # /** - * An alias of {@link dispatch_get()} - * - * @return void - */ +* 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 - */ +* 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); @@ -1119,39 +1406,45 @@ function dispatch_get($path_or_array, $callback, $options = array()) } /** - * Add a POST route - * - * @param string $path_or_array - * @param string $callback - * @param array $options (optional). See {@link route()} for available options. - * @return void - */ +* 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 - */ +* 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 - */ +* 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); @@ -1159,20 +1452,25 @@ function dispatch_delete($path_or_array, $callback, $options = array()) /** - * 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 - */ +* 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(); @@ -1196,36 +1494,47 @@ function route() } /** - * An alias of route(null): reset all routes - * - * @access private - * @return void - */ +* 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" - */ +* 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); + { + trigger_error("'$method' request method is unknown or unavailable.", E_USER_WARNING); + } if(is_array($path_or_array)) { @@ -1240,7 +1549,7 @@ function route_build($method, $path_or_array, $callback, $options = array()) $single_asterisk_subpattern = "(?:/([^\/]*))?"; $double_asterisk_subpattern = "(?:/(.*))?"; - $optionnal_slash_subpattern = "(?:/*?)"; + $optional_slash_subpattern = "(?:/*?)"; $no_slash_asterisk_subpattern = "(?:([^\/]*))?"; if($path[0] == "^") @@ -1250,7 +1559,7 @@ function route_build($method, $path_or_array, $callback, $options = array()) } else if(empty($path) || $path == "/") { - $pattern = "#^".$optionnal_slash_subpattern."$#"; + $pattern = "#^".$optional_slash_subpattern."$#"; } else { @@ -1262,9 +1571,7 @@ function route_build($method, $path_or_array, $callback, $options = array()) foreach($elts as $elt) { if(empty($elt)) continue; - $name = null; - # extracting double asterisk ** if($elt == "**"): $parsed[] = $double_asterisk_subpattern; @@ -1291,7 +1598,7 @@ function route_build($method, $path_or_array, $callback, $options = array()) $parsed_sub[] = preg_quote($sub_elt, "#"); $name = $parameters_count; } - // + # $parsed[] = "/".implode($no_slash_asterisk_subpattern, $parsed_sub); else: @@ -1299,14 +1606,14 @@ function route_build($method, $path_or_array, $callback, $options = array()) endif; - /* set parameters names */ + # 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"; + $pattern = "#^".implode('', $parsed).$optional_slash_subpattern."?$#i"; } return array( "method" => $method, @@ -1317,19 +1624,25 @@ function route_build($method, $path_or_array, $callback, $options = array()) } /** - * 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 - */ +* 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(); @@ -1368,33 +1681,36 @@ function route_find($method, $path) +// ----- +// ### 5. OUTPUT AND RENDERING -# ============================================================================ # -# 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 - */ +* 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); + $view_path = file_path(option('dir.views'),$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); @@ -1443,27 +1759,31 @@ function render($content_or_func, $layout = '', $locals = array()) } /** - * Returns a string to output - * - * Shortcut to render with no layout. - * - * @param string $content_or_func - * @param string $locals - * @return string - */ +* 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 - */ +* 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'))); @@ -1472,11 +1792,13 @@ function html($content_or_func, $layout = '', $locals = array()) } /** - * Set and return current layout - * - * @param string $function_or_file - * @return string - */ +* Set and return current layout +* +* @param string $function_or_file +* +* @return string +* +*/ function layout($function_or_file = null) { static $layout = null; @@ -1485,13 +1807,15 @@ function layout($function_or_file = null) } /** - * Returns xml output with proper http headers - * - * @param string $content_or_func - * @param string $layout - * @param string $locals - * @return string - */ +* 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'))); @@ -1500,13 +1824,14 @@ function xml($data) } /** - * Returns css output with proper http headers - * - * @param string $content_or_func - * @param string $layout - * @param string $locals - * @return string - */ +* 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'))); @@ -1515,13 +1840,14 @@ function css($content_or_func, $layout = '', $locals = array()) } /** - * Returns javacript output with proper http headers - * - * @param string $content_or_func - * @param string $layout - * @param string $locals - * @return string - */ +* 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'))); @@ -1530,13 +1856,14 @@ function js($content_or_func, $layout = '', $locals = array()) } /** - * Returns txt output with proper http headers - * - * @param string $content_or_func - * @param string $layout - * @param string $locals - * @return string - */ +* 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'))); @@ -1545,14 +1872,16 @@ function txt($content_or_func, $layout = '', $locals = array()) } /** - * 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 - */ +* 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'))); @@ -1560,25 +1889,26 @@ function json($data, $json_option = 0) } /** - * undocumented function - * - * @param string $filename - * @param string $return - * @return mixed number of bytes delivered or file output if $return = true - */ +* 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 - // { - // - // } + // 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)) { @@ -1592,42 +1922,39 @@ function render_file($filename, $return = false) } /** - * Call before_sending_header() if it exists, then send headers - * - * @param string $header - * @return void - */ +* 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); - } + if(!headers_sent()) + { + call_if_exists('before_sending_header', $header); + header($header, $replace, $code); + } } +// ----- +// ### 6. HELPERS - # # # - - - - -# ============================================================================ # -# 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 - */ +* 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(); @@ -1657,7 +1984,7 @@ function url_for($params = null) if(!filter_var_url($path)) { # it's a relative URL or an URL without a schema - $base_uri = option('base_uri'); + $base_uri = option('base.uri'); $path = file_path($base_uri, $path); } @@ -1681,14 +2008,16 @@ function url_for($params = null) } /** - * 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 - */ +* 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')); @@ -1696,17 +2025,19 @@ function h($str, $quote_style = ENT_NOQUOTES, $charset = null) } /** - * 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 - */ +* 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); @@ -1725,19 +2056,22 @@ function flash($name = null, $value = null) } /** - * 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 - */ +* 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; @@ -1761,13 +2095,16 @@ function flash_now($name = null, $value = null) } /** - * Delete current flash messages in session, and set new ones stored with - * flash function. - * Called before application exit. - * - * @access private - * @return void - */ +* 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')) @@ -1778,18 +2115,20 @@ function flash_sweep() } /** - * 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 - */ +* 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; @@ -1810,61 +2149,61 @@ function content_for($name = null, $content = null) } /** - * Stops capturing block of text - * - * @return void - */ +* 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 - */ +* Shows current memory and execution time of the application. +* +* Returns only execution time if memory_get_usage() is not 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() - LIM_START_MICROTIME) ); + $res = array( 'execution_time' => (microtime() - 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; - } + { + $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; + return $res; } - - # # # - +// ----- +// ### 7. UTILS - -# ============================================================================ # -# 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 - */ +* 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(); @@ -1874,50 +2213,54 @@ function call_if_exists($callback) } /** - * Define a constant unless it already exists - * - * @param string $name - * @param string $value - * @return void - */ +* 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 - */ +* 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 - */ +* 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 - */ +* 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 .= "/"; @@ -1930,12 +2273,13 @@ function require_once_dir($path, $pattern = "*.php", $prevents_output = true) } /** - * 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
- */
+* 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]'; };
@@ -1966,11 +2310,30 @@ function debug($var, $output_as_html = true)
   return $out;
 }
 
+/**
+* Syntactic sugar for var_export()
+* 
+* Params
+* 
+* @param (string) $var => the variable/object/array to dump
+* 
+* @return (string)
+* 
+* Examples
+* 
+*     
+* +*/ +function _dump($var) +{ + return var_export($var, true); +} -## HTTP utils _________________________________________________________________ +// ----- +// #### HTTP utils -### Constants: HTTP status codes +# Constants: HTTP status codes define( 'HTTP_CONTINUE', 100 ); define( 'HTTP_SWITCHING_PROTOCOLS', 101 ); @@ -2026,11 +2389,12 @@ function debug($var, $output_as_html = true) define( 'HTTP_NOT_EXTENDED', 510 ); /** - * Output proper HTTP header for a given HTTP code - * - * @param string $code - * @return void - */ +* Output proper HTTP header for a given HTTP code +* +* @param string $code +* +* @return void +*/ function status($code = 500) { if(!headers_sent()) @@ -2041,20 +2405,25 @@ function status($code = 500) } /** - * 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 - */ +* 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: @@ -2063,7 +2432,7 @@ function redirect_to($params) # $_SERVER['PHP_SELF'] and dirname() to make an absolute URI from a relative # one yourself. - # TODO make absolute uri + // ### TODO make absolute uri if(!headers_sent()) { $status = HTTP_MOVED_TEMPORARILY; # default for a redirection in PHP @@ -2091,12 +2460,14 @@ function redirect_to($params) } /** - * Http redirection - * - * @deprecated deprecated since version 0.4. Please use {@link redirect_to()} instead. - * @param string $url - * @return void - */ +* 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); @@ -2105,12 +2476,14 @@ function redirect($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 - */ +* 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( @@ -2175,11 +2548,12 @@ function http_response_status($num = null) } /** - * Checks if an HTTP response code is valid - * - * @param string $num - * @return bool - */ +* 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); @@ -2187,11 +2561,12 @@ function http_response_status_is_valid($num) } /** - * Returns an HTTP response status string for a given code - * - * @param string $num - * @return string - */ +* 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"]; @@ -2199,17 +2574,18 @@ function http_response_status_code($num) } /** - * 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 - */ +* 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(); @@ -2219,13 +2595,13 @@ function http_ua_accepts($type, $env = null) if($type) { - // Allow "html" vs "text/html" etc + # Allow "html" vs "text/html" etc if(!strpos($type, '/')) $type = mime_type($type); - // Check if we have a direct match + # Check if we have a direct match if(strpos($accept, $type) > -1) return true; - // Check if we have type/* + # Check if we have type/* $type_parts = explode('/', $type); $type = $type_parts[0].'/*'; return (strpos($accept, $type) > -1); @@ -2234,16 +2610,19 @@ function http_ua_accepts($type, $env = null) return false; } -## FILE utils _________________________________________________________________ +// ----- +// #### 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. - * (extracted from Orbit source http://orbit.luaforge.net/) - * - * @param string $ext - * @return string, array - */ +* Returns mime type for a given extension or if no extension is provided, +* all mime types in an associative array, with extensions as keys. +* (extracted from Orbit source http://orbit.luaforge.net/) +* +* @param string $ext +* +* @return string, array +*/ function mime_type($ext = null) { $types = array( @@ -2410,14 +2789,16 @@ function mime_type($ext = null) } /** - * Detect MIME Content-type for a file - * - * @param string $filename Path to the tested file. - * @return string - */ +* 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 */ + $ext = file_extension($filename); # strtolower isn't necessary if($mime = mime_type($ext)) return $mime; elseif (function_exists('finfo_open')) { @@ -2435,18 +2816,20 @@ function file_mime_content_type($filename) /** - * 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 - */ +* Read and output file content and return file size in bytes or status after +* closing file. +* +* This function is very efficient for outputting 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 + $chunksize = 1*(1024*1024); # how many bytes per chunk $buffer = ''; $cnt = 0; $handle = fopen($filename, 'rb'); @@ -2464,17 +2847,19 @@ function file_read_chunked($filename, $retbytes = true) ob_end_flush(); $status = fclose($handle); - if ($retbytes && $status) return $cnt; // return num. bytes delivered like readfile() does. + 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 - */ +* 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(); @@ -2483,16 +2868,16 @@ function file_path($path) $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 - */ +* Returns file extension or false if none +* +* @param string $filename +* +* @return string, false +*/ function file_extension($filename) { $pos = strrpos($filename, '.'); @@ -2501,11 +2886,12 @@ function file_extension($filename) } /** - * Checks if $filename is a text file - * - * @param string $filename - * @return bool - */ +* 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/"; @@ -2513,11 +2899,13 @@ function file_is_text($filename) } /** - * Checks if $filename is a binary file - * - * @param string $filename - * @return void - */ +* Checks if $filename is a binary file +* +* @param string $filename +* +* @return void +* +*/ function file_is_binary($filename) { $is_text = file_is_text($filename); @@ -2525,12 +2913,11 @@ function file_is_binary($filename) } /** - * Return or output file content - * - * @return string, int - * - **/ - +* 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); @@ -2539,11 +2926,12 @@ function file_read($filename, $return = false) } /** - * Returns an array of files contained in a directory - * - * @param string $dir - * @return array - */ +* Returns an array of files contained in a directory +* +* @param string $dir +* +* @return array +*/ function file_list_dir($dir) { $files = array(); @@ -2558,19 +2946,37 @@ function file_list_dir($dir) return $files; } -## Extra utils ________________________________________________________________ + + +// ----- +// #### Extra utils + + +/** + * Force a value to be an array. + * + * @param mixed $value The value to make sure is an array. + * + * @return array The provided $value if it was an array or array($value) + */ +function array_force($value) +{ + return (is_array($value) ? $value : array($value)); +} 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 - */ + * 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(); @@ -2596,15 +3002,15 @@ function array_replace( array &$array, array &$array1 ) } /** - * 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 - */ +* 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]+)?(.*)?$@'; @@ -2612,44 +3018,40 @@ function filter_var_url($str) 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 - */ +* 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); + $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); - } + 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. - * - */ +* 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 - * - */ + # for PHP 5 < PHP 5.2.0 function json_encode() { trigger_error( @@ -2660,5 +3062,5 @@ function json_encode() } - -# ================================= END ================================== # +#/EOF +?> \ No newline at end of file diff --git a/lib/limonade/assertions.php b/lib/limonade/assertions.php index 952bd59..e9b5f76 100644 --- a/lib/limonade/assertions.php +++ b/lib/limonade/assertions.php @@ -1,19 +1,22 @@ should be TRUE') { test_run_assertion(); @@ -110,24 +113,24 @@ function assert_trigger_error($callable, $args = array(), $message = '<1> should return assert('$trigger_errors < count($GLOBALS["limonade"]["test_errors"]); //'.$message); } -# TODO add web browser assertions assert_http_get, assert_http_response... as in SimpleTest (http://www.simpletest.org/en/web_tester_documentation.html) +# #### TODO add web browser assertions assert_http_get, assert_http_response... as in SimpleTest (http://www.simpletest.org/en/web_tester_documentation.html) function assert_header($response, $expected_name, $expected_value = null, $message = "expected header '%s' to be equal to '%s' but received '%s: %s'") { test_run_assertion(); - # see assert_header in http://github.com/fnando/voodoo-test/blob/f3b0994ef138a6ba94d5e7cef6c1fb1720797a86/lib/assertions.php + // see assert_header in http://github.com/fnando/voodoo-test/blob/f3b0994ef138a6ba94d5e7cef6c1fb1720797a86/lib/assertions.php $headers = preg_split("/^\s*$/ms", $response); - //var_dump($headers); + // var_dump($headers); $headers = preg_replace("/\s*$/sm", "", $headers[0]); - //var_dump($headers); + // var_dump($headers); $regex_header = str_replace("/", "\\/", $expected_name); $regex_header = str_replace(".", "\\.", $regex_header); $header = $expected_name; - # from http://www.faqs.org/rfcs/rfc2616 - # Field names are case-insensitive + // from http://www.faqs.org/rfcs/rfc2616 + // Field names are case-insensitive if ($expected_value) { $regex = "/^{$regex_header}:(.*?)$/ism"; $header .= ": {$expected_value}"; diff --git a/lib/limonade/public/css/screen.css b/lib/limonade/public/css/screen.css index cf8578e..0a0a73b 100644 --- a/lib/limonade/public/css/screen.css +++ b/lib/limonade/public/css/screen.css @@ -106,115 +106,115 @@ hr.space {background:#fff;color:#fff;} ----------------------------------------------------------------------- */ html { - background-color: #b4a689; - text-align: center; - } - body { - width: auto; /* 16 col */ - margin: 0 20px; - text-align: left; - /*background: #fff url(blueprint/src/grid.png);*/ - font-family: Verdana, Geneva, sans-serif; - color: #303030; - } - a, a:hover, a:visited { - color: #4596cd; - } - a:hover { - text-decoration: none; - } - #header, h1, h2, h3, h4 { - font-family: "Arial Black", Gadget, sans-serif; - } - h1 { - color: #b4a689; - font-size: 2em; - line-height: 2; - border-bottom: 7px solid #000; - margin-bottom: 0.75em; - margin-top:0; - } - h2 { - font-size: 1.5em; - color: #cddb12; - margin-top: 2em; - line-height: 0.8; - margin-bottom: 1.2em; - } - h3 { - font-size: 1.33em; - line-height: 0.89; - margin-top: 2.66em; - margin-bottom: 1.33em; - color:#95b383; - } - pre { - background-color: #cddb12; - font-size: 12px; - padding: 1.5em; - margin-bottom: 1.5em; - overflow: auto; - } - code { - font: 10px Monaco, "Courier New", Courier, monospace; - color: #0d562a; - } - #header { - /*position:relative;*/ - height: 187px; /* 12 lines */ - background: #fff url() no-repeat 0 0; - margin-top: 10px; + background-color: #b4a689; + text-align: center; + } + body { + width: auto; /* 16 col */ + margin: 0 20px; + text-align: left; + /*background: #fff url(blueprint/src/grid.png);*/ + font-family: Verdana, Geneva, sans-serif; + color: #303030; + } + a, a:hover, a:visited { + color: #4596cd; + } + a:hover { + text-decoration: none; + } + #header, h1, h2, h3, h4 { + font-family: "Arial Black", Gadget, sans-serif; + } + h1 { + color: #b4a689; + font-size: 2em; + line-height: 2; + border-bottom: 7px solid #000; + margin-bottom: 0.75em; + margin-top:0; + } + h2 { + font-size: 1.5em; + color: #cddb12; + margin-top: 2em; + line-height: 0.8; + margin-bottom: 1.2em; + } + h3 { + font-size: 1.33em; + line-height: 0.89; + margin-top: 2.66em; + margin-bottom: 1.33em; + color:#95b383; + } + pre { + background-color: #cddb12; + font-size: 12px; + padding: 1.5em; + margin-bottom: 1.5em; + overflow: auto; + } + code { + font: 10px Monaco, "Courier New", Courier, monospace; + color: #0d562a; + } + #header { + /*position:relative;*/ + height: 187px; /* 12 lines */ + background: #fff url() no-repeat 0 0; + margin-top: 10px; padding:0; - } - #header h1 { - position: absolute; - left:-9999px; - margin:0; - } - #footer { - padding: 0 55px; - background-color: #1a1818; - color: #999; - height: 2em; - line-height: 2em; - text-align: right; - } - #footer a { - color: #a3d8de; - text-decoration: none; - } - #footer a:hover { - color: #fff; - } - #content { - background-color: #fff; - padding: 0 55px; - min-height: 400px; - } - p.bt { - text-align:right; - } - p.bt a { - text-decoration:none; - padding: 2px 4px; - } - p.bt a:hover { - background-color: #4596cd; - color: #fff; - } - #debug-menu { - position: fixed; - top: 0px; - right:20px; - background-color: rgba(100,100,100, 0.7); - padding:5px; - } - #debug-menu a { - color: #fff; - font-size: 90%; - text-decoration:none; - } - #debug-menu a:hover { - text-decoration: underline; - } + } + #header h1 { + position: absolute; + left:-9999px; + margin:0; + } + #footer { + padding: 0 55px; + background-color: #1a1818; + color: #999; + height: 2em; + line-height: 2em; + text-align: right; + } + #footer a { + color: #a3d8de; + text-decoration: none; + } + #footer a:hover { + color: #fff; + } + #content { + background-color: #fff; + padding: 0 55px; + min-height: 400px; + } + p.bt { + text-align:right; + } + p.bt a { + text-decoration:none; + padding: 2px 4px; + } + p.bt a:hover { + background-color: #4596cd; + color: #fff; + } + #debug-menu { + position: fixed; + top: 0px; + right:20px; + background-color: rgba(100,100,100, 0.7); + padding:5px; + } + #debug-menu a { + color: #fff; + font-size: 90%; + text-decoration:none; + } + #debug-menu a:hover { + text-decoration: underline; + } \ No newline at end of file diff --git a/lib/limonade/public/img/404.png b/lib/limonade/public/img/404.png new file mode 100644 index 0000000000000000000000000000000000000000..902110e16191774a1e91bafb86b9a6e9d5c12357 GIT binary patch literal 23305 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 Jy^eFt{{fdPQLz93 literal 0 HcmV?d00001 diff --git a/lib/limonade/public/img/500.png b/lib/limonade/public/img/500.png new file mode 100644 index 0000000000000000000000000000000000000000..57c84c3d966c190121141e56c38759bb24aaae2e GIT binary patch literal 31056 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 A2LJ#7 literal 0 HcmV?d00001 diff --git a/lib/limonade/tests.php b/lib/limonade/tests.php index 5e723d0..73dd082 100644 --- a/lib/limonade/tests.php +++ b/lib/limonade/tests.php @@ -1,15 +1,11 @@ E_USER_NOTICE) - echo test_cli_format("!!! ERROR", "red") . " [$errno], $errstr in $errfile at line $errline\n"; - $GLOBALS["limonade"]["test_errors"][] = array($errno, $errstr, $errfile, $errline); - return true; +{ + if($errno < E_USER_ERROR || $errno > E_USER_NOTICE) + echo test_cli_format("!!! ERROR", "red") . " [$errno], $errstr in $errfile at line $errline\n"; + $GLOBALS["limonade"]["test_errors"][] = array($errno, $errstr, $errfile, $errline); + return true; } /** @@ -292,7 +288,8 @@ function test_assert_failure($script, $line, $message) $GLOBALS["limonade"]["test_cases"][$name]['failures']++; } -function test_cli_format($text, $format) { +function test_cli_format($text, $format) +{ $formats = array( "blue" => 34, "bold" => 1, @@ -310,41 +307,47 @@ function test_cli_format($text, $format) { return chr(27) . "[01;{$format} m{$text}" . chr(27) . "[00m"; } -/** - * Do HTTP request and return the response content. - * - * @param string $url - * @param string $method - * @param bool $include_header - * @return string - * @author Nando Vieira - */ -function test_request($url, $method="GET", $include_header=false, $post_data=array(), $http_header=array()) { - $method = strtoupper($method); - $allowed_methods = array("GET", "PUT", "POST", "DELETE", "HEAD"); - if(!in_array($method, $allowed_methods)) - { - $message = "The requested method '$method' is not allowed"; - return assert('false; //'.$message); - } - - $curl = curl_init($url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_HEADER, $include_header); - curl_setopt($curl, CURLOPT_HTTPHEADER, $http_header); - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - if($method == 'POST') - { - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); - } - if($method == 'HEAD') - { - curl_setopt($curl, CURLOPT_NOBODY, true); - } - $response = curl_exec($curl); - curl_close($curl); - - return $response; +## +# Do HTTP request and return the response content. +# +# @param string $url +# +# @param string $method +# +# @param bool $include_header +# +# @return string +# +# @author Nando Vieira +# +## +function test_request($url, $method="GET", $include_header=false, $post_data=array(), $http_header=array()) +{ + $method = strtoupper($method); + $allowed_methods = array("GET", "PUT", "POST", "DELETE", "HEAD"); + if(!in_array($method, $allowed_methods)) + { + $message = "The requested method '$method' is not allowed"; + return assert('false; //'.$message); + } + + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_HEADER, $include_header); + curl_setopt($curl, CURLOPT_HTTPHEADER, $http_header); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + if($method == 'POST') + { + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); + } + if($method == 'HEAD') + { + curl_setopt($curl, CURLOPT_NOBODY, true); + } + $response = curl_exec($curl); + curl_close($curl); + + return $response; } diff --git a/lib/limonade/views/_debug.html.php b/lib/limonade/views/_debug.html.php index ce40e3c..b6978a4 100644 --- a/lib/limonade/views/_debug.html.php +++ b/lib/limonade/views/_debug.html.php @@ -1,14 +1,14 @@ - ENV_PRODUCTION && option('debug')): ?> - + +

[] - (in line ) -

- + (in line ) +

+

Debug arguments

-
+

Options

diff --git a/lib/limonade/views/default_layout.php b/lib/limonade/views/default_layout.php index 7b15a30..316870f 100644 --- a/lib/limonade/views/default_layout.php +++ b/lib/limonade/views/default_layout.php @@ -1,22 +1,24 @@ - - - Limonade, the fizzy PHP micro-framework - - - - - -
- -
- -
+ + + + Limonade, the fizzy PHP micro-framework + + + + + -
- - + +
+ +
+ +
+
+
+ + From 13efd5365f02cdda0de3d870d5a587b3469a1c4c Mon Sep 17 00:00:00 2001 From: kematzy Date: Thu, 29 Dec 2011 10:07:56 +0800 Subject: [PATCH 5/6] Typo fixes --- TODO | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/TODO b/TODO index a04d622..6fa70af 100644 --- a/TODO +++ b/TODO @@ -6,9 +6,9 @@ ### 0.5.1 ### - cleanup documentation - - put everything in the github wiki + - put everything in the Github wiki - add a Getting Started section - - make the README hyper-simplified; acts as a resume, lits of features + and a table of content + - make the README hyper-simplified; acts as a resume, list of features + and a table of content - remove reference to html API: no time to maintain it and source code is enough. Maybe we can simply use a two-columns documentation generated with something like http://rtomayko.github.com/rocco/ ? - Add instructions about running tests - adding contributors/thanks in AUTHORS @@ -19,7 +19,7 @@ - in README, complete the section about hooks and user defined functions - add file controller feature - a controller can be a file if controller callback string ending with '.php' - - this allow to push direcly the procedural code of the controller in a file, without declaring a controller function + - this allow to push directly the procedural code of the controller in a file, without declaring a controller function - it end with a return, like a normal controller - code is loaded in a container function when route is built, just before execution @@ -34,7 +34,7 @@ - route_callback_[controller_]create() - route_callback_[controller_]call() - add a flash_keep() function like in rails (http://guides.rubyonrails.org/action_controller_overview.html#the-flash) -- in debug output, formating debug_backtrace to be me readable (like debug_backtrace output but with extra informations that can be toggled). Using show_settings view by kematzy ? http://github.com/kematzy/limonade/commit/02ad17260912757b73ff809acad8e970efafc4b4 +- in debug output, formatting debug_backtrace to be more readable (like debug_backtrace output but with extra informations that can be toggled). Using show_settings view by kematzy ? http://github.com/kematzy/limonade/commit/02ad17260912757b73ff809acad8e970efafc4b4 - improve security in render_file with a safe_dir option - new redirect_to (support for https) - Use callback pseudo-type instead of function in error user defined handling. @@ -56,16 +56,16 @@ #### Adding support for handling php settings with option() function -- this will help have more consistency, and setting php options at the begining of the run +- this will help have more consistency, and setting php options at the beginning of the run - all options that begins with `php_` are mapped with ini_set() options in read and write - adding an option('php_display_errors'); by default ini_set('display_errors', 0) in production env, but enabled if env is dev (unless matching option is enabled) -- a default callback `php_display_errors` will be there to set correct limonade debug level +- a default callback `php_display_errors` will be there to set correct Limonade debug level ### 0.8 ### - Zestify Limonade - - refactor main dipatching loop + - refactor main dispatching loop - allow zest response return in controllers From 758dec613de57f94bf837445fae375381afac3ac Mon Sep 17 00:00:00 2001 From: kematzy Date: Thu, 29 Dec 2011 10:18:00 +0800 Subject: [PATCH 6/6] Added new option() related functionality Working with option('a.value') can be pain sometimes and these added functions make it less painful. * option_isset('option.name') => returns True/False based upon if value returned is NULL * isset_option('option.name') => syntactic sugar for option_isset() * option_defined('option.name') => syntactic sugar for option_isset(). Reads better in some circumstances * option_undefined('option.name') => syntactic sugar for doing a " !option_isset() ". Reads better in some circumstances * option_equals('option.name', 'value') => tests for presence and value of option('value') at the same time. --- lib/limonade.php | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lib/limonade.php b/lib/limonade.php index 4b3d2a6..15a0354 100644 --- a/lib/limonade.php +++ b/lib/limonade.php @@ -232,6 +232,61 @@ function option($name = null, $values = null) } /** +* Tests if an option value is set (ie: not NULL) +* +* Provides a safe way to test for the presence of an option value without incurring a +* PHP Fatal error: Can't use function return value in write context +* +* +* // fails with [ PHP Fatal error: Can't use function return value in write context ] +* if ( isset( option('dir.views) ) ) { } +* +* // Works just fine +* if ( option_isset('dir.views) ) { } +* +* +* @param string $name => the option key to test for +* @return boolean TRUE if value is set, FALSE if value is undefined ie: NULL +*/ +function option_isset($name) +{ + $res = option($name); + return ( isset( $res); +} + +// syntactic sugar +function isset_option($name) { return option_isset($name); } +// syntactic sugar: reads better when testing for the value +function option_defined($name) { return option_isset($name); } +// syntactic sugar: reads better when testing for the value +// returns the opposite of option_isset() +function option_undefined($name) { return ! option_isset($name); } +function option_missing($name) { return ! option_isset($name); } + +/** + * Shortcut to test for presence and value of an option + * + * Provides a safe and quick way to test for the presence and value of an option setting. + * + * + * // the long way + * if ( option_isset('dir.views) && ( option('dir.views') === 'some value' ) ) { } + * + * // the smart way + * if ( option_equals('dir.views', 'some value') ) { } + * + * + * @param string $name + * @param string $value + * @return boolean => TRUE if present and the same, FALSE if undefined or not the same + */ +function option_equals($name, $value) +{ + if( option_undefined($name) ) { return FALSE; } + return ( option($name) === $value ); +} + +/** * Set and returns params * * Depending on provided arguments: