PageRenderTime 56ms CodeModel.GetById 2ms app.highlight 43ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/slim/Slim.php

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