rpicms

A CMS for the Raspberry Pi
git clone git://archive.git.mtrnord.blog/RpicmsTeam/rpicms.git
Log | Files | Refs | README | LICENSE

Slim.php (48223B)


      1 <?php
      2 /**
      3  * Slim - a micro PHP 5 framework
      4  *
      5  * @author      Josh Lockhart <info@slimframework.com>
      6  * @copyright   2011 Josh Lockhart
      7  * @link        http://www.slimframework.com
      8  * @license     http://www.slimframework.com/license
      9  * @version     2.4.2
     10  * @package     Slim
     11  *
     12  * MIT LICENSE
     13  *
     14  * Permission is hereby granted, free of charge, to any person obtaining
     15  * a copy of this software and associated documentation files (the
     16  * "Software"), to deal in the Software without restriction, including
     17  * without limitation the rights to use, copy, modify, merge, publish,
     18  * distribute, sublicense, and/or sell copies of the Software, and to
     19  * permit persons to whom the Software is furnished to do so, subject to
     20  * the following conditions:
     21  *
     22  * The above copyright notice and this permission notice shall be
     23  * included in all copies or substantial portions of the Software.
     24  *
     25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     26  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     27  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     28  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     29  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     30  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     31  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     32  */
     33 namespace Slim;
     34 
     35 // Ensure mcrypt constants are defined even if mcrypt extension is not loaded
     36 if (!extension_loaded('mcrypt')) {
     37     define('MCRYPT_MODE_CBC', 0);
     38     define('MCRYPT_RIJNDAEL_256', 0);
     39 }
     40 
     41 /**
     42  * Slim
     43  * @package  Slim
     44  * @author   Josh Lockhart
     45  * @since    1.0.0
     46  *
     47  * @property \Slim\Environment   $environment
     48  * @property \Slim\Http\Response $response
     49  * @property \Slim\Http\Request  $request
     50  * @property \Slim\Router        $router
     51  */
     52 class Slim
     53 {
     54     /**
     55      * @const string
     56      */
     57     const VERSION = '2.4.2';
     58 
     59     /**
     60      * @var \Slim\Helper\Set
     61      */
     62     public $container;
     63 
     64     /**
     65      * @var array[\Slim]
     66      */
     67     protected static $apps = array();
     68 
     69     /**
     70      * @var string
     71      */
     72     protected $name;
     73 
     74     /**
     75      * @var array
     76      */
     77     protected $middleware;
     78 
     79     /**
     80      * @var mixed Callable to be invoked if application error
     81      */
     82     protected $error;
     83 
     84     /**
     85      * @var mixed Callable to be invoked if no matching routes are found
     86      */
     87     protected $notFound;
     88 
     89     /**
     90      * @var array
     91      */
     92     protected $hooks = array(
     93         'slim.before' => array(array()),
     94         'slim.before.router' => array(array()),
     95         'slim.before.dispatch' => array(array()),
     96         'slim.after.dispatch' => array(array()),
     97         'slim.after.router' => array(array()),
     98         'slim.after' => array(array())
     99     );
    100 
    101     /********************************************************************************
    102     * PSR-0 Autoloader
    103     *
    104     * Do not use if you are using Composer to autoload dependencies.
    105     *******************************************************************************/
    106 
    107     /**
    108      * Slim PSR-0 autoloader
    109      */
    110     public static function autoload($className)
    111     {
    112         $thisClass = str_replace(__NAMESPACE__.'\\', '', __CLASS__);
    113 
    114         $baseDir = __DIR__;
    115 
    116         if (substr($baseDir, -strlen($thisClass)) === $thisClass) {
    117             $baseDir = substr($baseDir, 0, -strlen($thisClass));
    118         }
    119 
    120         $className = ltrim($className, '\\');
    121         $fileName  = $baseDir;
    122         $namespace = '';
    123         if ($lastNsPos = strripos($className, '\\')) {
    124             $namespace = substr($className, 0, $lastNsPos);
    125             $className = substr($className, $lastNsPos + 1);
    126             $fileName  .= str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
    127         }
    128         $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
    129 
    130         if (file_exists($fileName)) {
    131             require $fileName;
    132         }
    133     }
    134 
    135     /**
    136      * Register Slim's PSR-0 autoloader
    137      */
    138     public static function registerAutoloader()
    139     {
    140         spl_autoload_register(__NAMESPACE__ . "\\Slim::autoload");
    141     }
    142 
    143     /********************************************************************************
    144     * Instantiation and Configuration
    145     *******************************************************************************/
    146 
    147     /**
    148      * Constructor
    149      * @param  array $userSettings Associative array of application settings
    150      */
    151     public function __construct(array $userSettings = array())
    152     {
    153         // Setup IoC container
    154         $this->container = new \Slim\Helper\Set();
    155         $this->container['settings'] = array_merge(static::getDefaultSettings(), $userSettings);
    156 
    157         // Default environment
    158         $this->container->singleton('environment', function ($c) {
    159             return \Slim\Environment::getInstance();
    160         });
    161 
    162         // Default request
    163         $this->container->singleton('request', function ($c) {
    164             return new \Slim\Http\Request($c['environment']);
    165         });
    166 
    167         // Default response
    168         $this->container->singleton('response', function ($c) {
    169             return new \Slim\Http\Response();
    170         });
    171 
    172         // Default router
    173         $this->container->singleton('router', function ($c) {
    174             return new \Slim\Router();
    175         });
    176 
    177         // Default view
    178         $this->container->singleton('view', function ($c) {
    179             $viewClass = $c['settings']['view'];
    180             $templatesPath = $c['settings']['templates.path'];
    181 
    182             $view = ($viewClass instanceOf \Slim\View) ? $viewClass : new $viewClass;
    183             $view->setTemplatesDirectory($templatesPath);
    184             return $view;
    185         });
    186 
    187         // Default log writer
    188         $this->container->singleton('logWriter', function ($c) {
    189             $logWriter = $c['settings']['log.writer'];
    190 
    191             return is_object($logWriter) ? $logWriter : new \Slim\LogWriter($c['environment']['slim.errors']);
    192         });
    193 
    194         // Default log
    195         $this->container->singleton('log', function ($c) {
    196             $log = new \Slim\Log($c['logWriter']);
    197             $log->setEnabled($c['settings']['log.enabled']);
    198             $log->setLevel($c['settings']['log.level']);
    199             $env = $c['environment'];
    200             $env['slim.log'] = $log;
    201 
    202             return $log;
    203         });
    204 
    205         // Default mode
    206         $this->container['mode'] = function ($c) {
    207             $mode = $c['settings']['mode'];
    208 
    209             if (isset($_ENV['SLIM_MODE'])) {
    210                 $mode = $_ENV['SLIM_MODE'];
    211             } else {
    212                 $envMode = getenv('SLIM_MODE');
    213                 if ($envMode !== false) {
    214                     $mode = $envMode;
    215                 }
    216             }
    217 
    218             return $mode;
    219         };
    220 
    221         // Define default middleware stack
    222         $this->middleware = array($this);
    223         $this->add(new \Slim\Middleware\Flash());
    224         $this->add(new \Slim\Middleware\MethodOverride());
    225 
    226         // Make default if first instance
    227         if (is_null(static::getInstance())) {
    228             $this->setName('default');
    229         }
    230     }
    231 
    232     public function __get($name)
    233     {
    234         return $this->container[$name];
    235     }
    236 
    237     public function __set($name, $value)
    238     {
    239         $this->container[$name] = $value;
    240     }
    241 
    242     public function __isset($name)
    243     {
    244         return isset($this->container[$name]);
    245     }
    246 
    247     public function __unset($name)
    248     {
    249         unset($this->container[$name]);
    250     }
    251 
    252     /**
    253      * Get application instance by name
    254      * @param  string    $name The name of the Slim application
    255      * @return \Slim\Slim|null
    256      */
    257     public static function getInstance($name = 'default')
    258     {
    259         return isset(static::$apps[$name]) ? static::$apps[$name] : null;
    260     }
    261 
    262     /**
    263      * Set Slim application name
    264      * @param  string $name The name of this Slim application
    265      */
    266     public function setName($name)
    267     {
    268         $this->name = $name;
    269         static::$apps[$name] = $this;
    270     }
    271 
    272     /**
    273      * Get Slim application name
    274      * @return string|null
    275      */
    276     public function getName()
    277     {
    278         return $this->name;
    279     }
    280 
    281     /**
    282      * Get default application settings
    283      * @return array
    284      */
    285     public static function getDefaultSettings()
    286     {
    287         return array(
    288             // Application
    289             'mode' => 'development',
    290             // Debugging
    291             'debug' => true,
    292             // Logging
    293             'log.writer' => null,
    294             'log.level' => \Slim\Log::DEBUG,
    295             'log.enabled' => true,
    296             // View
    297             'templates.path' => './templates',
    298             'view' => '\Slim\View',
    299             // Cookies
    300             'cookies.encrypt' => false,
    301             'cookies.lifetime' => '20 minutes',
    302             'cookies.path' => '/',
    303             'cookies.domain' => null,
    304             'cookies.secure' => false,
    305             'cookies.httponly' => false,
    306             // Encryption
    307             'cookies.secret_key' => 'CHANGE_ME',
    308             'cookies.cipher' => MCRYPT_RIJNDAEL_256,
    309             'cookies.cipher_mode' => MCRYPT_MODE_CBC,
    310             // HTTP
    311             'http.version' => '1.1',
    312             // Routing
    313             'routes.case_sensitive' => true
    314         );
    315     }
    316 
    317     /**
    318      * Configure Slim Settings
    319      *
    320      * This method defines application settings and acts as a setter and a getter.
    321      *
    322      * If only one argument is specified and that argument is a string, the value
    323      * of the setting identified by the first argument will be returned, or NULL if
    324      * that setting does not exist.
    325      *
    326      * If only one argument is specified and that argument is an associative array,
    327      * the array will be merged into the existing application settings.
    328      *
    329      * If two arguments are provided, the first argument is the name of the setting
    330      * to be created or updated, and the second argument is the setting value.
    331      *
    332      * @param  string|array $name  If a string, the name of the setting to set or retrieve. Else an associated array of setting names and values
    333      * @param  mixed        $value If name is a string, the value of the setting identified by $name
    334      * @return mixed        The value of a setting if only one argument is a string
    335      */
    336     public function config($name, $value = null)
    337     {
    338         $c = $this->container;
    339 
    340         if (is_array($name)) {
    341             if (true === $value) {
    342                 $c['settings'] = array_merge_recursive($c['settings'], $name);
    343             } else {
    344                 $c['settings'] = array_merge($c['settings'], $name);
    345             }
    346         } elseif (func_num_args() === 1) {
    347             return isset($c['settings'][$name]) ? $c['settings'][$name] : null;
    348         } else {
    349             $settings = $c['settings'];
    350             $settings[$name] = $value;
    351             $c['settings'] = $settings;
    352         }
    353     }
    354 
    355     /********************************************************************************
    356     * Application Modes
    357     *******************************************************************************/
    358 
    359     /**
    360      * Get application mode
    361      *
    362      * This method determines the application mode. It first inspects the $_ENV
    363      * superglobal for key `SLIM_MODE`. If that is not found, it queries
    364      * the `getenv` function. Else, it uses the application `mode` setting.
    365      *
    366      * @return string
    367      */
    368     public function getMode()
    369     {
    370         return $this->mode;
    371     }
    372 
    373     /**
    374      * Configure Slim for a given mode
    375      *
    376      * This method will immediately invoke the callable if
    377      * the specified mode matches the current application mode.
    378      * Otherwise, the callable is ignored. This should be called
    379      * only _after_ you initialize your Slim app.
    380      *
    381      * @param  string $mode
    382      * @param  mixed  $callable
    383      * @return void
    384      */
    385     public function configureMode($mode, $callable)
    386     {
    387         if ($mode === $this->getMode() && is_callable($callable)) {
    388             call_user_func($callable);
    389         }
    390     }
    391 
    392     /********************************************************************************
    393     * Logging
    394     *******************************************************************************/
    395 
    396     /**
    397      * Get application log
    398      * @return \Slim\Log
    399      */
    400     public function getLog()
    401     {
    402         return $this->log;
    403     }
    404 
    405     /********************************************************************************
    406     * Routing
    407     *******************************************************************************/
    408 
    409     /**
    410      * Add GET|POST|PUT|PATCH|DELETE route
    411      *
    412      * Adds a new route to the router with associated callable. This
    413      * route will only be invoked when the HTTP request's method matches
    414      * this route's method.
    415      *
    416      * ARGUMENTS:
    417      *
    418      * First:       string  The URL pattern (REQUIRED)
    419      * In-Between:  mixed   Anything that returns TRUE for `is_callable` (OPTIONAL)
    420      * Last:        mixed   Anything that returns TRUE for `is_callable` (REQUIRED)
    421      *
    422      * The first argument is required and must always be the
    423      * route pattern (ie. '/books/:id').
    424      *
    425      * The last argument is required and must always be the callable object
    426      * to be invoked when the route matches an HTTP request.
    427      *
    428      * You may also provide an unlimited number of in-between arguments;
    429      * each interior argument must be callable and will be invoked in the
    430      * order specified before the route's callable is invoked.
    431      *
    432      * USAGE:
    433      *
    434      * Slim::get('/foo'[, middleware, middleware, ...], callable);
    435      *
    436      * @param   array (See notes above)
    437      * @return  \Slim\Route
    438      */
    439     protected function mapRoute($args)
    440     {
    441         $pattern = array_shift($args);
    442         $callable = array_pop($args);
    443         $route = new \Slim\Route($pattern, $callable, $this->settings['routes.case_sensitive']);
    444         $this->router->map($route);
    445         if (count($args) > 0) {
    446             $route->setMiddleware($args);
    447         }
    448 
    449         return $route;
    450     }
    451 
    452     /**
    453      * Add generic route without associated HTTP method
    454      * @see    mapRoute()
    455      * @return \Slim\Route
    456      */
    457     public function map()
    458     {
    459         $args = func_get_args();
    460 
    461         return $this->mapRoute($args);
    462     }
    463 
    464     /**
    465      * Add GET route
    466      * @see    mapRoute()
    467      * @return \Slim\Route
    468      */
    469     public function get()
    470     {
    471         $args = func_get_args();
    472 
    473         return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_GET, \Slim\Http\Request::METHOD_HEAD);
    474     }
    475 
    476     /**
    477      * Add POST route
    478      * @see    mapRoute()
    479      * @return \Slim\Route
    480      */
    481     public function post()
    482     {
    483         $args = func_get_args();
    484 
    485         return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_POST);
    486     }
    487 
    488     /**
    489      * Add PUT route
    490      * @see    mapRoute()
    491      * @return \Slim\Route
    492      */
    493     public function put()
    494     {
    495         $args = func_get_args();
    496 
    497         return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PUT);
    498     }
    499 
    500     /**
    501      * Add PATCH route
    502      * @see    mapRoute()
    503      * @return \Slim\Route
    504      */
    505     public function patch()
    506     {
    507         $args = func_get_args();
    508 
    509         return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PATCH);
    510     }
    511 
    512     /**
    513      * Add DELETE route
    514      * @see    mapRoute()
    515      * @return \Slim\Route
    516      */
    517     public function delete()
    518     {
    519         $args = func_get_args();
    520 
    521         return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_DELETE);
    522     }
    523 
    524     /**
    525      * Add OPTIONS route
    526      * @see    mapRoute()
    527      * @return \Slim\Route
    528      */
    529     public function options()
    530     {
    531         $args = func_get_args();
    532 
    533         return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_OPTIONS);
    534     }
    535 
    536     /**
    537      * Route Groups
    538      *
    539      * This method accepts a route pattern and a callback all Route
    540      * declarations in the callback will be prepended by the group(s)
    541      * that it is in
    542      *
    543      * Accepts the same parameters as a standard route so:
    544      * (pattern, middleware1, middleware2, ..., $callback)
    545      */
    546     public function group()
    547     {
    548         $args = func_get_args();
    549         $pattern = array_shift($args);
    550         $callable = array_pop($args);
    551         $this->router->pushGroup($pattern, $args);
    552         if (is_callable($callable)) {
    553             call_user_func($callable);
    554         }
    555         $this->router->popGroup();
    556     }
    557 
    558     /*
    559      * Add route for any HTTP method
    560      * @see    mapRoute()
    561      * @return \Slim\Route
    562      */
    563     public function any()
    564     {
    565         $args = func_get_args();
    566 
    567         return $this->mapRoute($args)->via("ANY");
    568     }
    569 
    570     /**
    571      * Not Found Handler
    572      *
    573      * This method defines or invokes the application-wide Not Found handler.
    574      * There are two contexts in which this method may be invoked:
    575      *
    576      * 1. When declaring the handler:
    577      *
    578      * If the $callable parameter is not null and is callable, this
    579      * method will register the callable to be invoked when no
    580      * routes match the current HTTP request. It WILL NOT invoke the callable.
    581      *
    582      * 2. When invoking the handler:
    583      *
    584      * If the $callable parameter is null, Slim assumes you want
    585      * to invoke an already-registered handler. If the handler has been
    586      * registered and is callable, it is invoked and sends a 404 HTTP Response
    587      * whose body is the output of the Not Found handler.
    588      *
    589      * @param  mixed $callable Anything that returns true for is_callable()
    590      */
    591     public function notFound ($callable = null)
    592     {
    593         if (is_callable($callable)) {
    594             $this->notFound = $callable;
    595         } else {
    596             ob_start();
    597             if (is_callable($this->notFound)) {
    598                 call_user_func($this->notFound);
    599             } else {
    600                 call_user_func(array($this, 'defaultNotFound'));
    601             }
    602             $this->halt(404, ob_get_clean());
    603         }
    604     }
    605 
    606     /**
    607      * Error Handler
    608      *
    609      * This method defines or invokes the application-wide Error handler.
    610      * There are two contexts in which this method may be invoked:
    611      *
    612      * 1. When declaring the handler:
    613      *
    614      * If the $argument parameter is callable, this
    615      * method will register the callable to be invoked when an uncaught
    616      * Exception is detected, or when otherwise explicitly invoked.
    617      * The handler WILL NOT be invoked in this context.
    618      *
    619      * 2. When invoking the handler:
    620      *
    621      * If the $argument parameter is not callable, Slim assumes you want
    622      * to invoke an already-registered handler. If the handler has been
    623      * registered and is callable, it is invoked and passed the caught Exception
    624      * as its one and only argument. The error handler's output is captured
    625      * into an output buffer and sent as the body of a 500 HTTP Response.
    626      *
    627      * @param  mixed $argument Callable|\Exception
    628      */
    629     public function error($argument = null)
    630     {
    631         if (is_callable($argument)) {
    632             //Register error handler
    633             $this->error = $argument;
    634         } else {
    635             //Invoke error handler
    636             $this->response->status(500);
    637             $this->response->body('');
    638             $this->response->write($this->callErrorHandler($argument));
    639             $this->stop();
    640         }
    641     }
    642 
    643     /**
    644      * Call error handler
    645      *
    646      * This will invoke the custom or default error handler
    647      * and RETURN its output.
    648      *
    649      * @param  \Exception|null $argument
    650      * @return string
    651      */
    652     protected function callErrorHandler($argument = null)
    653     {
    654         ob_start();
    655         if (is_callable($this->error)) {
    656             call_user_func_array($this->error, array($argument));
    657         } else {
    658             call_user_func_array(array($this, 'defaultError'), array($argument));
    659         }
    660 
    661         return ob_get_clean();
    662     }
    663 
    664     /********************************************************************************
    665     * Application Accessors
    666     *******************************************************************************/
    667 
    668     /**
    669      * Get a reference to the Environment object
    670      * @return \Slim\Environment
    671      */
    672     public function environment()
    673     {
    674         return $this->environment;
    675     }
    676 
    677     /**
    678      * Get the Request object
    679      * @return \Slim\Http\Request
    680      */
    681     public function request()
    682     {
    683         return $this->request;
    684     }
    685 
    686     /**
    687      * Get the Response object
    688      * @return \Slim\Http\Response
    689      */
    690     public function response()
    691     {
    692         return $this->response;
    693     }
    694 
    695     /**
    696      * Get the Router object
    697      * @return \Slim\Router
    698      */
    699     public function router()
    700     {
    701         return $this->router;
    702     }
    703 
    704     /**
    705      * Get and/or set the View
    706      *
    707      * This method declares the View to be used by the Slim application.
    708      * If the argument is a string, Slim will instantiate a new object
    709      * of the same class. If the argument is an instance of View or a subclass
    710      * of View, Slim will use the argument as the View.
    711      *
    712      * If a View already exists and this method is called to create a
    713      * new View, data already set in the existing View will be
    714      * transferred to the new View.
    715      *
    716      * @param  string|\Slim\View $viewClass The name or instance of a \Slim\View subclass
    717      * @return \Slim\View
    718      */
    719     public function view($viewClass = null)
    720     {
    721         if (!is_null($viewClass)) {
    722             $existingData = is_null($this->view) ? array() : $this->view->getData();
    723             if ($viewClass instanceOf \Slim\View) {
    724                 $this->view = $viewClass;
    725             } else {
    726                 $this->view = new $viewClass();
    727             }
    728             $this->view->appendData($existingData);
    729             $this->view->setTemplatesDirectory($this->config('templates.path'));
    730         }
    731 
    732         return $this->view;
    733     }
    734 
    735     /********************************************************************************
    736     * Rendering
    737     *******************************************************************************/
    738 
    739     /**
    740      * Render a template
    741      *
    742      * Call this method within a GET, POST, PUT, PATCH, DELETE, NOT FOUND, or ERROR
    743      * callable to render a template whose output is appended to the
    744      * current HTTP response body. How the template is rendered is
    745      * delegated to the current View.
    746      *
    747      * @param  string $template The name of the template passed into the view's render() method
    748      * @param  array  $data     Associative array of data made available to the view
    749      * @param  int    $status   The HTTP response status code to use (optional)
    750      */
    751     public function render($template, $data = array(), $status = null)
    752     {
    753         if (!is_null($status)) {
    754             $this->response->status($status);
    755         }
    756         $this->view->appendData($data);
    757         $this->view->display($template);
    758     }
    759 
    760     /********************************************************************************
    761     * HTTP Caching
    762     *******************************************************************************/
    763 
    764     /**
    765      * Set Last-Modified HTTP Response Header
    766      *
    767      * Set the HTTP 'Last-Modified' header and stop if a conditional
    768      * GET request's `If-Modified-Since` header matches the last modified time
    769      * of the resource. The `time` argument is a UNIX timestamp integer value.
    770      * When the current request includes an 'If-Modified-Since' header that
    771      * matches the specified last modified time, the application will stop
    772      * and send a '304 Not Modified' response to the client.
    773      *
    774      * @param  int                       $time The last modified UNIX timestamp
    775      * @throws \InvalidArgumentException If provided timestamp is not an integer
    776      */
    777     public function lastModified($time)
    778     {
    779         if (is_integer($time)) {
    780             $this->response->headers->set('Last-Modified', gmdate('D, d M Y H:i:s T', $time));
    781             if ($time === strtotime($this->request->headers->get('IF_MODIFIED_SINCE'))) {
    782                 $this->halt(304);
    783             }
    784         } else {
    785             throw new \InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.');
    786         }
    787     }
    788 
    789     /**
    790      * Set ETag HTTP Response Header
    791      *
    792      * Set the etag header and stop if the conditional GET request matches.
    793      * The `value` argument is a unique identifier for the current resource.
    794      * The `type` argument indicates whether the etag should be used as a strong or
    795      * weak cache validator.
    796      *
    797      * When the current request includes an 'If-None-Match' header with
    798      * a matching etag, execution is immediately stopped. If the request
    799      * method is GET or HEAD, a '304 Not Modified' response is sent.
    800      *
    801      * @param  string                    $value The etag value
    802      * @param  string                    $type  The type of etag to create; either "strong" or "weak"
    803      * @throws \InvalidArgumentException If provided type is invalid
    804      */
    805     public function etag($value, $type = 'strong')
    806     {
    807         //Ensure type is correct
    808         if (!in_array($type, array('strong', 'weak'))) {
    809             throw new \InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".');
    810         }
    811 
    812         //Set etag value
    813         $value = '"' . $value . '"';
    814         if ($type === 'weak') {
    815             $value = 'W/'.$value;
    816         }
    817         $this->response['ETag'] = $value;
    818 
    819         //Check conditional GET
    820         if ($etagsHeader = $this->request->headers->get('IF_NONE_MATCH')) {
    821             $etags = preg_split('@\s*,\s*@', $etagsHeader);
    822             if (in_array($value, $etags) || in_array('*', $etags)) {
    823                 $this->halt(304);
    824             }
    825         }
    826     }
    827 
    828     /**
    829      * Set Expires HTTP response header
    830      *
    831      * The `Expires` header tells the HTTP client the time at which
    832      * the current resource should be considered stale. At that time the HTTP
    833      * client will send a conditional GET request to the server; the server
    834      * may return a 200 OK if the resource has changed, else a 304 Not Modified
    835      * if the resource has not changed. The `Expires` header should be used in
    836      * conjunction with the `etag()` or `lastModified()` methods above.
    837      *
    838      * @param string|int    $time   If string, a time to be parsed by `strtotime()`;
    839      *                              If int, a UNIX timestamp;
    840      */
    841     public function expires($time)
    842     {
    843         if (is_string($time)) {
    844             $time = strtotime($time);
    845         }
    846         $this->response->headers->set('Expires', gmdate('D, d M Y H:i:s T', $time));
    847     }
    848 
    849     /********************************************************************************
    850     * HTTP Cookies
    851     *******************************************************************************/
    852 
    853     /**
    854      * Set HTTP cookie to be sent with the HTTP response
    855      *
    856      * @param string     $name      The cookie name
    857      * @param string     $value     The cookie value
    858      * @param int|string $time      The duration of the cookie;
    859      *                                  If integer, should be UNIX timestamp;
    860      *                                  If string, converted to UNIX timestamp with `strtotime`;
    861      * @param string     $path      The path on the server in which the cookie will be available on
    862      * @param string     $domain    The domain that the cookie is available to
    863      * @param bool       $secure    Indicates that the cookie should only be transmitted over a secure
    864      *                              HTTPS connection to/from the client
    865      * @param bool       $httponly  When TRUE the cookie will be made accessible only through the HTTP protocol
    866      */
    867     public function setCookie($name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null)
    868     {
    869         $settings = array(
    870             'value' => $value,
    871             'expires' => is_null($time) ? $this->config('cookies.lifetime') : $time,
    872             'path' => is_null($path) ? $this->config('cookies.path') : $path,
    873             'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain,
    874             'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure,
    875             'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly
    876         );
    877         $this->response->cookies->set($name, $settings);
    878     }
    879 
    880     /**
    881      * Get value of HTTP cookie from the current HTTP request
    882      *
    883      * Return the value of a cookie from the current HTTP request,
    884      * or return NULL if cookie does not exist. Cookies created during
    885      * the current request will not be available until the next request.
    886      *
    887      * @param  string      $name
    888      * @param  bool        $deleteIfInvalid
    889      * @return string|null
    890      */
    891     public function getCookie($name, $deleteIfInvalid = true)
    892     {
    893         // Get cookie value
    894         $value = $this->request->cookies->get($name);
    895 
    896         // Decode if encrypted
    897         if ($this->config('cookies.encrypt')) {
    898             $value = \Slim\Http\Util::decodeSecureCookie(
    899                 $value,
    900                 $this->config('cookies.secret_key'),
    901                 $this->config('cookies.cipher'),
    902                 $this->config('cookies.cipher_mode')
    903             );
    904             if ($value === false && $deleteIfInvalid) {
    905                 $this->deleteCookie($name);
    906             }
    907         }
    908 
    909         return $value;
    910     }
    911 
    912     /**
    913      * DEPRECATION WARNING! Use `setCookie` with the `cookies.encrypt` app setting set to `true`.
    914      *
    915      * Set encrypted HTTP cookie
    916      *
    917      * @param string    $name       The cookie name
    918      * @param mixed     $value      The cookie value
    919      * @param mixed     $expires    The duration of the cookie;
    920      *                                  If integer, should be UNIX timestamp;
    921      *                                  If string, converted to UNIX timestamp with `strtotime`;
    922      * @param string    $path       The path on the server in which the cookie will be available on
    923      * @param string    $domain     The domain that the cookie is available to
    924      * @param bool      $secure     Indicates that the cookie should only be transmitted over a secure
    925      *                              HTTPS connection from the client
    926      * @param  bool     $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
    927      */
    928     public function setEncryptedCookie($name, $value, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false)
    929     {
    930         $this->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly);
    931     }
    932 
    933     /**
    934      * DEPRECATION WARNING! Use `getCookie` with the `cookies.encrypt` app setting set to `true`.
    935      *
    936      * Get value of encrypted HTTP cookie
    937      *
    938      * Return the value of an encrypted cookie from the current HTTP request,
    939      * or return NULL if cookie does not exist. Encrypted cookies created during
    940      * the current request will not be available until the next request.
    941      *
    942      * @param  string       $name
    943      * @param  bool         $deleteIfInvalid
    944      * @return string|bool
    945      */
    946     public function getEncryptedCookie($name, $deleteIfInvalid = true)
    947     {
    948         return $this->getCookie($name, $deleteIfInvalid);
    949     }
    950 
    951     /**
    952      * Delete HTTP cookie (encrypted or unencrypted)
    953      *
    954      * Remove a Cookie from the client. This method will overwrite an existing Cookie
    955      * with a new, empty, auto-expiring Cookie. This method's arguments must match
    956      * the original Cookie's respective arguments for the original Cookie to be
    957      * removed. If any of this method's arguments are omitted or set to NULL, the
    958      * default Cookie setting values (set during Slim::init) will be used instead.
    959      *
    960      * @param string    $name       The cookie name
    961      * @param string    $path       The path on the server in which the cookie will be available on
    962      * @param string    $domain     The domain that the cookie is available to
    963      * @param bool      $secure     Indicates that the cookie should only be transmitted over a secure
    964      *                              HTTPS connection from the client
    965      * @param  bool     $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
    966      */
    967     public function deleteCookie($name, $path = null, $domain = null, $secure = null, $httponly = null)
    968     {
    969         $settings = array(
    970             'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain,
    971             'path' => is_null($path) ? $this->config('cookies.path') : $path,
    972             'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure,
    973             'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly
    974         );
    975         $this->response->cookies->remove($name, $settings);
    976     }
    977 
    978     /********************************************************************************
    979     * Helper Methods
    980     *******************************************************************************/
    981 
    982     /**
    983      * Get the absolute path to this Slim application's root directory
    984      *
    985      * This method returns the absolute path to the Slim application's
    986      * directory. If the Slim application is installed in a public-accessible
    987      * sub-directory, the sub-directory path will be included. This method
    988      * will always return an absolute path WITH a trailing slash.
    989      *
    990      * @return string
    991      */
    992     public function root()
    993     {
    994         return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/';
    995     }
    996 
    997     /**
    998      * Clean current output buffer
    999      */
   1000     protected function cleanBuffer()
   1001     {
   1002         if (ob_get_level() !== 0) {
   1003             ob_clean();
   1004         }
   1005     }
   1006 
   1007     /**
   1008      * Stop
   1009      *
   1010      * The thrown exception will be caught in application's `call()` method
   1011      * and the response will be sent as is to the HTTP client.
   1012      *
   1013      * @throws \Slim\Exception\Stop
   1014      */
   1015     public function stop()
   1016     {
   1017         throw new \Slim\Exception\Stop();
   1018     }
   1019 
   1020     /**
   1021      * Halt
   1022      *
   1023      * Stop the application and immediately send the response with a
   1024      * specific status and body to the HTTP client. This may send any
   1025      * type of response: info, success, redirect, client error, or server error.
   1026      * If you need to render a template AND customize the response status,
   1027      * use the application's `render()` method instead.
   1028      *
   1029      * @param  int      $status     The HTTP response status
   1030      * @param  string   $message    The HTTP response body
   1031      */
   1032     public function halt($status, $message = '')
   1033     {
   1034         $this->cleanBuffer();
   1035         $this->response->status($status);
   1036         $this->response->body($message);
   1037         $this->stop();
   1038     }
   1039 
   1040     /**
   1041      * Pass
   1042      *
   1043      * The thrown exception is caught in the application's `call()` method causing
   1044      * the router's current iteration to stop and continue to the subsequent route if available.
   1045      * If no subsequent matching routes are found, a 404 response will be sent to the client.
   1046      *
   1047      * @throws \Slim\Exception\Pass
   1048      */
   1049     public function pass()
   1050     {
   1051         $this->cleanBuffer();
   1052         throw new \Slim\Exception\Pass();
   1053     }
   1054 
   1055     /**
   1056      * Set the HTTP response Content-Type
   1057      * @param  string   $type   The Content-Type for the Response (ie. text/html)
   1058      */
   1059     public function contentType($type)
   1060     {
   1061         $this->response->headers->set('Content-Type', $type);
   1062     }
   1063 
   1064     /**
   1065      * Set the HTTP response status code
   1066      * @param  int      $code     The HTTP response status code
   1067      */
   1068     public function status($code)
   1069     {
   1070         $this->response->setStatus($code);
   1071     }
   1072 
   1073     /**
   1074      * Get the URL for a named route
   1075      * @param  string               $name       The route name
   1076      * @param  array                $params     Associative array of URL parameters and replacement values
   1077      * @throws \RuntimeException    If named route does not exist
   1078      * @return string
   1079      */
   1080     public function urlFor($name, $params = array())
   1081     {
   1082         return $this->request->getRootUri() . $this->router->urlFor($name, $params);
   1083     }
   1084 
   1085     /**
   1086      * Redirect
   1087      *
   1088      * This method immediately redirects to a new URL. By default,
   1089      * this issues a 302 Found response; this is considered the default
   1090      * generic redirect response. You may also specify another valid
   1091      * 3xx status code if you want. This method will automatically set the
   1092      * HTTP Location header for you using the URL parameter.
   1093      *
   1094      * @param  string   $url        The destination URL
   1095      * @param  int      $status     The HTTP redirect status code (optional)
   1096      */
   1097     public function redirect($url, $status = 302)
   1098     {
   1099         $this->response->redirect($url, $status);
   1100         $this->halt($status);
   1101     }
   1102     
   1103     /**
   1104      * RedirectTo
   1105      * 
   1106      * Redirects to a specific named route
   1107      * 
   1108      * @param string    $route      The route name
   1109      * @param array     $params     Associative array of URL parameters and replacement values
   1110      */
   1111     public function redirectTo($route, $params = array(), $status = 302){
   1112         $this->redirect($this->urlFor($route, $params), $status);
   1113     }
   1114 
   1115     /********************************************************************************
   1116     * Flash Messages
   1117     *******************************************************************************/
   1118 
   1119     /**
   1120      * Set flash message for subsequent request
   1121      * @param  string   $key
   1122      * @param  mixed    $value
   1123      */
   1124     public function flash($key, $value)
   1125     {
   1126         if (isset($this->environment['slim.flash'])) {
   1127             $this->environment['slim.flash']->set($key, $value);
   1128         }
   1129     }
   1130 
   1131     /**
   1132      * Set flash message for current request
   1133      * @param  string   $key
   1134      * @param  mixed    $value
   1135      */
   1136     public function flashNow($key, $value)
   1137     {
   1138         if (isset($this->environment['slim.flash'])) {
   1139             $this->environment['slim.flash']->now($key, $value);
   1140         }
   1141     }
   1142 
   1143     /**
   1144      * Keep flash messages from previous request for subsequent request
   1145      */
   1146     public function flashKeep()
   1147     {
   1148         if (isset($this->environment['slim.flash'])) {
   1149             $this->environment['slim.flash']->keep();
   1150         }
   1151     }
   1152 
   1153     /********************************************************************************
   1154     * Hooks
   1155     *******************************************************************************/
   1156 
   1157     /**
   1158      * Assign hook
   1159      * @param  string   $name       The hook name
   1160      * @param  mixed    $callable   A callable object
   1161      * @param  int      $priority   The hook priority; 0 = high, 10 = low
   1162      */
   1163     public function hook($name, $callable, $priority = 10)
   1164     {
   1165         if (!isset($this->hooks[$name])) {
   1166             $this->hooks[$name] = array(array());
   1167         }
   1168         if (is_callable($callable)) {
   1169             $this->hooks[$name][(int) $priority][] = $callable;
   1170         }
   1171     }
   1172 
   1173     /**
   1174      * Invoke hook
   1175      * @param  string   $name       The hook name
   1176      * @param  mixed    $hookArg    (Optional) Argument for hooked functions
   1177      */
   1178     public function applyHook($name, $hookArg = null)
   1179     {
   1180         if (!isset($this->hooks[$name])) {
   1181             $this->hooks[$name] = array(array());
   1182         }
   1183         if (!empty($this->hooks[$name])) {
   1184             // Sort by priority, low to high, if there's more than one priority
   1185             if (count($this->hooks[$name]) > 1) {
   1186                 ksort($this->hooks[$name]);
   1187             }
   1188             foreach ($this->hooks[$name] as $priority) {
   1189                 if (!empty($priority)) {
   1190                     foreach ($priority as $callable) {
   1191                         call_user_func($callable, $hookArg);
   1192                     }
   1193                 }
   1194             }
   1195         }
   1196     }
   1197 
   1198     /**
   1199      * Get hook listeners
   1200      *
   1201      * Return an array of registered hooks. If `$name` is a valid
   1202      * hook name, only the listeners attached to that hook are returned.
   1203      * Else, all listeners are returned as an associative array whose
   1204      * keys are hook names and whose values are arrays of listeners.
   1205      *
   1206      * @param  string     $name     A hook name (Optional)
   1207      * @return array|null
   1208      */
   1209     public function getHooks($name = null)
   1210     {
   1211         if (!is_null($name)) {
   1212             return isset($this->hooks[(string) $name]) ? $this->hooks[(string) $name] : null;
   1213         } else {
   1214             return $this->hooks;
   1215         }
   1216     }
   1217 
   1218     /**
   1219      * Clear hook listeners
   1220      *
   1221      * Clear all listeners for all hooks. If `$name` is
   1222      * a valid hook name, only the listeners attached
   1223      * to that hook will be cleared.
   1224      *
   1225      * @param  string   $name   A hook name (Optional)
   1226      */
   1227     public function clearHooks($name = null)
   1228     {
   1229         if (!is_null($name) && isset($this->hooks[(string) $name])) {
   1230             $this->hooks[(string) $name] = array(array());
   1231         } else {
   1232             foreach ($this->hooks as $key => $value) {
   1233                 $this->hooks[$key] = array(array());
   1234             }
   1235         }
   1236     }
   1237 
   1238     /********************************************************************************
   1239     * Middleware
   1240     *******************************************************************************/
   1241 
   1242     /**
   1243      * Add middleware
   1244      *
   1245      * This method prepends new middleware to the application middleware stack.
   1246      * The argument must be an instance that subclasses Slim_Middleware.
   1247      *
   1248      * @param \Slim\Middleware
   1249      */
   1250     public function add(\Slim\Middleware $newMiddleware)
   1251     {
   1252         if(in_array($newMiddleware, $this->middleware)) {
   1253             $middleware_class = get_class($newMiddleware);
   1254             throw new \RuntimeException("Circular Middleware setup detected. Tried to queue the same Middleware instance ({$middleware_class}) twice.");
   1255         }
   1256         $newMiddleware->setApplication($this);
   1257         $newMiddleware->setNextMiddleware($this->middleware[0]);
   1258         array_unshift($this->middleware, $newMiddleware);
   1259     }
   1260 
   1261     /********************************************************************************
   1262     * Runner
   1263     *******************************************************************************/
   1264 
   1265     /**
   1266      * Run
   1267      *
   1268      * This method invokes the middleware stack, including the core Slim application;
   1269      * the result is an array of HTTP status, header, and body. These three items
   1270      * are returned to the HTTP client.
   1271      */
   1272     public function run()
   1273     {
   1274         set_error_handler(array('\Slim\Slim', 'handleErrors'));
   1275 
   1276         //Apply final outer middleware layers
   1277         if ($this->config('debug')) {
   1278             //Apply pretty exceptions only in debug to avoid accidental information leakage in production
   1279             $this->add(new \Slim\Middleware\PrettyExceptions());
   1280         }
   1281 
   1282         //Invoke middleware and application stack
   1283         $this->middleware[0]->call();
   1284 
   1285         //Fetch status, header, and body
   1286         list($status, $headers, $body) = $this->response->finalize();
   1287 
   1288         // Serialize cookies (with optional encryption)
   1289         \Slim\Http\Util::serializeCookies($headers, $this->response->cookies, $this->settings);
   1290 
   1291         //Send headers
   1292         if (headers_sent() === false) {
   1293             //Send status
   1294             if (strpos(PHP_SAPI, 'cgi') === 0) {
   1295                 header(sprintf('Status: %s', \Slim\Http\Response::getMessageForCode($status)));
   1296             } else {
   1297                 header(sprintf('HTTP/%s %s', $this->config('http.version'), \Slim\Http\Response::getMessageForCode($status)));
   1298             }
   1299 
   1300             //Send headers
   1301             foreach ($headers as $name => $value) {
   1302                 $hValues = explode("\n", $value);
   1303                 foreach ($hValues as $hVal) {
   1304                     header("$name: $hVal", false);
   1305                 }
   1306             }
   1307         }
   1308 
   1309         //Send body, but only if it isn't a HEAD request
   1310         if (!$this->request->isHead()) {
   1311             echo $body;
   1312         }
   1313 
   1314         $this->applyHook('slim.after');
   1315 
   1316         restore_error_handler();
   1317     }
   1318 
   1319     /**
   1320      * Call
   1321      *
   1322      * This method finds and iterates all route objects that match the current request URI.
   1323      */
   1324     public function call()
   1325     {
   1326         try {
   1327             if (isset($this->environment['slim.flash'])) {
   1328                 $this->view()->setData('flash', $this->environment['slim.flash']);
   1329             }
   1330             $this->applyHook('slim.before');
   1331             ob_start();
   1332             $this->applyHook('slim.before.router');
   1333             $dispatched = false;
   1334             $matchedRoutes = $this->router->getMatchedRoutes($this->request->getMethod(), $this->request->getResourceUri());
   1335             foreach ($matchedRoutes as $route) {
   1336                 try {
   1337                     $this->applyHook('slim.before.dispatch');
   1338                     $dispatched = $route->dispatch();
   1339                     $this->applyHook('slim.after.dispatch');
   1340                     if ($dispatched) {
   1341                         break;
   1342                     }
   1343                 } catch (\Slim\Exception\Pass $e) {
   1344                     continue;
   1345                 }
   1346             }
   1347             if (!$dispatched) {
   1348                 $this->notFound();
   1349             }
   1350             $this->applyHook('slim.after.router');
   1351             $this->stop();
   1352         } catch (\Slim\Exception\Stop $e) {
   1353             $this->response()->write(ob_get_clean());
   1354         } catch (\Exception $e) {
   1355             if ($this->config('debug')) {
   1356                 throw $e;
   1357             } else {
   1358                 try {
   1359                     $this->error($e);
   1360                 } catch (\Slim\Exception\Stop $e) {
   1361                     // Do nothing
   1362                 }
   1363             }
   1364         }
   1365     }
   1366 
   1367     /********************************************************************************
   1368     * Error Handling and Debugging
   1369     *******************************************************************************/
   1370 
   1371     /**
   1372      * Convert errors into ErrorException objects
   1373      *
   1374      * This method catches PHP errors and converts them into \ErrorException objects;
   1375      * these \ErrorException objects are then thrown and caught by Slim's
   1376      * built-in or custom error handlers.
   1377      *
   1378      * @param  int            $errno   The numeric type of the Error
   1379      * @param  string         $errstr  The error message
   1380      * @param  string         $errfile The absolute path to the affected file
   1381      * @param  int            $errline The line number of the error in the affected file
   1382      * @return bool
   1383      * @throws \ErrorException
   1384      */
   1385     public static function handleErrors($errno, $errstr = '', $errfile = '', $errline = '')
   1386     {
   1387         if (!($errno & error_reporting())) {
   1388             return;
   1389         }
   1390 
   1391         throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
   1392     }
   1393 
   1394     /**
   1395      * Generate diagnostic template markup
   1396      *
   1397      * This method accepts a title and body content to generate an HTML document layout.
   1398      *
   1399      * @param  string   $title  The title of the HTML template
   1400      * @param  string   $body   The body content of the HTML template
   1401      * @return string
   1402      */
   1403     protected static function generateTemplateMarkup($title, $body)
   1404     {
   1405         return sprintf("<html><head><title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:65px;}</style></head><body><h1>%s</h1>%s</body></html>", $title, $title, $body);
   1406     }
   1407 
   1408     /**
   1409      * Default Not Found handler
   1410      */
   1411     protected function defaultNotFound()
   1412     {
   1413         echo static::generateTemplateMarkup('404 Page Not Found', '<p>The page you are looking for could not be found. Check the address bar to ensure your URL is spelled correctly. If all else fails, you can visit our home page at the link below.</p><a href="' . $this->request->getRootUri() . '/">Visit the Home Page</a>');
   1414     }
   1415 
   1416     /**
   1417      * Default Error handler
   1418      */
   1419     protected function defaultError($e)
   1420     {
   1421         $this->getLog()->error($e);
   1422         echo self::generateTemplateMarkup('Error', '<p>A website error has occurred. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>');
   1423     }
   1424 }