PageRenderTime 35ms CodeModel.GetById 3ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/slim/Slim/Slim.php

http://github.com/eryx/php-framework-benchmark
PHP | 1174 lines | 464 code | 92 blank | 618 comment | 74 complexity | d353e1fdad2f3313f1bd72171c81629c 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        }
 579        return $this->view;
 580    }
 581
 582    /***** RENDERING *****/
 583
 584    /**
 585     * Render a template
 586     *
 587     * Call this method within a GET, POST, PUT, DELETE, NOT FOUND, or ERROR
 588     * callable to render a template whose output is appended to the
 589     * current HTTP response body. How the template is rendered is
 590     * delegated to the current View.
 591     *
 592     * @param   string  $template   The name of the template passed into the View::render method
 593     * @param   array   $data       Associative array of data made available to the View
 594     * @param   int     $status     The HTTP response status code to use (Optional)
 595     * @return  void
 596     */
 597    public function render( $template, $data = array(), $status = null ) {
 598        $templatesPath = $this->config('templates.path');
 599        //Legacy support
 600        if ( is_null($templatesPath) ) {
 601            $templatesPath = $this->config('templates_dir');
 602        }
 603        $this->view->setTemplatesDirectory($templatesPath);
 604        if ( !is_null($status) ) {
 605            $this->response->status($status);
 606        }
 607        $this->view->appendData($data);
 608        $this->view->display($template);
 609    }
 610
 611    /***** HTTP CACHING *****/
 612
 613    /**
 614     * Set Last-Modified HTTP Response Header
 615     *
 616     * Set the HTTP 'Last-Modified' header and stop if a conditional
 617     * GET request's `If-Modified-Since` header matches the last modified time
 618     * of the resource. The `time` argument is a UNIX timestamp integer value.
 619     * When the current request includes an 'If-Modified-Since' header that
 620     * matches the specified last modified time, the application will stop
 621     * and send a '304 Not Modified' response to the client.
 622     *
 623     * @param   int                         $time   The last modified UNIX timestamp
 624     * @throws  SlimException                       Returns HTTP 304 Not Modified response if resource last modified time matches `If-Modified-Since` header
 625     * @throws  InvalidArgumentException            If provided timestamp is not an integer
 626     * @return  void
 627     */
 628    public function lastModified( $time ) {
 629        if ( is_integer($time) ) {
 630            $this->response->header('Last-Modified', date(DATE_RFC1123, $time));
 631            if ( $time === strtotime($this->request->headers('IF_MODIFIED_SINCE')) ) $this->halt(304);
 632        } else {
 633            throw new InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.');
 634        }
 635    }
 636
 637    /**
 638     * Set ETag HTTP Response Header
 639     *
 640     * Set the etag header and stop if the conditional GET request matches.
 641     * The `value` argument is a unique identifier for the current resource.
 642     * The `type` argument indicates whether the etag should be used as a strong or
 643     * weak cache validator.
 644     *
 645     * When the current request includes an 'If-None-Match' header with
 646     * a matching etag, execution is immediately stopped. If the request
 647     * method is GET or HEAD, a '304 Not Modified' response is sent.
 648     *
 649     * @param   string                      $value  The etag value
 650     * @param   string                      $type   The type of etag to create; either "strong" or "weak"
 651     * @throws  InvalidArgumentException            If provided type is invalid
 652     * @return  void
 653     */
 654    public function etag( $value, $type = 'strong' ) {
 655
 656        //Ensure type is correct
 657        if ( !in_array($type, array('strong', 'weak')) ) {
 658            throw new InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".');
 659        }
 660
 661        //Set etag value
 662        $value = '"' . $value . '"';
 663        if ( $type === 'weak' ) $value = 'W/'.$value;
 664        $this->response->header('ETag', $value);
 665
 666        //Check conditional GET
 667        if ( $etagsHeader = $this->request->headers('IF_NONE_MATCH')) {
 668            $etags = preg_split('@\s*,\s*@', $etagsHeader);
 669            if ( in_array($value, $etags) || in_array('*', $etags) ) $this->halt(304);
 670        }
 671
 672    }
 673
 674    /***** COOKIES *****/
 675
 676    /**
 677     * Set a normal, unencrypted Cookie
 678     *
 679     * @param   string  $name       The cookie name
 680     * @param   mixed   $value      The cookie value
 681     * @param   mixed   $time       The duration of the cookie;
 682     *                              If integer, should be UNIX timestamp;
 683     *                              If string, converted to UNIX timestamp with `strtotime`;
 684     * @param   string  $path       The path on the server in which the cookie will be available on
 685     * @param   string  $domain     The domain that the cookie is available to
 686     * @param   bool    $secure     Indicates that the cookie should only be transmitted over a secure
 687     *                              HTTPS connection to/from the client
 688     * @param   bool    $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
 689     * @return  void
 690     */
 691    public function setCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
 692        $time = is_null($time) ? $this->config('cookies.lifetime') : $time;
 693        $path = is_null($path) ? $this->config('cookies.path') : $path;
 694        $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
 695        $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
 696        $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
 697        $this->response->getCookieJar()->setClassicCookie($name, $value, $time, $path, $domain, $secure, $httponly);
 698    }
 699
 700    /**
 701     * Get the value of a Cookie from the current HTTP Request
 702     *
 703     * Return the value of a cookie from the current HTTP request,
 704     * or return NULL if cookie does not exist. Cookies created during
 705     * the current request will not be available until the next request.
 706     *
 707     * @param   string $name
 708     * @return  string|null
 709     */
 710    public function getCookie( $name ) {
 711        return $this->request->cookies($name);
 712    }
 713
 714    /**
 715     * Set an encrypted Cookie
 716     *
 717     * @param   string  $name       The cookie name
 718     * @param   mixed   $value      The cookie value
 719     * @param   mixed   $time       The duration of the cookie;
 720     *                              If integer, should be UNIX timestamp;
 721     *                              If string, converted to UNIX timestamp with `strtotime`;
 722     * @param   string  $path       The path on the server in which the cookie will be available on
 723     * @param   string  $domain     The domain that the cookie is available to
 724     * @param   bool    $secure     Indicates that the cookie should only be transmitted over a secure
 725     *                              HTTPS connection from the client
 726     * @param   bool    $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
 727     * @return  void
 728     */
 729    public function setEncryptedCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
 730        $time = is_null($time) ? $this->config('cookies.lifetime') : $time;
 731        $path = is_null($path) ? $this->config('cookies.path') : $path;
 732        $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
 733        $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
 734        $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
 735        $userId = $this->config('cookies.user_id');
 736        $this->response->getCookieJar()->setCookie($name, $value, $userId, $time, $path, $domain, $secure, $httponly);
 737    }
 738
 739    /**
 740     * Get the value of an encrypted Cookie from the current HTTP request
 741     *
 742     * Return the value of an encrypted cookie from the current HTTP request,
 743     * or return NULL if cookie does not exist. Encrypted cookies created during
 744     * the current request will not be available until the next request.
 745     *
 746     * @param   string $name
 747     * @return  string|null
 748     */
 749    public function getEncryptedCookie( $name ) {
 750        $value = $this->response->getCookieJar()->getCookieValue($name);
 751        return ($value === false) ? null : $value;
 752    }
 753
 754    /**
 755     * Delete a Cookie (for both normal or encrypted Cookies)
 756     *
 757     * Remove a Cookie from the client. This method will overwrite an existing Cookie
 758     * with a new, empty, auto-expiring Cookie. This method's arguments must match
 759     * the original Cookie's respective arguments for the original Cookie to be
 760     * removed. If any of this method's arguments are omitted or set to NULL, the
 761     * default Cookie setting values (set during Slim::init) will be used instead.
 762     *
 763     * @param   string  $name       The cookie name
 764     * @param   string  $path       The path on the server in which the cookie will be available on
 765     * @param   string  $domain     The domain that the cookie is available to
 766     * @param   bool    $secure     Indicates that the cookie should only be transmitted over a secure
 767     *                              HTTPS connection from the client
 768     * @param   bool    $httponly   When TRUE the cookie will be made accessible only through the HTTP protocol
 769     * @return  void
 770     */
 771    public function deleteCookie( $name, $path = null, $domain = null, $secure = null, $httponly = null ) {
 772        $path = is_null($path) ? $this->config('cookies.path') : $path;
 773        $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
 774        $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
 775        $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
 776        $this->response->getCookieJar()->deleteCookie( $name, $path, $domain, $secure, $httponly );
 777    }
 778
 779    /***** HELPERS *****/
 780
 781    /**
 782     * Get the Slim application's absolute directory path
 783     *
 784     * This method returns the absolute path to the Slim application's
 785     * directory. If the Slim application is installed in a public-accessible
 786     * sub-directory, the sub-directory path will be included. This method
 787     * will always return an absolute path WITH a trailing slash.
 788     *
 789     * @return string
 790     */
 791    public function root() {
 792        return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/';
 793    }
 794
 795    /**
 796     * Stop
 797     *
 798     * Send the current Response as is and stop executing the Slim
 799     * application. The thrown exception will be caught by the Slim
 800     * custom exception handler which exits this script.
 801     *
 802     * @throws  Slim_Exception_Stop
 803     * @return  void
 804     */
 805    public function stop() {
 806        $flash = $this->view->getData('flash');
 807        if ( $flash ) {
 808            $flash->save();
 809        }
 810        session_write_close();
 811        $this->response->send();
 812        throw new Slim_Exception_Stop();
 813    }
 814
 815    /**
 816     * Halt
 817     *
 818     * Halt the application and immediately send an HTTP response with a
 819     * specific status code and body. This may be used to send any type of
 820     * response: info, success, redirect, client error, or server error.
 821     * If you need to render a template AND customize the response status,
 822     * you should use Slim::render() instead.
 823     *
 824     * @param   int                 $status     The HTTP response status
 825     * @param   string              $message    The HTTP response body
 826     * @return  void
 827     */
 828    public function halt( $status, $message = '') {
 829        if ( ob_get_level() !== 0 ) {
 830            ob_clean();
 831        }
 832        $this->response->status($status);
 833        $this->response->body($message);
 834        $this->stop();
 835    }
 836
 837    /**
 838     * Pass
 839     *
 840     * This method will cause the Router::dispatch method to ignore
 841     * the current route and continue to the next matching route in the
 842     * dispatch loop. If no subsequent mathing routes are found,
 843     * a 404 Not Found response will be sent to the client.
 844     *
 845     * @throws  Slim_Exception_Pass
 846     * @return  void
 847     */
 848    public function pass() {
 849        if ( ob_get_level() !== 0 ) {
 850            ob_clean();
 851        }
 852        throw new Slim_Exception_Pass();
 853    }
 854
 855    /**
 856     * Set the HTTP response Content-Type
 857     * @param   string $type The Content-Type for the Response (ie. text/html)
 858     * @return  void
 859     */
 860    public function contentType( $type ) {
 861        $this->response->header('Content-Type', $type);
 862    }
 863
 864    /**
 865     * Set the HTTP response status code
 866     * @param   int $status The HTTP response status code
 867     * @return  void
 868     */
 869    public function status( $code ) {
 870        $this->response->status($code);
 871    }
 872
 873    /**
 874     * Get the URL for a named Route
 875     * @param   string          $name       The route name
 876     * @param   array           $params     Key-value array of URL parameters
 877     * @throws  RuntimeException            If named route does not exist
 878     * @return  string
 879     */
 880    public function urlFor( $name, $params = array() ) {
 881        return $this->router->urlFor($name, $params);
 882    }
 883
 884    /**
 885     * Redirect
 886     *
 887     * This method immediately redirects to a new URL. By default,
 888     * this issues a 302 Found response; this is considered the default
 889     * generic redirect response. You may also specify another valid
 890     * 3xx status code if you want. This method will automatically set the
 891     * HTTP Location header for you using the URL parameter and place the
 892     * destination URL into the response body.
 893     *
 894     * @param   string                      $url        The destination URL
 895     * @param   int                         $status     The HTTP redirect status code (Optional)
 896     * @throws  InvalidArgumentException                If status parameter is not a valid 3xx status code
 897     * @return  void
 898     */
 899    public function redirect( $url, $status = 302 ) {
 900        if ( $status >= 300 && $status <= 307 ) {
 901            $this->response->header('Location', (string)$url);
 902            $this->halt($status, (string)$url);
 903        } else {
 904            throw new InvalidArgumentException('Slim::redirect only accepts HTTP 300-307 status codes.');
 905        }
 906    }
 907
 908    /***** FLASH *****/
 909
 910    /**
 911     * Set flash message for subsequent request
 912     * @param   string    $key
 913     * @param   mixed     $value
 914     * @return  void
 915     */
 916    public function flash( $key, $value ) {
 917        $this->view->getData('flash')->set($key, $value);
 918    }
 919
 920    /**
 921     * Set flash message for current request
 922     * @param   string    $key
 923     * @param   mixed     $value
 924     * @return  void
 925     */
 926    public function flashNow( $key, $value ) {
 927        $this->view->getData('flash')->now($key, $value);
 928    }
 929
 930    /**
 931     * Keep flash messages from previous request for subsequent request
 932     * @return void
 933     */
 934    public function flashKeep() {
 935        $this->view->getData('flash')->keep();
 936    }
 937
 938    /***** HOOKS *****/
 939
 940    /**
 941     * Assign hook
 942     * @param   string  $name       The hook name
 943     * @param   mixed   $callable   A callable object
 944     * @param   int     $priority   The hook priority; 0 = high, 10 = low
 945     * @return  void
 946     */
 947    public function hook( $name, $callable, $priority = 10 ) {
 948        if ( !isset($this->hooks[$name]) ) {
 949            $this->hooks[$name] = array(array());
 950        }
 951        if ( is_callable($callable) ) {
 952            $this->hooks[$name][(int)$priority][] = $callable;
 953        }
 954    }
 955
 956    /**
 957     * Invoke hook
 958     * @param   string  $name       The hook name
 959     * @param   mixed   $hookArgs   (Optional) Argument for hooked functions
 960     * @return  mixed
 961     */
 962    public function applyHook( $name, $hookArg = null ) {
 963        if ( !isset($this->hooks[$name]) ) {
 964            $this->hooks[$name] = array(array());
 965        }
 966        if( !empty($this->hooks[$name]) ) {
 967            // Sort by priority, low to high, if there's more than one priority
 968            if ( count($this->hooks[$name]) > 1 ) {
 969                ksort($this->hooks[$name]);
 970            }
 971            foreach( $this->hooks[$name] as $priority ) {
 972                if( !empty($priority) ) {
 973                    foreach($priority as $callable) {
 974                        $hookArg = call_user_func($callable, $hookArg);
 975                    }
 976                }
 977            }
 978            return $hookArg;
 979        }
 980    }
 981
 982    /**
 983     * Get hook listeners
 984     *
 985     * Return an array of registered hooks. If `$name` is a valid
 986     * hook name, only the listeners attached to that hook are returned.
 987     * Else, all listeners are returned as an associative array whose
 988     * keys are hook names and whose values are arrays of listeners.
 989     *
 990     * @param   string      $name A hook name (Optional)
 991     * @return  array|null
 992     */
 993    public function getHooks( $name = null ) {
 994        if ( !is_null($name) ) {
 995            return isset($this->hooks[(string)$name]) ? $this->hooks[(string)$name] : null;
 996        } else {
 997            return $this->hooks;
 998        }
 999    }
1000
1001    /**
1002     * Clear hook listeners
1003     *
1004     * Clear all listeners for all hooks. If `$name` is
1005     * a valid hook name, only the listeners attached
1006     * to that hook will be cleared.
1007     *
1008     * @param   string  $name   A hook name (Optional)
1009     * @return  void
1010     */
1011    public function clearHooks( $name = null ) {
1012        if ( !is_null($name) && isset($this->hooks[(string)$name]) ) {
1013            $this->hooks[(string)$name] = array(array());
1014        } else {
1015            foreach( $this->hooks as $key => $value ) {
1016                $this->hooks[$key] = array(array());
1017            }
1018        }
1019    }
1020
1021    /***** RUN SLIM *****/
1022
1023    /**
1024     * Run the Slim application
1025     *
1026     * This method is the "meat and potatoes" of Slim and should be the last
1027     * method called. This fires up Slim, invokes the Route that matches
1028     * the current request, and returns the response to the client.
1029     *
1030     * This method will invoke the Not Found handler if no matching
1031     * routes are found.
1032     *
1033     * This method will also catch any unexpected Exceptions thrown by this
1034     * application; the Exceptions will be logged to this application's log
1035     * and rethrown to the global Exception handler.
1036     *
1037     * @return void
1038     */
1039    public function run() {
1040        try {
1041            try {
1042                $this->applyHook('slim.before');
1043                ob_start();
1044                $this->applyHook('slim.before.router');
1045                $dispatched = false;
1046                $httpMethod = $this->request()->getMethod();
1047                $httpMethodsAllowed = array();
1048                foreach ( $this->router as $route ) {
1049                    if ( $route->supportsHttpMethod($httpMethod) ) {
1050                        try {
1051                            $this->applyHook('slim.before.dispatch');
1052                            $dispatched = $route->dispatch();
1053                            $this->applyHook('slim.after.dispatch');
1054                            if ( $dispatched ) {
1055                                break;
1056                            }
1057                        } catch ( Slim_Exception_Pass $e ) {
1058                            continue;
1059                        }
1060                    } else {
1061                        $httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods());
1062                    }
1063                }
1064                if ( !$dispatched ) {
1065                    if ( $httpMethodsAllowed ) {
1066                        $this->response()->header('Allow', implode(' ', $httpMethodsAllowed));
1067                        $this->halt(405);
1068                    } else {
1069                        $this->notFound();
1070                    }
1071                }
1072                $this->response()->write(ob_get_clean());
1073                $this->applyHook('slim.after.router');
1074                $this->view->getData('flash')->save();
1075                session_write_close();
1076                $this->response->send();
1077                $this->applyHook('slim.after');
1078            } catch ( Slim_Exception_RequestSlash $e ) {
1079                $this->redirect($this->request->getRootUri() . $this->request->getResourceUri() . '/', 301);
1080            } catch ( Exception $e ) {
1081                if ( $e instanceof Slim_Exception_Stop ) throw $e;
1082                $this->getLog()->error($e);
1083                if ( $this->config('debug') === true ) {
1084                    $this->halt(500, self::generateErrorMarkup($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()));
1085                } else {
1086                    $this->error($e);
1087                }
1088            }
1089        } catch ( Slim_Exception_Stop $e ) {
1090            //Exit application context
1091        }
1092    }
1093
1094    /***** EXCEPTION AND ERROR HANDLING *****/
1095
1096    /**
1097     * Handle errors
1098     *
1099     * This is the global Error handler that will catch reportable Errors
1100     * and convert them into ErrorExceptions that are caught and handled
1101     * by each Slim application.
1102     *
1103     * @param   int     $errno      The numeric type of the Error
1104     * @param   string  $errstr     The error message
1105     * @param   string  $errfile    The absolute path to the affected file
1106     * @param   int     $errline    The line number of the error in the affected file
1107     * @return  true
1108     * @throws  ErrorException
1109     */
1110    public static function handleErrors( $errno, $errstr = '', $errfile = '', $errline = '' ) {
1111        if ( error_reporting() & $errno ) {
1112            throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
1113        }
1114        return true;
1115    }
1116
1117    /**
1118     * Generate markup for error message
1119     *
1120     * This method accepts details about an error or exception and
1121     * generates HTML markup for the 500 response body that will
1122     * be sent to the client.
1123     *
1124     * @param   string  $message    The error message
1125     * @param   string  $file       The absolute file path to the affected file
1126     * @param   int     $line       The line number in the affected file
1127     * @param   string  $trace      A stack trace of the error
1128     * @return  string
1129     */
1130    protected static function generateErrorMarkup( $message, $file = '', $line = '', $trace = '' ) {
1131        $body = '<p>The application could not run because of the following error:</p>';
1132        $body .= "<h2>Details:</h2><strong>Message:</strong> $message<br/>";
1133        if ( $file !== '' ) $body .= "<strong>File:</strong> $file<br/>";
1134        if ( $line !== '' ) $body .= "<strong>Line:</strong> $line<br/>";
1135        if ( $trace !== '' ) $body .= '<h2>Stack Trace:</h2>' . nl2br($trace);
1136        return self::generateTemplateMarkup('Slim Application Error', $body);
1137    }
1138
1139    /**
1140     * Generate default template markup
1141     *
1142     * This method accepts a title and body content to generate
1143     * an HTML page. This is primarily used to generate the layout markup
1144     * for Error handlers and Not Found handlers.
1145     *
1146     * @param   string  $title The title of the HTML template
1147     * @param   string  $body The body content of the HTML template
1148     * @return  string
1149     */
1150    protected static function generateTemplateMarkup( $title, $body ) {
1151        $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>";
1152        $html .= "<h1>$title</h1>";
1153        $html .= $body;
1154        $html .= '</body></html>';
1155        return $html;
1156    }
1157
1158    /**
1159     * Default Not Found handler
1160     * @return void
1161     */
1162    protected function defaultNotFound() {
1163        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>');
1164    }
1165
1166    /**
1167     * Default Error handler
1168     * @return void
1169     */
1170    protected function defaultError() {
1171        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>');
1172    }
1173
1174}