PageRenderTime 10ms CodeModel.GetById 35ms app.highlight 58ms RepoModel.GetById 126ms app.codeStats 1ms

/lib/limonade.php

https://github.com/liana866/FileZ
PHP | 2482 lines | 1390 code | 242 blank | 850 comment | 234 complexity | 923308602fa41f85b8550c4a6b859245 MD5 | raw file

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

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

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