PageRenderTime 63ms CodeModel.GetById 4ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/limonade.php

https://github.com/cbrumelle/limonade
PHP | 2375 lines | 1310 code | 236 blank | 829 comment | 214 complexity | 3c7131e6cb16229a5d037a33b52f1f5b MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2                                                                  
   3# ============================================================================ #
   4
   5/**
   6 *  L I M O N A D E
   7 * 
   8 *  a PHP micro framework.
   9 * 
  10 *  For more informations: {@link http://github/sofadesign/limonade}
  11 *  
  12 *  @author Fabrice Luraine
  13 *  @copyright Copyright (c) 2009 Fabrice Luraine
  14 *  @license http://opensource.org/licenses/mit-license.php The MIT License
  15 *  @package limonade
  16 */
  17
  18#   -----------------------------------------------------------------------    #
  19#    Copyright (c) 2009 Fabrice Luraine                                        #
  20#                                                                              #
  21#    Permission is hereby granted, free of charge, to any person               #
  22#    obtaining a copy of this software and associated documentation            #
  23#    files (the "Software"), to deal in the Software without                   #
  24#    restriction, including without limitation the rights to use,              #
  25#    copy, modify, merge, publish, distribute, sublicense, and/or sell         #
  26#    copies of the Software, and to permit persons to whom the                 #
  27#    Software is furnished to do so, subject to the following                  #
  28#    conditions:                                                               #
  29#                                                                              #
  30#    The above copyright notice and this permission notice shall be            #
  31#    included in all copies or substantial portions of the Software.           #
  32#                                                                              #
  33#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,           #
  34#    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES           #
  35#    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                  #
  36#    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT               #
  37#    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,              #
  38#    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING              #
  39#    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR             #
  40#    OTHER DEALINGS IN THE SOFTWARE.                                           #
  41# ============================================================================ # 
  42
  43
  44
  45
  46
  47
  48
  49
  50# ============================================================================ #
  51#    0. PREPARE                                                                #
  52# ============================================================================ #
  53
  54## CONSTANTS __________________________________________________________________
  55/**
  56 * Limonade version
  57 */
  58define('LIMONADE',              '0.5.0');
  59define('LIM_START_MICROTIME',   (float)substr(microtime(), 0, 10));
  60define('LIM_SESSION_NAME',      'Fresh_and_Minty_Limonade_App');
  61define('LIM_SESSION_FLASH_KEY', '_lim_flash_messages');
  62define('LIM_START_MEMORY',      memory_get_usage());
  63define('E_LIM_HTTP',            32768);
  64define('E_LIM_PHP',             65536);
  65define('E_LIM_DEPRECATED',      35000);
  66define('NOT_FOUND',             404);
  67define('SERVER_ERROR',          500);
  68define('ENV_PRODUCTION',        10);
  69define('ENV_DEVELOPMENT',       100);
  70define('X-SENDFILE',            10);
  71define('X-LIGHTTPD-SEND-FILE',  20);
  72
  73# for PHP 5.3.0 <
  74if(!defined('E_DEPRECATED'))      define('E_DEPRECATED', 8192);
  75if(!defined('E_USER_DEPRECATED')) define('E_USER_DEPRECATED', 16384);
  76
  77
  78## SETTING BASIC SECURITY _____________________________________________________
  79
  80# A. Unsets all global variables set from a superglobal array
  81
  82/**
  83 * @access private
  84 * @return void
  85 */
  86function unregister_globals()
  87{
  88  $args = func_get_args();
  89  foreach($args as $k => $v)
  90    if(array_key_exists($k, $GLOBALS)) unset($GLOBALS[$k]);
  91}
  92
  93if(ini_get('register_globals'))
  94{
  95  unregister_globals( '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', 
  96                      '_ENV', '_FILES');
  97  ini_set('register_globals', 0);
  98}
  99
 100# B. removing magic quotes
 101
 102/**
 103 * @access private
 104 * @param string $array 
 105 * @return array
 106 */
 107function remove_magic_quotes($array)
 108{
 109  foreach ($array as $k => $v)
 110    $array[$k] = is_array($v) ? remove_magic_quotes($v) : stripslashes($v);
 111  return $array;
 112}
 113
 114if (get_magic_quotes_gpc())
 115{
 116  $_GET    = remove_magic_quotes($_GET);
 117  $_POST   = remove_magic_quotes($_POST);
 118  $_COOKIE = remove_magic_quotes($_COOKIE);
 119  ini_set('magic_quotes_gpc', 0);
 120}
 121
 122if(function_exists('set_magic_quotes_runtime') && get_magic_quotes_runtime()) set_magic_quotes_runtime(false);
 123
 124# C. Disable error display
 125#    by default, no error reporting; it will be switched on later in run().
 126#    ini_set('display_errors', 1); must be called explicitly in app file
 127#    if you want to show errors before running app
 128ini_set('display_errors', 0);
 129
 130## SETTING INTERNAL ROUTES _____________________________________________________
 131
 132dispatch(array("/_lim_css/*.css", array('_lim_css_filename')), 'render_limonade_css');
 133  /**
 134   * Internal controller that responds to route /_lim_css/*.css
 135   *
 136   * @access private
 137   * @return string
 138   */
 139  function render_limonade_css()
 140  {
 141    option('views_dir', file_path(option('limonade_public_dir'), 'css'));
 142    $fpath = file_path(params('_lim_css_filename').".css");
 143    return css($fpath, null); // with no layout
 144  }
 145
 146dispatch(array("/_lim_public/**", array('_lim_public_file')), 'render_limonade_file');
 147  /**
 148   * Internal controller that responds to route /_lim_public/**
 149   *
 150   * @access private
 151   * @return void
 152   */
 153  function render_limonade_file()
 154  {
 155    $fpath = file_path(option('limonade_public_dir'), params('_lim_public_file'));
 156    return render_file($fpath, true);
 157  }
 158
 159
 160
 161
 162                                     # # #
 163
 164
 165
 166
 167# ============================================================================ #
 168#    1. BASE                                                                   #
 169# ============================================================================ #
 170 
 171## ABSTRACTS ___________________________________________________________________
 172
 173# function configure(){}
 174# function before(){}
 175# function after(){}
 176# function not_found(){}
 177# function server_error(){}
 178# function route_missing(){}
 179# function before_exit(){}
 180
 181
 182## MAIN PUBLIC FUNCTIONS _______________________________________________________
 183
 184/**
 185 * Set and returns options values
 186 * 
 187 * If multiple values are provided, set $name option with an array of those values.
 188 * If there is only one value, set $name option with the provided $values
 189 *
 190 * @param string $name 
 191 * @param mixed  $values,... 
 192 * @return mixed option value for $name if $name argument is provided, else return all options
 193 */
 194function option($name = null, $values = null)
 195{
 196  static $options = array();
 197  $args = func_get_args();
 198  $name = array_shift($args);
 199  if(is_null($name)) return $options;
 200  if(!empty($args))
 201  {
 202    $options[$name] = count($args) > 1 ? $args : $args[0];
 203  }
 204  if(array_key_exists($name, $options)) return $options[$name];
 205  return;
 206}
 207
 208/**
 209 * Set and returns params
 210 * 
 211 * Depending on provided arguments:
 212 * 
 213 *  * Reset params if first argument is null
 214 * 
 215 *  * If first argument is an array, merge it with current params
 216 * 
 217 *  * If there is a second argument $value, set param $name (first argument) with $value
 218 * <code>
 219 *  params('name', 'Doe') // set 'name' => 'Doe'
 220 * </code>
 221 *  * If there is more than 2 arguments, set param $name (first argument) value with
 222 *    an array of next arguments
 223 * <code>
 224 *  params('months', 'jan', 'feb', 'mar') // set 'month' => array('months', 'jan', 'feb', 'mar')
 225 * </code>
 226 * 
 227 * @param mixed $name_or_array_or_null could be null || array of params || name of a param (optional)
 228 * @param mixed $value,... for the $name param (optional)
 229 * @return mixed all params, or one if a first argument $name is provided
 230 */
 231function params($name_or_array_or_null = null, $value = null)
 232{
 233  static $params = array();
 234  $args = func_get_args();
 235
 236  if(func_num_args() > 0)
 237  {
 238    $name = array_shift($args);
 239    if(is_null($name))
 240    {
 241      # Reset params
 242      $params = array();
 243      return $params;
 244    }
 245    if(is_array($name))
 246    {
 247      $params = array_merge($params, $name);
 248      return $params;
 249    }
 250    $nargs = count($args);
 251    if($nargs > 0)
 252    {
 253      $value = $nargs > 1 ? $args : $args[0];
 254      $params[$name] = $value;
 255    }
 256    return array_key_exists($name,$params) ? $params[$name] : null;
 257  }
 258
 259  return $params;
 260}
 261
 262/**
 263 * Set and returns template variables
 264 * 
 265 * If multiple values are provided, set $name variable with an array of those values.
 266 * If there is only one value, set $name variable with the provided $values
 267 *
 268 * @param string $name 
 269 * @param mixed  $values,... 
 270 * @return mixed variable value for $name if $name argument is provided, else return all variables
 271 */
 272function set($name = null, $values = null)
 273{
 274  static $vars = array();
 275  $args = func_get_args();
 276  $name = array_shift($args);
 277  if(is_null($name)) return $vars;
 278  if(!empty($args))
 279  {
 280    $vars[$name] = count($args) > 1 ? $args : $args[0];
 281  }
 282  if(array_key_exists($name, $vars)) return $vars[$name];
 283  return $vars;
 284}
 285
 286/**
 287 * Sets a template variable with a value or a default value if value is empty
 288 *
 289 * @param string $name 
 290 * @param string $value 
 291 * @param string $default 
 292 * @return mixed setted value
 293 */
 294function set_or_default($name, $value, $default)
 295{
 296  return set($name, value_or_default($value, $default));
 297}
 298
 299/**
 300 * Running application
 301 *
 302 * @param string $env 
 303 * @return void
 304 */
 305function run($env = null)
 306{
 307  if(is_null($env)) $env = env();
 308   
 309  # 0. Set default configuration
 310  $root_dir  = dirname(app_file());
 311  $base_path = dirname(file_path($env['SERVER']['SCRIPT_NAME']));
 312  $base_file = basename($env['SERVER']['SCRIPT_NAME']);
 313  $base_uri  = file_path($base_path, (($base_file == 'index.php') ? '?' : $base_file.'?'));
 314  $lim_dir   = dirname(__FILE__);
 315  option('root_dir',           $root_dir);
 316  option('base_path',          $base_path);
 317  option('base_uri',           $base_uri); // set it manually if you use url_rewriting
 318  option('limonade_dir',       file_path($lim_dir));
 319  option('limonade_views_dir', file_path($lim_dir, 'limonade', 'views'));
 320  option('limonade_public_dir',file_path($lim_dir, 'limonade', 'public'));
 321  option('public_dir',         file_path($root_dir, 'public'));
 322  option('views_dir',          file_path($root_dir, 'views'));
 323  option('controllers_dir',    file_path($root_dir, 'controllers'));
 324  option('lib_dir',            file_path($root_dir, 'lib'));
 325  option('error_views_dir',    option('limonade_views_dir'));
 326  option('env',                ENV_PRODUCTION);
 327  option('debug',              true);
 328  option('session',            LIM_SESSION_NAME); // true, false or the name of your session
 329  option('encoding',           'utf-8');
 330  option('gzip',               false);
 331  option('x-sendfile',         0); // 0: disabled, 
 332                                   // X-SENDFILE: for Apache and Lighttpd v. >= 1.5,
 333                                   // X-LIGHTTPD-SEND-FILE: for Apache and Lighttpd v. < 1.5
 334
 335  # 1. Set handlers
 336  # 1.1 Set error handling
 337  ini_set('display_errors', 1);
 338  set_error_handler('error_handler_dispatcher', E_ALL ^ E_NOTICE);
 339  
 340  # 1.2 Register shutdown function
 341  register_shutdown_function('stop_and_exit');
 342
 343  # 2. Set user configuration
 344  call_if_exists('configure');
 345  
 346  # 2.1 Set gzip compression if defined
 347  if(is_bool(option('gzip')) && option('gzip'))
 348  {
 349    ini_set('zlib.output_compression', '1');
 350  }
 351
 352  # 3. Loading libs
 353  require_once_dir(option('lib_dir'));
 354
 355  # 4. Starting session
 356  if(!defined('SID') && option('session'))
 357  {
 358    if(!is_bool(option('session'))) session_name(option('session'));
 359    if(!session_start()) trigger_error("An error occured while trying to start the session", E_USER_WARNING);
 360  }
 361
 362  # 5. Set some default methods if needed
 363  if(!function_exists('after'))
 364  {
 365    function after($output)
 366    {
 367      return $output;
 368    }
 369  }
 370  if(!function_exists('route_missing'))
 371  {
 372    function route_missing($request_method, $request_uri)
 373    {
 374      halt(NOT_FOUND, "($request_method) $request_uri");
 375    }
 376  }
 377
 378  # 6. Check request
 379  if($rm = request_method())
 380  {
 381    if(request_is_head()) ob_start(); // then no output
 382
 383    if(!request_method_is_allowed($rm))
 384      halt(HTTP_NOT_IMPLEMENTED, "The requested method <code>'$rm'</code> is not implemented");
 385
 386    # 6.1 Check matching route
 387    if($route = route_find($rm, request_uri()))
 388    {
 389      params($route['params']);
 390
 391      # 6.2 Load controllers dir
 392      require_once_dir(option('controllers_dir'));
 393
 394      if(is_callable($route['function']))
 395      {
 396        # 6.3 Call before function
 397        call_if_exists('before', $route);
 398
 399        # 6.4 Call matching controller function and output result
 400        if($output = call_user_func_array($route['function'], array_values($route['params'])))
 401        {
 402          echo after(error_notices_render() . $output);
 403        }
 404      }
 405      else halt(SERVER_ERROR, "Routing error: undefined function '{\
 406        ['function']}'", $route);      
 407    }
 408    else route_missing($rm, request_uri());
 409
 410  }
 411  else halt(HTTP_NOT_IMPLEMENTED, "The requested method <code>'$rm'</code> is not implemented");
 412}
 413
 414/**
 415 * Stop and exit limonade application
 416 *
 417 * @access private 
 418 * @param boolean exit or not
 419 * @return void
 420 */
 421function stop_and_exit($exit = true)
 422{
 423  call_if_exists('before_exit');
 424  $flash_sweep = true;
 425  $headers = headers_list();
 426  foreach($headers as $header)
 427  {
 428    // If a Content-Type header exists, flash_sweep only if is text/html
 429    // Else if there's no Content-Type header, flash_sweep by default
 430    if(stripos($header, 'Content-Type:') === 0)
 431    {
 432      $flash_sweep = stripos($header, 'Content-Type: text/html') === 0;
 433      break;
 434    }
 435  }
 436  if($flash_sweep) flash_sweep();
 437  if(defined('SID')) session_write_close();
 438  if(request_is_head()) ob_end_clean();
 439  if($exit) exit;
 440}
 441
 442/**
 443 * Returns limonade environment variables:
 444 *
 445 * 'SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE', 
 446 * 'GET', 'POST', 'PUT', 'DELETE'
 447 * 
 448 * If a null argument is passed, reset and rebuild environment
 449 *
 450 * @param null @reset reset and rebuild environment
 451 * @return array
 452 */
 453function env($reset = null)
 454{
 455  static $env = array();
 456  if(func_num_args() > 0)
 457  {
 458    $args = func_get_args();
 459    if(is_null($args[0])) $env = array();
 460  }
 461
 462  if(empty($env))
 463  {
 464    if(empty($GLOBALS['_SERVER']))
 465    {
 466      // Fixing empty $GLOBALS['_SERVER'] bug 
 467      // http://sofadesign.lighthouseapp.com/projects/29612-limonade/tickets/29-env-is-empty
 468      $GLOBALS['_SERVER']  =& $_SERVER;
 469      $GLOBALS['_FILES']   =& $_FILES;
 470      $GLOBALS['_REQUEST'] =& $_REQUEST;
 471      $GLOBALS['_SESSION'] =& $_SESSION;
 472      $GLOBALS['_ENV']     =& $_ENV;
 473      $GLOBALS['_COOKIE']  =& $_COOKIE;
 474    }
 475
 476    $glo_names = array('SERVER', 'FILES', 'REQUEST', 'SESSION', 'ENV', 'COOKIE');
 477
 478    $vars = array_merge($glo_names, request_methods());
 479    foreach($vars as $var)
 480    {
 481      $varname = "_$var";
 482      if(!array_key_exists($varname, $GLOBALS)) $GLOBALS[$varname] = array();
 483      $env[$var] =& $GLOBALS[$varname];
 484    }
 485
 486    $method = request_method($env);
 487    if($method == 'PUT' || $method == 'DELETE')
 488    {
 489      $varname = "_$method";
 490      if(array_key_exists('_method', $_POST) && $_POST['_method'] == $method)
 491      {
 492        foreach($_POST as $k => $v)
 493        {
 494          if($k == "_method") continue;
 495          $GLOBALS[$varname][$k] = $v;
 496        }
 497      }
 498      else
 499      {
 500        parse_str(file_get_contents('php://input'), $GLOBALS[$varname]);
 501      }
 502    }
 503  }
 504  return $env;
 505}
 506
 507/**
 508 * Returns application root file path
 509 *
 510 * @return string
 511 */
 512function app_file()
 513{
 514  static $file;
 515  if(empty($file))
 516  {
 517    $stacktrace = array_pop(debug_backtrace());
 518    $file = $stacktrace['file'];
 519  }
 520  return file_path($file);
 521}
 522
 523
 524
 525
 526                                     # # #
 527
 528
 529
 530
 531# ============================================================================ #
 532#    2. ERROR                                                                  #
 533# ============================================================================ #
 534 
 535/**
 536 * Associate a function with error code(s) and return all associations
 537 *
 538 * @param string $errno 
 539 * @param string $function 
 540 * @return array
 541 */
 542function error($errno = null, $function = null)
 543{
 544  static $errors = array();
 545  if(func_num_args() > 0)
 546  {
 547    $errors[] = array('errno'=>$errno, 'function'=> $function);
 548  }
 549  return $errors;
 550}
 551
 552/**
 553 * Raise an error, passing a given error number and an optional message,
 554 * then exit.
 555 * Error number should be a HTTP status code or a php user error (E_USER...)
 556 * $errno and $msg arguments can be passsed in any order
 557 * If no arguments are passed, default $errno is SERVER_ERROR (500)
 558 *
 559 * @param int,string $errno Error number or message string
 560 * @param string,string $msg Message string or error number
 561 * @param mixed $debug_args extra data provided for debugging
 562 * @return void
 563 */
 564function halt($errno = SERVER_ERROR, $msg = '', $debug_args = null)
 565{
 566  $args = func_get_args();
 567  $error = array_shift($args);
 568
 569  # switch $errno and $msg args
 570  # TODO cleanup / refactoring
 571  if(is_string($errno))
 572  {
 573   $msg = $errno;
 574   $oldmsg = array_shift($args);
 575   $errno = empty($oldmsg) ? SERVER_ERROR : $oldmsg;
 576  }
 577  else if(!empty($args)) $msg = array_shift($args);
 578
 579  if(empty($msg) && $errno == NOT_FOUND) $msg = request_uri();
 580  if(empty($msg)) $msg = "";
 581  if(!empty($args)) $debug_args = $args;
 582  set('_lim_err_debug_args', $debug_args);
 583
 584  error_handler_dispatcher($errno, $msg, null, null);
 585
 586}
 587
 588/**
 589 * Internal error handler dispatcher
 590 * Find and call matching error handler and exit
 591 * If no match found, call default error handler
 592 *
 593 * @access private
 594 * @param int $errno 
 595 * @param string $errstr 
 596 * @param string $errfile 
 597 * @param string $errline 
 598 * @return void
 599 */
 600function error_handler_dispatcher($errno, $errstr, $errfile, $errline)
 601{
 602  $back_trace = debug_backtrace();
 603  while($trace = array_shift($back_trace))
 604  {
 605    if($trace['function'] == 'halt')
 606    {
 607      $errfile = $trace['file'];
 608      $errline = $trace['line'];
 609      break;
 610    }
 611  }  
 612
 613  # Notices and warning won't halt execution
 614  if(error_wont_halt_app($errno))
 615  {
 616    error_notice($errno, $errstr, $errfile, $errline);
 617  	return;
 618  }
 619  else
 620  {
 621    # Other errors will stop application
 622    $handlers = error();
 623    $is_http_err = http_response_status_is_valid($errno);
 624    foreach($handlers as $handler)
 625    {
 626      $e = is_array($handler['errno']) ? $handler['errno'] : array($handler['errno']);
 627      while($ee = array_shift($e))
 628      {
 629        if($ee == $errno || $ee == E_LIM_PHP || ($ee == E_LIM_HTTP && $is_http_err))
 630        {
 631          echo call_if_exists($handler['function'], $errno, $errstr, $errfile, $errline);
 632          exit;
 633        }
 634      }
 635    }
 636    echo error_default_handler($errno, $errstr, $errfile, $errline);
 637  }
 638}
 639
 640
 641/**
 642 * Default error handler
 643 *
 644 * @param string $errno 
 645 * @param string $errstr 
 646 * @param string $errfile 
 647 * @param string $errline 
 648 * @return string error output
 649 */
 650function error_default_handler($errno, $errstr, $errfile, $errline)
 651{
 652  $is_http_err = http_response_status_is_valid($errno);
 653  $http_error_code = $is_http_err ? $errno : SERVER_ERROR;
 654
 655  status($http_error_code);
 656
 657  return $http_error_code == NOT_FOUND ?
 658            error_not_found_output($errno, $errstr, $errfile, $errline) :
 659            error_server_error_output($errno, $errstr, $errfile, $errline);                    
 660}
 661
 662/**
 663 * Returns not found error output
 664 *
 665 * @access private
 666 * @param string $msg 
 667 * @return string
 668 */
 669function error_not_found_output($errno, $errstr, $errfile, $errline)
 670{
 671  if(!function_exists('not_found'))
 672  {
 673    /**
 674     * Default not found error output
 675     *
 676     * @param string $errno 
 677     * @param string $errstr 
 678     * @param string $errfile 
 679     * @param string $errline 
 680     * @return string
 681     */
 682    function not_found($errno, $errstr, $errfile=null, $errline=null)
 683    {
 684      option('views_dir', option('error_views_dir'));
 685      $msg = h(rawurldecode($errstr));
 686      return html("<h1>Page not found:</h1><p><code>{$msg}</code></p>", error_layout());
 687    }
 688  }
 689  return not_found($errno, $errstr, $errfile, $errline);
 690}
 691
 692/**
 693 * Returns server error output
 694 *
 695 * @access private
 696 * @param int $errno 
 697 * @param string $errstr 
 698 * @param string $errfile 
 699 * @param string $errline 
 700 * @return string
 701 */
 702function error_server_error_output($errno, $errstr, $errfile, $errline)
 703{
 704  if(!function_exists('server_error'))
 705  {
 706    /**
 707     * Default server error output
 708     *
 709     * @param string $errno 
 710     * @param string $errstr 
 711     * @param string $errfile 
 712     * @param string $errline 
 713     * @return string
 714     */
 715    function server_error($errno, $errstr, $errfile=null, $errline=null)
 716    {
 717      $is_http_error = http_response_status_is_valid($errno);
 718      $args = compact('errno', 'errstr', 'errfile', 'errline', 'is_http_error');
 719      option('views_dir', option('limonade_views_dir'));
 720      $html = render('error.html.php', null, $args);	
 721      option('views_dir', option('error_views_dir'));
 722      return html($html, error_layout(), $args);
 723    }
 724  }
 725  return server_error($errno, $errstr, $errfile, $errline);
 726}
 727
 728/**
 729 * Set and returns error output layout
 730 *
 731 * @param string $layout 
 732 * @return string
 733 */
 734function error_layout($layout = false)
 735{
 736  static $o_layout = 'default_layout.php';
 737  if($layout !== false)
 738  {
 739    option('error_views_dir', option('views_dir'));
 740    $o_layout = $layout;
 741  }
 742  return $o_layout;
 743}
 744
 745
 746/**
 747 * Set a notice if arguments are provided
 748 * Returns all stored notices.
 749 * If $errno argument is null, reset the notices array
 750 *
 751 * @access private
 752 * @param string, null $str 
 753 * @return array
 754 */
 755function error_notice($errno = false, $errstr = null, $errfile = null, $errline = null)
 756{
 757  static $notices = array();
 758  if($errno) $notices[] = compact('errno', 'errstr', 'errfile', 'errline');
 759  else if(is_null($errno)) $notices = array();
 760  return $notices;
 761}
 762
 763/**
 764 * Returns notices output rendering and reset notices
 765 *
 766 * @return string
 767 */
 768function error_notices_render()
 769{
 770  if(option('debug') && option('env') > ENV_PRODUCTION)
 771  {
 772    $notices = error_notice();
 773    error_notice(null); // reset notices
 774    $c_view_dir = option('views_dir'); // keep for restore after render
 775    option('views_dir', option('limonade_views_dir'));
 776    $o = render('_notices.html.php', null, array('notices' => $notices));
 777    option('views_dir', $c_view_dir); // restore current views dir
 778
 779    return $o;
 780  }
 781}
 782
 783/**
 784 * Checks if an error is will halt application execution. 
 785 * Notices and warnings will not.
 786 *
 787 * @access private
 788 * @param string $num error code number
 789 * @return boolean
 790 */
 791function error_wont_halt_app($num)
 792{
 793  return $num == E_NOTICE ||
 794         $num == E_WARNING ||
 795         $num == E_CORE_WARNING ||
 796         $num == E_COMPILE_WARNING ||
 797         $num == E_USER_WARNING ||
 798         $num == E_USER_NOTICE ||
 799         $num == E_DEPRECATED ||
 800         $num == E_USER_DEPRECATED ||
 801         $num == E_LIM_DEPRECATED;
 802}
 803
 804
 805
 806/**
 807 * return error code name for a given code num, or return all errors names
 808 *
 809 * @param string $num 
 810 * @return mixed
 811 */
 812function error_type($num = null)
 813{
 814  $types = array (
 815              E_ERROR              => 'ERROR',
 816              E_WARNING            => 'WARNING',
 817              E_PARSE              => 'PARSING ERROR',
 818              E_NOTICE             => 'NOTICE',
 819              E_CORE_ERROR         => 'CORE ERROR',
 820              E_CORE_WARNING       => 'CORE WARNING',
 821              E_COMPILE_ERROR      => 'COMPILE ERROR',
 822              E_COMPILE_WARNING    => 'COMPILE WARNING',
 823              E_USER_ERROR         => 'USER ERROR',
 824              E_USER_WARNING       => 'USER WARNING',
 825              E_USER_NOTICE        => 'USER NOTICE',
 826              E_STRICT             => 'STRICT NOTICE',
 827              E_RECOVERABLE_ERROR  => 'RECOVERABLE ERROR',
 828              E_DEPRECATED         => 'DEPRECATED WARNING',
 829              E_USER_DEPRECATED    => 'USER DEPRECATED WARNING',
 830              E_LIM_DEPRECATED     => 'LIMONADE DEPRECATED WARNING'
 831              );
 832  return is_null($num) ? $types : $types[$num];
 833}
 834
 835/**
 836 * Returns http response status for a given error number
 837 *
 838 * @param string $errno 
 839 * @return int
 840 */
 841function error_http_status($errno)
 842{
 843  $code = http_response_status_is_valid($errno) ? $errno : SERVER_ERROR;
 844  return http_response_status($code);
 845}
 846
 847
 848
 849
 850                                     # # #
 851
 852
 853
 854
 855# ============================================================================ #
 856#    3. REQUEST                                                                #
 857# ============================================================================ #
 858 
 859/**
 860 * Returns current request method for a given environment or current one
 861 *
 862 * @param string $env 
 863 * @return string
 864 */
 865function request_method($env = null)
 866{
 867  if(is_null($env)) $env = env();
 868  $m = array_key_exists('REQUEST_METHOD', $env['SERVER']) ? $env['SERVER']['REQUEST_METHOD'] : null;
 869  if($m == "POST" && array_key_exists('_method', $env['POST'])) 
 870    $m = strtoupper($env['POST']['_method']);
 871  if(!in_array(strtoupper($m), request_methods()))
 872  {
 873    trigger_error("'$m' request method is unkown or unavailable.", E_USER_WARNING);
 874    $m = false;
 875  }
 876  return $m;
 877}
 878
 879/**
 880 * Checks if a request method or current one is allowed
 881 *
 882 * @param string $m 
 883 * @return bool
 884 */
 885function request_method_is_allowed($m = null)
 886{
 887  if(is_null($m)) $m = request_method();
 888  return in_array(strtoupper($m), request_methods());
 889}
 890
 891/**
 892 * Checks if request method is GET
 893 *
 894 * @param string $env 
 895 * @return bool
 896 */
 897function request_is_get($env = null)
 898{
 899  return request_method($env) == "GET";
 900}
 901
 902/**
 903 * Checks if request method is POST
 904 *
 905 * @param string $env 
 906 * @return bool
 907 */
 908function request_is_post($env = null)
 909{
 910  return request_method($env) == "POST";
 911}
 912
 913/**
 914 * Checks if request method is PUT
 915 *
 916 * @param string $env 
 917 * @return bool
 918 */
 919function request_is_put($env = null)
 920{
 921  return request_method($env) == "PUT";
 922}
 923
 924/**
 925 * Checks if request method is DELETE
 926 *
 927 * @param string $env 
 928 * @return bool
 929 */
 930function request_is_delete($env = null)
 931{
 932  return request_method($env) == "DELETE";
 933}
 934
 935/**
 936 * Checks if request method is HEAD
 937 *
 938 * @param string $env 
 939 * @return bool
 940 */
 941function request_is_head($env = null)
 942{
 943  return request_method($env) == "HEAD";
 944}
 945
 946/**
 947 * Returns allowed request methods
 948 *
 949 * @return array
 950 */
 951function request_methods()
 952{
 953  return array("GET","POST","PUT","DELETE", "HEAD");
 954}
 955
 956/**
 957 * Returns current request uri (the path that will be compared with routes)
 958 * 
 959 * (Inspired from codeigniter URI::_fetch_uri_string method)
 960 *
 961 * @return string
 962 */
 963function request_uri($env = null)
 964{
 965  static $uri = null;
 966  if(is_null($env))
 967  {
 968    if(!is_null($uri)) return $uri;
 969    $env = env();
 970  }
 971
 972  if(array_key_exists('uri', $env['GET']))
 973  {
 974    $uri = $env['GET']['uri'];
 975  }
 976  else if(array_key_exists('u', $env['GET']))
 977  {
 978    $uri = $env['GET']['u'];
 979  }
 980  // bug: dot are converted to _... so we can't use it...
 981  // else if (count($env['GET']) == 1 && trim(key($env['GET']), '/') != '')
 982  // {
 983  //  $uri = key($env['GET']);
 984  // }
 985  else
 986  {
 987    $app_file = app_file();
 988    $path_info = isset($env['SERVER']['PATH_INFO']) ? $env['SERVER']['PATH_INFO'] : @getenv('PATH_INFO');
 989    $query_string =  isset($env['SERVER']['QUERY_STRING']) ? $env['SERVER']['QUERY_STRING'] : @getenv('QUERY_STRING');
 990
 991    // Is there a PATH_INFO variable?
 992  	// Note: some servers seem to have trouble with getenv() so we'll test it two ways
 993  	if (trim($path_info, '/') != '' && $path_info != "/".$app_file)
 994  	{
 995  	  $uri = $path_info;
 996  	}
 997  	// No PATH_INFO?... What about QUERY_STRING?
 998  	elseif (trim($query_string, '/') != '')
 999  	{
1000  	  $uri = $query_string;
1001  	}
1002  	elseif(array_key_exists('REQUEST_URI', $env['SERVER']) && !empty($env['SERVER']['REQUEST_URI']))
1003  	{
1004  	  $request_uri = rtrim(rawurldecode($env['SERVER']['REQUEST_URI']), '?/').'/';
1005  	  $base_path = $env['SERVER']['SCRIPT_NAME'];
1006
1007      if($request_uri."index.php" == $base_path) $request_uri .= "index.php";
1008  	  $uri = str_replace($base_path, '', $request_uri);
1009  	}
1010  	elseif($env['SERVER']['argc'] > 1 && trim($env['SERVER']['argv'][1], '/') != '')
1011    {
1012      $uri = $env['SERVER']['argv'][1];
1013    }
1014  }
1015
1016  $uri = rtrim($uri, "/"); # removes ending /
1017  if(empty($uri))
1018  {
1019    $uri = '/';
1020  }
1021  else if($uri[0] != '/')
1022  {
1023    $uri = '/' . $uri; # add a leading slash
1024  }
1025  return rawurldecode($uri);
1026}
1027
1028
1029
1030
1031                                     # # #
1032
1033
1034
1035
1036# ============================================================================ #
1037#    4. ROUTER                                                                 #
1038# ============================================================================ #
1039 
1040/**
1041 * An alias of {@link dispatch_get()}
1042 *
1043 * @return void
1044 */
1045function dispatch($path_or_array, $function, $options = array())
1046{
1047  dispatch_get($path_or_array, $function, $options);
1048}
1049
1050/**
1051 * Add a GET route. Also automatically defines a HEAD route.
1052 *
1053 * @param string $path_or_array 
1054 * @param string $function
1055 * @param array $options (optional). See {@link route()} for available options.
1056 * @return void
1057 */
1058function dispatch_get($path_or_array, $function, $options = array())
1059{
1060  route("GET", $path_or_array, $function, $options);
1061  route("HEAD", $path_or_array, $function, $options);
1062}
1063
1064/**
1065 * Add a POST route
1066 *
1067 * @param string $path_or_array 
1068 * @param string $function
1069 * @param array $options (optional). See {@link route()} for available options.
1070 * @return void
1071 */
1072function dispatch_post($path_or_array, $function, $options = array())
1073{
1074  route("POST", $path_or_array, $function, $options);
1075}
1076
1077/**
1078 * Add a PUT route
1079 *
1080 * @param string $path_or_array 
1081 * @param string $function
1082 * @param array $options (optional). See {@link route()} for available options.
1083 * @return void
1084 */
1085function dispatch_put($path_or_array, $function, $options = array())
1086{
1087  route("PUT", $path_or_array, $function, $options);
1088}
1089
1090/**
1091 * Add a DELETE route
1092 *
1093 * @param string $path_or_array 
1094 * @param string $function
1095 * @param array $options (optional). See {@link route()} for available options.
1096 * @return void
1097 */
1098function dispatch_delete($path_or_array, $function, $options = array())
1099{
1100  route("DELETE", $path_or_array, $function, $options);
1101}
1102
1103
1104/**
1105 * Add route if required params are provided.
1106 * Delete all routes if null is passed as a unique argument
1107 * Return all routes
1108 * 
1109 * @see route_build()
1110 * @access private
1111 * @param string $method 
1112 * @param string|array $path_or_array 
1113 * @param callback $func
1114 * @param array $options (optional)
1115 * @return array
1116 */
1117function route()
1118{
1119  static $routes = array();
1120  $nargs = func_num_args();
1121  if( $nargs > 0)
1122  {
1123    $args = func_get_args();
1124    if($nargs === 1 && is_null($args[0])) $routes = array();
1125    else if($nargs < 3) trigger_error("Missing arguments for route()", E_USER_ERROR);
1126    else
1127    {
1128      $method        = $args[0];
1129      $path_or_array = $args[1];
1130      $func          = $args[2];
1131      $options       = $nargs > 3 ? $args[3] : array();
1132
1133      $routes[] = route_build($method, $path_or_array, $func, $options);
1134    }
1135  }
1136  return $routes;
1137}
1138
1139/**
1140 * An alias of route(null): reset all routes
1141 * 
1142 * @access private
1143 * @return void
1144 */
1145function route_reset()
1146{
1147  route(null);
1148}
1149
1150/**
1151 * Build a route and return it
1152 *
1153 * @access private
1154 * @param string $method allowed http method (one of those returned by {@link request_methods()})
1155 * @param string|array $path_or_array 
1156 * @param callback $func callback function called when route is found. It can be
1157 *   a function, an object method, a static method or a closure.
1158 *   See {@link http://php.net/manual/en/language.pseudo-types.php#language.types.callback php documentation}
1159 *   to learn more about callbacks.
1160 * @param array $options (optional). Available options: 
1161 *   - 'params' key with an array of parameters: for parametrized routes.
1162 *     those parameters will be merged with routes parameters.
1163 * @return array array with keys "method", "pattern", "names", "function", "options"
1164 */
1165function route_build($method, $path_or_array, $func, $options = array())
1166{
1167  $method = strtoupper($method);
1168  if(!in_array($method, request_methods())) 
1169    trigger_error("'$method' request method is unkown or unavailable.", E_USER_WARNING);
1170
1171  if(is_array($path_or_array))
1172  {
1173    $path  = array_shift($path_or_array);
1174    $names = $path_or_array[0];
1175  }
1176  else
1177  {
1178    $path  = $path_or_array;
1179    $names = array();
1180  }
1181
1182  $single_asterisk_subpattern   = "(?:/([^\/]*))?";
1183  $double_asterisk_subpattern   = "(?:/(.*))?";
1184  $optionnal_slash_subpattern   = "(?:/*?)";
1185  $no_slash_asterisk_subpattern = "(?:([^\/]*))?";
1186
1187  if($path[0] == "^")
1188  {
1189    if($path{strlen($path) - 1} != "$") $path .= "$";
1190     $pattern = "#".$path."#i";
1191  }
1192  else if(empty($path) || $path == "/")
1193  {
1194    $pattern = "#^".$optionnal_slash_subpattern."$#";
1195  }
1196  else
1197  {
1198    $parsed = array();
1199    $elts = explode('/', $path);
1200
1201    $parameters_count = 0;
1202
1203    foreach($elts as $elt)
1204    {
1205      if(empty($elt)) continue;
1206
1207      $name = null; 
1208
1209      # extracting double asterisk **
1210      if($elt == "**"):
1211        $parsed[] = $double_asterisk_subpattern;
1212        $name = $parameters_count;
1213
1214      # extracting single asterisk *
1215      elseif($elt == "*"):
1216        $parsed[] = $single_asterisk_subpattern;
1217        $name = $parameters_count;
1218
1219      # extracting named parameters :my_param 
1220      elseif($elt[0] == ":"):
1221        if(preg_match('/^:([^\:]+)$/', $elt, $matches))
1222        {
1223          $parsed[] = $single_asterisk_subpattern;
1224          $name = $matches[1];
1225        };
1226
1227      elseif(strpos($elt, '*') !== false):
1228        $sub_elts = explode('*', $elt);
1229        $parsed_sub = array();
1230        foreach($sub_elts as $sub_elt)
1231        {
1232          $parsed_sub[] = preg_quote($sub_elt, "#");
1233          $name = $parameters_count;
1234        }
1235        // 
1236        $parsed[] = "/".implode($no_slash_asterisk_subpattern, $parsed_sub);
1237
1238      else:
1239        $parsed[] = "/".preg_quote($elt, "#");
1240
1241      endif;
1242
1243      /* set parameters names */ 
1244      if(is_null($name)) continue;
1245      if(!array_key_exists($parameters_count, $names) || is_null($names[$parameters_count]))
1246        $names[$parameters_count] = $name;
1247      $parameters_count++;
1248    }
1249
1250    $pattern = "#^".implode('', $parsed).$optionnal_slash_subpattern."?$#i";
1251  }
1252
1253  return array( "method"       => $method,
1254                "pattern"      => $pattern,
1255                "names"        => $names,
1256                "function"     => $func,
1257                "options"      => $options  );
1258}
1259
1260/**
1261 * Find a route and returns it.
1262 * If not found, returns false.
1263 * Routes are checked from first added to last added.
1264 *
1265 * @access private
1266 * @param string $method 
1267 * @param string $path
1268 * @return array,false
1269 */
1270function route_find($method, $path)
1271{
1272  $routes = route();
1273  $method = strtoupper($method);
1274  foreach($routes as $route)
1275  {
1276    if($method == $route["method"] && preg_match($route["pattern"], $path, $matches))
1277    {
1278      $options = $route["options"];
1279      $params = array_key_exists('params', $options) ? $options["params"] : array();
1280      if(count($matches) > 1)
1281      {
1282        array_shift($matches);
1283        $n_matches = count($matches);
1284        $names     = array_values($route["names"]);
1285        $n_names   = count($names);
1286        if( $n_matches < $n_names )
1287        {
1288          $a = array_fill(0, $n_names - $n_matches, null);
1289          $matches = array_merge($matches, $a);
1290        }
1291        else if( $n_matches > $n_names )
1292        {
1293          $names = range($n_names, $n_matches - 1);
1294        }
1295        $params = array_replace($params, array_combine($names, $matches));
1296      }
1297      $route["params"] = $params;
1298      return $route;
1299    }
1300  }
1301  return false;
1302}
1303
1304
1305
1306
1307
1308# ============================================================================ #
1309#    OUTPUT AND RENDERING                                                      #
1310# ============================================================================ #
1311
1312/**
1313 * Returns a string to output
1314 * 
1315 * It might use a a template file or function, a formatted string (like {@link sprintf()}).
1316 * It could be embraced by a layout or not.
1317 * Local vars can be passed in addition to variables made available with the {@link set()}
1318 * function.
1319 *
1320 * @param string $content_or_func 
1321 * @param string $layout 
1322 * @param string $locals 
1323 * @return string
1324 */
1325function render($content_or_func, $layout = '', $locals = array())
1326{
1327  $args = func_get_args();
1328  $content_or_func = array_shift($args);
1329  $layout = count($args) > 0 ? array_shift($args) : layout();
1330  $view_path = file_path(option('views_dir'),$content_or_func);
1331  $vars = array_merge(set(), $locals);
1332
1333  $flash = flash_now();
1334  if(array_key_exists('flash', $vars)) trigger_error('A $flash variable is already passed to view. Flash messages will only be accessible through flash_now()', E_USER_NOTICE);  
1335  else if(!empty($flash)) $vars['flash'] = $flash;
1336
1337  $infinite_loop = false;
1338
1339  # Avoid infinite loop: this function is in the backtrace ?
1340  if(function_exists($content_or_func))
1341  {
1342    $back_trace = debug_backtrace();
1343    while($trace = array_shift($back_trace))
1344    {
1345      if($trace['function'] == strtolower($content_or_func))
1346      {
1347        $infinite_loop = true;
1348        break;
1349      }
1350    }
1351  }
1352
1353  if(function_exists($content_or_func) && !$infinite_loop)
1354  {
1355    ob_start();
1356    call_user_func($content_or_func, $vars);
1357    $content = ob_get_clean();
1358  }
1359  elseif(file_exists($view_path))
1360  {
1361    ob_start();
1362    extract($vars);
1363    include $view_path;
1364    $content = ob_get_clean();
1365  }
1366  else
1367  {
1368    if(substr_count($content_or_func, '%') !== count($vars)) $content = $content_or_func;
1369    else $content = vsprintf($content_or_func, $vars);
1370  }
1371
1372  if(empty($layout)) return $content;
1373
1374  return render($layout, null, array('content' => $content));
1375}
1376
1377/**
1378 * Returns a string to output
1379 * 
1380 * Shortcut to render with no layout.
1381 *
1382 * @param string $content_or_func 
1383 * @param string $locals 
1384 * @return string
1385 */
1386function partial($content_or_func, $locals = array())
1387{
1388  return render($content_or_func, null, $locals);
1389}
1390
1391/**
1392 * Returns html output with proper http headers
1393 *
1394 * @param string $content_or_func 
1395 * @param string $layout 
1396 * @param string $locals 
1397 * @return string
1398 */ 
1399function html($content_or_func, $layout = '', $locals = array())
1400{
1401  if(!headers_sent()) header('Content-Type: text/html; charset='.strtolower(option('encoding')));
1402  $args = func_get_args();
1403  return call_user_func_array('render', $args);
1404}
1405
1406/**
1407 * Set and return current layout
1408 *
1409 * @param string $function_or_file 
1410 * @return string
1411 */
1412function layout($function_or_file = null)
1413{
1414  static $layout = null;
1415  if(func_num_args() > 0) $layout = $function_or_file;
1416  return $layout;
1417}
1418
1419/**
1420 * Returns xml output with proper http headers
1421 *
1422 * @param string $content_or_func 
1423 * @param string $layout 
1424 * @param string $locals 
1425 * @return string
1426 */
1427function xml($data)
1428{
1429  if(!headers_sent()) header('Content-Type: text/xml; charset='.strtolower(option('encoding')));
1430  $args = func_get_args();
1431  return call_user_func_array('render', $args);
1432}
1433
1434/**
1435 * Returns css output with proper http headers
1436 *
1437 * @param string $content_or_func 
1438 * @param string $layout 
1439 * @param string $locals 
1440 * @return string
1441 */
1442function css($content_or_func, $layout = '', $locals = array())
1443{
1444  if(!headers_sent()) header('Content-Type: text/css; charset='.strtolower(option('encoding')));
1445  $args = func_get_args();
1446  return call_user_func_array('render', $args);
1447}
1448
1449/**
1450 * Returns txt output with proper http headers
1451 *
1452 * @param string $content_or_func 
1453 * @param string $layout 
1454 * @param string $locals 
1455 * @return string
1456 */
1457function txt($content_or_func, $layout = '', $locals = array())
1458{
1459  if(!headers_sent()) header('Content-Type: text/plain; charset='.strtolower(option('encoding')));
1460  $args = func_get_args();
1461  return call_user_func_array('render', $args);
1462}
1463
1464/**
1465 * Returns json representation of data with proper http headers
1466 *
1467 * @param string $data 
1468 * @param int $json_option
1469 * @return string
1470 */
1471function json($data, $json_option = 0)
1472{
1473  if(!headers_sent()) header('Content-Type: application/x-javascript; charset='.strtolower(option('encoding')));
1474  return version_compare(PHP_VERSION, '5.3.0', '>=') ? json_encode($data, $json_option) : json_encode($data);
1475}
1476
1477/**
1478 * undocumented function
1479 *
1480 * @param string $filename 
1481 * @param string $return 
1482 * @return mixed number of bytes delivered or file output if $return = true
1483 */
1484function render_file($filename, $return = false)
1485{
1486  # TODO implements X-SENDFILE headers
1487  // if($x-sendfile = option('x-sendfile'))
1488  // {
1489  //    // add a X-Sendfile header for apache and Lighttpd >= 1.5
1490  //    if($x-sendfile > X-SENDFILE) // add a X-LIGHTTPD-send-file header 
1491  //   
1492  // }
1493  // else
1494  // {
1495  //   
1496  // }
1497  $filename = str_replace('../', '', $filename);
1498  if(file_exists($filename))
1499  {
1500    $content_type = mime_type(file_extension($filename));
1501    $header = 'Content-type: '.$content_type;
1502    if(file_is_text($filename)) $header .= '; charset='.strtolower(option('encoding'));
1503    if(!headers_sent()) header($header);
1504    return file_read($filename, $return);
1505  }
1506  else halt(NOT_FOUND, "unknown filename $filename");
1507}
1508
1509
1510
1511
1512
1513
1514                                     # # #
1515
1516
1517
1518
1519# ============================================================================ #
1520#    5. HELPERS                                                                #
1521# ============================================================================ #
1522
1523/**
1524 * Returns an url composed of params joined with /
1525 *
1526 * @param string $params,... 
1527 * @return string
1528 */ 
1529function url_for($params = null)
1530{
1531  $paths  = array();
1532  $params = func_get_args();
1533  $first  = true;
1534  foreach($params as $param)
1535  {
1536    if($first)
1537    {
1538      if(filter_var($param , FILTER_VALIDATE_URL))
1539      {
1540        $paths[] = $param;
1541        continue;
1542      }
1543    }
1544    $p = explode('/',$param);
1545    foreach($p as $v)
1546    {
1547      if(!empty($v)) $paths[] = str_replace('%23', '#', rawurlencode($v));
1548    }
1549  }
1550
1551  $path = rtrim(implode('/', $paths), '/');
1552  
1553  if(!filter_var($path , FILTER_VALIDATE_URL)) 
1554  {
1555    # it's a relative URL or an URL without a schema
1556    $base_uri = option('base_uri');
1557    $path = file_path($base_uri, $path);
1558  }
1559
1560  if(DIRECTORY_SEPARATOR != '/') $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
1561
1562  return $path;
1563}
1564
1565/**
1566 * An alias of {@link htmlspecialchars()}.
1567 * If no $charset is provided, uses option('encoding') value
1568 *
1569 * @param string $str 
1570 * @param string $quote_style 
1571 * @param string $charset 
1572 * @return void
1573 */
1574function h($str, $quote_style = ENT_NOQUOTES, $charset = null)
1575{
1576  if(is_null($charset)) $charset = strtoupper(option('encoding'));
1577  return htmlspecialchars($str, $quote_style, $charset); 
1578}
1579
1580/**
1581 * Set and returns flash messages that will be available in the next action
1582 * via the {@link flash_now()} function or the view variable <code>$flash</code>.
1583 * 
1584 * If multiple values are provided, set <code>$name</code> variable with an array of those values.
1585 * If there is only one value, set <code>$name</code> variable with the provided $values
1586 * or if it's <code>$name</code> is an array, merge it with current messages.
1587 *
1588 * @param string, array $name 
1589 * @param mixed  $values,... 
1590 * @return mixed variable value for $name if $name argument is provided, else return all variables
1591 */
1592function flash($name = null, $value = null)
1593{
1594  if(!defined('SID')) trigger_error("Flash messages can't be used because session isn't enabled", E_USER_WARNING);
1595  static $messages = array();
1596  $args = func_get_args();
1597  $name = array_shift($args);
1598  if(is_null($name)) return $messages;
1599  if(is_array($name)) return $messages = array_merge($messages, $name);
1600  if(!empty($args))
1601  {
1602    $messages[$name] = count($args) > 1 ? $args : $args[0];
1603  }
1604  if(array_key_exists($name, $messages)) return $messages[$name];
1605  return $messages;
1606}
1607
1608/**
1609 * Set and returns flash messages available for the current action, included those
1610 * defined in the previous action with {@link flash()}
1611 * Those messages will also be passed to the views and made available in the 
1612 * <code>$flash</code> variable.
1613 * 
1614 * If multiple values are provided, set <code>$name</code> variable with an array of those values.
1615 * If there is only one value, set <code>$name</code> variable with the provided $values
1616 * or if it's <code>$name</code> is an array, merge it with current messages.
1617 *
1618 * @param string, array $name 
1619 * @param mixed  $values,... 
1620 * @return mixed variable value for $name if $name argument is provided, else return all variables
1621 */
1622function flash_now($name = null, $value = null)
1623{
1624  static $messages = null;
1625  if(is_null($messages))
1626  {
1627    $fkey = LIM_SESSION_FLASH_KEY;
1628    $messages = array();
1629    if(defined('SID') && array_key_exists($fkey, $_SESSION)) $messages = $_SESSION[$fkey];
1630  }
1631  $args = func_get_args();
1632  $name = array_shift($args);
1633  if(is_null($name)) return $messages;
1634  if(is_array($name)) return $messages = array_merge($messages, $name);
1635  if(!empty($args))
1636  {
1637    $messages[$name] = count($args) > 1 ? $args : $args[0];
1638  }
1639  if(array_key_exists($name, $messages)) return $messages[$name];
1640  return $messages;
1641}
1642
1643/**
1644 * Delete current flash messages in session, and set new ones stored with 
1645 * flash function.
1646 * Called before application exit.
1647 *
1648 * @access private
1649 * @return void
1650 */
1651function flash_sweep()
1652{
1653  if(defined('SID'))
1654  {
1655    $fkey = LIM_SESSION_FLASH_KEY;
1656    $_SESSION[$fkey] = flash();
1657  }
1658}
1659
1660/**
1661 * Starts capturing block of text
1662 *
1663 * Calling without params stops capturing (same as end_content_for()).
1664 * After capturing the captured block is put into a variable
1665 * named $name for later use in layouts. If second parameter
1666 * is supplied, its content will be used instead of capturing
1667 * a block of text.
1668 *
1669 * @param string $name
1670 * @param string $content
1671 * @return void
1672 */
1673function content_for($name = null, $content = null)
1674{
1675  static $_name = null;
1676  if(is_null($name) && !is_null($_name))
1677  {
1678    set($_name, ob_get_clean());
1679    $_name = null;	
1680  }
1681  elseif(!is_null($name) && !isset($content))
1682  {
1683    $_name = $name;	
1684    ob_start();
1685  }
1686  elseif(isset($name, $content))
1687  {
1688    set($name, $content);
1689  }
1690}
1691
1692/**
1693 * Stops capturing block of text
1694 *
1695 * @return void
1696 */
1697function end_content_for()
1698{
1699  content_for();
1700}
1701
1702/**
1703 * Shows current memory and execution time of the application.
1704 * 
1705 * @access public
1706 * @return array
1707 */
1708function benchmark()
1709{
1710  $current_mem_usage = memory_get_usage();
1711  $execution_time = microtime() - LIM_START_MICROTIME;
1712  
1713  return array(
1714    'current_memory' => $current_mem_usage,
1715    'start_memory' => LIM_START_MEMORY,
1716    'average_memory' => (LIM_START_MEMORY + $current_mem_usage) / 2,
1717    'execution_time' => $execution_time
1718  );
1719}
1720
1721
1722
1723
1724                                     # # #
1725
1726
1727
1728
1729# ============================================================================ #
1730#    6. UTILS                                                                  #
1731# ============================================================================ #
1732 
1733/**
1734 * Calls a function if exists
1735 *
1736 * @param callback $func a function stored in a string variable, 
1737 *   or an object and the name of a method within the object
1738 *   See {@link http://php.net/manual/en/language.pseudo-types.php#language.types.callback php documentation}
1739 *   to learn more about callbacks.
1740 * @param mixed $arg,.. (optional)
1741 * @return mixed
1742 */
1743function call_if_exists($func)
1744{
1745  $args = func_get_args();
1746  $func = array_shift($args);
1747  if(is_callable($func)) return call_user_func_array($func, $args);
1748  return;
1749}
1750
1751/**
1752 * Define a constant unless it already exists
1753 *
1754 * @param string $name 
1755 * @param string $value 
1756 * @return void
1757 */
1758function define_unless_exists($name, $value)
1759{
1760  if(!defined($name)) define($name, $value);
1761}
1762
1763/**
1764 * Return a default value if provided value is empty
1765 *
1766 * @param mixed $value 
1767 * @param mixed $default default value returned if $value is empty
1768 * @return mixed
1769 */
1770function value_or_default($value, $default)
1771{
1772  return empty($value) ? $default : $value;
1773}
1774
1775/**
1776 * An alias of {@link value_or_default()}
1777 *
1778 * 
1779 * @param mixed $value 
1780 * @param mixed $default 
1781 * @return mixed
1782 */
1783function v($value, $default)
1784{
1785  return value_or_default($value, $default);
1786}
1787
1788/**
1789 * Load php files with require_once in a given dir
1790 *
1791 * @param string $path Path in which are the file to load
1792 * @param string $pattern a regexp pattern that filter files to load
1793 * @param bool $prevents_output security option that prevents output
1794 * @return array paths of loaded files
1795 */
1796function require_once_dir($path, $pattern = "*.php", $prevents_output = true)
1797{
1798  if($path[strlen($path) - 1] != "/") $path .= "/";
1799  $filenames = glob($path.$pattern);
1800  if(!is_array($filenames)) $filenames = array();
1801  if($prevents_output) ob_start();
1802  foreach($filenames as $filename) require_once $filename;
1803  if($prevents_output) ob_end_clean();
1804  return $filenames;
1805}
1806
1807
1808## HTTP utils  _________________________________________________________________
1809
1810
1811### Constants: HTTP status codes
1812
1813define( 'HTTP_CONTINUE',                      100 );
1814define( 'HTTP_SWITCHING_PROTOCOLS',           101 );
1815define( 'HTTP_PROCESSING',                    102 );
1816define( 'HTTP_OK',                            200 );
1817define( 'HTTP_CREATED',                       201 );
1818define( 'HTTP_ACCEPTED',                      202 );
1819define( 'HTTP_NON_AUTHORITATIVE',             203 );
1820define( 'HTTP_NO_CONTENT',                    

Large files files are truncated, but you can click here to view the full file