PageRenderTime 67ms CodeModel.GetById 3ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/limonade.php

https://bitbucket.org/ashwanthkumar/blueignis-ui
PHP | 2667 lines | 1479 code | 257 blank | 931 comment | 243 complexity | 92b99614d100f43dc0e4ab729a746736 MD5 | raw file

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

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

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