PageRenderTime 52ms CodeModel.GetById 11ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 0ms

/creation/Slim/Slim.php

https://bitbucket.org/spo6/chronoview
PHP | 1169 lines | 460 code | 92 blank | 617 comment | 73 complexity | a7f2d65789c4078cae5be449aa91eba1 MD5 | raw file
   1<?php
   2/**
   3 * Slim - a micro PHP 5 framework
   4 *
   5 * @author      Josh Lockhart <info@joshlockhart.com>
   6 * @copyright   2011 Josh Lockhart
   7 * @link        http://www.slimframework.com
   8 * @license     http://www.slimframework.com/license
   9 * @version     1.5.0
  10 *
  11 * MIT LICENSE
  12 *
  13 * Permission is hereby granted, free of charge, to any person obtaining
  14 * a copy of this software and associated documentation files (the
  15 * "Software"), to deal in the Software without restriction, including
  16 * without limitation the rights to use, copy, modify, merge, publish,
  17 * distribute, sublicense, and/or sell copies of the Software, and to
  18 * permit persons to whom the Software is furnished to do so, subject to
  19 * the following conditions:
  20 *
  21 * The above copyright notice and this permission notice shall be
  22 * included in all copies or substantial portions of the Software.
  23 *
  24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  28 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  29 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  30 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31 */
  32
  33//Ensure PHP session IDs only use the characters [a-z0-9]
  34ini_set('session.hash_bits_per_character', 4);
  35ini_set('session.hash_function', 0);
  36
  37//Slim's Encryted Cookies rely on libmcyrpt and these two constants.
  38//If libmycrpt is unavailable, we ensure the expected constants
  39//are available to avoid errors.
  40if ( !defined('MCRYPT_RIJNDAEL_256') ) {
  41    define('MCRYPT_RIJNDAEL_256', 0);
  42}
  43if ( !defined('MCRYPT_MODE_CBC') ) {
  44    define('MCRYPT_MODE_CBC', 0);
  45}
  46
  47//This determines which errors are reported by PHP. By default, all
  48//errors (including E_STRICT) are reported.
  49error_reporting(E_ALL | E_STRICT);
  50
  51//This tells PHP to auto-load classes using Slim's autoloader; this will
  52//only auto-load a class file located in the same directory as Slim.php
  53//whose file name (excluding the final dot and extension) is the same
  54//as its class name (case-sensitive). For example, "View.php" will be
  55//loaded when Slim uses the "View" class for the first time.
  56spl_autoload_register(array('Slim', 'autoload'));
  57
  58//PHP 5.3 will complain if you don't set a timezone. If you do not
  59//specify your own timezone before requiring Slim, this tells PHP to use UTC.
  60if ( @date_default_timezone_set(date_default_timezone_get()) === false ) {
  61    date_default_timezone_set('UTC');
  62}
  63
  64/**
  65 * Slim
  66 *
  67 * @package Slim
  68 * @author  Josh Lockhart <info@joshlockhart.com>
  69 * @since   Version 1.0
  70 */
  71class Slim {
  72
  73    /**
  74     * @var array[Slim]
  75     */
  76    protected static $apps = array();
  77
  78    /**
  79     * @var string
  80     */
  81    protected $name;
  82
  83    /**
  84     * @var Slim_Http_Request
  85     */
  86    protected $request;
  87
  88    /**
  89     * @var Slim_Http_Response
  90     */
  91    protected $response;
  92
  93    /**
  94     * @var Slim_Router
  95     */
  96    protected $router;
  97
  98    /**
  99     * @var Slim_View
 100     */
 101    protected $view;
 102
 103    /**
 104     * @var Slim_Log
 105     */
 106    protected $log;
 107
 108    /**
 109     * @var array Key-value array of application settings
 110     */
 111    protected $settings;
 112
 113    /**
 114     * @var string The application mode
 115     */
 116    protected $mode;
 117
 118    /**
 119     * @var array Plugin hooks
 120     */
 121    protected $hooks = array(
 122        'slim.before' => array(array()),
 123        'slim.before.router' => array(array()),
 124        'slim.before.dispatch' => array(array()),
 125        'slim.after.dispatch' => array(array()),
 126        'slim.after.router' => array(array()),
 127        'slim.after' => array(array())
 128    );
 129
 130    /**
 131     * Slim auto-loader
 132     *
 133     * This method lazy-loads class files when a given class if first used.
 134     * Class files must exist in the same directory as this file and be named
 135     * the same as its class definition (excluding the dot and extension).
 136     *
 137     * @return void
 138     */
 139    public static function autoload( $class ) {
 140        if ( strpos($class, 'Slim') !== 0 ) {
 141            return;
 142        }
 143        $file = dirname(__FILE__) . '/' . str_replace('_', DIRECTORY_SEPARATOR, substr($class,5)) . '.php';
 144        if ( file_exists($file) ) {
 145            require $file;
 146        }
 147    }
 148
 149    /***** INITIALIZATION *****/
 150
 151    /**
 152     * Constructor
 153     * @param   array $userSettings
 154     * @return  void
 155     */
 156    public function __construct( $userSettings = array() ) {
 157        //Merge application settings
 158        $this->settings = array_merge(array(
 159            //Mode
 160            'mode' => 'development',
 161            //Logging
 162            'log.enable' => false,
 163            'log.logger' => null,
 164            'log.path' => './logs',
 165            'log.level' => 4,
 166            //Debugging
 167            'debug' => true,
 168            //View
 169            'templates.path' => './templates',
 170            'view' => 'Slim_View',
 171            //Settings for all cookies
 172            'cookies.lifetime' => '20 minutes',
 173            'cookies.path' => '/',
 174            'cookies.domain' => '',
 175            'cookies.secure' => false,
 176            'cookies.httponly' => false,
 177            //Settings for encrypted cookies
 178            'cookies.secret_key' => 'CHANGE_ME',
 179            'cookies.cipher' => MCRYPT_RIJNDAEL_256,
 180            'cookies.cipher_mode' => MCRYPT_MODE_CBC,
 181            'cookies.encrypt' => true,
 182            'cookies.user_id' => 'DEFAULT',
 183            //Session handler
 184            'session.handler' => new Slim_Session_Handler_Cookies(),
 185            'session.flash_key' => 'flash',
 186            //HTTP
 187            'http.version' => null
 188        ), $userSettings);
 189
 190        //Determine application mode
 191        $this->getMode();
 192
 193        //Setup HTTP request and response handling
 194        $this->request = new Slim_Http_Request();
 195        $this->response = new Slim_Http_Response($this->request);
 196        $this->response->setCookieJar(new Slim_Http_CookieJar($this->settings['cookies.secret_key'], array(
 197            'high_confidentiality' => $this->settings['cookies.encrypt'],
 198            'mcrypt_algorithm' => $this->settings['cookies.cipher'],
 199            'mcrypt_mode' => $this->settings['cookies.cipher_mode'],
 200            'enable_ssl' => $this->settings['cookies.secure']
 201        )));
 202        $this->response->httpVersion($this->settings['http.version']);
 203        $this->router = new Slim_Router($this->request);
 204
 205        //Start session if not already started
 206        if ( session_id() === '' ) {
 207            $sessionHandler = $this->config('session.handler');
 208            if ( $sessionHandler instanceof Slim_Session_Handler ) {
 209                $sessionHandler->register($this);
 210            }
 211            session_cache_limiter(false); 
 212            session_start();
 213        }
 214
 215        //Setup view with flash messaging
 216        $this->view($this->config('view'))->setData('flash', new Slim_Session_Flash($this->config('session.flash_key')));
 217
 218        //Set app name
 219        if ( !isset(self::$apps['default']) ) {
 220            $this->setName('default');
 221        }
 222
 223        //Set global Error handler after Slim app instantiated
 224        set_error_handler(array('Slim', 'handleErrors'));
 225    }
 226
 227    /**
 228     * Get application mode
 229     * @return string
 230     */
 231    public function getMode() {
 232        if ( !isset($this->mode) ) {
 233            if ( isset($_ENV['SLIM_MODE']) ) {
 234                $this->mode = (string)$_ENV['SLIM_MODE'];
 235            } else {
 236                $envMode = getenv('SLIM_MODE');
 237                if ( $envMode !== false ) {
 238                    $this->mode = $envMode;
 239                } else {
 240                    $this->mode = (string)$this->config('mode');
 241                }
 242            }
 243        }
 244        return $this->mode;
 245    }
 246
 247    /***** NAMING *****/
 248
 249    /**
 250     * Get Slim application with name
 251     * @param   string      $name The name of the Slim application to fetch
 252     * @return  Slim|null
 253     */
 254    public static function getInstance( $name = 'default' ) {
 255        return isset(self::$apps[(string)$name]) ? self::$apps[(string)$name] : null;
 256    }
 257
 258    /**
 259     * Set Slim application name
 260     * @param string $name The name of this Slim application
 261     * @return void
 262     */
 263    public function setName( $name ) {
 264        $this->name = $name;
 265        self::$apps[$name] = $this;
 266    }
 267
 268    /**
 269     * Get Slim application name
 270     * @return string|null
 271     */
 272    public function getName() {
 273        return $this->name;
 274    }
 275
 276    /***** LOGGING *****/
 277
 278    /**
 279     * Get application Log (lazy-loaded)
 280     * @return Slim_Log
 281     */
 282    public function getLog() {
 283        if ( !isset($this->log) ) {
 284            $this->log = new Slim_Log();
 285            $this->log->setEnabled($this->config('log.enable'));
 286            $logger = $this->config('log.logger');
 287            if ( $logger ) {
 288                $this->log->setLogger($logger);
 289            } else {
 290                $this->log->setLogger(new Slim_Logger($this->config('log.path'), $this->config('log.level')));
 291            }
 292        }
 293        return $this->log;
 294    }
 295
 296    /***** CONFIGURATION *****/
 297
 298    /**
 299     * Configure Slim for a given mode
 300     *
 301     * This method will immediately invoke the callable if
 302     * the specified mode matches the current application mode.
 303     * Otherwise, the callable is ignored. This should be called
 304     * only _after_ you initialize your Slim app.
 305     *
 306     * @param   string  $mode
 307     * @param   mixed   $callable
 308     * @return  void
 309     */
 310    public function configureMode( $mode, $callable ) {
 311        if ( $mode === $this->getMode() && is_callable($callable) ) {
 312            call_user_func($callable);
 313        }
 314    }
 315
 316    /**
 317     * Configure Slim Settings
 318     *
 319     * This method defines application settings and acts as a setter and a getter.
 320     *
 321     * If only one argument is specified and that argument is a string, the value
 322     * of the setting identified by the first argument will be returned, or NULL if
 323     * that setting does not exist.
 324     *
 325     * If only one argument is specified and that argument is an associative array,
 326     * the array will be merged into the existing application settings.
 327     *
 328     * If two arguments are provided, the first argument is the name of the setting
 329     * to be created or updated, and the second argument is the setting value.
 330     *
 331     * @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
 332     * @param   mixed           $value  If name is a string, the value of the setting identified by $name
 333     * @return  mixed           The value of a setting if only one argument is a string
 334     */
 335    public function config( $name, $value = null ) {
 336        if ( func_num_args() === 1 ) {
 337            if ( is_array($name) ) {
 338                $this->settings = array_merge($this->settings, $name);
 339            } else {
 340                return in_array($name, array_keys($this->settings)) ? $this->settings[$name] : null;
 341            }
 342        } else {
 343            $this->settings[$name] = $value;
 344        }
 345    }
 346
 347    /***** ROUTING *****/
 348
 349    /**
 350     * Add GET|POST|PUT|DELETE route
 351     *
 352     * Adds a new route to the router with associated callable. This
 353     * route will only be invoked when the HTTP request's method matches
 354     * this route's method.
 355     *
 356     * ARGUMENTS:
 357     *
 358     * First:       string  The URL pattern (REQUIRED)
 359     * In-Between:  mixed   Anything that returns TRUE for `is_callable` (OPTIONAL)
 360     * Last:        mixed   Anything that returns TRUE for `is_callable` (REQUIRED)
 361     *
 362     * The first argument is required and must always be the
 363     * route pattern (ie. '/books/:id').
 364     *
 365     * The last argument is required and must always be the callable object
 366     * to be invoked when the route matches an HTTP request.
 367     *
 368     * You may also provide an unlimited number of in-between arguments;
 369     * each interior argument must be callable and will be invoked in the
 370     * order specified before the route's callable is invoked.
 371     *
 372     * USAGE:
 373     *
 374     * Slim::get('/foo'[, middleware, middleware, ...], callable);
 375     *
 376     * @param   array (See notes above)
 377     * @return  Slim_Route
 378     */
 379    protected function mapRoute($args) {
 380        $pattern = array_shift($args);
 381        $callable = array_pop($args);
 382        $route = $this->router->map($pattern, $callable);
 383        if ( count($args) > 0 ) {
 384            $route->setMiddleware($args);
 385        }
 386        return $route;
 387    }
 388
 389    /**
 390     * Add generic route without associated HTTP method
 391     * @see Slim::mapRoute
 392     * @return Slim_Route
 393     */
 394    public function map() {
 395        $args = func_get_args();
 396        return $this->mapRoute($args);
 397    }
 398
 399    /**
 400     * Add GET route
 401     * @see     Slim::mapRoute
 402     * @return  Slim_Route
 403     */
 404    public function get() {
 405        $args = func_get_args();
 406        return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_GET, Slim_Http_Request::METHOD_HEAD);
 407    }
 408
 409    /**
 410     * Add POST route
 411     * @see     Slim::mapRoute
 412     * @return  Slim_Route
 413     */
 414    public function post() {
 415        $args = func_get_args();
 416        return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_POST);
 417    }
 418
 419    /**
 420     * Add PUT route
 421     * @see     Slim::mapRoute
 422     * @return  Slim_Route
 423     */
 424    public function put() {
 425        $args = func_get_args();
 426        return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_PUT);
 427    }
 428
 429    /**
 430     * Add DELETE route
 431     * @see     Slim::mapRoute
 432     * @return  Slim_Route
 433     */
 434    public function delete() {
 435        $args = func_get_args();
 436        return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_DELETE);
 437    }
 438
 439    /**
 440     * Add OPTIONS route
 441     * @see     Slim::mapRoute
 442     * @return  Slim_Route
 443     */
 444    public function options() {
 445        $args = func_get_args();
 446        return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_OPTIONS);
 447    }
 448
 449    /**
 450     * Not Found Handler
 451     *
 452     * This method defines or invokes the application-wide Not Found handler.
 453     * There are two contexts in which this method may be invoked:
 454     *
 455     * 1. When declaring the handler:
 456     *
 457     * If the $callable parameter is not null and is callable, this
 458     * method will register the callable to be invoked when no
 459     * routes match the current HTTP request. It WILL NOT invoke the callable.
 460     *
 461     * 2. When invoking the handler:
 462     *
 463     * If the $callable parameter is null, Slim assumes you want
 464     * to invoke an already-registered handler. If the handler has been
 465     * registered and is callable, it is invoked and sends a 404 HTTP Response
 466     * whose body is the output of the Not Found handler.
 467     *
 468     * @param   mixed $callable Anything that returns true for is_callable()
 469     * @return  void
 470     */
 471    public function notFound( $callable = null ) {
 472        if ( !is_null($callable) ) {
 473            $this->router->notFound($callable);
 474        } else {
 475            ob_start();
 476            $customNotFoundHandler = $this->router->notFound();
 477            if ( is_callable($customNotFoundHandler) ) {
 478                call_user_func($customNotFoundHandler);
 479            } else {
 480                call_user_func(array($this, 'defaultNotFound'));
 481            }
 482            $this->halt(404, ob_get_clean());
 483        }
 484    }
 485
 486    /**
 487     * Error Handler
 488     *
 489     * This method defines or invokes the application-wide Error handler.
 490     * There are two contexts in which this method may be invoked:
 491     *
 492     * 1. When declaring the handler:
 493     *
 494     * If the $argument parameter is callable, this
 495     * method will register the callable to be invoked when an uncaught
 496     * Exception is detected, or when otherwise explicitly invoked.
 497     * The handler WILL NOT be invoked in this context.
 498     *
 499     * 2. When invoking the handler:
 500     *
 501     * If the $argument parameter is not callable, Slim assumes you want
 502     * to invoke an already-registered handler. If the handler has been
 503     * registered and is callable, it is invoked and passed the caught Exception
 504     * as its one and only argument. The error handler's output is captured
 505     * into an output buffer and sent as the body of a 500 HTTP Response.
 506     *
 507     * @param   mixed $argument Callable|Exception
 508     * @return  void
 509     */
 510    public function error( $argument = null ) {
 511        if ( is_callable($argument) ) {
 512            //Register error handler
 513            $this->router->error($argument);
 514        } else {
 515            //Invoke error handler
 516            ob_start();
 517            $customErrorHandler = $this->router->error();
 518            if ( is_callable($customErrorHandler) ) {
 519                call_user_func_array($customErrorHandler, array($argument));
 520            } else {
 521                call_user_func_array(array($this, 'defaultError'), array($argument));
 522            }
 523            $this->halt(500, ob_get_clean());
 524        }
 525    }
 526
 527    /***** ACCESSORS *****/
 528
 529    /**
 530     * Get the Request object
 531     * @return Slim_Http_Request
 532     */
 533    public function request() {
 534        return $this->request;
 535    }
 536
 537    /**
 538     * Get the Response object
 539     * @return Slim_Http_Response
 540     */
 541    public function response() {
 542        return $this->response;
 543    }
 544
 545    /**
 546     * Get the Router object
 547     * @return Slim_Router
 548     */
 549    public function router() {
 550        return $this->router;
 551    }
 552
 553    /**
 554     * Get and/or set the View
 555     *
 556     * This method declares the View to be used by the Slim application.
 557     * If the argument is a string, Slim will instantiate a new object
 558     * of the same class. If the argument is an instance of View or a subclass
 559     * of View, Slim will use the argument as the View.
 560     *
 561     * If a View already exists and this method is called to create a
 562     * new View, data already set in the existing View will be
 563     * transferred to the new View.
 564     *
 565     * @param   string|Slim_View $viewClass  The name of a Slim_View class;
 566     *                                       An instance of Slim_View;
 567     * @return  Slim_View
 568     */
 569    public function view( $viewClass = null ) {
 570        if ( !is_null($viewClass) ) {
 571            $existingData = is_null($this->view) ? array() : $this->view->getData();
 572            if ( $viewClass instanceOf Slim_View ) {
 573                $this->view = $viewClass;
 574            } else {
 575                $this->view = new $viewClass();
 576            }
 577            $this->view->appendData($existingData);
 578            $this->view->setTemplatesDirectory($this->config('templates.path'));
 579        }
 580        return $this->view;
 581    }
 582
 583    /***** RENDERING *****/
 584
 585    /**
 586     * Render a template
 587     *
 588     * Call this method within a GET, POST, PUT, DELETE, NOT FOUND, or ERROR
 589     * callable to render a template whose output is appended to the
 590     * current HTTP response body. How the template is rendered is
 591     * delegated to the current View.
 592     *
 593     * @param   string  $template   The name of the template passed into the View::render method
 594     * @param   array   $data       Associative array of data made available to the View
 595     * @param   int     $status     The HTTP response status code to use (Optional)
 596     * @return  void
 597     */
 598    public function render( $template, $data = array(), $status = null ) {
 599        if ( !is_null($status) ) {
 600            $this->response->status($status);
 601        }
 602        $this->view->appendData($data);
 603        $this->view->display($template);
 604    }
 605
 606    /***** HTTP CACHING *****/
 607
 608    /**
 609     * Set Last-Modified HTTP Response Header
 610     *
 611     * Set the HTTP 'Last-Modified' header and stop if a conditional
 612     * GET request's `If-Modified-Since` header matches the last modified time
 613     * of the resource. The `time` argument is a UNIX timestamp integer value.
 614     * When the current request includes an 'If-Modified-Since' header that
 615     * matches the specified last modified time, the application will stop
 616     * and send a '304 Not Modified' response to the client.
 617     *
 618     * @param   int                         $time   The last modified UNIX timestamp
 619     * @throws  SlimException                       Returns HTTP 304 Not Modified response if resource last modified time matches `If-Modified-Since` header
 620     * @throws  InvalidArgumentException            If provided timestamp is not an integer
 621     * @return  void
 622     */
 623    public function lastModified( $time ) {
 624        if ( is_integer($time) ) {
 625            $this->response->header('Last-Modified', date(DATE_RFC1123, $time));
 626            if ( $time === strtotime($this->request->headers('IF_MODIFIED_SINCE')) ) $this->halt(304);
 627        } else {
 628            throw new InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.');
 629        }
 630    }
 631
 632    /**
 633     * Set ETag HTTP Response Header
 634     *
 635     * Set the etag header and stop if the conditional GET request matches.
 636     * The `value` argument is a unique identifier for the current resource.
 637     * The `type` argument indicates whether the etag should be used as a strong or
 638     * weak cache validator.
 639     *
 640     * When the current request includes an 'If-None-Match' header with
 641     * a matching etag, execution is immediately stopped. If the request
 642     * method is GET or HEAD, a '304 Not Modified' response is sent.
 643     *
 644     * @param   string                      $value  The etag value
 645     * @param   string                      $type   The type of etag to create; either "strong" or "weak"
 646     * @throws  InvalidArgumentException            If provided type is invalid
 647     * @return  void
 648     */
 649    public function etag( $value, $type = 'strong' ) {
 650
 651        //Ensure type is correct
 652        if ( !in_array($type, array('strong', 'weak')) ) {
 653            throw new InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".');
 654        }
 655
 656        //Set etag value
 657        $value = '"' . $value . '"';
 658        if ( $type === 'weak' ) $value = 'W/'.$value;
 659        $this->response->header('ETag', $value);
 660
 661        //Check conditional GET
 662        if ( $etagsHeader = $this->request->headers('IF_NONE_MATCH')) {
 663            $etags = preg_split('@\s*,\s*@', $etagsHeader);
 664            if ( in_array($value, $etags) || in_array('*', $etags) ) $this->halt(304);
 665        }
 666
 667    }
 668
 669    /***** COOKIES *****/
 670
 671    /**
 672     * Set a normal, unencrypted Cookie
 673     *
 674     * @param   string  $name       The cookie name
 675     * @param   mixed   $value      The cookie value
 676     * @param   mixed   $time       The duration of the cookie;
 677     *                              If integer, should be UNIX timestamp;
 678     *                              If string, converted to UNIX timestamp with `strtotime`;
 679     * @param   string  $path       The path on the server in which the cookie will be available on
 680     * @param   string  $domain     The domain that the cookie is available to
 681     * @param   bool    $secure     Indicates that the cookie should only be transmitted over a secure
 682     *                              HTTPS connection to/from the client
 683     * @param   bool    $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
 684     * @return  void
 685     */
 686    public function setCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
 687        $time = is_null($time) ? $this->config('cookies.lifetime') : $time;
 688        $path = is_null($path) ? $this->config('cookies.path') : $path;
 689        $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
 690        $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
 691        $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
 692        $this->response->getCookieJar()->setClassicCookie($name, $value, $time, $path, $domain, $secure, $httponly);
 693    }
 694
 695    /**
 696     * Get the value of a Cookie from the current HTTP Request
 697     *
 698     * Return the value of a cookie from the current HTTP request,
 699     * or return NULL if cookie does not exist. Cookies created during
 700     * the current request will not be available until the next request.
 701     *
 702     * @param   string $name
 703     * @return  string|null
 704     */
 705    public function getCookie( $name ) {
 706        return $this->request->cookies($name);
 707    }
 708
 709    /**
 710     * Set an encrypted Cookie
 711     *
 712     * @param   string  $name       The cookie name
 713     * @param   mixed   $value      The cookie value
 714     * @param   mixed   $time       The duration of the cookie;
 715     *                              If integer, should be UNIX timestamp;
 716     *                              If string, converted to UNIX timestamp with `strtotime`;
 717     * @param   string  $path       The path on the server in which the cookie will be available on
 718     * @param   string  $domain     The domain that the cookie is available to
 719     * @param   bool    $secure     Indicates that the cookie should only be transmitted over a secure
 720     *                              HTTPS connection from the client
 721     * @param   bool    $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
 722     * @return  void
 723     */
 724    public function setEncryptedCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
 725        $time = is_null($time) ? $this->config('cookies.lifetime') : $time;
 726        $path = is_null($path) ? $this->config('cookies.path') : $path;
 727        $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
 728        $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
 729        $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
 730        $userId = $this->config('cookies.user_id');
 731        $this->response->getCookieJar()->setCookie($name, $value, $userId, $time, $path, $domain, $secure, $httponly);
 732    }
 733
 734    /**
 735     * Get the value of an encrypted Cookie from the current HTTP request
 736     *
 737     * Return the value of an encrypted cookie from the current HTTP request,
 738     * or return NULL if cookie does not exist. Encrypted cookies created during
 739     * the current request will not be available until the next request.
 740     *
 741     * @param   string $name
 742     * @return  string|null
 743     */
 744    public function getEncryptedCookie( $name ) {
 745        $value = $this->response->getCookieJar()->getCookieValue($name);
 746        return ($value === false) ? null : $value;
 747    }
 748
 749    /**
 750     * Delete a Cookie (for both normal or encrypted Cookies)
 751     *
 752     * Remove a Cookie from the client. This method will overwrite an existing Cookie
 753     * with a new, empty, auto-expiring Cookie. This method's arguments must match
 754     * the original Cookie's respective arguments for the original Cookie to be
 755     * removed. If any of this method's arguments are omitted or set to NULL, the
 756     * default Cookie setting values (set during Slim::init) will be used instead.
 757     *
 758     * @param   string  $name       The cookie name
 759     * @param   string  $path       The path on the server in which the cookie will be available on
 760     * @param   string  $domain     The domain that the cookie is available to
 761     * @param   bool    $secure     Indicates that the cookie should only be transmitted over a secure
 762     *                              HTTPS connection from the client
 763     * @param   bool    $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
 764     * @return  void
 765     */
 766    public function deleteCookie( $name, $path = null, $domain = null, $secure = null, $httponly = null ) {
 767        $path = is_null($path) ? $this->config('cookies.path') : $path;
 768        $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
 769        $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
 770        $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
 771        $this->response->getCookieJar()->deleteCookie( $name, $path, $domain, $secure, $httponly );
 772    }
 773
 774    /***** HELPERS *****/
 775
 776    /**
 777     * Get the Slim application's absolute directory path
 778     *
 779     * This method returns the absolute path to the Slim application's
 780     * directory. If the Slim application is installed in a public-accessible
 781     * sub-directory, the sub-directory path will be included. This method
 782     * will always return an absolute path WITH a trailing slash.
 783     *
 784     * @return string
 785     */
 786    public function root() {
 787        return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/';
 788    }
 789
 790    /**
 791     * Stop
 792     *
 793     * Send the current Response as is and stop executing the Slim
 794     * application. The thrown exception will be caught by the Slim
 795     * custom exception handler which exits this script.
 796     *
 797     * @throws  Slim_Exception_Stop
 798     * @return  void
 799     */
 800    public function stop() {
 801        $flash = $this->view->getData('flash');
 802        if ( $flash ) {
 803            $flash->save();
 804        }
 805        session_write_close();
 806        $this->response->send();
 807        throw new Slim_Exception_Stop();
 808    }
 809
 810    /**
 811     * Halt
 812     *
 813     * Halt the application and immediately send an HTTP response with a
 814     * specific status code and body. This may be used to send any type of
 815     * response: info, success, redirect, client error, or server error.
 816     * If you need to render a template AND customize the response status,
 817     * you should use Slim::render() instead.
 818     *
 819     * @param   int                 $status     The HTTP response status
 820     * @param   string              $message    The HTTP response body
 821     * @return  void
 822     */
 823    public function halt( $status, $message = '') {
 824        if ( ob_get_level() !== 0 ) {
 825            ob_clean();
 826        }
 827        $this->response->status($status);
 828        $this->response->body($message);
 829        $this->stop();
 830    }
 831
 832    /**
 833     * Pass
 834     *
 835     * This method will cause the Router::dispatch method to ignore
 836     * the current route and continue to the next matching route in the
 837     * dispatch loop. If no subsequent mathing routes are found,
 838     * a 404 Not Found response will be sent to the client.
 839     *
 840     * @throws  Slim_Exception_Pass
 841     * @return  void
 842     */
 843    public function pass() {
 844        if ( ob_get_level() !== 0 ) {
 845            ob_clean();
 846        }
 847        throw new Slim_Exception_Pass();
 848    }
 849
 850    /**
 851     * Set the HTTP response Content-Type
 852     * @param   string $type The Content-Type for the Response (ie. text/html)
 853     * @return  void
 854     */
 855    public function contentType( $type ) {
 856        $this->response->header('Content-Type', $type);
 857    }
 858
 859    /**
 860     * Set the HTTP response status code
 861     * @param   int $status The HTTP response status code
 862     * @return  void
 863     */
 864    public function status( $code ) {
 865        $this->response->status($code);
 866    }
 867
 868    /**
 869     * Get the URL for a named Route
 870     * @param   string          $name       The route name
 871     * @param   array           $params     Key-value array of URL parameters
 872     * @throws  RuntimeException            If named route does not exist
 873     * @return  string
 874     */
 875    public function urlFor( $name, $params = array() ) {
 876        return $this->router->urlFor($name, $params);
 877    }
 878
 879    /**
 880     * Redirect
 881     *
 882     * This method immediately redirects to a new URL. By default,
 883     * this issues a 302 Found response; this is considered the default
 884     * generic redirect response. You may also specify another valid
 885     * 3xx status code if you want. This method will automatically set the
 886     * HTTP Location header for you using the URL parameter and place the
 887     * destination URL into the response body.
 888     *
 889     * @param   string                      $url        The destination URL
 890     * @param   int                         $status     The HTTP redirect status code (Optional)
 891     * @throws  InvalidArgumentException                If status parameter is not a valid 3xx status code
 892     * @return  void
 893     */
 894    public function redirect( $url, $status = 302 ) {
 895        if ( $status >= 300 && $status <= 307 ) {
 896            $this->response->header('Location', (string)$url);
 897            $this->halt($status, (string)$url);
 898        } else {
 899            throw new InvalidArgumentException('Slim::redirect only accepts HTTP 300-307 status codes.');
 900        }
 901    }
 902
 903    /***** FLASH *****/
 904
 905    /**
 906     * Set flash message for subsequent request
 907     * @param   string    $key
 908     * @param   mixed     $value
 909     * @return  void
 910     */
 911    public function flash( $key, $value ) {
 912        $this->view->getData('flash')->set($key, $value);
 913    }
 914
 915    /**
 916     * Set flash message for current request
 917     * @param   string    $key
 918     * @param   mixed     $value
 919     * @return  void
 920     */
 921    public function flashNow( $key, $value ) {
 922        $this->view->getData('flash')->now($key, $value);
 923    }
 924
 925    /**
 926     * Keep flash messages from previous request for subsequent request
 927     * @return void
 928     */
 929    public function flashKeep() {
 930        $this->view->getData('flash')->keep();
 931    }
 932
 933    /***** HOOKS *****/
 934
 935    /**
 936     * Assign hook
 937     * @param   string  $name       The hook name
 938     * @param   mixed   $callable   A callable object
 939     * @param   int     $priority   The hook priority; 0 = high, 10 = low
 940     * @return  void
 941     */
 942    public function hook( $name, $callable, $priority = 10 ) {
 943        if ( !isset($this->hooks[$name]) ) {
 944            $this->hooks[$name] = array(array());
 945        }
 946        if ( is_callable($callable) ) {
 947            $this->hooks[$name][(int)$priority][] = $callable;
 948        }
 949    }
 950
 951    /**
 952     * Invoke hook
 953     * @param   string  $name       The hook name
 954     * @param   mixed   $hookArgs   (Optional) Argument for hooked functions
 955     * @return  mixed
 956     */
 957    public function applyHook( $name, $hookArg = null ) {
 958        if ( !isset($this->hooks[$name]) ) {
 959            $this->hooks[$name] = array(array());
 960        }
 961        if( !empty($this->hooks[$name]) ) {
 962            // Sort by priority, low to high, if there's more than one priority
 963            if ( count($this->hooks[$name]) > 1 ) {
 964                ksort($this->hooks[$name]);
 965            }
 966            foreach( $this->hooks[$name] as $priority ) {
 967                if( !empty($priority) ) {
 968                    foreach($priority as $callable) {
 969                        $hookArg = call_user_func($callable, $hookArg);
 970                    }
 971                }
 972            }
 973            return $hookArg;
 974        }
 975    }
 976
 977    /**
 978     * Get hook listeners
 979     *
 980     * Return an array of registered hooks. If `$name` is a valid
 981     * hook name, only the listeners attached to that hook are returned.
 982     * Else, all listeners are returned as an associative array whose
 983     * keys are hook names and whose values are arrays of listeners.
 984     *
 985     * @param   string      $name A hook name (Optional)
 986     * @return  array|null
 987     */
 988    public function getHooks( $name = null ) {
 989        if ( !is_null($name) ) {
 990            return isset($this->hooks[(string)$name]) ? $this->hooks[(string)$name] : null;
 991        } else {
 992            return $this->hooks;
 993        }
 994    }
 995
 996    /**
 997     * Clear hook listeners
 998     *
 999     * Clear all listeners for all hooks. If `$name` is
1000     * a valid hook name, only the listeners attached
1001     * to that hook will be cleared.
1002     *
1003     * @param   string  $name   A hook name (Optional)
1004     * @return  void
1005     */
1006    public function clearHooks( $name = null ) {
1007        if ( !is_null($name) && isset($this->hooks[(string)$name]) ) {
1008            $this->hooks[(string)$name] = array(array());
1009        } else {
1010            foreach( $this->hooks as $key => $value ) {
1011                $this->hooks[$key] = array(array());
1012            }
1013        }
1014    }
1015
1016    /***** RUN SLIM *****/
1017
1018    /**
1019     * Run the Slim application
1020     *
1021     * This method is the "meat and potatoes" of Slim and should be the last
1022     * method called. This fires up Slim, invokes the Route that matches
1023     * the current request, and returns the response to the client.
1024     *
1025     * This method will invoke the Not Found handler if no matching
1026     * routes are found.
1027     *
1028     * This method will also catch any unexpected Exceptions thrown by this
1029     * application; the Exceptions will be logged to this application's log
1030     * and rethrown to the global Exception handler.
1031     *
1032     * @return void
1033     */
1034    public function run() {
1035        try {
1036            try {
1037                $this->applyHook('slim.before');
1038                ob_start();
1039                $this->applyHook('slim.before.router');
1040                $dispatched = false;
1041                $httpMethod = $this->request()->getMethod();
1042                $httpMethodsAllowed = array();
1043                foreach ( $this->router as $route ) {
1044                    if ( $route->supportsHttpMethod($httpMethod) ) {
1045                        try {
1046                            $this->applyHook('slim.before.dispatch');
1047                            $dispatched = $route->dispatch();
1048                            $this->applyHook('slim.after.dispatch');
1049                            if ( $dispatched ) {
1050                                break;
1051                            }
1052                        } catch ( Slim_Exception_Pass $e ) {
1053                            continue;
1054                        }
1055                    } else {
1056                        $httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods());
1057                    }
1058                }
1059                if ( !$dispatched ) {
1060                    if ( $httpMethodsAllowed ) {
1061                        $this->response()->header('Allow', implode(' ', $httpMethodsAllowed));
1062                        $this->halt(405);
1063                    } else {
1064                        $this->notFound();
1065                    }
1066                }
1067                $this->response()->write(ob_get_clean());
1068                $this->applyHook('slim.after.router');
1069                $this->view->getData('flash')->save();
1070                session_write_close();
1071                $this->response->send();
1072                $this->applyHook('slim.after');
1073            } catch ( Slim_Exception_RequestSlash $e ) {
1074                $this->redirect($this->request->getRootUri() . $this->request->getResourceUri() . '/', 301);
1075            } catch ( Exception $e ) {
1076                if ( $e instanceof Slim_Exception_Stop ) throw $e;
1077                $this->getLog()->error($e);
1078                if ( $this->config('debug') === true ) {
1079                    $this->halt(500, self::generateErrorMarkup($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()));
1080                } else {
1081                    $this->error($e);
1082                }
1083            }
1084        } catch ( Slim_Exception_Stop $e ) {
1085            //Exit application context
1086        }
1087    }
1088
1089    /***** EXCEPTION AND ERROR HANDLING *****/
1090
1091    /**
1092     * Handle errors
1093     *
1094     * This is the global Error handler that will catch reportable Errors
1095     * and convert them into ErrorExceptions that are caught and handled
1096     * by each Slim application.
1097     *
1098     * @param   int     $errno      The numeric type of the Error
1099     * @param   string  $errstr     The error message
1100     * @param   string  $errfile    The absolute path to the affected file
1101     * @param   int     $errline    The line number of the error in the affected file
1102     * @return  true
1103     * @throws  ErrorException
1104     */
1105    public static function handleErrors( $errno, $errstr = '', $errfile = '', $errline = '' ) {
1106        if ( error_reporting() & $errno ) {
1107            throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
1108        }
1109        return true;
1110    }
1111
1112    /**
1113     * Generate markup for error message
1114     *
1115     * This method accepts details about an error or exception and
1116     * generates HTML markup for the 500 response body that will
1117     * be sent to the client.
1118     *
1119     * @param   string  $message    The error message
1120     * @param   string  $file       The absolute file path to the affected file
1121     * @param   int     $line       The line number in the affected file
1122     * @param   string  $trace      A stack trace of the error
1123     * @return  string
1124     */
1125    protected static function generateErrorMarkup( $message, $file = '', $line = '', $trace = '' ) {
1126        $body = '<p>The application could not run because of the following error:</p>';
1127        $body .= "<h2>Details:</h2><strong>Message:</strong> $message<br/>";
1128        if ( $file !== '' ) $body .= "<strong>File:</strong> $file<br/>";
1129        if ( $line !== '' ) $body .= "<strong>Line:</strong> $line<br/>";
1130        if ( $trace !== '' ) $body .= '<h2>Stack Trace:</h2>' . nl2br($trace);
1131        return self::generateTemplateMarkup('Slim Application Error', $body);
1132    }
1133
1134    /**
1135     * Generate default template markup
1136     *
1137     * This method accepts a title and body content to generate
1138     * an HTML page. This is primarily used to generate the layout markup
1139     * for Error handlers and Not Found handlers.
1140     *
1141     * @param   string  $title The title of the HTML template
1142     * @param   string  $body The body content of the HTML template
1143     * @return  string
1144     */
1145    protected static function generateTemplateMarkup( $title, $body ) {
1146        $html = "<html><head><title>$title</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>";
1147        $html .= "<h1>$title</h1>";
1148        $html .= $body;
1149        $html .= '</body></html>';
1150        return $html;
1151    }
1152
1153    /**
1154     * Default Not Found handler
1155     * @return void
1156     */
1157    protected function defaultNotFound() {
1158        echo self::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>');
1159    }
1160
1161    /**
1162     * Default Error handler
1163     * @return void
1164     */
1165    protected function defaultError() {
1166        echo self::generateTemplateMarkup('Error', '<p>A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>');
1167    }
1168
1169}