PageRenderTime 93ms CodeModel.GetById 4ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/limonade.php

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

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