commit a6edee97b7e7fdaa664e0eaabb7acf645bc55b4a
Author: Colin Mitchell
Date: Fri Apr 13 19:47:29 2012 -0400
initial commit
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..7455d4f
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,11 @@
+RewriteEngine On
+
+# Some hosts may require you to use the `RewriteBase` directive.
+# If you need to use the `RewriteBase` directive, it should be the
+# absolute physical path to the directory that contains this htaccess file.
+#
+# RewriteBase /
+
+RewriteCond %{REQUEST_FILENAME} !-f
+#RewriteRule ^ index.php [QSA,L]
+RewriteRule (.*) index.php?x=$1 [QSA,L]
diff --git a/GopherGetter.php b/GopherGetter.php
new file mode 100644
index 0000000..d734837
--- /dev/null
+++ b/GopherGetter.php
@@ -0,0 +1,64 @@
+uri = "gopher:/$u";
+
+ // split host and port from uri
+ $data = parse_url($this->uri);
+
+ $this->host = $data['host'];
+ $this->port = array_key_exists('port', $data) ? $data['port'] : 70;
+
+ // strip the leading slash from the path
+ if ( array_key_exists('path', $data) ) {
+ $this->path = ltrim($data['path'], '/');
+ }
+ else {
+ $this->path = "/";
+ }
+ }
+
+ function isValid() {
+ return isSet($this->host) && isSet($this->port) && isSet($this->path);
+ }
+
+ function get() {
+ $cache = new JG_Cache('/tmp'); //Make sure it exists and is writeable
+
+ $this->result = "";
+ $this->errstr = "";
+ $this->errno = "";
+
+
+ $this->result = $cache->get('key', 1000000);
+ if ( $this->result === FALSE ) {
+ // echo $path;
+
+ $fp = stream_socket_client("tcp://$this->host:$this->port", $this->errno, $this->errstr, 30);
+
+ if (!$fp) {
+ return FALSE;
+ }
+ else {
+ fwrite($fp, "$this->path\r\n");
+ while (!feof($fp)) {
+ $this->result .= fgets($fp, 1024);
+ }
+ fclose($fp);
+ }
+
+ $cache->set($this->uri, $this->result);
+ }
+
+ return TRUE;
+ }
+};
+?>
\ No newline at end of file
diff --git a/JG_Cache.php b/JG_Cache.php
new file mode 100644
index 0000000..f308618
--- /dev/null
+++ b/JG_Cache.php
@@ -0,0 +1,105 @@
+dir = $dir;
+ }
+
+ private function _name($key)
+ {
+ return sprintf("%s/%s", $this->dir, sha1($key));
+ }
+
+ public function get($key, $expiration = 3600)
+ {
+
+ if ( !is_dir($this->dir) OR !is_writable($this->dir))
+ {
+ return FALSE;
+ }
+
+ $cache_path = $this->_name($key);
+
+ if (!@file_exists($cache_path))
+ {
+ return FALSE;
+ }
+
+ if (filemtime($cache_path) < (time() - $expiration))
+ {
+ $this->clear($key);
+ return FALSE;
+ }
+
+ if (!$fp = @fopen($cache_path, 'rb'))
+ {
+ return FALSE;
+ }
+
+ flock($fp, LOCK_SH);
+
+ $cache = '';
+
+ if (filesize($cache_path) > 0)
+ {
+ $cache = unserialize(fread($fp, filesize($cache_path)));
+ }
+ else
+ {
+ $cache = NULL;
+ }
+
+ flock($fp, LOCK_UN);
+ fclose($fp);
+
+ return $cache;
+ }
+
+ public function set($key, $data)
+ {
+
+ if ( !is_dir($this->dir) OR !is_writable($this->dir))
+ {
+ return FALSE;
+ }
+
+ $cache_path = $this->_name($key);
+
+ if ( ! $fp = fopen($cache_path, 'wb'))
+ {
+ return FALSE;
+ }
+
+ if (flock($fp, LOCK_EX))
+ {
+ fwrite($fp, serialize($data));
+ flock($fp, LOCK_UN);
+ }
+ else
+ {
+ return FALSE;
+ }
+ fclose($fp);
+ @chmod($cache_path, 0777);
+ return TRUE;
+ }
+
+ public function clear($key)
+ {
+ $cache_path = $this->_name($key);
+
+ if (file_exists($cache_path))
+ {
+ unlink($cache_path);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eec67e7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Josh Lockhart
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..e7edf6f
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,163 @@
+# Slim Framework for PHP 5
+
+Slim is a micro framework for PHP 5 that helps you quickly write simple yet powerful RESTful web applications and APIs. Slim is easy to use for both beginners and professionals. Slim favors cleanliness over terseness and common cases over edge cases. Its interface is simple, intuitive, and extensively documented — both online and in the code itself. Thank you for choosing Slim for your next project. I think you're going to love it.
+
+## Features
+
+* Clean and simple [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) for writing powerful web applications
+* HTTP routing
+ * Supports all standard and custom HTTP request methods
+ * Route parameters and conditions
+ * Route redirects
+ * Route passing
+ * Route halting
+ * Route middleware
+ * Named routes and `urlFor()` helper
+* Easy configuration
+* Easy templating with custom Views (e.g. Twig, Mustache, Smarty)
+* Flash messaging
+* Signed cookies with AES-256 encryption
+* HTTP caching (ETag and Last-Modified)
+* Logging
+* Error handling
+ * Custom Not Found handler
+ * Custom Error handler
+ * Debugging
+* Built upon the Rack protocol
+* Extensible middleware and hook architecture
+* Supports PHP >= 5.2.0
+
+## "Hello World" application (PHP >= 5.3)
+
+The Slim Framework for PHP 5 supports anonymous functions. This is the preferred method to define Slim application routes. This example assumes you have setup URL rewriting with your web server (see below).
+
+```php
+get('/hello/:name', function ($name) {
+ echo "Hello, $name!";
+});
+$app->run();
+```
+
+## "Hello World" application (PHP < 5.3)
+
+If you are running PHP < 5.3, the second argument to the application's `get()` instance method is the name of a callable function instead of an anonymous function. This example assumes you have setup URL rewriting with your web server (see below).
+
+```php
+get('/hello/:name', 'hello');
+function hello($name) {
+ echo "Hello, $name!";
+}
+$app->run();
+```
+
+## Get Started
+
+### Install Slim
+
+Download the Slim Framework for PHP 5 and unzip the downloaded file into your virtual host's public directory. Slim will work in a sub-directory, too.
+
+### Setup your webserver
+
+#### Apache
+
+Ensure the `.htaccess` and `index.php` files are in the same public-accessible directory. The `.htaccess` file should contain this code:
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [QSA,L]
+
+#### Nginx
+
+Your nginx configuration file should contain this code (along with other settings you may need) in your `location` block:
+
+ if (!-f $request_filename) {
+ rewrite ^ /index.php last;
+ }
+
+This assumes that Slim's `index.php` is in the root folder of your project (www root).
+
+#### lighttpd ####
+
+Your lighttpd configuration file should contain this code (along with other settings you may need). This code requires lighttpd >= 1.4.24.
+
+ url.rewrite-if-not-file = ("^" => "/index.php")
+
+This assumes that Slim's `index.php` is in the root folder of your project (www root).
+
+### Build Your Application
+
+Your Slim application will be defined in `index.php`. First, `require` the Slim Framework:
+
+```php
+require 'Slim/Slim.php';
+```
+
+Next, initialize the Slim application:
+
+```php
+$app = new Slim();
+```
+
+Next, define your application's routes:
+
+```php
+$app->get('/hello/:name', function ($name) {
+ echo "Hello $name";
+});
+```
+
+Finally, run your Slim application:
+
+```php
+ $app->run();
+```
+
+For more information about building an application with the Slim Framework, refer to the [official documentation](http://github.com/codeguy/Slim/wiki/Slim-Framework-Documentation).
+
+## Documentation
+
+* [Stable Branch Documentation](http://www.slimframework.com/documentation/stable)
+* [Development Branch Documentation](http://www.slimframework.com/documentation/develop)
+
+## Community
+
+### Forum
+
+Visit Slim's official forum and knowledge base at where you can find announcements, chat with fellow Slim users, ask questions, help others, or show off your cool Slim Framework apps.
+
+### Twitter
+
+Follow [@slimphp](http://www.twitter.com/slimphp) on Twitter to receive the very latest news and updates about the framework.
+
+### IRC
+
+You can find me, Josh Lockhart, hanging out in the ##slim chat room during the day. Feel free to say hi, ask questions, or just hang out. If you're on a Mac, check out Colloquy; if you're on a PC, check out mIRC; if you're on Linux, I'm sure you already know what you're doing.
+
+## Resources
+
+Additional resources (ie. custom Views and plugins) are available online in a separate repository.
+
+
+
+Here are more links that may also be useful.
+
+* Road Map:
+* Source Code:
+
+## About the Author
+
+Slim is created and maintained by Josh Lockhart, a web developer by day at [New Media Campaigns](http://www.newmediacampaigns.com), and a [hacker by night](http://github.com/codeguy).
+
+Slim is in active development, and test coverage is continually improving.
+
+## Open Source License
+
+Slim is released under the MIT public license.
+
+
diff --git a/Slim/Environment.php b/Slim/Environment.php
new file mode 100644
index 0000000..c401add
--- /dev/null
+++ b/Slim/Environment.php
@@ -0,0 +1,225 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Environment
+ *
+ * This class creates and returns a key/value array of common
+ * environment variables for the current HTTP request.
+ *
+ * This is a singleton class; derived environment variables will
+ * be common across multiple Slim applications.
+ *
+ * This class matches the Rack (Ruby) specification as closely
+ * as possible. More information available below.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Environment implements ArrayAccess, IteratorAggregate {
+ /**
+ * @var array
+ */
+ protected $properties;
+
+ /**
+ * @var Slim_Environment
+ */
+ protected static $environment;
+
+ /**
+ * Get environment instance (singleton)
+ *
+ * This creates and/or returns an Environment instance (singleton)
+ * derived from $_SERVER variables. You may override the global server
+ * variables by using `Environment::mock()` instead.
+ *
+ * @param bool $refresh Refresh properties using global server variables?
+ * @return Slim_Environment
+ */
+ public static function getInstance( $refresh = false ) {
+ if ( is_null(self::$environment) || $refresh ) {
+ self::$environment = new self();
+ }
+ return self::$environment;
+ }
+
+ /**
+ * Get mock environment instance
+ *
+ * @param array $userSettings
+ * @return Environment
+ */
+ public static function mock( $userSettings = array() ) {
+ self::$environment = new self(array_merge(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '',
+ 'PATH_INFO' => '',
+ 'QUERY_STRING' => '',
+ 'SERVER_NAME' => 'localhost',
+ 'SERVER_PORT' => 80,
+ 'ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'ACCEPT_LANGUAGE' => 'en-US,en;q=0.8',
+ 'ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
+ 'USER_AGENT' => 'Slim Framework',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => @fopen('php://stderr', 'w')
+ ), $userSettings));
+ return self::$environment;
+ }
+
+ /**
+ * Constructor (private access)
+ *
+ * @param array|null $settings If present, these are used instead of global server variables
+ * @return void
+ */
+ private function __construct( $settings = null ) {
+ if ( $settings ) {
+ $this->properties = $settings;
+ } else {
+ $env = array();
+
+ //The HTTP request method
+ $env['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD'];
+
+ //The IP
+ $env['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
+
+ /**
+ * Application paths
+ *
+ * This derives two paths: SCRIPT_NAME and PATH_INFO. The SCRIPT_NAME
+ * is the real, physical path to the application, be it in the root
+ * directory or a subdirectory of the public document root. The PATH_INFO is the
+ * virtual path to the requested resource within the application context.
+ *
+ * With htaccess, the SCRIPT_NAME will be an absolute path (without file name);
+ * if not using htaccess, it will also include the file name. If it is "/",
+ * it is set to an empty string (since it cannot have a trailing slash).
+ *
+ * The PATH_INFO will be an absolute path with a leading slash; this will be
+ * used for application routing.
+ */
+ if ( strpos($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']) === 0 ) {
+ $env['SCRIPT_NAME'] = $_SERVER['SCRIPT_NAME']; //Without URL rewrite
+ } else {
+ $env['SCRIPT_NAME'] = pathinfo($_SERVER['SCRIPT_NAME'], PATHINFO_DIRNAME); //With URL rewrite
+ }
+ $env['PATH_INFO'] = substr_replace($_SERVER['REQUEST_URI'], '', 0, strlen($env['SCRIPT_NAME']));
+ if ( strpos($env['PATH_INFO'], '?') !== false ) {
+ $env['PATH_INFO'] = substr_replace($env['PATH_INFO'], '', strpos($env['PATH_INFO'], '?')); //query string is not removed automatically
+ }
+ $env['SCRIPT_NAME'] = rtrim($env['SCRIPT_NAME'], '/');
+ $env['PATH_INFO'] = '/' . ltrim($env['PATH_INFO'], '/');
+
+ //The portion of the request URI following the '?'
+ $env['QUERY_STRING'] = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
+
+ //Name of server host that is running the script
+ $env['SERVER_NAME'] = $_SERVER['SERVER_NAME'];
+
+ //Number of server port that is running the script
+ $env['SERVER_PORT'] = $_SERVER['SERVER_PORT'];
+
+ //HTTP request headers
+ $specialHeaders = array('CONTENT_TYPE', 'CONTENT_LENGTH', 'PHP_AUTH_USER', 'PHP_AUTH_PW', 'PHP_AUTH_DIGEST', 'AUTH_TYPE');
+ foreach ( $_SERVER as $key => $value ) {
+ if ( strpos($key, 'HTTP_') === 0 ) {
+ $env[substr($key, 5)] = $value;
+ } else if ( strpos($key, 'X_') === 0 || in_array($key, $specialHeaders) ) {
+ $env[$key] = $value;
+ }
+ }
+
+ //Is the application running under HTTPS or HTTP protocol?
+ $env['slim.url_scheme'] = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https';
+
+ //Input stream (readable one time only; not available for mutipart/form-data requests)
+ $rawInput = @file_get_contents('php://input');
+ if ( !$rawInput ) {
+ $rawInput = '';
+ }
+ $env['slim.input'] = $rawInput;
+
+ //Error stream
+ $env['slim.errors'] = fopen('php://stderr', 'w');
+
+ $this->properties = $env;
+ }
+ }
+
+ /**
+ * Array Access: Offset Exists
+ */
+ public function offsetExists( $offset ) {
+ return isset($this->properties[$offset]);
+ }
+
+ /**
+ * Array Access: Offset Get
+ */
+ public function offsetGet( $offset ) {
+ if ( isset($this->properties[$offset]) ) {
+ return $this->properties[$offset];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Array Access: Offset Set
+ */
+ public function offsetSet( $offset, $value ) {
+ $this->properties[$offset] = $value;
+ }
+
+ /**
+ * Array Access: Offset Unset
+ */
+ public function offsetUnset( $offset ) {
+ unset($this->properties[$offset]);
+ }
+
+ /**
+ * IteratorAggregate
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator() {
+ return new ArrayIterator($this->properties);
+ }
+}
diff --git a/Slim/Exception/Pass.php b/Slim/Exception/Pass.php
new file mode 100644
index 0000000..d480a3b
--- /dev/null
+++ b/Slim/Exception/Pass.php
@@ -0,0 +1,46 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Pass Exception
+ *
+ * This Exception will cause the Router::dispatch method
+ * to skip the current matching route and continue to the next
+ * matching route. If no subsequent routes are found, a
+ * HTTP 404 Not Found response will be sent to the client.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Exception_Pass extends Exception {}
\ No newline at end of file
diff --git a/Slim/Exception/RequestSlash.php b/Slim/Exception/RequestSlash.php
new file mode 100644
index 0000000..94e4b41
--- /dev/null
+++ b/Slim/Exception/RequestSlash.php
@@ -0,0 +1,47 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Request Slash Exception
+ *
+ * This Exception is thrown when Slim detects a matching route
+ * (defined with a trailing slash) and the HTTP request
+ * matches the route but does not have a trailing slash. This
+ * exception will be caught in `Slim::run` and trigger a 301 redirect
+ * to the same resource URI with a trailing slash.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Exception_RequestSlash extends Exception {}
\ No newline at end of file
diff --git a/Slim/Exception/Stop.php b/Slim/Exception/Stop.php
new file mode 100644
index 0000000..32caf8e
--- /dev/null
+++ b/Slim/Exception/Stop.php
@@ -0,0 +1,44 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Stop Exception
+ *
+ * This Exception is thrown when the Slim application needs to abort
+ * processing and return control flow to the outer PHP script.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Exception_Stop extends Exception {}
\ No newline at end of file
diff --git a/Slim/Http/Headers.php b/Slim/Http/Headers.php
new file mode 100644
index 0000000..9ec2da1
--- /dev/null
+++ b/Slim/Http/Headers.php
@@ -0,0 +1,167 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+ /**
+ * HTTP Headers
+ *
+ * This class is an abstraction of the HTTP response headers and
+ * provides array access to the header list while automatically
+ * stores and retrieves headers with lowercase canonical keys regardless
+ * of the input format.
+ *
+ * This class also implements the `Iterator` and `Countable`
+ * interfaces for even more convenient usage.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Http_Headers implements ArrayAccess, Iterator, Countable {
+ /**
+ * @var array HTTP headers
+ */
+ protected $headers;
+
+ /**
+ * @var array Map canonical header name to original header name
+ */
+ protected $map;
+
+ /**
+ * Constructor
+ * @param array $headers
+ * @return void
+ */
+ public function __construct( $headers = array() ) {
+ $this->merge($headers);
+ }
+
+ /**
+ * Merge Headers
+ * @param array $headers
+ * @return void
+ */
+ public function merge( $headers ) {
+ foreach ( $headers as $name => $value ) {
+ $this[$name] = $value;
+ }
+ }
+
+ /**
+ * Transform header name into canonical form
+ * @param string $name
+ * @return string
+ */
+ protected function canonical( $name ) {
+ return strtolower(trim($name));
+ }
+
+ /**
+ * Array Access: Offset Exists
+ */
+ public function offsetExists( $offset ) {
+ return isset($this->headers[$this->canonical($offset)]);
+ }
+
+ /**
+ * Array Access: Offset Get
+ */
+ public function offsetGet( $offset ) {
+ $canonical = $this->canonical($offset);
+ if ( isset($this->headers[$canonical]) ) {
+ return $this->headers[$canonical];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Array Access: Offset Set
+ */
+ public function offsetSet( $offset, $value ) {
+ $canonical = $this->canonical($offset);
+ $this->headers[$canonical] = $value;
+ $this->map[$canonical] = $offset;
+ }
+
+ /**
+ * Array Access: Offset Unset
+ */
+ public function offsetUnset( $offset ) {
+ $canonical = $this->canonical($offset);
+ unset($this->headers[$canonical], $this->map[$canonical]);
+ }
+
+ /**
+ * Countable: Count
+ */
+ public function count() {
+ return count($this->headers);
+ }
+
+ /**
+ * Iterator: Rewind
+ */
+ public function rewind() {
+ reset($this->headers);
+ }
+
+ /**
+ * Iterator: Current
+ */
+ public function current() {
+ return current($this->headers);
+ }
+
+ /**
+ * Iterator: Key
+ */
+ public function key() {
+ $key = key($this->headers);
+ return $this->map[$key];
+ }
+
+ /**
+ * Iterator: Next
+ */
+ public function next() {
+ return next($this->headers);
+ }
+
+ /**
+ * Iterator: Valid
+ */
+ public function valid() {
+ return current($this->headers) !== false;
+ }
+}
\ No newline at end of file
diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php
new file mode 100644
index 0000000..a836725
--- /dev/null
+++ b/Slim/Http/Request.php
@@ -0,0 +1,517 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Slim HTTP Request
+ *
+ * This class provides a human-friendly interface to the Slim environment variables;
+ * environment variables are passed by reference and will be modified directly.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Http_Request {
+ const METHOD_HEAD = 'HEAD';
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+ const METHOD_PUT = 'PUT';
+ const METHOD_DELETE = 'DELETE';
+ const METHOD_OPTIONS = 'OPTIONS';
+ const METHOD_OVERRIDE = '_METHOD';
+
+ /**
+ * @var array
+ */
+ protected static $formDataMediaTypes = array('application/x-www-form-urlencoded');
+
+ /**
+ * @var array
+ */
+ protected $env;
+
+ /**
+ * Constructor
+ * @param array $env
+ * @see Slim_Environment
+ */
+ public function __construct( $env ) {
+ $this->env = $env;
+ }
+
+ /**
+ * Get HTTP method
+ * @return string
+ */
+ public function getMethod() {
+ return $this->env['REQUEST_METHOD'];
+ }
+
+ /**
+ * Is this a GET request?
+ * @return bool
+ */
+ public function isGet() {
+ return $this->getMethod() === self::METHOD_GET;
+ }
+
+ /**
+ * Is this a POST request?
+ * @return bool
+ */
+ public function isPost() {
+ return $this->getMethod() === self::METHOD_POST;
+ }
+
+ /**
+ * Is this a PUT request?
+ * @return bool
+ */
+ public function isPut() {
+ return $this->getMethod() === self::METHOD_PUT;
+ }
+
+ /**
+ * Is this a DELETE request?
+ * @return bool
+ */
+ public function isDelete() {
+ return $this->getMethod() === self::METHOD_DELETE;
+ }
+
+ /**
+ * Is this a HEAD request?
+ * @return bool
+ */
+ public function isHead() {
+ return $this->getMethod() === self::METHOD_HEAD;
+ }
+
+ /**
+ * Is this a OPTIONS request?
+ * @return bool
+ */
+ public function isOptions() {
+ return $this->getMethod() === self::METHOD_OPTIONS;
+ }
+
+ /**
+ * Is this an AJAX request?
+ * @return bool
+ */
+ public function isAjax() {
+ if ( $this->params('isajax') ) {
+ return true;
+ } else if ( isset($this->env['X_REQUESTED_WITH']) && $this->env['X_REQUESTED_WITH'] === 'XMLHttpRequest' ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Is this an XHR request? (alias of Slim_Http_Request::isAjax)
+ * @return bool
+ */
+ public function isXhr() {
+ return $this->isAjax();
+ }
+
+ /**
+ * Fetch GET and POST data
+ *
+ * This method returns a union of GET and POST data as a key-value array, or the value
+ * of the array key if requested; if the array key does not exist, NULL is returned.
+ *
+ * @param string $key
+ * @return array|mixed|null
+ */
+ public function params( $key = null ) {
+ $union = array_merge($this->get(), $this->post());
+ if ( $key ) {
+ if ( isset($union[$key]) ) {
+ return $union[$key];
+ } else {
+ return null;
+ }
+ } else {
+ return $union;
+ }
+ }
+
+ /**
+ * Fetch GET data
+ *
+ * This method returns a key-value array of data sent in the HTTP request query string, or
+ * the value of the array key if requested; if the array key does not exist, NULL is returned.
+ *
+ * @param string $key
+ * @return array|mixed|null
+ */
+ public function get( $key = null ) {
+ if ( !isset($this->env['slim.request.query_hash']) ) {
+ $output = array();
+ if ( function_exists('mb_parse_str') && !isset($this->env['slim.tests.ignore_multibyte']) ) {
+ mb_parse_str($this->env['QUERY_STRING'], $output);
+ } else {
+ parse_str($this->env['QUERY_STRING'], $output);
+ }
+ $this->env['slim.request.query_hash'] = Slim_Http_Util::stripSlashesIfMagicQuotes($output);
+ }
+ if ( $key ) {
+ if ( isset($this->env['slim.request.query_hash'][$key]) ) {
+ return $this->env['slim.request.query_hash'][$key];
+ } else {
+ return null;
+ }
+ } else {
+ return $this->env['slim.request.query_hash'];
+ }
+ }
+
+ /**
+ * Fetch POST data
+ *
+ * This method returns a key-value array of data sent in the HTTP request body, or
+ * the value of a hash key if requested; if the array key does not exist, NULL is returned.
+ *
+ * @param string $key
+ * @return array|mixed|null
+ * @throws RuntimeException If environment input is not available
+ */
+ public function post( $key = null ) {
+ if ( !isset($this->env['slim.input']) ) {
+ throw new RuntimeException('Missing slim.input in environment variables');
+ }
+ if ( !isset($this->env['slim.request.form_hash']) ) {
+ $this->env['slim.request.form_hash'] = array();
+ if ( $this->isFormData() ) {
+ $output = array();
+ if ( function_exists('mb_parse_str') && !isset($this->env['slim.tests.ignore_multibyte']) ) {
+ mb_parse_str($this->env['slim.input'], $output);
+ } else {
+ parse_str($this->env['slim.input'], $output);
+ }
+ $this->env['slim.request.form_hash'] = Slim_Http_Util::stripSlashesIfMagicQuotes($output);
+ }
+ }
+ if ( $key ) {
+ if ( isset($this->env['slim.request.form_hash'][$key]) ) {
+ return $this->env['slim.request.form_hash'][$key];
+ } else {
+ return null;
+ }
+ } else {
+ return $this->env['slim.request.form_hash'];
+ }
+ }
+
+ /**
+ * Fetch PUT data (alias for Slim_Http_Request::post)
+ * @param string $key
+ * @return array|mixed|null
+ */
+ public function put( $key = null ) {
+ return $this->post($key);
+ }
+
+ /**
+ * Fetch COOKIE data
+ *
+ * This method returns a key-value array of Cookie data sent in the HTTP request, or
+ * the value of a array key if requested; if the array key does not exist, NULL is returned.
+ *
+ * @param string $key
+ * @return array|string|null
+ */
+ public function cookies( $key = null ) {
+ if ( !isset($this->env['slim.request.cookie_hash']) ) {
+ $cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : '';
+ $this->env['slim.request.cookie_hash'] = Slim_Http_Util::parseCookieHeader($cookieHeader);
+ }
+ if ( $key ) {
+ if ( isset($this->env['slim.request.cookie_hash'][$key]) ) {
+ return $this->env['slim.request.cookie_hash'][$key];
+ } else {
+ return null;
+ }
+ } else {
+ return $this->env['slim.request.cookie_hash'];
+ }
+ }
+
+ /**
+ * Does the Request body contain parseable form data?
+ * @return bool
+ */
+ public function isFormData() {
+ $method = isset($this->env['slim.method_override.original_method']) ? $this->env['slim.method_override.original_method'] : $this->getMethod();
+ return ( $method === self::METHOD_POST && is_null($this->getContentType()) ) || in_array($this->getMediaType(), self::$formDataMediaTypes);
+ }
+
+ /**
+ * Get Headers
+ *
+ * This method returns a key-value array of headers sent in the HTTP request, or
+ * the value of a hash key if requested; if the array key does not exist, NULL is returned.
+ *
+ * @param string $key
+ * @param mixed $default The default value returned if the requested header is not available
+ * @return mixed
+ */
+ public function headers( $key = null, $default = null ) {
+ if ( $key ) {
+ $key = strtoupper($key);
+ $key = str_replace('-', '_', $key);
+ $key = preg_replace('@^HTTP_@', '', $key);
+ if ( isset($this->env[$key]) ) {
+ return $this->env[$key];
+ } else {
+ return $default;
+ }
+ } else {
+ $headers = array();
+ foreach ( $this->env as $key => $value ) {
+ if ( strpos($key, 'slim.') !== 0 ) {
+ $headers[$key] = $value;
+ }
+ }
+ return $headers;
+ }
+ }
+
+ /**
+ * Get Body
+ * @return string
+ */
+ public function getBody() {
+ return $this->env['slim.input'];
+ }
+
+ /**
+ * Get Content Type
+ * @return string
+ */
+ public function getContentType() {
+ if ( isset($this->env['CONTENT_TYPE']) ) {
+ return $this->env['CONTENT_TYPE'];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get Media Type (type/subtype within Content Type header)
+ * @return string|null
+ */
+ public function getMediaType() {
+ $contentType = $this->getContentType();
+ if ( $contentType ) {
+ $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
+ return strtolower($contentTypeParts[0]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get Media Type Params
+ * @return array
+ */
+ public function getMediaTypeParams() {
+ $contentType = $this->getContentType();
+ $contentTypeParams = array();
+ if ( $contentType ) {
+ $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
+ $contentTypePartsLength = count($contentTypeParts);
+ for ( $i = 1; $i < $contentTypePartsLength; $i++ ) {
+ $paramParts = explode('=', $contentTypeParts[$i]);
+ $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
+ }
+ }
+ return $contentTypeParams;
+ }
+
+ /**
+ * Get Content Charset
+ * @return string|null
+ */
+ public function getContentCharset() {
+ $mediaTypeParams = $this->getMediaTypeParams();
+ if ( isset($mediaTypeParams['charset']) ) {
+ return $mediaTypeParams['charset'];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get Content-Length
+ * @return int
+ */
+ public function getContentLength() {
+ if ( isset($this->env['CONTENT_LENGTH']) ) {
+ return (int)$this->env['CONTENT_LENGTH'];
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Get Host
+ * @return string
+ */
+ public function getHost() {
+ if ( isset($this->env['HOST']) ) {
+ return $this->env['HOST'];
+ } else {
+ return $this->env['SERVER_NAME'];
+ }
+ }
+
+ /**
+ * Get Host with Port
+ * @return string
+ */
+ public function getHostWithPort() {
+ return sprintf('%s:%s', $this->getHost(), $this->getPort());
+ }
+
+ /**
+ * Get Port
+ * @return int
+ */
+ public function getPort() {
+ return (int)$this->env['SERVER_PORT'];
+ }
+
+ /**
+ * Get Scheme (https or http)
+ * @return string
+ */
+ public function getScheme() {
+ return $this->env['slim.url_scheme'];
+ }
+
+ /**
+ * Get Script Name (physical path)
+ * @return string
+ */
+ public function getScriptName() {
+ return $this->env['SCRIPT_NAME'];
+ }
+
+ /**
+ * LEGACY: Get Root URI (alias for Slim_Http_Request::getScriptName)
+ * @return string
+ */
+ public function getRootUri() {
+ return $this->getScriptName();
+ }
+
+ /**
+ * Get Path (physical path + virtual path)
+ * @return string
+ */
+ public function getPath() {
+ return $this->getScriptName() . $this->getPathInfo();
+ }
+
+ /**
+ * Get Path Info (virtual path)
+ * @return string
+ */
+ public function getPathInfo() {
+ return $this->env['PATH_INFO'];
+ }
+
+ /**
+ * LEGACY: Get Resource URI (alias for Slim_Http_Request::getPathInfo)
+ * @return string
+ */
+ public function getResourceUri() {
+ return $this->getPathInfo();
+ }
+
+ /**
+ * Get URL (scheme + host [ + port if non-standard ])
+ * @return string
+ */
+ public function getUrl() {
+ $url = $this->getScheme() . '://' . $this->getHost();
+ if ( ( $this->getScheme() === 'https' && $this->getPort() !== 443 ) || ( $this->getScheme() === 'http' && $this->getPort() !== 80 ) ) {
+ $url .= sprintf(':%s', $this->getPort());
+ }
+ return $url;
+ }
+
+ /**
+ * Get IP
+ * @return string
+ */
+ public function getIp() {
+ return $this->env['REMOTE_ADDR'];
+ }
+
+ /**
+ * Get Referrer
+ * @return string|null
+ */
+ public function getReferrer() {
+ if ( isset($this->env['REFERER']) ) {
+ return $this->env['REFERER'];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get Referer (for those who can't spell)
+ * @return string|null
+ */
+ public function getReferer() {
+ return $this->getReferrer();
+ }
+
+ /**
+ * Get User Agent
+ * @return string|null
+ */
+ public function getUserAgent() {
+ if ( isset($this->env['USER_AGENT']) ) {
+ return $this->env['USER_AGENT'];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/Slim/Http/Response.php b/Slim/Http/Response.php
new file mode 100644
index 0000000..aa62d61
--- /dev/null
+++ b/Slim/Http/Response.php
@@ -0,0 +1,424 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Response
+ *
+ * This is a simple abstraction over top an HTTP response. This
+ * provides methods to set the HTTP status, the HTTP headers,
+ * and the HTTP body.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Http_Response implements ArrayAccess, Countable, IteratorAggregate {
+ /**
+ * @var int HTTP status code
+ */
+ protected $status;
+
+ /**
+ * @var Slim_Http_Headers List of HTTP response headers
+ * @see Slim_Http_Headers
+ */
+ protected $header;
+
+ /**
+ * @var string HTTP response body
+ */
+ protected $body;
+
+ /**
+ * @var int Length of HTTP response body
+ */
+ protected $length;
+
+ /**
+ * @var array HTTP response codes and messages
+ */
+ protected static $messages = array(
+ //Informational 1xx
+ 100 => '100 Continue',
+ 101 => '101 Switching Protocols',
+ //Successful 2xx
+ 200 => '200 OK',
+ 201 => '201 Created',
+ 202 => '202 Accepted',
+ 203 => '203 Non-Authoritative Information',
+ 204 => '204 No Content',
+ 205 => '205 Reset Content',
+ 206 => '206 Partial Content',
+ //Redirection 3xx
+ 300 => '300 Multiple Choices',
+ 301 => '301 Moved Permanently',
+ 302 => '302 Found',
+ 303 => '303 See Other',
+ 304 => '304 Not Modified',
+ 305 => '305 Use Proxy',
+ 306 => '306 (Unused)',
+ 307 => '307 Temporary Redirect',
+ //Client Error 4xx
+ 400 => '400 Bad Request',
+ 401 => '401 Unauthorized',
+ 402 => '402 Payment Required',
+ 403 => '403 Forbidden',
+ 404 => '404 Not Found',
+ 405 => '405 Method Not Allowed',
+ 406 => '406 Not Acceptable',
+ 407 => '407 Proxy Authentication Required',
+ 408 => '408 Request Timeout',
+ 409 => '409 Conflict',
+ 410 => '410 Gone',
+ 411 => '411 Length Required',
+ 412 => '412 Precondition Failed',
+ 413 => '413 Request Entity Too Large',
+ 414 => '414 Request-URI Too Long',
+ 415 => '415 Unsupported Media Type',
+ 416 => '416 Requested Range Not Satisfiable',
+ 417 => '417 Expectation Failed',
+ 422 => '422 Unprocessable Entity',
+ 423 => '423 Locked',
+ //Server Error 5xx
+ 500 => '500 Internal Server Error',
+ 501 => '501 Not Implemented',
+ 502 => '502 Bad Gateway',
+ 503 => '503 Service Unavailable',
+ 504 => '504 Gateway Timeout',
+ 505 => '505 HTTP Version Not Supported'
+ );
+
+ /**
+ * Constructor
+ * @param string $body The HTTP response body
+ * @param int $status The HTTP response status
+ * @param Slim_Http_Headers|array $header The HTTP response headers
+ */
+ public function __construct( $body = '', $status = 200, $header = array() ) {
+ $this->status = (int)$status;
+ $headers = array();
+ foreach ( $header as $key => $value ) {
+ $headers[$key] = $value;
+ }
+ $this->header = new Slim_Http_Headers(array_merge(array('Content-Type' => 'text/html'), $headers));
+ $this->body = '';
+ $this->write($body);
+ }
+
+ /**
+ * Get and set status
+ * @param int|null $status
+ * @return int
+ */
+ public function status( $status = null ) {
+ if ( !is_null($status) ) {
+ $this->status = (int)$status;
+ }
+ return $this->status;
+ }
+
+ /**
+ * Get and set header
+ * @param string $name Header name
+ * @param string|null $value Header value
+ * @return string Header value
+ */
+ public function header( $name, $value = null ) {
+ if ( !is_null($value) ) {
+ $this[$name] = $value;
+ }
+ return $this[$name];
+ }
+
+ /**
+ * Get headers
+ * @return Slim_Http_Headers
+ */
+ public function headers() {
+ return $this->header;
+ }
+
+ /**
+ * Get and set body
+ * @param string|null $body Content of HTTP response body
+ * @return string
+ */
+ public function body( $body = null ) {
+ if ( !is_null($body) ) {
+ $this->write($body, true);
+ }
+ return $this->body;
+ }
+
+ /**
+ * Get and set length
+ * @param int|null $length
+ * @return int
+ */
+ public function length( $length = null ) {
+ if ( !is_null($length) ) {
+ $this->length = (int)$length;
+ }
+ return $this->length;
+ }
+
+ /**
+ * Append HTTP response body
+ * @param string $body Content to append to the current HTTP response body
+ * @param bool $replace Overwrite existing response body?
+ * @return string The updated HTTP response body
+ */
+ public function write( $body, $replace = false ) {
+ if ( $replace ) {
+ $this->body = $body;
+ } else {
+ $this->body .= (string)$body;
+ }
+ $this->length = strlen($this->body);
+ return $this->body;
+ }
+
+ /**
+ * Finalize
+ *
+ * This prepares this response and returns an array
+ * of [status, headers, body]. This array is passed to outer middleware
+ * if available or directly to the Slim run method.
+ *
+ * @return array[int status, array headers, string body]
+ */
+ public function finalize() {
+ if ( in_array($this->status, array(204, 304)) ) {
+ unset($this['Content-Type'], $this['Content-Length']);
+ return array($this->status, $this->header, '');
+ } else {
+ return array($this->status, $this->header, $this->body);
+ }
+ }
+
+ /**
+ * Set cookie
+ *
+ * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
+ * header on its own and delegates this responsibility to the `Slim_Http_Util` class. This
+ * response's header is passed by reference to the utility class and is directly modified. By not
+ * relying on PHP's native implementation, Slim allows middleware the opportunity to massage or
+ * analyze the raw header before the response is ultimately delivered to the HTTP client.
+ *
+ * @param string $name The name of the cookie
+ * @param string|array $value If string, the value of cookie; if array, properties for
+ * cookie including: value, expire, path, domain, secure, httponly
+ */
+ public function setCookie( $name, $value ) {
+ Slim_Http_Util::setCookieHeader($this->header, $name, $value);
+ }
+
+ /**
+ * Delete cookie
+ *
+ * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
+ * header on its own and delegates this responsibility to the `Slim_Http_Util` class. This
+ * response's header is passed by reference to the utility class and is directly modified. By not
+ * relying on PHP's native implementation, Slim allows middleware the opportunity to massage or
+ * analyze the raw header before the response is ultimately delivered to the HTTP client.
+ *
+ * This method will set a cookie with the given name that has an expiration time in the past; this will
+ * prompt the HTTP client to invalidate and remove the client-side cookie. Optionally, you may
+ * also pass a key/value array as the second argument. If the "domain" key is present in this
+ * array, only the Cookie with the given name AND domain will be removed. The invalidating cookie
+ * sent with this response will adopt all properties of the second argument.
+ *
+ * @param string $name The name of the cookie
+ * @param array $value Properties for cookie including: value, expire, path, domain, secure, httponly
+ */
+ public function deleteCookie( $name, $value = array() ) {
+ Slim_Http_Util::deleteCookieHeader($this->header, $name, $value);
+ }
+
+ /**
+ * Redirect
+ *
+ * This method prepares this response to return an HTTP Redirect response
+ * to the HTTP client.
+ *
+ * @param string $url The redirect destination
+ * @param int $status The redirect HTTP status code
+ */
+ public function redirect ( $url, $status = 302 ) {
+ $this->status = $status;
+ $this['Location'] = $url;
+ }
+
+ /**
+ * Helpers: Empty?
+ * @return bool
+ */
+ public function isEmpty() {
+ return in_array($this->status, array(201, 204, 304));
+ }
+
+ /**
+ * Helpers: Informational?
+ * @return bool
+ */
+ public function isInformational() {
+ return $this->status >= 100 && $this->status < 200;
+ }
+
+ /**
+ * Helpers: OK?
+ * @return bool
+ */
+ public function isOk() {
+ return $this->status === 200;
+ }
+
+ /**
+ * Helpers: Successful?
+ * @return bool
+ */
+ public function isSuccessful() {
+ return $this->status >= 200 && $this->status < 300;
+ }
+
+ /**
+ * Helpers: Redirect?
+ * @return bool
+ */
+ public function isRedirect() {
+ return in_array($this->status, array(301, 302, 303, 307));
+ }
+
+ /**
+ * Helpers: Redirection?
+ * @return bool
+ */
+ public function isRedirection() {
+ return $this->status >= 300 && $this->status < 400;
+ }
+
+ /**
+ * Helpers: Forbidden?
+ * @return bool
+ */
+ public function isForbidden() {
+ return $this->status === 403;
+ }
+
+ /**
+ * Helpers: Not Found?
+ * @return bool
+ */
+ public function isNotFound() {
+ return $this->status === 404;
+ }
+
+ /**
+ * Helpers: Client error?
+ * @return bool
+ */
+ public function isClientError() {
+ return $this->status >= 400 && $this->status < 500;
+ }
+
+ /**
+ * Helpers: Server Error?
+ * @return bool
+ */
+ public function isServerError() {
+ return $this->status >= 500 && $this->status < 600;
+ }
+
+ /**
+ * Array Access: Offset Exists
+ */
+ public function offsetExists( $offset ) {
+ return isset($this->header[$offset]);
+ }
+
+ /**
+ * Array Access: Offset Get
+ */
+ public function offsetGet( $offset ) {
+ if ( isset($this->header[$offset]) ) {
+ return $this->header[$offset];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Array Access: Offset Set
+ */
+ public function offsetSet( $offset, $value ) {
+ $this->header[$offset] = $value;
+ }
+
+ /**
+ * Array Access: Offset Unset
+ */
+ public function offsetUnset( $offset ) {
+ unset($this->header[$offset]);
+ }
+
+ /**
+ * Countable: Count
+ */
+ public function count() {
+ return count($this->header);
+ }
+
+ /**
+ * Get Iterator
+ *
+ * This returns the contained `Slim_Http_Headers` instance which
+ * is itself iterable.
+ *
+ * @return Slim_Http_Headers
+ */
+ public function getIterator() {
+ return $this->header;
+ }
+
+ /**
+ * Get message for HTTP status code
+ * @return string|null
+ */
+ public static function getMessageForCode( $status ) {
+ if ( isset(self::$messages[$status]) ) {
+ return self::$messages[$status];
+ } else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Slim/Http/Stream.php b/Slim/Http/Stream.php
new file mode 100644
index 0000000..0ef67fa
--- /dev/null
+++ b/Slim/Http/Stream.php
@@ -0,0 +1,110 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Slim HTTP Stream
+ *
+ * This class substitutes for `Slim_Http_Response` when
+ * `streamFile()`, `streamData()`, or `streamProcess()` is
+ * invoked. At which time, the Slim app's response property
+ * is set to an instance of `Slim_Http_Stream`.
+ *
+ * This class implements a `finalize()` and `write()` method
+ * just like `Slim_Http_Response` (polymorphism) so that the framework's
+ * expectations are still met.
+ *
+ * In the Slim app's `run()` method, if it encounters a response body
+ * that is not a string, the response body object's `process()` method
+ * is invoked; otherwise, Slim will `echo()` the string response body.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Http_Stream {
+ /**
+ * @var mixed This is a Slim_Stream_* instance that performs the actual streaming
+ */
+ protected $streamer;
+
+ /**
+ * @var array
+ */
+ protected $options;
+
+ /**
+ * Constructor
+ * @param mixed $streamer
+ * @param array $options
+ * @return void
+ */
+ public function __construct( $streamer, $options ) {
+ $this->streamer = $streamer;
+ $this->options = array_merge(array(
+ 'name' => 'foo',
+ 'type' => 'application/octet-stream',
+ 'disposition' => 'attachment', //or "inline"
+ 'encoding' => 'binary', //or "ascii"
+ 'allow_cache' => false
+ ), $options);
+ }
+
+ /**
+ * Finalize
+ * @return array[status, header, body]
+ */
+ public function finalize() {
+ $headers = new Slim_Http_Headers();
+ if ( $this->options['allow_cache'] ) {
+ $headers['Pragma'] = 'public'; //Used by HTTP/1.0 clients
+ $headers['Cache-Control'] = 'public';
+ } else {
+ $headers['Pragma'] = 'no-cache'; //Used by HTTP/1.0 clients
+ $headers['Cache-Control'] = 'no-cache';
+ }
+ $headers['Content-Type'] = $this->options['type'];
+ $headers['Content-Disposition'] = sprintf('%s; filename=%s', $this->options['disposition'], $this->options['name']);
+ $headers['Content-Transfer-Encoding'] = $this->options['encoding'];
+ return array(200, $headers, $this->streamer);
+ }
+
+ /**
+ * Write
+ *
+ * This is a dummy method to satisfy the framework's expectations. In the future
+ * some refactoring may allow this method to be removed. For now, carry on. Nothing
+ * to see here folks.
+ *
+ * @return void
+ */
+ public function write( $body ) {}
+}
diff --git a/Slim/Http/Util.php b/Slim/Http/Util.php
new file mode 100644
index 0000000..96093c7
--- /dev/null
+++ b/Slim/Http/Util.php
@@ -0,0 +1,364 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Slim HTTP Utilities
+ *
+ * This class provides useful methods for handling HTTP requests.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Http_Util {
+ /**
+ * Strip slashes from string or array
+ *
+ * This method strips slashes from its input. By default, this method will only
+ * strip slashes from its input if magic quotes are enabled. Otherwise, you may
+ * override the magic quotes setting with either TRUE or FALSE as the send argument
+ * to force this method to strip or not strip slashes from its input.
+ *
+ * @var array|string $rawData
+ * @return array|string
+ */
+ public static function stripSlashesIfMagicQuotes( $rawData, $overrideStripSlashes = null ) {
+ $strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes;
+ if ( $strip ) {
+ return self::_stripSlashes($rawData);
+ } else {
+ return $rawData;
+ }
+ }
+
+ /**
+ * Strip slashes from string or array
+ * @param array|string $rawData
+ * @return array|string
+ */
+ protected static function _stripSlashes( $rawData ) {
+ return is_array($rawData) ? array_map(array('self', '_stripSlashes'), $rawData) : stripslashes($rawData);
+ }
+
+ /**
+ * Encrypt data
+ *
+ * This method will encrypt data using a given key, vector, and cipher.
+ * By default, this will encrypt data using the RIJNDAEL/AES 256 bit cipher. You
+ * may override the default cipher and cipher mode by passing your own desired
+ * cipher and cipher mode as the final key-value array argument.
+ *
+ * @param string $data The unencrypted data
+ * @param string $key The encryption key
+ * @param string $iv The encryption initialization vector
+ * @param array $settings Optional key-value array with custom algorithm and mode
+ * @return string
+ */
+ public static function encrypt( $data, $key, $iv, $settings = array() ) {
+ if ( $data === '' || !extension_loaded('mcrypt') ) {
+ return $data;
+ }
+
+ //Merge settings with defaults
+ $settings = array_merge(array(
+ 'algorithm' => MCRYPT_RIJNDAEL_256,
+ 'mode' => MCRYPT_MODE_CBC
+ ), $settings);
+
+ //Get module
+ $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
+
+ //Validate IV
+ $ivSize = mcrypt_enc_get_iv_size($module);
+ if ( strlen($iv) > $ivSize ) {
+ $iv = substr($iv, 0, $ivSize);
+ }
+
+ //Validate key
+ $keySize = mcrypt_enc_get_key_size($module);
+ if ( strlen($key) > $keySize ) {
+ $key = substr($key, 0, $keySize);
+ }
+
+ //Encrypt value
+ mcrypt_generic_init($module, $key, $iv);
+ $res = @mcrypt_generic($module, $data);
+ mcrypt_generic_deinit($module);
+
+ return $res;
+ }
+
+ /**
+ * Decrypt data
+ *
+ * This method will decrypt data using a given key, vector, and cipher.
+ * By default, this will decrypt data using the RIJNDAEL/AES 256 bit cipher. You
+ * may override the default cipher and cipher mode by passing your own desired
+ * cipher and cipher mode as the final key-value array argument.
+ *
+ * @param string $data The encrypted data
+ * @param string $key The encryption key
+ * @param string $iv The encryption initialization vector
+ * @param array $settings Optional key-value array with custom algorithm and mode
+ * @return string
+ */
+ public static function decrypt( $data, $key, $iv, $settings = array() ) {
+ if ( $data === '' || !extension_loaded('mcrypt') ) {
+ return $data;
+ }
+
+ //Merge settings with defaults
+ $settings = array_merge(array(
+ 'algorithm' => MCRYPT_RIJNDAEL_256,
+ 'mode' => MCRYPT_MODE_CBC
+ ), $settings);
+
+ //Get module
+ $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
+
+ //Validate IV
+ $ivSize = mcrypt_enc_get_iv_size($module);
+ if ( strlen($iv) > $ivSize ) {
+ $iv = substr($iv, 0, $ivSize);
+ }
+
+ //Validate key
+ $keySize = mcrypt_enc_get_key_size($module);
+ if ( strlen($key) > $keySize ) {
+ $key = substr($key, 0, $keySize);
+ }
+
+ //Decrypt value
+ mcrypt_generic_init($module, $key, $iv);
+ $decryptedData = @mdecrypt_generic($module, $data);
+ $res = str_replace("\x0", '', $decryptedData);
+ mcrypt_generic_deinit($module);
+
+ return $res;
+ }
+
+ /**
+ * Encode secure cookie value
+ *
+ * This method will create the secure value of an HTTP cookie. The
+ * cookie value is encrypted and hashed so that its value is
+ * secure and checked for integrity when read in subsequent requests.
+ *
+ * @param string $value The unsecure HTTP cookie value
+ * @param int $expires The UNIX timestamp at which this cookie will expire
+ * @param string $secret The secret key used to hash the cookie value
+ * @param int $algorithm The algorithm to use for encryption
+ * @param int $mode The algorithm mode to use for encryption
+ * @param string
+ */
+ public static function encodeSecureCookie( $value, $expires, $secret, $algorithm, $mode ) {
+ $key = hash_hmac('sha1', $expires, $secret);
+ $iv = md5($expires);
+ $secureString = base64_encode(self::encrypt($value, $key, $iv, array(
+ 'algorithm' => $algorithm,
+ 'mode' => $mode
+ )));
+ $verificationString = hash_hmac('sha1', $expires . $value, $key);
+ return implode('|', array($expires, $secureString, $verificationString));
+ }
+
+ /**
+ * Decode secure cookie value
+ *
+ * This method will decode the secure value of an HTTP cookie. The
+ * cookie value is encrypted and hashed so that its value is
+ * secure and checked for integrity when read in subsequent requests.
+ *
+ * @param string $value The secure HTTP cookie value
+ * @param int $expires The UNIX timestamp at which this cookie will expire
+ * @param string $secret The secret key used to hash the cookie value
+ * @param int $algorithm The algorithm to use for encryption
+ * @param int $mode The algorithm mode to use for encryption
+ * @param string
+ */
+ public static function decodeSecureCookie( $value, $secret, $algorithm, $mode ) {
+ if ( $value ) {
+ $value = explode('|', $value);
+ if ( count($value) === 3 && ( (int)$value[0] === 0 || (int)$value[0] > time() ) ) {
+ $key = hash_hmac('sha1', $value[0], $secret);
+ $iv = md5($value[0]);
+ $data = self::decrypt(base64_decode($value[1]), $key, $iv, array(
+ 'algorithm' => $algorithm,
+ 'mode' => $mode
+ ));
+ $verificationString = hash_hmac('sha1', $value[0] . $data, $key);
+ if ( $verificationString === $value[2] ) {
+ return $data;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Set HTTP cookie header
+ *
+ * This method will construct and set the HTTP `Set-Cookie` header. Slim
+ * uses this method instead of PHP's native `setcookie` method. This allows
+ * more control of the HTTP header irrespective of the native implementation's
+ * dependency on PHP versions.
+ *
+ * This method accepts the Slim_Http_Headers object by reference as its
+ * first argument; this method directly modifies this object instead of
+ * returning a value.
+ *
+ * @param array $header
+ * @param string $name
+ * @param string $value
+ * @return void
+ */
+ public static function setCookieHeader( &$header, $name, $value ) {
+ //Build cookie header
+ if ( is_array($value) ) {
+ $domain = '';
+ $path = '';
+ $expires = '';
+ $secure = '';
+ $httponly = '';
+ if ( isset($value['domain']) && $value['domain'] ) {
+ $domain = '; domain=' . $value['domain'];
+ }
+ if ( isset($value['path']) && $value['path'] ) {
+ $path = '; path=' . $value['path'];
+ }
+ if ( isset($value['expires']) ) {
+ if ( is_string($value['expires']) ) {
+ $timestamp = strtotime($value['expires']);
+ } else {
+ $timestamp = (int)$value['expires'];
+ }
+ if ( $timestamp !== 0 ) {
+ $expires = '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp);
+ }
+ }
+ if ( isset($value['secure']) && $value['secure'] ) {
+ $secure = '; secure';
+ }
+ if ( isset($value['httponly']) && $value['httponly'] ) {
+ $httponly = '; HttpOnly';
+ }
+ $cookie = sprintf('%s=%s%s', urlencode($name), urlencode((string)$value['value']), $domain . $path . $expires . $secure . $httponly);
+ } else {
+ $cookie = sprintf('%s=%s', urlencode($name), urlencode((string)$value));
+ }
+
+ //Set cookie header
+ if ( !isset($header['Set-Cookie']) || $header['Set-Cookie'] === '' ) {
+ $header['Set-Cookie'] = $cookie;
+ } else {
+ $header['Set-Cookie'] = implode("\n", array($header['Set-Cookie'], $cookie));
+ }
+ }
+
+ /**
+ * Delete HTTP cookie header
+ *
+ * This method will construct and set the HTTP `Set-Cookie` header to invalidate
+ * a client-side HTTP cookie. If a cookie with the same name (and, optionally, domain)
+ * is already set in the HTTP response, it will also be removed. Slim uses this method
+ * instead of PHP's native `setcookie` method. This allows more control of the HTTP header
+ * irrespective of PHP's native implementation's dependency on PHP versions.
+ *
+ * This method accepts the Slim_Http_Headers object by reference as its
+ * first argument; this method directly modifies this object instead of
+ * returning a value.
+ *
+ * @param array $header
+ * @param string $name
+ * @param string $value
+ * @return void
+ */
+ public static function deleteCookieHeader( &$header, $name, $value = array() ) {
+ //Remove affected cookies from current response header
+ $cookiesOld = array();
+ $cookiesNew = array();
+ if ( isset($header['Set-Cookie']) ) {
+ $cookiesOld = explode("\n", $header['Set-Cookie']);
+ }
+ foreach ( $cookiesOld as $c ) {
+ if ( isset($value['domain']) && $value['domain'] ) {
+ $regex = sprintf('@%s=.*domain=%s@', urlencode($name), preg_quote($value['domain']));
+ } else {
+ $regex = sprintf('@%s=@', urlencode($name));
+ }
+ if ( preg_match($regex, $c) === 0 ) {
+ $cookiesNew[] = $c;
+ }
+ }
+ if ( $cookiesNew ) {
+ $header['Set-Cookie'] = implode("\n", $cookiesNew);
+ } else {
+ unset($header['Set-Cookie']);
+ }
+
+ //Set invalidating cookie to clear client-side cookie
+ self::setCookieHeader($header, $name, array_merge(array('value' => '', 'path' => null, 'domain' => null, 'expires' => time() - 100), $value));
+ }
+
+ /**
+ * Parse cookie header
+ *
+ * This method will parse the HTTP requst's `Cookie` header
+ * and extract cookies into an associative array.
+ *
+ * @param string
+ * @return array
+ */
+ public static function parseCookieHeader( $header ) {
+ $cookies = array();
+ $header = rtrim($header, "\r\n");
+ $headerPieces = preg_split('@\s*;\s*@', $header);
+ foreach ( $headerPieces as $c ) {
+ $cParts = explode('=', $c);
+ if ( count($cParts) === 2 ) {
+ $key = urldecode($cParts[0]);
+ $value = urldecode($cParts[1]);
+ if ( isset($cookies[$key]) ) {
+ if ( is_array($cookies[$key]) ) {
+ $cookies[$key][] = $value;
+ } else {
+ $cookies[$key] = array($cookies[$key], $value);
+ }
+ } else {
+ $cookies[$key] = $value;
+ }
+ }
+ }
+ return $cookies;
+ }
+}
\ No newline at end of file
diff --git a/Slim/Log.php b/Slim/Log.php
new file mode 100644
index 0000000..e647878
--- /dev/null
+++ b/Slim/Log.php
@@ -0,0 +1,219 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Log
+ *
+ * This is the primary logger for a Slim application. You may provide
+ * a Log Writer in conjunction with this Log to write to various output
+ * destinations (e.g. a file). This class provides this interface:
+ *
+ * debug( mixed $object )
+ * info( mixed $object )
+ * warn( mixed $object )
+ * error( mixed $object )
+ * fatal( mixed $object )
+ *
+ * This class assumes only that your Log Writer has a public `write()` method
+ * that accepts any object as its one and only argument. The Log Writer
+ * class may write or send its argument anywhere: a file, STDERR,
+ * a remote web API, etc. The possibilities are endless.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Log {
+ /**
+ * @var array
+ */
+ static protected $levels = array(
+ 0 => 'FATAL',
+ 1 => 'ERROR',
+ 2 => 'WARN',
+ 3 => 'INFO',
+ 4 => 'DEBUG'
+ );
+
+ /**
+ * @var mixed
+ */
+ protected $writer;
+
+ /**
+ * @var bool
+ */
+ protected $enabled;
+
+ /**
+ * @var int
+ */
+ protected $level;
+
+ /**
+ * Constructor
+ * @param mixed $writer
+ * @return void
+ */
+ public function __construct( $writer ) {
+ $this->writer = $writer;
+ $this->enabled = true;
+ $this->level = 4;
+ }
+
+ /**
+ * Is logging enabled?
+ * @return bool
+ */
+ public function getEnabled() {
+ return $this->enabled;
+ }
+
+ /**
+ * Enable or disable logging
+ * @param bool $enabled
+ * @return void
+ */
+ public function setEnabled( $enabled ) {
+ if ( $enabled ) {
+ $this->enabled = true;
+ } else {
+ $this->enabled = false;
+ }
+ }
+
+ /**
+ * Set level
+ * @param int $level
+ * @return void
+ * @throws InvalidArgumentException
+ */
+ public function setLevel( $level ) {
+ if ( !isset(self::$levels[$level]) ) {
+ throw new InvalidArgumentException('Invalid log level');
+ }
+ $this->level = $level;
+ }
+
+ /**
+ * Get level
+ * @return int
+ */
+ public function getLevel() {
+ return $this->level;
+ }
+
+ /**
+ * Set writer
+ * @param mixed $writer
+ * @return void
+ */
+ public function setWriter( $writer ) {
+ $this->writer = $writer;
+ }
+
+ /**
+ * Get writer
+ * @return mixed
+ */
+ public function getWriter() {
+ return $this->writer;
+ }
+
+ /**
+ * Is logging enabled?
+ * @return bool
+ */
+ public function isEnabled() {
+ return $this->enabled;
+ }
+
+ /**
+ * Log debug message
+ * @param mixed $object
+ * @return mixed|false What the Logger returns, or false if Logger not set or not enabled
+ */
+ public function debug( $object ) {
+ return $this->log($object, 4);
+ }
+
+ /**
+ * Log info message
+ * @param mixed $object
+ * @return mixed|false What the Logger returns, or false if Logger not set or not enabled
+ */
+ public function info( $object ) {
+ return $this->log($object, 3);
+ }
+
+ /**
+ * Log warn message
+ * @param mixed $object
+ * @return mixed|false What the Logger returns, or false if Logger not set or not enabled
+ */
+ public function warn( $object ) {
+ return $this->log($object, 2);
+ }
+
+ /**
+ * Log error message
+ * @param mixed $object
+ * @return mixed|false What the Logger returns, or false if Logger not set or not enabled
+ */
+ public function error( $object ) {
+ return $this->log($object, 1);
+ }
+
+ /**
+ * Log fatal message
+ * @param mixed $object
+ * @return mixed|false What the Logger returns, or false if Logger not set or not enabled
+ */
+ public function fatal( $object ) {
+ return $this->log($object, 0);
+ }
+
+ /**
+ * Log message
+ * @param mixed The object to log
+ * @param int The message level
+ * @return int|false
+ */
+ protected function log( $object, $level ) {
+ if ( $this->enabled && $this->writer && $level <= $this->level ) {
+ return $this->writer->write($object);
+ } else {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Slim/LogFileWriter.php b/Slim/LogFileWriter.php
new file mode 100644
index 0000000..6526122
--- /dev/null
+++ b/Slim/LogFileWriter.php
@@ -0,0 +1,71 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Log File Writer
+ *
+ * This class is used by Slim_Log to write log messages to a valid, writable
+ * resource handle (e.g. a file or STDERR).
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_LogFileWriter {
+ /**
+ * @var resource
+ */
+ protected $resource;
+
+ /**
+ * Constructor
+ * @param resource $resource
+ * @return void
+ * @throws InvalidArgumentException
+ */
+ public function __construct( $resource ) {
+ if ( !is_resource($resource) ) {
+ throw new InvalidArgumentException('Cannot create LogFileWriter. Invalid resource handle.');
+ }
+ $this->resource = $resource;
+ }
+
+ /**
+ * Write message
+ * @param mixed $message
+ * @return int|false
+ */
+ public function write( $message ) {
+ return fwrite($this->resource, (string)$message . PHP_EOL);
+ }
+}
\ No newline at end of file
diff --git a/Slim/Middleware.php b/Slim/Middleware.php
new file mode 100644
index 0000000..b5c4b1d
--- /dev/null
+++ b/Slim/Middleware.php
@@ -0,0 +1,112 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Middleware
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+abstract class Slim_Middleware {
+ /**
+ * @var Slim Reference to the primary Slim application instance
+ */
+ protected $app;
+
+ /**
+ * @var mixed Reference to the next downstream middleware
+ */
+ protected $next;
+
+ /**
+ * Set application
+ *
+ * This method injects the primary Slim application instance into
+ * this middleware.
+ *
+ * @param Slim $application
+ * @return void
+ */
+ final public function setApplication( $application ) {
+ $this->app = $application;
+ }
+
+ /**
+ * Get application
+ *
+ * This method retrieves the application previously injected
+ * into this middleware.
+ *
+ * @return Slim
+ */
+ final public function getApplication() {
+ return $this->app;
+ }
+
+ /**
+ * Set next middleware
+ *
+ * This method injects the next downstream middleware into
+ * this middleware so that it may optionally be called
+ * when appropriate.
+ *
+ * @param Slim|Slim_Middleware
+ * @return void
+ */
+ final public function setNextMiddleware( $nextMiddleware ) {
+ $this->next = $nextMiddleware;
+ }
+
+ /**
+ * Get next middleware
+ *
+ * This method retrieves the next downstream middleware
+ * previously injected into this middleware.
+ *
+ * @return Slim|Slim_Middleware
+ */
+ final public function getNextMiddleware() {
+ return $this->next;
+ }
+
+ /**
+ * Call
+ *
+ * Perform actions specific to this middleware and optionally
+ * call the next downstream middleware.
+ *
+ * @return void
+ */
+ abstract public function call();
+}
\ No newline at end of file
diff --git a/Slim/Middleware/ContentTypes.php b/Slim/Middleware/ContentTypes.php
new file mode 100644
index 0000000..377cb7b
--- /dev/null
+++ b/Slim/Middleware/ContentTypes.php
@@ -0,0 +1,157 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+ /**
+ * Content Types
+ *
+ * This is middleware for a Slim application that intercepts
+ * the HTTP request body and parses it into the appropriate
+ * PHP data structure if possible; else it returns the HTTP
+ * request body unchanged. This is particularly useful
+ * for preparing the HTTP request body for an XML or JSON API.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Middleware_ContentTypes extends Slim_Middleware {
+ /**
+ * @var array
+ */
+ protected $contentTypes;
+
+ /**
+ * Constructor
+ * @param array $settings
+ */
+ public function __construct( $settings = array() ) {
+ $this->contentTypes = array_merge(array(
+ 'application/json' => array($this, 'parseJson'),
+ 'application/xml' => array($this, 'parseXml'),
+ 'text/xml' => array($this, 'parseXml'),
+ 'text/csv' => array($this, 'parseCsv')
+ ), $settings);
+ }
+
+ /**
+ * Call
+ * @return void
+ */
+ public function call() {
+ $env = $this->app->environment();
+ if ( isset($env['CONTENT_TYPE']) ) {
+ $env['slim.input_original'] = $env['slim.input'];
+ $env['slim.input'] = $this->parse($env['slim.input'], $env['CONTENT_TYPE']);
+ }
+ $this->next->call();
+ }
+
+ /**
+ * Parse input
+ *
+ * This method will attempt to parse the request body
+ * based on its content type if available.
+ *
+ * @param string $input
+ * @param string $contentType
+ * @return mixed
+ */
+ protected function parse ( $input, $contentType ) {
+ if ( isset($this->contentTypes[$contentType]) && is_callable($this->contentTypes[$contentType]) ) {
+ $result = call_user_func($this->contentTypes[$contentType], $input);
+ if ( $result ) {
+ return $result;
+ }
+ }
+ return $input;
+ }
+
+ /**
+ * Parse JSON
+ *
+ * This method converts the raw JSON input
+ * into an associative array.
+ *
+ * @param string $input
+ * @return array|string
+ */
+ protected function parseJson( $input ) {
+ if ( function_exists('json_decode') ) {
+ $result = json_decode($input, true);
+ if ( $result ) {
+ return $result;
+ }
+ }
+ }
+
+ /**
+ * Parse XML
+ *
+ * This method creates a SimpleXMLElement
+ * based upon the XML input. If the SimpleXML
+ * extension is not available, the raw input
+ * will be returned unchanged.
+ *
+ * @param string $input
+ * @return SimpleXMLElement|string
+ */
+ protected function parseXml( $input ) {
+ if ( class_exists('SimpleXMLElement') ) {
+ try {
+ return new SimpleXMLElement($input);
+ } catch ( Exception $e ) {}
+ }
+ return $input;
+ }
+
+ /**
+ * Parse CSV
+ *
+ * This method parses CSV content into a numeric array
+ * containing an array of data for each CSV line.
+ *
+ * @param string $input
+ * @return array
+ */
+ protected function parseCsv( $input ) {
+ $temp = fopen('php://memory', 'rw');
+ fwrite($temp, $input);
+ fseek($temp, 0);
+ $res = array();
+ while ( ($data = fgetcsv($temp)) !== false ) {
+ $res[] = $data;
+ }
+ fclose($temp);
+ return $res;
+ }
+}
\ No newline at end of file
diff --git a/Slim/Middleware/Flash.php b/Slim/Middleware/Flash.php
new file mode 100644
index 0000000..8f8ecf2
--- /dev/null
+++ b/Slim/Middleware/Flash.php
@@ -0,0 +1,170 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+ /**
+ * Flash
+ *
+ * This is middleware for a Slim application that enables
+ * Flash messaging between HTTP requests. This allows you
+ * set Flash messages for the current request, for the next request,
+ * or to retain messages from the previous request through to
+ * the next request.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Middleware_Flash extends Slim_Middleware implements ArrayAccess {
+ /**
+ * @var array
+ */
+ protected $settings;
+
+ /**
+ * @var array
+ */
+ protected $messages;
+
+ /**
+ * Constructor
+ * @param Slim $app
+ * @param array $settings
+ * @return void
+ */
+ public function __construct( $settings = array() ) {
+ $this->settings = array_merge(array('key' => 'slim.flash'), $settings);
+ $this->messages = array(
+ 'prev' => isset($_SESSION[$this->settings['key']]) ? $_SESSION[$this->settings['key']] : array(), //flash messages from prev request
+ 'next' => array(), //flash messages for next request
+ 'now' => array() //flash messages for current request
+ );
+ }
+
+ /**
+ * Call
+ * @return void
+ */
+ public function call() {
+ $env = $this->app->environment();
+ $env['slim.flash'] = $this;
+ $this->next->call();
+ $this->save();
+ }
+
+ /**
+ * Now
+ *
+ * Specify a flash message for a given key to be shown for the current request
+ *
+ * @param string $key
+ * @param string $value
+ * @return void
+ */
+ public function now( $key, $value ) {
+ $this->messages['now'][(string)$key] = $value;
+ }
+
+ /**
+ * Set
+ *
+ * Specify a flash message for a given key to be shown for the next request
+ *
+ * @param string $key
+ * @param string $value
+ * @return void
+ */
+ public function set( $key, $value ) {
+ $this->messages['next'][(string)$key] = $value;
+ }
+
+ /**
+ * Keep
+ *
+ * Retain flash messages from the previous request for the next request
+ *
+ * @return void
+ */
+ public function keep() {
+ foreach ( $this->messages['prev'] as $key => $val ) {
+ $this->messages['next'][$key] = $val;
+ }
+ }
+
+ /**
+ * Save
+ */
+ public function save() {
+ $_SESSION[$this->settings['key']] = $this->messages['next'];
+ }
+
+ /**
+ * Get messages
+ *
+ * Return array of flash messages to be shown for the current request
+ *
+ * @return array
+ */
+ public function getMessages() {
+ return array_merge($this->messages['prev'], $this->messages['now']);
+ }
+
+ /**
+ * Array Access: Offset Exists
+ */
+ public function offsetExists( $offset ) {
+ $messages = $this->getMessages();
+ return isset($messages[$offset]);
+ }
+
+ /**
+ * Array Access: Offset Get
+ */
+ public function offsetGet( $offset ) {
+ $messages = $this->getMessages();
+ return isset($messages[$offset]) ? $messages[$offset] : null;
+ }
+
+ /**
+ * Array Access: Offset Set
+ */
+ public function offsetSet( $offset, $value ) {
+ $this->now($offset, $value);
+ }
+
+ /**
+ * Array Access: Offset Unset
+ */
+ public function offsetUnset( $offset ) {
+ unset($this->messages['prev'][$offset], $this->messages['now'][$offset]);
+ }
+}
\ No newline at end of file
diff --git a/Slim/Middleware/MethodOverride.php b/Slim/Middleware/MethodOverride.php
new file mode 100644
index 0000000..bb1dbcf
--- /dev/null
+++ b/Slim/Middleware/MethodOverride.php
@@ -0,0 +1,93 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+ /**
+ * HTTP Method Override
+ *
+ * This is middleware for a Slim application that allows traditional
+ * desktop browsers to submit psuedo PUT and DELETE requests by relying
+ * on a pre-determined request parameter. Without this middleware,
+ * desktop browsers are only able to submit GET and POST requests.
+ *
+ * This middleware is included automatically!
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Middleware_MethodOverride extends Slim_Middleware {
+ /**
+ * @var array
+ */
+ protected $settings;
+
+ /**
+ * Constructor
+ * @param Slim $app
+ * @param array $settings
+ * @return void
+ */
+ public function __construct( $settings = array() ) {
+ $this->settings = array_merge(array('key' => '_METHOD'), $settings);
+ }
+
+ /**
+ * Call
+ *
+ * Implements Slim middleware interface. This method is invoked and passed
+ * an array of environemnt variables. This middleware inspects the environment
+ * variables for the HTTP method override parameter; if found, this middleware
+ * modifies the environment settings so downstream middleware and/or the Slim
+ * application will treat the request with the desired HTTP method.
+ *
+ * @param array $env
+ * @return array[status, header, body]
+ */
+ public function call() {
+ $env = $this->app->environment();
+ if ( isset($env['X_HTTP_METHOD_OVERRIDE']) ) {
+ // Header commonly used by Backbone.js and others
+ $env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
+ $env['REQUEST_METHOD'] = strtoupper($env['X_HTTP_METHOD_OVERRIDE']);
+ } else if ( isset($env['REQUEST_METHOD']) && $env['REQUEST_METHOD'] === 'POST' ) {
+ // HTML Form Override
+ $req = new Slim_Http_Request($env);
+ $method = $req->post($this->settings['key']);
+ if ( $method ) {
+ $env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
+ $env['REQUEST_METHOD'] = strtoupper($method);
+ }
+ }
+ $this->next->call();
+ }
+}
\ No newline at end of file
diff --git a/Slim/Middleware/PrettyExceptions.php b/Slim/Middleware/PrettyExceptions.php
new file mode 100644
index 0000000..2afa2da
--- /dev/null
+++ b/Slim/Middleware/PrettyExceptions.php
@@ -0,0 +1,108 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Pretty Exceptions
+ *
+ * This middleware catches any Exception thrown by the surrounded
+ * application and displays a developer-friendly diagnostic screen.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Middleware_PrettyExceptions extends Slim_Middleware {
+ /**
+ * @var array
+ */
+ protected $settings;
+
+ /**
+ * Constructor
+ * @param Slim|middleware $app
+ * @param array $settings
+ */
+ public function __construct( $settings = array() ) {
+ $this->settings = $settings;
+ }
+
+ /**
+ * Call
+ * @return void
+ */
+ public function call() {
+ try {
+ $this->next->call();
+ } catch ( Exception $e ) {
+ $env = $this->app->environment();
+ $env['slim.log']->error($e);
+ $this->app->response()->status(500);
+ $this->app->response()->body($this->renderBody($env, $e));
+ }
+ }
+
+ /**
+ * Render response body
+ * @param array $env
+ * @param Exception $exception
+ * @return string
+ */
+ protected function renderBody( &$env, $exception ) {
+ $title = 'Slim Application Error';
+ $code = $exception->getCode();
+ $message = $exception->getMessage();
+ $file = $exception->getFile();
+ $line = $exception->getLine();
+ $trace = $exception->getTraceAsString();
+ $html = sprintf('
%s
', $title);
+ $html .= '
The application could not run because of the following error:
', $trace);
+ }
+ return sprintf("%s%s", $title, $html);
+ }
+}
\ No newline at end of file
diff --git a/Slim/Middleware/SessionCookie.php b/Slim/Middleware/SessionCookie.php
new file mode 100644
index 0000000..54afb18
--- /dev/null
+++ b/Slim/Middleware/SessionCookie.php
@@ -0,0 +1,143 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Session Cookie
+ *
+ * This class provides an HTTP cookie storage mechanism
+ * for session data. This class avoids using a PHP session
+ * and instead serializes/unserializes the $_SESSION global
+ * variable to/from an HTTP cookie.
+ *
+ * If a secret key is provided with this middleware, the HTTP
+ * cookie will be checked for integrity to ensure the client-side
+ * cookie is not changed.
+ *
+ * You should NEVER store sensitive data in a client-side cookie
+ * in any format, encrypted or not. If you need to store sensitive
+ * user information in a session, you should rely on PHP's native
+ * session implementation, or use other middleware to store
+ * session data in a database or alternative server-side cache.
+ *
+ * Because this class stores serialized session data in an HTTP cookie,
+ * you are inherently limtied to 4 Kb. If you attempt to store
+ * more than this amount, serialization will fail.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.6.0
+ */
+class Slim_Middleware_SessionCookie extends Slim_Middleware {
+ /**
+ * @var array
+ */
+ protected $settings;
+
+ /**
+ * Constructor
+ *
+ * @param array $settings
+ * @return void
+ */
+ public function __construct( $settings = array() ) {
+ $this->settings = array_merge(array(
+ 'expires' => '20 minutes',
+ 'path' => '/',
+ 'domain' => null,
+ 'secure' => false,
+ 'httponly' => false,
+ 'name' => 'slim_session',
+ 'secret' => 'CHANGE_ME',
+ 'cipher' => MCRYPT_RIJNDAEL_256,
+ 'cipher_mode' => MCRYPT_MODE_CBC
+ ), $settings);
+ if ( is_string($this->settings['expires']) ) {
+ $this->settings['expires'] = strtotime($this->settings['expires']);
+ }
+ }
+
+ /**
+ * Call
+ * @return void
+ */
+ public function call() {
+ $this->loadSession();
+ $this->next->call();
+ $this->saveSession();
+ }
+
+ /**
+ * Load session
+ * @param array $env
+ * @return void
+ */
+ protected function loadSession() {
+ $value = Slim_Http_Util::decodeSecureCookie(
+ $this->app->request()->cookies($this->settings['name']),
+ $this->settings['secret'],
+ $this->settings['cipher'],
+ $this->settings['cipher_mode']
+ );
+ if ( $value ) {
+ $_SESSION = unserialize($value);
+ } else {
+ $_SESSION = array();
+ }
+ }
+
+ /**
+ * Save session
+ * @return void
+ */
+ protected function saveSession() {
+ $value = Slim_Http_Util::encodeSecureCookie(
+ serialize($_SESSION),
+ $this->settings['expires'],
+ $this->settings['secret'],
+ $this->settings['cipher'],
+ $this->settings['cipher_mode']
+ );
+ if ( strlen($value) > 4096 ) {
+ $this->app->getLog()->error('WARNING! Slim_Middleware_SessionCookie data size is larger than 4KB. Content save failed.');
+ } else {
+ $this->app->response()->setCookie($this->settings['name'], array(
+ 'value' => $value,
+ 'domain' => $this->settings['domain'],
+ 'path' => $this->settings['path'],
+ 'expires' => $this->settings['expires'],
+ 'secure' => $this->settings['secure'],
+ 'httponly' => $this->settings['httponly']
+ ));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Slim/Route.php b/Slim/Route.php
new file mode 100644
index 0000000..c6816ad
--- /dev/null
+++ b/Slim/Route.php
@@ -0,0 +1,400 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Route
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Route {
+ /**
+ * @var string The route pattern (ie. "/books/:id")
+ */
+ protected $pattern;
+
+ /**
+ * @var mixed The callable associated with this route
+ */
+ protected $callable;
+
+ /**
+ * @var array Conditions for this route's URL parameters
+ */
+ protected $conditions = array();
+
+ /**
+ * @var array Default conditions applied to all Route instances
+ */
+ protected static $defaultConditions = array();
+
+ /**
+ * @var string The name of this route (optional)
+ */
+ protected $name;
+
+ /**
+ * @var array Key-value array of URL parameters
+ */
+ protected $params = array();
+
+ /**
+ * @var array HTTP methods supported by this Route
+ */
+ protected $methods = array();
+
+ /**
+ * @var Slim_Router The Router to which this Route belongs
+ */
+ protected $router;
+
+ /**
+ * @var array[Callable] Middleware
+ */
+ protected $middleware = array();
+
+ /**
+ * Constructor
+ * @param string $pattern The URL pattern (ie. "/books/:id")
+ * @param mixed $callable Anything that returns TRUE for is_callable()
+ */
+ public function __construct( $pattern, $callable ) {
+ $this->setPattern($pattern);
+ $this->setCallable($callable);
+ $this->setConditions(self::getDefaultConditions());
+ }
+
+ /**
+ * Set default route conditions for all instances
+ * @param array $defaultConditions
+ * @return void
+ */
+ public static function setDefaultConditions( array $defaultConditions ) {
+ self::$defaultConditions = $defaultConditions;
+ }
+
+ /**
+ * Get default route conditions for all instances
+ * @return array
+ */
+ public static function getDefaultConditions() {
+ return self::$defaultConditions;
+ }
+
+ /**
+ * Get route pattern
+ * @return string
+ */
+ public function getPattern() {
+ return $this->pattern;
+ }
+
+ /**
+ * Set route pattern
+ * @param string $pattern
+ * @return void
+ */
+ public function setPattern( $pattern ) {
+ $this->pattern = str_replace(')', ')?', (string)$pattern);
+ }
+
+ /**
+ * Get route callable
+ * @return mixed
+ */
+ public function getCallable() {
+ return $this->callable;
+ }
+
+ /**
+ * Set route callable
+ * @param mixed $callable
+ * @return void
+ */
+ public function setCallable($callable) {
+ $this->callable = $callable;
+ }
+
+ /**
+ * Get route conditions
+ * @return array
+ */
+ public function getConditions() {
+ return $this->conditions;
+ }
+
+ /**
+ * Set route conditions
+ * @param array $conditions
+ * @return void
+ */
+ public function setConditions( array $conditions ) {
+ $this->conditions = $conditions;
+ }
+
+ /**
+ * Get route name
+ * @return string|null
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Set route name
+ * @param string $name
+ * @return void
+ */
+ public function setName( $name ) {
+ $this->name = (string)$name;
+ $this->router->addNamedRoute($this->name, $this);
+ }
+
+ /**
+ * Get route parameters
+ * @return array
+ */
+ public function getParams() {
+ return $this->params;
+ }
+
+ /**
+ * Add supported HTTP method(s)
+ * @return void
+ */
+ public function setHttpMethods() {
+ $args = func_get_args();
+ $this->methods = $args;
+ }
+
+ /**
+ * Get supported HTTP methods
+ * @return array
+ */
+ public function getHttpMethods() {
+ return $this->methods;
+ }
+
+ /**
+ * Append supported HTTP methods
+ * @return void
+ */
+ public function appendHttpMethods() {
+ $args = func_get_args();
+ $this->methods = array_merge($this->methods, $args);
+ }
+
+ /**
+ * Append supported HTTP methods (alias for Route::appendHttpMethods)
+ * @return Slim_Route
+ */
+ public function via() {
+ $args = func_get_args();
+ $this->methods = array_merge($this->methods, $args);
+ return $this;
+ }
+
+ /**
+ * Detect support for an HTTP method
+ * @return bool
+ */
+ public function supportsHttpMethod( $method ) {
+ return in_array($method, $this->methods);
+ }
+
+ /**
+ * Get router
+ * @return Slim_Router
+ */
+ public function getRouter() {
+ return $this->router;
+ }
+
+ /**
+ * Set router
+ * @param Slim_Router $router
+ * @return void
+ */
+ public function setRouter( Slim_Router $router ) {
+ $this->router = $router;
+ }
+
+ /**
+ * Get middleware
+ * @return array[Callable]
+ */
+ public function getMiddleware() {
+ return $this->middleware;
+ }
+
+ /**
+ * Set middleware
+ *
+ * This method allows middleware to be assigned to a specific Route.
+ * If the method argument `is_callable` (including callable arrays!),
+ * we directly append the argument to `$this->middleware`. Else, we
+ * assume the argument is an array of callables and merge the array
+ * with `$this->middleware`. Even if non-callables are included in the
+ * argument array, we still merge them; we lazily check each item
+ * against `is_callable` during Route::dispatch().
+ *
+ * @param Callable|array[Callable]
+ * @return Slim_Route
+ * @throws InvalidArgumentException If argument is not callable or not an array
+ */
+ public function setMiddleware( $middleware ) {
+ if ( is_callable($middleware) ) {
+ $this->middleware[] = $middleware;
+ } else if ( is_array($middleware) ) {
+ $this->middleware = array_merge($this->middleware, $middleware);
+ } else {
+ throw new InvalidArgumentException('Route middleware must be callable or an array of callables');
+ }
+ return $this;
+ }
+
+ /**
+ * Matches URI?
+ *
+ * Parse this route's pattern, and then compare it to an HTTP resource URI
+ * This method was modeled after the techniques demonstrated by Dan Sosedoff at:
+ *
+ * http://blog.sosedoff.com/2009/09/20/rails-like-php-url-router/
+ *
+ * @param string $resourceUri A Request URI
+ * @return bool
+ */
+ public function matches( $resourceUri ) {
+ //Extract URL params
+ preg_match_all('@:([\w]+)@', $this->pattern, $paramNames, PREG_PATTERN_ORDER);
+ $paramNames = $paramNames[0];
+
+ //Convert URL params into regex patterns, construct a regex for this route
+ $patternAsRegex = preg_replace_callback('@:[\w]+@', array($this, 'convertPatternToRegex'), $this->pattern);
+ if ( substr($this->pattern, -1) === '/' ) {
+ $patternAsRegex = $patternAsRegex . '?';
+ }
+ $patternAsRegex = '@^' . $patternAsRegex . '$@';
+
+ //Cache URL params' names and values if this route matches the current HTTP request
+ if ( preg_match($patternAsRegex, $resourceUri, $paramValues) ) {
+ array_shift($paramValues);
+ foreach ( $paramNames as $index => $value ) {
+ $val = substr($value, 1);
+ if ( isset($paramValues[$val]) ) {
+ $this->params[$val] = urldecode($paramValues[$val]);
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Convert a URL parameter (ie. ":id") into a regular expression
+ * @param array URL parameters
+ * @return string Regular expression for URL parameter
+ */
+ protected function convertPatternToRegex( $matches ) {
+ $key = str_replace(':', '', $matches[0]);
+ if ( array_key_exists($key, $this->conditions) ) {
+ return '(?P<' . $key . '>' . $this->conditions[$key] . ')';
+ } else {
+ return '(?P<' . $key . '>[a-zA-Z0-9_\-\.\!\~\*\\\'\(\)\:\@\&\=\$\+,%]+)';
+ }
+ }
+
+ /**
+ * Set route name
+ * @param string $name The name of the route
+ * @return Slim_Route
+ */
+ public function name( $name ) {
+ $this->setName($name);
+ return $this;
+ }
+
+ /**
+ * Merge route conditions
+ * @param array $conditions Key-value array of URL parameter conditions
+ * @return Slim_Route
+ */
+ public function conditions( array $conditions ) {
+ $this->conditions = array_merge($this->conditions, $conditions);
+ return $this;
+ }
+
+ /**
+ * Dispatch route
+ *
+ * This method invokes this route's callable. If middleware is
+ * registered for this route, each callable middleware is invoked in
+ * the order specified.
+ *
+ * This method is smart about trailing slashes on the route pattern.
+ * If this route's pattern is defined with a trailing slash, and if the
+ * current request URI does not have a trailing slash but otherwise
+ * matches this route's pattern, a Slim_Exception_RequestSlash
+ * will be thrown triggering an HTTP 301 Permanent Redirect to the same
+ * URI _with_ a trailing slash. This Exception is caught in the
+ * `Slim::run` loop. If this route's pattern is defined without a
+ * trailing slash, and if the current request URI does have a trailing
+ * slash, this route will not be matched and a 404 Not Found
+ * response will be sent if no subsequent matching routes are found.
+ *
+ * @return bool Was route callable invoked successfully?
+ * @throws Slim_Exception_RequestSlash
+ */
+ public function dispatch() {
+ if ( substr($this->pattern, -1) === '/' && substr($this->router->getRequest()->getResourceUri(), -1) !== '/' ) {
+ throw new Slim_Exception_RequestSlash();
+ }
+
+ //Invoke middleware
+ $req = $this->router->getRequest();
+ $res = $this->router->getResponse();
+ foreach ( $this->middleware as $mw ) {
+ if ( is_callable($mw) ) {
+ call_user_func_array($mw, array($req, $res, $this));
+ }
+ }
+
+ //Invoke callable
+ if ( is_callable($this->getCallable()) ) {
+ call_user_func_array($this->callable, array_values($this->params));
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/Slim/Router.php b/Slim/Router.php
new file mode 100644
index 0000000..1ce8a31
--- /dev/null
+++ b/Slim/Router.php
@@ -0,0 +1,237 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Router
+ *
+ * This class organizes Route objects and, upon request, will
+ * return an iterator for routes that match the HTTP request URI.
+ *
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim_Router implements IteratorAggregate {
+ /**
+ * @var Slim_Http_Request
+ */
+ protected $request;
+
+ /**
+ * @var Slim_Http_Response
+ */
+ protected $response;
+
+ /**
+ * @var array Lookup hash of all routes
+ */
+ protected $routes;
+
+ /**
+ * @var array Lookup hash of named routes, keyed by route name
+ */
+ protected $namedRoutes;
+
+ /**
+ * @var array Array of routes that match the Request URI (lazy-loaded)
+ */
+ protected $matchedRoutes;
+
+ /**
+ * @var mixed Callable to be invoked if no matching routes are found
+ */
+ protected $notFound;
+
+ /**
+ * @var mixed Callable to be invoked if application error
+ */
+ protected $error;
+
+ /**
+ * Constructor
+ * @param Slim_Http_Request $request The HTTP request object
+ * @param Slim_Http_Response $response The HTTP response object
+ */
+ public function __construct( Slim_Http_Request $request, Slim_Http_Response $response ) {
+ $this->request = $request;
+ $this->response = $response;
+ $this->routes = array();
+ $this->namedRoutes = array();
+ }
+
+ /**
+ * Get Iterator
+ * @return ArrayIterator
+ */
+ public function getIterator() {
+ return new ArrayIterator($this->getMatchedRoutes());
+ }
+
+ /**
+ * Get Request
+ * @return Slim_Http_Request
+ */
+ public function getRequest() {
+ return $this->request;
+ }
+
+ /**
+ * Get Response
+ * @return Slim_Http_Response
+ */
+ public function getResponse() {
+ return $this->response;
+ }
+
+ /**
+ * Return routes that match the current request
+ * @return array[Slim_Route]
+ */
+ public function getMatchedRoutes( $reload = false ) {
+ if ( $reload || is_null($this->matchedRoutes) ) {
+ $this->matchedRoutes = array();
+ foreach ( $this->routes as $route ) {
+ if ( $route->matches($this->request->getResourceUri()) ) {
+ $this->matchedRoutes[] = $route;
+ }
+ }
+ }
+ return $this->matchedRoutes;
+ }
+
+ /**
+ * Map a route to a callback function
+ * @param string $pattern The URL pattern (ie. "/books/:id")
+ * @param mixed $callable Anything that returns TRUE for is_callable()
+ * @return Slim_Route
+ */
+ public function map( $pattern, $callable ) {
+ $route = new Slim_Route($pattern, $callable);
+ $route->setRouter($this);
+ $this->routes[] = $route;
+ return $route;
+ }
+
+ /**
+ * Get URL for named route
+ * @param string $name The name of the route
+ * @param array Associative array of URL parameter names and values
+ * @throws RuntimeException If named route not found
+ * @return string The URL for the given route populated with the given parameters
+ */
+ public function urlFor( $name, $params = array() ) {
+ if ( !$this->hasNamedRoute($name) ) {
+ throw new RuntimeException('Named route not found for name: ' . $name);
+ }
+ $pattern = $this->getNamedRoute($name)->getPattern();
+ $search = $replace = array();
+ foreach ( $params as $key => $value ) {
+ $search[] = ':' . $key;
+ $replace[] = $value;
+ }
+ $pattern = str_replace($search, $replace, $pattern);
+ //Remove remnants of unpopulated, trailing optional pattern segments
+ return preg_replace(array(
+ '@\(\/?:.+\/??\)\??@',
+ '@\?|\(|\)@'
+ ), '', $this->request->getRootUri() . $pattern);
+ }
+
+ /**
+ * Add named route
+ * @param string $name The route name
+ * @param Slim_Route $route The route object
+ * @throws RuntimeException If a named route already exists with the same name
+ * @return void
+ */
+ public function addNamedRoute( $name, Slim_Route $route ) {
+ if ( $this->hasNamedRoute($name) ) {
+ throw new RuntimeException('Named route already exists with name: ' . $name);
+ }
+ $this->namedRoutes[(string)$name] = $route;
+ }
+
+ /**
+ * Has named route
+ * @param string $name The route name
+ * @return bool
+ */
+ public function hasNamedRoute( $name ) {
+ return isset($this->namedRoutes[(string)$name]);
+ }
+
+ /**
+ * Get named route
+ * @param string $name
+ * @return Slim_Route|null
+ */
+ public function getNamedRoute( $name ) {
+ if ( $this->hasNamedRoute($name) ) {
+ return $this->namedRoutes[(string)$name];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get named routes
+ * @return ArrayIterator
+ */
+ public function getNamedRoutes() {
+ return new ArrayIterator($this->namedRoutes);
+ }
+
+ /**
+ * Register a 404 Not Found callback
+ * @param mixed $callable Anything that returns TRUE for is_callable()
+ * @return mixed
+ */
+ public function notFound( $callable = null ) {
+ if ( is_callable($callable) ) {
+ $this->notFound = $callable;
+ }
+ return $this->notFound;
+ }
+
+ /**
+ * Register a 500 Error callback
+ * @param mixed $callable Anything that returns TRUE for is_callable()
+ * @return mixed
+ */
+ public function error( $callable = null ) {
+ if ( is_callable($callable) ) {
+ $this->error = $callable;
+ }
+ return $this->error;
+ }
+}
\ No newline at end of file
diff --git a/Slim/Slim.php b/Slim/Slim.php
new file mode 100644
index 0000000..5819263
--- /dev/null
+++ b/Slim/Slim.php
@@ -0,0 +1,1275 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ * @package Slim
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Slim
+ * @package Slim
+ * @author Josh Lockhart
+ * @since 1.0.0
+ */
+class Slim {
+ /**
+ * @const string
+ */
+ const VERSION = '1.6.0';
+
+ /**
+ * @var array[Slim]
+ */
+ protected static $apps = array();
+
+ /**
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * @var array
+ */
+ protected $environment;
+
+ /**
+ * @var Slim_Http_Request
+ */
+ protected $request;
+
+ /**
+ * @var Slim_Http_Response
+ */
+ protected $response;
+
+ /**
+ * @var Slim_Router
+ */
+ protected $router;
+
+ /**
+ * @var Slim_View
+ */
+ protected $view;
+
+ /**
+ * @var array
+ */
+ protected $settings;
+
+ /**
+ * @var string
+ */
+ protected $mode;
+
+ /**
+ * @var array
+ */
+ protected $middleware;
+
+ /**
+ * @var array
+ */
+ protected $hooks = array(
+ 'slim.before' => array(array()),
+ 'slim.before.router' => array(array()),
+ 'slim.before.dispatch' => array(array()),
+ 'slim.after.dispatch' => array(array()),
+ 'slim.after.router' => array(array()),
+ 'slim.after' => array(array())
+ );
+
+ /**
+ * Slim autoloader
+ *
+ * Lazy-loads class files when a given class is first referenced.
+ * Class files must exist in the same directory as this file and be named
+ * the same as its class definition (excluding the dot and extension).
+ *
+ * @return void
+ */
+ public static function autoload( $class ) {
+ if ( strpos($class, 'Slim') !== 0 ) {
+ return;
+ }
+ $file = dirname(__FILE__) . '/' . str_replace('_', DIRECTORY_SEPARATOR, substr($class,5)) . '.php';
+ if ( file_exists($file) ) {
+ require $file;
+ }
+ }
+
+ /***** INSTANTIATION *****/
+
+ /**
+ * Constructor
+ * @param array $userSettings Key-Value array of application settings
+ * @return void
+ */
+ public function __construct( $userSettings = array() ) {
+ //Setup Slim application
+ $this->settings = array_merge(self::getDefaultSettings(), $userSettings);
+ if ( $this->config('install_autoloader') ) {
+ spl_autoload_register(array('Slim', 'autoload'));
+ }
+ $this->environment = Slim_Environment::getInstance();
+ $this->request = new Slim_Http_Request($this->environment);
+ $this->response = new Slim_Http_Response();
+ $this->router = new Slim_Router($this->request, $this->response);
+ $this->middleware = array($this);
+ $this->add(new Slim_Middleware_Flash());
+ $this->add(new Slim_Middleware_MethodOverride());
+
+ //Determine application mode
+ $this->getMode();
+
+ //Setup view
+ $this->view($this->config('view'));
+
+ //Make default if first instance
+ if ( is_null(self::getInstance()) ) {
+ $this->setName('default');
+ }
+
+ //Set default logger that writes to stderr (may be overridden with middleware)
+ $logWriter = $this->config('log.writer');
+ if ( !$logWriter ) {
+ $logWriter = new Slim_LogFileWriter($this->environment['slim.errors']);
+ }
+ $log = new Slim_Log($logWriter);
+ $log->setEnabled($this->config('log.enabled'));
+ $log->setLevel($this->config('log.level'));
+ $this->environment['slim.log'] = $log;
+ }
+
+ /**
+ * Get Slim application instance by name
+ * @param string $name The name of the Slim application to fetch
+ * @return Slim|null
+ */
+ public static function getInstance( $name = 'default' ) {
+ return isset(self::$apps[$name]) ? self::$apps[$name] : null;
+ }
+
+ /**
+ * Set Slim application name
+ * @param string $name The name of this Slim application
+ * @return void
+ */
+ public function setName( $name ) {
+ $this->name = $name;
+ self::$apps[$name] = $this;
+ }
+
+ /**
+ * Get Slim application name
+ * @return string|null
+ */
+ public function getName() {
+ return $this->name;
+ }
+
+ /***** SETTINGS *****/
+
+ /**
+ * Get default application settings
+ * @return array
+ */
+ public static function getDefaultSettings() {
+ return array(
+ //Application
+ 'install_autoloader' => true,
+ 'mode' => 'development',
+ //Debugging
+ 'debug' => true,
+ //Logging
+ 'log.writer' => null,
+ 'log.level' => 4,
+ 'log.enabled' => true,
+ //View
+ 'templates.path' => './templates',
+ 'view' => 'Slim_View',
+ //Cookies
+ 'cookies.lifetime' => '20 minutes',
+ 'cookies.path' => '/',
+ 'cookies.domain' => null,
+ 'cookies.secure' => false,
+ 'cookies.httponly' => false,
+ //Encryption
+ 'cookies.secret_key' => 'CHANGE_ME',
+ 'cookies.cipher' => MCRYPT_RIJNDAEL_256,
+ 'cookies.cipher_mode' => MCRYPT_MODE_CBC,
+ //HTTP
+ 'http.version' => '1.1'
+ );
+ }
+
+ /**
+ * Configure Slim Settings
+ *
+ * This method defines application settings and acts as a setter and a getter.
+ *
+ * If only one argument is specified and that argument is a string, the value
+ * of the setting identified by the first argument will be returned, or NULL if
+ * that setting does not exist.
+ *
+ * If only one argument is specified and that argument is an associative array,
+ * the array will be merged into the existing application settings.
+ *
+ * If two arguments are provided, the first argument is the name of the setting
+ * to be created or updated, and the second argument is the setting value.
+ *
+ * @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
+ * @param mixed $value If name is a string, the value of the setting identified by $name
+ * @return mixed The value of a setting if only one argument is a string
+ */
+ public function config( $name, $value = null ) {
+ if ( func_num_args() === 1 ) {
+ if ( is_array($name) ) {
+ $this->settings = array_merge($this->settings, $name);
+ } else {
+ return isset($this->settings[$name]) ? $this->settings[$name] : null;
+ }
+ } else {
+ $this->settings[$name] = $value;
+ }
+ }
+
+ /***** MODES *****/
+
+ /**
+ * Get application mode
+ *
+ * This method determines the application mode. It first inspects the $_ENV
+ * superglobal for key `SLIM_MODE`. If that is not found, it queries
+ * the `getenv` function. Else, it uses the application `mode` setting.
+ *
+ * @return string
+ */
+ public function getMode() {
+ if ( !isset($this->mode) ) {
+ if ( isset($_ENV['SLIM_MODE']) ) {
+ $this->mode = $_ENV['SLIM_MODE'];
+ } else {
+ $envMode = getenv('SLIM_MODE');
+ if ( $envMode !== false ) {
+ $this->mode = $envMode;
+ } else {
+ $this->mode = $this->config('mode');
+ }
+ }
+ }
+ return $this->mode;
+ }
+
+ /**
+ * Configure Slim for a given mode
+ *
+ * This method will immediately invoke the callable if
+ * the specified mode matches the current application mode.
+ * Otherwise, the callable is ignored. This should be called
+ * only _after_ you initialize your Slim app.
+ *
+ * @param string $mode
+ * @param mixed $callable
+ * @return void
+ */
+ public function configureMode( $mode, $callable ) {
+ if ( $mode === $this->getMode() && is_callable($callable) ) {
+ call_user_func($callable);
+ }
+ }
+
+ /***** LOGGING *****/
+
+ /**
+ * Get application log
+ * @return Slim_Log
+ */
+ public function getLog() {
+ return $this->environment['slim.log'];
+ }
+
+ /***** ROUTING *****/
+
+ /**
+ * Add GET|POST|PUT|DELETE route
+ *
+ * Adds a new route to the router with associated callable. This
+ * route will only be invoked when the HTTP request's method matches
+ * this route's method.
+ *
+ * ARGUMENTS:
+ *
+ * First: string The URL pattern (REQUIRED)
+ * In-Between: mixed Anything that returns TRUE for `is_callable` (OPTIONAL)
+ * Last: mixed Anything that returns TRUE for `is_callable` (REQUIRED)
+ *
+ * The first argument is required and must always be the
+ * route pattern (ie. '/books/:id').
+ *
+ * The last argument is required and must always be the callable object
+ * to be invoked when the route matches an HTTP request.
+ *
+ * You may also provide an unlimited number of in-between arguments;
+ * each interior argument must be callable and will be invoked in the
+ * order specified before the route's callable is invoked.
+ *
+ * USAGE:
+ *
+ * Slim::get('/foo'[, middleware, middleware, ...], callable);
+ *
+ * @param array (See notes above)
+ * @return Slim_Route
+ */
+ protected function mapRoute( $args ) {
+ $pattern = array_shift($args);
+ $callable = array_pop($args);
+ $route = $this->router->map($pattern, $callable);
+ if ( count($args) > 0 ) {
+ $route->setMiddleware($args);
+ }
+ return $route;
+ }
+
+ /**
+ * Add generic route without associated HTTP method
+ * @see Slim::mapRoute
+ * @return Slim_Route
+ */
+ public function map() {
+ $args = func_get_args();
+ return $this->mapRoute($args);
+ }
+
+ /**
+ * Add GET route
+ * @see Slim::mapRoute
+ * @return Slim_Route
+ */
+ public function get() {
+ $args = func_get_args();
+ return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_GET, Slim_Http_Request::METHOD_HEAD);
+ }
+
+ /**
+ * Add POST route
+ * @see Slim::mapRoute
+ * @return Slim_Route
+ */
+ public function post() {
+ $args = func_get_args();
+ return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_POST);
+ }
+
+ /**
+ * Add PUT route
+ * @see Slim::mapRoute
+ * @return Slim_Route
+ */
+ public function put() {
+ $args = func_get_args();
+ return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_PUT);
+ }
+
+ /**
+ * Add DELETE route
+ * @see Slim::mapRoute
+ * @return Slim_Route
+ */
+ public function delete() {
+ $args = func_get_args();
+ return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_DELETE);
+ }
+
+ /**
+ * Add OPTIONS route
+ * @see Slim::mapRoute
+ * @return Slim_Route
+ */
+ public function options() {
+ $args = func_get_args();
+ return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_OPTIONS);
+ }
+
+ /**
+ * Not Found Handler
+ *
+ * This method defines or invokes the application-wide Not Found handler.
+ * There are two contexts in which this method may be invoked:
+ *
+ * 1. When declaring the handler:
+ *
+ * If the $callable parameter is not null and is callable, this
+ * method will register the callable to be invoked when no
+ * routes match the current HTTP request. It WILL NOT invoke the callable.
+ *
+ * 2. When invoking the handler:
+ *
+ * If the $callable parameter is null, Slim assumes you want
+ * to invoke an already-registered handler. If the handler has been
+ * registered and is callable, it is invoked and sends a 404 HTTP Response
+ * whose body is the output of the Not Found handler.
+ *
+ * @param mixed $callable Anything that returns true for is_callable()
+ * @return void
+ */
+ public function notFound( $callable = null ) {
+ if ( !is_null($callable) ) {
+ $this->router->notFound($callable);
+ } else {
+ ob_start();
+ $customNotFoundHandler = $this->router->notFound();
+ if ( is_callable($customNotFoundHandler) ) {
+ call_user_func($customNotFoundHandler);
+ } else {
+ call_user_func(array($this, 'defaultNotFound'));
+ }
+ $this->halt(404, ob_get_clean());
+ }
+ }
+
+ /**
+ * Error Handler
+ *
+ * This method defines or invokes the application-wide Error handler.
+ * There are two contexts in which this method may be invoked:
+ *
+ * 1. When declaring the handler:
+ *
+ * If the $argument parameter is callable, this
+ * method will register the callable to be invoked when an uncaught
+ * Exception is detected, or when otherwise explicitly invoked.
+ * The handler WILL NOT be invoked in this context.
+ *
+ * 2. When invoking the handler:
+ *
+ * If the $argument parameter is not callable, Slim assumes you want
+ * to invoke an already-registered handler. If the handler has been
+ * registered and is callable, it is invoked and passed the caught Exception
+ * as its one and only argument. The error handler's output is captured
+ * into an output buffer and sent as the body of a 500 HTTP Response.
+ *
+ * @param mixed $argument Callable|Exception
+ * @return void
+ */
+ public function error( $argument = null ) {
+ if ( is_callable($argument) ) {
+ //Register error handler
+ $this->router->error($argument);
+ } else {
+ //Invoke error handler
+ $this->response->status(500);
+ $this->response->body('');
+ $this->response->write($this->callErrorHandler($argument));
+ $this->stop();
+ }
+ }
+
+ /**
+ * Call error handler
+ *
+ * This will invoke the custom or default error handler
+ * and RETURN its output.
+ *
+ * @param Exception|null $argument
+ * @return string
+ */
+ protected function callErrorHandler( $argument = null ) {
+ ob_start();
+ $customErrorHandler = $this->router->error();
+ if ( is_callable($customErrorHandler) ) {
+ call_user_func_array($customErrorHandler, array($argument));
+ } else {
+ call_user_func_array(array($this, 'defaultError'), array($argument));
+ }
+ return ob_get_clean();
+ }
+
+ /***** ACCESSORS *****/
+
+ /**
+ * Get a reference to the Environment object
+ * @return Slim_Environment
+ */
+ public function environment() {
+ return $this->environment;
+ }
+
+ /**
+ * Get the Request object
+ * @return Slim_Http_Request
+ */
+ public function request() {
+ return $this->request;
+ }
+
+ /**
+ * Get the Response object
+ * @return Slim_Http_Response
+ */
+ public function response() {
+ return $this->response;
+ }
+
+ /**
+ * Get the Router object
+ * @return Slim_Router
+ */
+ public function router() {
+ return $this->router;
+ }
+
+ /**
+ * Get and/or set the View
+ *
+ * This method declares the View to be used by the Slim application.
+ * If the argument is a string, Slim will instantiate a new object
+ * of the same class. If the argument is an instance of View or a subclass
+ * of View, Slim will use the argument as the View.
+ *
+ * If a View already exists and this method is called to create a
+ * new View, data already set in the existing View will be
+ * transferred to the new View.
+ *
+ * @param string|Slim_View $viewClass The name or instance of a Slim_View class
+ * @return Slim_View
+ */
+ public function view( $viewClass = null ) {
+ if ( !is_null($viewClass) ) {
+ $existingData = is_null($this->view) ? array() : $this->view->getData();
+ if ( $viewClass instanceOf Slim_View ) {
+ $this->view = $viewClass;
+ } else {
+ $this->view = new $viewClass();
+ }
+ $this->view->appendData($existingData);
+ $this->view->setTemplatesDirectory($this->config('templates.path'));
+ }
+ return $this->view;
+ }
+
+ /***** RENDERING *****/
+
+ /**
+ * Render a template
+ *
+ * Call this method within a GET, POST, PUT, DELETE, NOT FOUND, or ERROR
+ * callable to render a template whose output is appended to the
+ * current HTTP response body. How the template is rendered is
+ * delegated to the current View.
+ *
+ * @param string $template The name of the template passed into the View::render method
+ * @param array $data Associative array of data made available to the View
+ * @param int $status The HTTP response status code to use (Optional)
+ * @return void
+ */
+ public function render( $template, $data = array(), $status = null ) {
+ if ( !is_null($status) ) {
+ $this->response->status($status);
+ }
+ $this->view->setTemplatesDirectory($this->config('templates.path'));
+ $this->view->appendData($data);
+ $this->view->display($template);
+ }
+
+ /***** HTTP CACHING *****/
+
+ /**
+ * Set Last-Modified HTTP Response Header
+ *
+ * Set the HTTP 'Last-Modified' header and stop if a conditional
+ * GET request's `If-Modified-Since` header matches the last modified time
+ * of the resource. The `time` argument is a UNIX timestamp integer value.
+ * When the current request includes an 'If-Modified-Since' header that
+ * matches the specified last modified time, the application will stop
+ * and send a '304 Not Modified' response to the client.
+ *
+ * @param int $time The last modified UNIX timestamp
+ * @throws SlimException Returns HTTP 304 Not Modified response if resource last modified time matches `If-Modified-Since` header
+ * @throws InvalidArgumentException If provided timestamp is not an integer
+ * @return void
+ */
+ public function lastModified( $time ) {
+ if ( is_integer($time) ) {
+ $this->response['Last-Modified'] = date(DATE_RFC1123, $time);
+ if ( $time === strtotime($this->request->headers('IF_MODIFIED_SINCE')) ) $this->halt(304);
+ } else {
+ throw new InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.');
+ }
+ }
+
+ /**
+ * Set ETag HTTP Response Header
+ *
+ * Set the etag header and stop if the conditional GET request matches.
+ * The `value` argument is a unique identifier for the current resource.
+ * The `type` argument indicates whether the etag should be used as a strong or
+ * weak cache validator.
+ *
+ * When the current request includes an 'If-None-Match' header with
+ * a matching etag, execution is immediately stopped. If the request
+ * method is GET or HEAD, a '304 Not Modified' response is sent.
+ *
+ * @param string $value The etag value
+ * @param string $type The type of etag to create; either "strong" or "weak"
+ * @throws InvalidArgumentException If provided type is invalid
+ * @return void
+ */
+ public function etag( $value, $type = 'strong' ) {
+ //Ensure type is correct
+ if ( !in_array($type, array('strong', 'weak')) ) {
+ throw new InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".');
+ }
+
+ //Set etag value
+ $value = '"' . $value . '"';
+ if ( $type === 'weak' ) $value = 'W/'.$value;
+ $this->response['ETag'] = $value;
+
+ //Check conditional GET
+ if ( $etagsHeader = $this->request->headers('IF_NONE_MATCH')) {
+ $etags = preg_split('@\s*,\s*@', $etagsHeader);
+ if ( in_array($value, $etags) || in_array('*', $etags) ) $this->halt(304);
+ }
+ }
+
+ /**
+ * Set Expires HTTP response header
+ *
+ * The `Expires` header tells the HTTP client the time at which
+ * the current resource should be considered stale. At that time the HTTP
+ * client will send a conditional GET request to the server; the server
+ * may return a 200 OK if the resource has changed, else a 304 Not Modified
+ * if the resource has not changed. The `Expires` header should be used in
+ * conjunction with the `etag()` or `lastModified()` methods above.
+ *
+ * @param string|int $time If string, a time to be parsed by `strtotime()`;
+ * If int, a UNIX timestamp;
+ * @return void
+ */
+ public function expires( $time ) {
+ if ( is_string($time) ) {
+ $time = strtotime($time);
+ }
+ $this->response['Expires'] = gmdate(DATE_RFC1123, $time);
+ }
+
+ /***** COOKIES *****/
+
+ /**
+ * Set a normal, unencrypted Cookie
+ *
+ * @param string $name The cookie name
+ * @param string $value The cookie value
+ * @param int|string $time The duration of the cookie;
+ * If integer, should be UNIX timestamp;
+ * If string, converted to UNIX timestamp with `strtotime`;
+ * @param string $path The path on the server in which the cookie will be available on
+ * @param string $domain The domain that the cookie is available to
+ * @param bool $secure Indicates that the cookie should only be transmitted over a secure
+ * HTTPS connection to/from the client
+ * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol
+ * @return void
+ */
+ public function setCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
+ $this->response->setCookie($name, array(
+ 'value' => $value,
+ 'expires' => is_null($time) ? $this->config('cookies.lifetime') : $time,
+ 'path' => is_null($path) ? $this->config('cookies.path') : $path,
+ 'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain,
+ 'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure,
+ 'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly
+ ));
+ }
+
+ /**
+ * Get the value of a Cookie from the current HTTP Request
+ *
+ * Return the value of a cookie from the current HTTP request,
+ * or return NULL if cookie does not exist. Cookies created during
+ * the current request will not be available until the next request.
+ *
+ * @param string $name
+ * @return string|null
+ */
+ public function getCookie( $name ) {
+ return $this->request->cookies($name);
+ }
+
+ /**
+ * Set an encrypted Cookie
+ *
+ * @param string $name The cookie name
+ * @param mixed $value The cookie value
+ * @param mixed $expires The duration of the cookie;
+ * If integer, should be UNIX timestamp;
+ * If string, converted to UNIX timestamp with `strtotime`;
+ * @param string $path The path on the server in which the cookie will be available on
+ * @param string $domain The domain that the cookie is available to
+ * @param bool $secure Indicates that the cookie should only be transmitted over a secure
+ * HTTPS connection from the client
+ * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol
+ * @return void
+ */
+ public function setEncryptedCookie( $name, $value, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
+ $expires = is_null($expires) ? $this->config('cookies.lifetime') : $expires;
+ if ( is_string($expires) ) {
+ $expires = strtotime($expires);
+ }
+ $secureValue = Slim_Http_Util::encodeSecureCookie(
+ $value,
+ $expires,
+ $this->config('cookies.secret_key'),
+ $this->config('cookies.cipher'),
+ $this->config('cookies.cipher_mode')
+ );
+ $this->setCookie($name, $secureValue, $expires, $path, $domain, $secure, $httponly);
+ }
+
+ /**
+ * Get the value of an encrypted Cookie from the current HTTP request
+ *
+ * Return the value of an encrypted cookie from the current HTTP request,
+ * or return NULL if cookie does not exist. Encrypted cookies created during
+ * the current request will not be available until the next request.
+ *
+ * @param string $name
+ * @return string|false
+ */
+ public function getEncryptedCookie( $name, $deleteIfInvalid = true ) {
+ $value = Slim_Http_Util::decodeSecureCookie(
+ $this->request->cookies($name),
+ $this->config('cookies.secret_key'),
+ $this->config('cookies.cipher'),
+ $this->config('cookies.cipher_mode')
+ );
+ if ( $value === false && $deleteIfInvalid ) {
+ $this->deleteCookie($name);
+ }
+ return $value;
+ }
+
+ /**
+ * Delete a Cookie (for both normal or encrypted Cookies)
+ *
+ * Remove a Cookie from the client. This method will overwrite an existing Cookie
+ * with a new, empty, auto-expiring Cookie. This method's arguments must match
+ * the original Cookie's respective arguments for the original Cookie to be
+ * removed. If any of this method's arguments are omitted or set to NULL, the
+ * default Cookie setting values (set during Slim::init) will be used instead.
+ *
+ * @param string $name The cookie name
+ * @param string $path The path on the server in which the cookie will be available on
+ * @param string $domain The domain that the cookie is available to
+ * @param bool $secure Indicates that the cookie should only be transmitted over a secure
+ * HTTPS connection from the client
+ * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol
+ * @return void
+ */
+ public function deleteCookie( $name, $path = null, $domain = null, $secure = null, $httponly = null ) {
+ $this->response->deleteCookie($name, array(
+ 'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain,
+ 'path' => is_null($path) ? $this->config('cookies.path') : $path,
+ 'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure,
+ 'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly
+ ));
+ }
+
+ /***** HELPERS *****/
+
+ /**
+ * Get the absolute path to this Slim application's root directory
+ *
+ * This method returns the absolute path to the Slim application's
+ * directory. If the Slim application is installed in a public-accessible
+ * sub-directory, the sub-directory path will be included. This method
+ * will always return an absolute path WITH a trailing slash.
+ *
+ * @return string
+ */
+ public function root() {
+ return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/';
+ }
+
+ /**
+ * Clean buffer
+ *
+ * Clear the current output buffer to avoid sending invalid
+ * body content to the HTTP client.
+ *
+ * @return void
+ */
+ protected function cleanBuffer() {
+ if ( ob_get_level() !== 0 ) {
+ ob_clean();
+ }
+ }
+
+ /**
+ * Stop
+ *
+ * The thrown exception will be caught in Slim::call()
+ * and the response will be sent as is to the HTTP client.
+ *
+ * @throws Slim_Exception_Stop
+ * @return void
+ */
+ public function stop() {
+ throw new Slim_Exception_Stop();
+ }
+
+ /**
+ * Halt
+ *
+ * Stop the application and immediately send the response with a
+ * specific status and body to the HTTP client. This may send any
+ * type of response: info, success, redirect, client error, or server error.
+ * If you need to render a template AND customize the response status,
+ * use Slim::render() instead.
+ *
+ * @param int $status The HTTP response status
+ * @param string $message The HTTP response body
+ * @return void
+ */
+ public function halt( $status, $message = '' ) {
+ $this->cleanBuffer();
+ $this->response->status($status);
+ $this->response->body($message);
+ $this->stop(); }
+
+ /**
+ * Pass
+ *
+ * The thrown exception is caught in Slim::call() causing
+ * the current Router iteration to be ignored while continuing
+ * to the subsequent Route if available. If no subsequent matching
+ * routes are found, a 404 response will be sent to the client.
+ *
+ * @throws Slim_Exception_Pass
+ * @return void
+ */
+ public function pass() {
+ $this->cleanBuffer();
+ throw new Slim_Exception_Pass();
+ }
+
+ /**
+ * Set the HTTP response Content-Type
+ * @param string $type The Content-Type for the Response (ie. text/html)
+ * @return void
+ */
+ public function contentType( $type ) {
+ $this->response['Content-Type'] = $type;
+ }
+
+ /**
+ * Set the HTTP response status code
+ * @param int $status The HTTP response status code
+ * @return void
+ */
+ public function status( $code ) {
+ $this->response->status($code);
+ }
+
+ /**
+ * Get the URL for a named Route
+ * @param string $name The route name
+ * @param array $params Key-value array of URL parameters
+ * @throws RuntimeException If named route does not exist
+ * @return string
+ */
+ public function urlFor( $name, $params = array() ) {
+ return $this->router->urlFor($name, $params);
+ }
+
+ /**
+ * Redirect
+ *
+ * This method immediately redirects to a new URL. By default,
+ * this issues a 302 Found response; this is considered the default
+ * generic redirect response. You may also specify another valid
+ * 3xx status code if you want. This method will automatically set the
+ * HTTP Location header for you using the URL parameter and place the
+ * destination URL into the response body.
+ *
+ * @param string $url The destination URL
+ * @param int $status The HTTP redirect status code (Optional)
+ * @return void
+ */
+ public function redirect( $url, $status = 302 ) {
+ $this->response->redirect($url, $status);
+ $this->halt($status, $url); }
+
+ /***** FLASH *****/
+
+ /**
+ * Set flash message for subsequent request
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function flash( $key, $value ) {
+ if ( isset($this->environment['slim.flash']) ) {
+ $this->environment['slim.flash']->set($key, $value);
+ }
+ }
+
+ /**
+ * Set flash message for current request
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function flashNow( $key, $value ) {
+ if ( isset($this->environment['slim.flash']) ) {
+ $this->environment['slim.flash']->now($key, $value);
+ }
+ }
+
+ /**
+ * Keep flash messages from previous request for subsequent request
+ * @return void
+ */
+ public function flashKeep() {
+ if ( isset($this->environment['slim.flash']) ) {
+ $this->environment['slim.flash']->keep();
+ }
+ }
+
+ /***** HOOKS *****/
+
+ /**
+ * Assign hook
+ * @param string $name The hook name
+ * @param mixed $callable A callable object
+ * @param int $priority The hook priority; 0 = high, 10 = low
+ * @return void
+ */
+ public function hook( $name, $callable, $priority = 10 ) {
+ if ( !isset($this->hooks[$name]) ) {
+ $this->hooks[$name] = array(array());
+ }
+ if ( is_callable($callable) ) {
+ $this->hooks[$name][(int)$priority][] = $callable;
+ }
+ }
+
+ /**
+ * Invoke hook
+ * @param string $name The hook name
+ * @param mixed $hookArgs (Optional) Argument for hooked functions
+ * @return mixed
+ */
+ public function applyHook( $name, $hookArg = null ) {
+ if ( !isset($this->hooks[$name]) ) {
+ $this->hooks[$name] = array(array());
+ }
+ if( !empty($this->hooks[$name]) ) {
+ // Sort by priority, low to high, if there's more than one priority
+ if ( count($this->hooks[$name]) > 1 ) {
+ ksort($this->hooks[$name]);
+ }
+ foreach( $this->hooks[$name] as $priority ) {
+ if( !empty($priority) ) {
+ foreach($priority as $callable) {
+ $hookArg = call_user_func($callable, $hookArg);
+ }
+ }
+ }
+ return $hookArg; } }
+
+ /**
+ * Get hook listeners
+ *
+ * Return an array of registered hooks. If `$name` is a valid
+ * hook name, only the listeners attached to that hook are returned.
+ * Else, all listeners are returned as an associative array whose
+ * keys are hook names and whose values are arrays of listeners.
+ *
+ * @param string $name A hook name (Optional)
+ * @return array|null
+ */
+ public function getHooks( $name = null ) {
+ if ( !is_null($name) ) {
+ return isset($this->hooks[(string)$name]) ? $this->hooks[(string)$name] : null;
+ } else {
+ return $this->hooks;
+ }
+ }
+
+ /**
+ * Clear hook listeners
+ *
+ * Clear all listeners for all hooks. If `$name` is
+ * a valid hook name, only the listeners attached
+ * to that hook will be cleared.
+ *
+ * @param string $name A hook name (Optional)
+ * @return void
+ */
+ public function clearHooks( $name = null ) {
+ if ( !is_null($name) && isset($this->hooks[(string)$name]) ) {
+ $this->hooks[(string)$name] = array(array());
+ } else {
+ foreach( $this->hooks as $key => $value ) {
+ $this->hooks[$key] = array(array());
+ }
+ }
+ }
+
+ /***** STREAMING *****/
+
+ /**
+ * Stream file
+ *
+ * This method will immediately begin streaming the specified file
+ * to the HTTP client and exit the Slim application. By default,
+ * the file delivered to the HTTP client will use the basename
+ * of the specified file; you may override the file name
+ * using the second argument.
+ *
+ * @param string $path The relative or absolute path to the file
+ * @param array $userOptions
+ * @return void
+ */
+ public function streamFile( $path, $userOptions = array() ) {
+ $defaults = array('name' => basename($path));
+ $options = array_merge($defaults, $userOptions);
+ $this->response = new Slim_Http_Stream(new Slim_Stream_File($path, $options), $options);
+ $this->stop();
+ }
+
+ /**
+ * Stream data
+ *
+ * This method will immediately begin streaming the specified data
+ * to the HTTP client and exit the Slim application. You are encouraged
+ * to specify the name of the file delivered to the HTTP client using
+ * the second argument.
+ *
+ * @param string $data
+ * @param array $userOptions
+ * @return void
+ */
+ public function streamData( $data, $userOptions = array() ) {
+ $this->response = new Slim_Http_Stream(new Slim_Stream_Data($data, $userOptions), $userOptions);
+ $this->stop();
+ }
+
+ /**
+ * Stream process output
+ *
+ * This method will immediately begin streaming the process output
+ * to the HTTP client and exit the Slim application. You are encouraged
+ * to specify the name of the file delivered to the HTTP client using
+ * the second argument. This method WILL NOT escape shell arguments for you.
+ *
+ * @param string $process The process command. Escape shell arguments!
+ * @param array $userOptions
+ * @return void
+ */
+ public function streamProcess( $process, $userOptions = array() ) {
+ $this->response = new Slim_Http_Stream(new Slim_Stream_Process($process, $userOptions), $userOptions);
+ $this->stop();
+ }
+
+ /***** APPLICATION MIDDLEWARE *****/
+
+ /**
+ * Add middleware
+ *
+ * This method prepends new middleware to the application middleware stack.
+ * The argument must be an instance that subclasses Slim_Middleware.
+ *
+ * @param Slim_Middleware
+ * @return void
+ */
+ public function add( Slim_Middleware $newMiddleware ) {
+ $newMiddleware->setApplication($this);
+ $newMiddleware->setNextMiddleware($this->middleware[0]);
+ array_unshift($this->middleware, $newMiddleware);
+ }
+
+ /***** RUN SLIM *****/
+
+ /**
+ * Run
+ *
+ * This method invokes the middleware stack, including the core Slim application;
+ * the result is an array of HTTP status, header, and body. These three items
+ * are returned to the HTTP client.
+ *
+ * @return void
+ */
+ public function run() {
+ set_error_handler(array('Slim', 'handleErrors'));
+
+ //Apply final outer middleware layers
+ $this->add(new Slim_Middleware_PrettyExceptions());
+
+ //Invoke middleware and application stack
+ $this->middleware[0]->call();
+
+ //Fetch status, header, and body
+ list($status, $header, $body) = $this->response->finalize();
+
+ //Send headers
+ if ( headers_sent() === false ) {
+ //Send status
+ if ( strpos(PHP_SAPI, 'cgi') === 0 ) {
+ header(sprintf('Status: %s', Slim_Http_Response::getMessageForCode($status)));
+ } else {
+ header(sprintf('HTTP/%s %s', $this->config('http.version'), Slim_Http_Response::getMessageForCode($status)));
+ }
+
+ //Send headers
+ foreach ( $header as $name => $value ) {
+ $hValues = explode("\n", $value);
+ foreach ( $hValues as $hVal ) {
+ header("$name: $hVal", false);
+ }
+ }
+ }
+
+ //Send body
+ if ( is_string($body) ) {
+ echo $body;
+ } else {
+ $body->process();
+ }
+
+ restore_error_handler();
+ }
+
+ /**
+ * Call
+ *
+ * Iterate each matching Route until all Routes are exhausted.
+ *
+ * @return void
+ */
+ public function call() {
+ try {
+ if ( isset($this->environment['slim.flash']) ) {
+ $this->view()->setData('flash', $this->environment['slim.flash']);
+ }
+ $this->applyHook('slim.before');
+ ob_start();
+ $this->applyHook('slim.before.router');
+ $dispatched = false;
+ $httpMethodsAllowed = array();
+ foreach ( $this->router as $route ) {
+ if ( $route->supportsHttpMethod($this->environment['REQUEST_METHOD']) ) {
+ try {
+ $this->applyHook('slim.before.dispatch');
+ $dispatched = $route->dispatch();
+ $this->applyHook('slim.after.dispatch');
+ if ( $dispatched ) {
+ break;
+ }
+ } catch ( Slim_Exception_Pass $e ) {
+ continue;
+ }
+ } else { $httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods()); }
+ }
+ if ( !$dispatched ) {
+ if ( $httpMethodsAllowed ) {
+ $this->response['Allow'] = implode(' ', $httpMethodsAllowed);
+ $this->halt(405, 'HTTP method not allowed for the requested resource. Use one of these instead: ' . implode(', ', $httpMethodsAllowed)); } else { $this->notFound(); } }
+ $this->applyHook('slim.after.router');
+ $this->response->write(ob_get_clean());
+ $this->applyHook('slim.after');
+ $this->stop();
+ } catch ( Slim_Exception_Stop $e ) {
+ $this->response()->write(ob_get_contents());
+ } catch ( Slim_Exception_RequestSlash $e ) {
+ $this->response->redirect($this->request->getPath() . '/', 301);
+ } catch ( Exception $e ) {
+ if ( $this->config('debug') ){
+ throw $e;
+ } else {
+ try { $this->error($e); } catch ( Slim_Exception_Stop $e ) {}
+ }
+ }
+ }
+
+ /***** EXCEPTION AND ERROR HANDLING *****/
+
+ /**
+ * Handle errors
+ *
+ * This is the global Error handler that will catch reportable Errors
+ * and convert them into ErrorExceptions that are caught and handled
+ * by each Slim application.
+ *
+ * @param int $errno The numeric type of the Error
+ * @param string $errstr The error message
+ * @param string $errfile The absolute path to the affected file
+ * @param int $errline The line number of the error in the affected file
+ * @return true
+ * @throws ErrorException
+ */
+ public static function handleErrors( $errno, $errstr = '', $errfile = '', $errline = '' ) {
+ if ( error_reporting() & $errno ) {
+ throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
+ }
+ return true;
+ }
+
+ /**
+ * Generate default template markup
+ *
+ * This method accepts a title and body content to generate
+ * an HTML page. This is primarily used to generate the layout markup
+ * for Error handlers and Not Found handlers.
+ *
+ * @param string $title The title of the HTML template
+ * @param string $body The body content of the HTML template
+ * @return string
+ */
+ protected static function generateTemplateMarkup( $title, $body ) {
+ return sprintf("%s
%s
%s", $title, $title, $body);
+ }
+
+ /**
+ * Default Not Found handler
+ * @return void
+ */
+ protected function defaultNotFound() {
+ echo self::generateTemplateMarkup('404 Page Not Found', '
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.
Visit the Home Page');
+ }
+
+ /**
+ * Default Error handler
+ * @return void
+ */
+ protected function defaultError() {
+ echo self::generateTemplateMarkup('Error', '
A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.
'},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery);
\ No newline at end of file
diff --git a/assets/js/gopher.js b/assets/js/gopher.js
new file mode 100644
index 0000000..fa574ab
--- /dev/null
+++ b/assets/js/gopher.js
@@ -0,0 +1,145 @@
+(function( $ ){
+
+ //DirEntity ::= Type User_Name Tab Selector Tab Host Tab Port CR-LF
+ //1The QuIX Auditorium 1/audio quix.us 70 +
+ var entryPattern = /^(.)(.*?)\t(.*?)\t(.*?)\t(\d+).*/;
+
+ function gopher_parsedir (dirent) {
+ //var entryPattern = /^(.)(.*?)\t(.*?)\t(.*?)\t(.*?)\u000d\u000a$/;
+ // http://kevin.vanzonneveld.net
+ // + original by: Brett Zamir (http://brett-zamir.me)
+ // * example 1: var entry = gopher_parsedir('0All about my gopher site.\t/allabout.txt\tgopher.example.com\t70\u000d\u000a');
+ // * example 1: entry.title;
+ // * returns 1: 'All about my gopher site.'
+
+ /* Types
+ * 0 = plain text file
+ * 1 = directory menu listing
+ * 2 = CSO search query
+ * 3 = error message
+ * 4 = BinHex encoded text file
+ * 5 = binary archive file
+ * 6 = UUEncoded text file
+ * 7 = search engine query
+ * 8 = telnet session pointer
+ * 9 = binary file
+ * g = Graphics file format, primarily a GIF file
+ * h = HTML file
+ * i = informational message
+ * s = Audio file format, primarily a WAV file
+ */
+
+ var entry = dirent.match(entryPattern);
+ if (entry === null) {
+ return {};
+ }
+
+ var type = entry[1];
+ switch (type) {
+ case 'i':
+ type = 255; // GOPHER_INFO
+ break;
+ case '1':
+ type = 1; // GOPHER_DIRECTORY
+ break;
+ case '0':
+ type = 0; // GOPHER_DOCUMENT
+ break;
+ case '4':
+ type = 4; // GOPHER_BINHEX
+ break;
+ case '5':
+ type = 5; // GOPHER_DOSBINARY
+ break;
+ case '6':
+ type = 6; // GOPHER_UUENCODED
+ break;
+ case '9':
+ type = 9; // GOPHER_BINARY
+ break;
+ case 'h':
+ type = 254; // GOPHER_HTTP
+ break;
+ default:
+ return {
+ type: -1,
+ data: dirent
+ }; // GOPHER_UNKNOWN
+ }
+ return {
+ type: type,
+ title: entry[2],
+ path: entry[3],
+ host: entry[4],
+ port: entry[5]
+ };
+ };
+
+ var parseGopher = function(data) {
+ var lines = [];
+ data = data.split("\n");
+ for ( var i = 0; i < data.length; i++ ) {
+ lines.push(gopher_parsedir(data[i]));
+ }
+
+ return lines;
+ };
+
+ var methods = {
+ init : function( options ) {
+
+ },
+ shouldRender : function( ) {
+ var data = $(this).html().trim().split("\n");
+ return data[0].match(entryPattern) !== null;
+ },
+ parse : function( ) {
+ var data = $(this).html().trim();
+ return parseGopher(data);
+ },
+ fromGopher : function(d) {
+ var data, entries;
+
+ if ( typeof(d) === "undefined" ) {
+ data = $(this).html().trim();
+ }
+ else {
+ data = d;
+ }
+ entries = parseGopher(data);
+
+ $(this).html("");
+
+ for ( var i = 0; i < entries.length; i++ ) {
+ var e = entries[i];
+ var href = "/" + e.host + e.path;
+ var text = e.title;
+ var result;
+
+ if ( e.path == "" ) {
+ result = text;
+ }
+ else {
+ result = $("").attr("href", href).html(text);
+ }
+
+ $(this).append(result).append(" ");
+ }
+
+ }
+ };
+
+ $.fn.gopher = function( method ) {
+ // Method calling logic
+ if ( methods[method] ) {
+ return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
+ } else if ( typeof method === 'object' || ! method ) {
+ return methods.init.apply( this, arguments );
+ } else {
+ $.error( 'Method ' + method + ' does not exist on jQuery.gopher' );
+ }
+ };
+
+})( jQuery );
+
+//https://raw.github.com/kvz/phpjs/master/functions/net-gopher/gopher_parsedir.js
diff --git a/assets/js/gopherSpec.js b/assets/js/gopherSpec.js
new file mode 100644
index 0000000..15e9131
--- /dev/null
+++ b/assets/js/gopherSpec.js
@@ -0,0 +1,5 @@
+describe("gopher", function() {
+ it("makes testing JavaScript awesome!", function() {
+ expect(yourCode).toBeLotsBetter();
+ });
+});
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..8c8bcbe
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,21 @@
+{
+ "name": "slim/slim",
+ "type": "library",
+ "description": "Slim Framework, a micro framework for PHP 5",
+ "keywords": ["templating","extensions"],
+ "homepage": "http://github.com/codeguy/Slim",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Josh Lockhart",
+ "email": "info@joshlockhart.com",
+ "homepage": "http://www.joshlockhart.com/"
+ }
+ ],
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "autoload": {
+ "psr-0": { "Slim": "Slim/" }
+ }
+}
diff --git a/docs/caching-etag.markdown b/docs/caching-etag.markdown
new file mode 100644
index 0000000..f62f7cd
--- /dev/null
+++ b/docs/caching-etag.markdown
@@ -0,0 +1,12 @@
+# Etag [caching-etag] #
+
+A Slim application provides built-in support for HTTP caching using ETags. An ETag is a unique identifier for a resource URI. When an ETag header is set with the `etag()` method, the HTTP client will send an **If-None-Match** header with each subsequent HTTP request of the same resource URI. If the ETag value for the resource URI matches the **If-None-Match** HTTP request header, the Slim application will return a **304 Not Modified** HTTP response that will prompt the HTTP client to continue using its cache; this also prevents the Slim application from serving the entire markup for the resource URI, saving bandwidth and response time.
+
+Setting an ETag with Slim is very simple. Invoke the `etag()` application method in your route callback, passing it a unique ID as the first and only argument.
+
+ $app->get('/foo', function () use ($app) {
+ $app->etag('unique-id');
+ echo "This will be cached after the initial request!";
+ });
+
+That’s it. Make sure the unique ETag ID *is unique for the given resource*. Also make sure the ETag unique ID changes as your resource changes; otherwise, the HTTP client will continue serving its outdated cache.
\ No newline at end of file
diff --git a/docs/caching-expires.markdown b/docs/caching-expires.markdown
new file mode 100644
index 0000000..4dabf86
--- /dev/null
+++ b/docs/caching-expires.markdown
@@ -0,0 +1,11 @@
+# Expires [caching-expires] #
+
+Used in conjunction with the Slim application's `etag()` or `lastModified()` methods, the `expires()` method sets an `Expires:` header on the HTTP response informing the HTTP client when its client-side cache for the current resource should be considered stale. The HTTP client will continue serving from its client-side cache until the expiration date is reached, at which time the HTTP client will send a conditional GET request to the Slim application.
+
+The `expires()` method accepts one argument: an integer UNIX timestamp, or a string to be parsed with `strtotime()`.
+
+ $app->get('/foo', function () use ($app) {
+ $app->etag('unique-resource-id');
+ $app->expires('+1 week');
+ echo "This will be cached client-side for one week";
+ });
\ No newline at end of file
diff --git a/docs/caching-last-modified.markdown b/docs/caching-last-modified.markdown
new file mode 100644
index 0000000..df0f39e
--- /dev/null
+++ b/docs/caching-last-modified.markdown
@@ -0,0 +1,10 @@
+# Last-Modified [caching-last-modified] #
+
+A Slim application provides built-in support for HTTP caching using the resource’s last modified date. When you specify a last modified date, Slim tells the HTTP client the date and time the current resource was last modified. The HTTP client will then send a **If-Modified-Since** header with each subsequent HTTP request for the given resource URI. If the last modification date you specify matches the **If-Modified-Since** HTTP request header, the Slim application will return a **304 Not Modified** HTTP response that will prompt the HTTP client to use its cache; this also prevents the Slim application from serving the entire markup for the resource URI saving bandwidth and response time.
+
+Setting a last modified date with Slim is very simple. You only need to invoke the `lastModified()` application instance method in your route callback passing in a UNIX timestamp that represents the last modification date for the given resource. Be sure the `lastModified()` application instance method's timestamp updates along with the resource’s last modification date; otherwise, the browser client will continue serving its outdated cache.
+
+ $app->get('/foo', function () use ($app) {
+ $app->lastModified(1286139652);
+ echo "This will be cached after the initial request!";
+ });
\ No newline at end of file
diff --git a/docs/caching.markdown b/docs/caching.markdown
new file mode 100644
index 0000000..2c46d3b
--- /dev/null
+++ b/docs/caching.markdown
@@ -0,0 +1,9 @@
+# HTTP Caching [caching] #
+
+A Slim application provides built-in support for HTTP caching with its `etag()`, `lastModified()`, and `expires()` instance methods. It is best to use _one_ of `etag()` or `lastModified()` — in conjunction with `expires()` — per route; never use _both_ `etag()` and `lastModified()` together in the same route callback.
+
+The `etag()` and `lastModified()` methods should be invoked in a route callback *before* other code; this allows Slim to check conditional GET requests _before_ processing the route callback's remaining code.
+
+Both `etag()` and `lastModified()` instruct the HTTP client to store the resource response in a client-side cache. The `expires()` method indicates to the HTTP client when the client-side cache should be considered stale.
+
+More details for each method are below.
\ No newline at end of file
diff --git a/docs/environment.markdown b/docs/environment.markdown
new file mode 100644
index 0000000..fc485dc
--- /dev/null
+++ b/docs/environment.markdown
@@ -0,0 +1,56 @@
+# The Environment [environment] #
+
+The Slim Framework implements a derivation of the [Rack protocol](http://rack.rubyforge.org/doc/files/SPEC.html). When you instantiate a Slim application, it immediately inspects the `$_SERVER` superglobal and derives a set of environment variables that dictate application behavior.
+
+## What is the Environment? ##
+
+A Slim application's "environment" is an associative array of settings that are parsed once and made accessible to the Slim application and its middleware. You are free to modify the environment variables during runtime; changes will propagate immediately throughout the application.
+
+When you instantiate a Slim application, the environment variables are derived from the `$_SERVER` superglobal; you do not need to set these yourself. However, you are free to modify or supplement these variables in [Slim middleware](#middleware).
+
+These variables are fundamental to determining how your Slim application runs: the resource URI, the HTTP method, the HTTP request body, the URL query parameters, error output, and more. Middleware, described later, gives you the power to — among other things — manipulate environment variables before and/or after the Slim application is run.
+
+## Environment Variables ##
+
+The following text respectfully borrows the same information originally available at . The environment array **must** include these variables:
+
+REQUEST_METHOD
+: The HTTP request method. This is required and may never be an empty string.
+
+SCRIPT_NAME
+: The initial portion of the request URI's "path" that corresponds to the physical directory in which the Slim application is installed --- so that the application knows its virtual "location". This may be an empty string if the application is installed in the top-level of the public document root directory. This will never have a trailing slash.
+
+PATH_INFO
+: The remaining portion of the request URI's "path" that determines the "virtual" location of the HTTP request's target resource within the Slim application context. This will always have a leading slash; it may or may not have a trailing slash.
+
+QUERY_STRING
+: The part of the HTTP request's URI after, but not including, the "?". This is required but may be an empty string.
+
+SERVER_NAME
+: When combined with **SCRIPT\_NAME** and **PATH\_INFO**, this can be used to create a fully qualified URL to an application resource. However, if **HTTP_HOST** is present, that should be used instead of this. This is required and may never be an empty string.
+
+SERVER_PORT
+: When combined with **SCRIPT\_NAME** and **PATH\_INFO**, this can be used to create a fully qualified URL to any application resource. This is required and may never be an empty string.
+
+HTTP_*
+: Variables matching the HTTP request headers sent by the client. The existence of these variables correspond with those sent in the current HTTP request.
+
+slim.url_scheme
+: Will be "http" or "https" depending on the HTTP request URL.
+
+slim.input
+: Will be a string representing the raw HTTP request body. If the HTTP request body is empty (e.g. with a GET request), this will be an empty string.
+
+slim.errors
+: Must always be a writable resource; by default, this is a write-only resource handle to **php://stderr**.
+
+The Slim application can store its own data in the environment, too. The environment array's keys must contain at least one dot, and should be prefixed uniquely (e.g. "prefix.foo"). The prefix **slim.** is reserved for use by Slim itself and must not be used otherwise. The environment must not contain the keys **HTTP\_CONTENT\_TYPE** or **HTTP\_CONTENT\_LENGTH** (use the versions without **HTTP\_**). The CGI keys (named without a period) must have String values. There are the following restrictions:
+
+* slim.url_scheme must either be "http" or "https".
+* slim.input must be a string.
+* There must be a valid, writable resource in "slim.errors".
+* The **REQUEST\_METHOD** must be a valid token.
+* The **SCRIPT\_NAME**, if non-empty, must start with /
+* The **PATH\_INFO**, if non-empty, must start with /
+* The **CONTENT_LENGTH**, if given, must consist of digits only.
+* One of **SCRIPT\_NAME** or **PATH\_INFO** must be set. **PATH\_INFO** should be / if **SCRIPT\_NAME** is empty. **SCRIPT\_NAME** never should be /, but instead be an empty string.
\ No newline at end of file
diff --git a/docs/errors-debug.markdown b/docs/errors-debug.markdown
new file mode 100644
index 0000000..63a98d3
--- /dev/null
+++ b/docs/errors-debug.markdown
@@ -0,0 +1,19 @@
+# Debugging [errors-debug] #
+
+You can enable debugging during application instantiation with this setting:
+
+ $app = new Slim(array(
+ 'debug' => true
+ ));
+
+You may also enable debugging during runtime with the Slim application's `config()` instance method.
+
+ $app = new Slim();
+
+ //Enable debugging (on by default)
+ $app->config('debug', true);
+
+ //Disable debugging
+ $app->config('debug', false);
+
+If debugging is enabled and an exception or error occurs, a detailed error message will appear with the error description, the affected file, the file line number, and a stack trace. If debugging is disabled, your [custom Error handler](#error-handler) will be invoked instead.
\ No newline at end of file
diff --git a/docs/errors-error-handler.markdown b/docs/errors-error-handler.markdown
new file mode 100644
index 0000000..55e4b14
--- /dev/null
+++ b/docs/errors-error-handler.markdown
@@ -0,0 +1,29 @@
+# Error Handler [error-handler] #
+
+You may use the Slim application's `error()` instance method to specify a custom error handler to be invoked when an error or exception occurs. Custom error handlers are only invoked if application debugging is disabled.
+
+A custom error handler should render a user-friendly message that mitigates user confusion. Similar to the Slim application's `notFound()` instance method, the `error()` instance method acts as both a getter and a setter.
+
+## Set a Custom Error Handler ##
+
+You may set a custom error handler by passing a callable into the `error()` instance method as the first and only argument.
+
+ $app = new Slim();
+
+ //PHP >= 5.3
+ $app->error(function ( Exception $e ) use ($app) {
+ $app->render('error.php');
+ });
+
+ //PHP < 5.3
+ $app->error('custom_error_handler');
+ function custom_error_handler( Exception $e ){
+ $app = Slim::getInstance();
+ $app->render('error.php');
+ }
+
+In this example, notice how the custom error handler accepts the caught Exception as its argument. This allows you to respond appropriately to different exceptions.
+
+## Invoke a Custom Error Handler ##
+
+Usually, the Slim application will automatically invoke the error handler when an exception or error occurs. However, you may also manually invoke the error handler with the Slim application's `error()` method (without an argument).
\ No newline at end of file
diff --git a/docs/errors-notfound-handler.markdown b/docs/errors-notfound-handler.markdown
new file mode 100644
index 0000000..153a395
--- /dev/null
+++ b/docs/errors-notfound-handler.markdown
@@ -0,0 +1,35 @@
+# Not Found Handler [not-found-handler] #
+
+It is an inevitability that someone will request a page that does not exist. The Slim application lets you easily define a custom **Not Found** handler with the Slim application's `notFound()` instance method. The Not Found handler will be invoked when a matching route is not found for the current HTTP request. This method may be invoked in two different contexts.
+
+## When defining the handler ##
+
+If you invoke the Slim application's `notFound()` instance method and specify a callable object as its first and only argument, this method will register the callable object as the Not Found handler. However, the registered handler will not be invoked.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->notFound(function () use ($app) {
+ $app->render('404.html');
+ });
+
+ //For PHP < 5.3
+ $app->notFound('custom_not_found_callback');
+ function custom_not_found_callback() {
+ $app = Slim::getInstance();
+ $app->render('404.html');
+ }
+
+## When invoking the Not Found handler ##
+
+If you invoke the Slim application's `notFound()` instance method without any arguments, this method will invoke the previously registered Not Found handler.
+
+ $app = new Slim();
+
+ $app->get('/hello/:name', function ($name) use ($app) {
+ if ( $name === 'Waldo' ) {
+ $app->notFound();
+ } else {
+ echo "Hello, $name";
+ }
+ });
\ No newline at end of file
diff --git a/docs/errors-output.markdown b/docs/errors-output.markdown
new file mode 100644
index 0000000..d6dbd30
--- /dev/null
+++ b/docs/errors-output.markdown
@@ -0,0 +1,18 @@
+# Error Output [errors-output] #
+
+The Slim environment will always contain a key **slim.errors** with a value that is a writable resource to which log and error messages may be written. The Slim application's [Slim_Log](#logging) object will write log messages to **slim.errors** whenever an Exception is caught or the `Slim_Log` object is manually invoked.
+
+If you want to redirect error output to a different location, you can define your own writable resource by modifying the Slim application's environment settings. I recommend you use [middleware](#middleware) to update the environment like this:
+
+ class CustomErrorMiddleware extends Slim_Middleware {
+ public function call() {
+ // Set new error output
+ $env = $this->app->environment();
+ $env['slim.errors'] = fopen('/path/to/custom/output', 'w');
+
+ // Call next middleware
+ $this->next->call();
+ }
+ }
+
+Remember, **slim.errors** does not have to point to a file; it can point to any valid writable resource.
\ No newline at end of file
diff --git a/docs/errors-reporting.markdown b/docs/errors-reporting.markdown
new file mode 100644
index 0000000..e2095c5
--- /dev/null
+++ b/docs/errors-reporting.markdown
@@ -0,0 +1,3 @@
+# Error Reporting [errors-reporting] #
+
+By default, Slim will report **E\_ALL** and **E\_STRICT** errors. You can change the error reporting by editing **Slim/Slim.php**; the error reporting definition is at the top of that file. Only reported errors will be handled by Slim. Unreported errors will be ignored or handled elsewhere.
\ No newline at end of file
diff --git a/docs/errors.markdown b/docs/errors.markdown
new file mode 100644
index 0000000..ca25534
--- /dev/null
+++ b/docs/errors.markdown
@@ -0,0 +1,7 @@
+# Error Handling [errors] #
+
+Let’s face it: sometimes things go wrong. It is important to intercept errors and respond to them appropriately. A Slim application provides helper methods to respond to errors and exceptions.
+
+Each Slim application handles its own errors and exceptions. If there are multiple Slim applications in the same PHP script, each application will catch and handle its own errors and exceptions. Errors or exceptions generated outside of a Slim application must be caught and handled elsewhere.
+
+To prevent recoverable errors from stopping the entire PHP script, Slim converts errors into `ErrorException` instances that are caught and handled by a default or custom Slim application error handler.
\ No newline at end of file
diff --git a/docs/flash.markdown b/docs/flash.markdown
new file mode 100644
index 0000000..e8ec612
--- /dev/null
+++ b/docs/flash.markdown
@@ -0,0 +1,25 @@
+# Flash Messaging [flash] #
+
+Slim supports flash messaging much like Rails and other larger web frameworks. Flash messaging allows you to define messages that will persist until the next HTTP request but no further. This is helpful to display messages to the user after a given event or error occurs.
+
+As shown below, the Slim application's `flash()` and `flashNow()` instance methods accept two arguments: a key and a message. The key may be whatever you want and defines how the message will be accessed in the View templates. For example, if I invoke the Slim application's `flash('foo', 'The foo message')` instance method with those arguments, I can access that message in the next request’s templates with `flash['foo']`.
+
+Flash messages are persisted with sessions; sessions are required for flash messages to work. Flash messages are stored in `$\_SESSION['flash']`.
+
+## Flash
+
+The Slim application's `flash()` instance method sets a message that will be available in the next request’s view templates. The message in this example will be available in the variable `flash['error']` in the next request’s view templates.
+
+ $app->flash('error', 'User email is required');
+
+## Flash Now
+
+The Slim application's `flashNow()` instance method sets a message that will be available in the current request’s view templates. Messages set with the `flashNow()` application instance method will not be available in the next request. The message in the example below will be available in the variable `flash['info']` in the current request’s view templates.
+
+ $app->flashNow('info', 'Your credit card is expired');
+
+## Flash Keep
+
+This method tells the Slim application to keep existing flash messages set in the previous request so they will be available to the next request. This method is helpful for persisting flash messages across HTTP redirects.
+
+ $app->flashKeep();
\ No newline at end of file
diff --git a/docs/hooks-custom.markdown b/docs/hooks-custom.markdown
new file mode 100644
index 0000000..39a4d8d
--- /dev/null
+++ b/docs/hooks-custom.markdown
@@ -0,0 +1,10 @@
+# Custom Hooks [hooks-custom] #
+
+Custom hooks may also be created and invoked in a Slim application. When a custom hook is invoked with `applyHook()`, it will invoke all callables assigned to that hook. This is exactly how the Slim application's [default hooks](#hooks-default) work. In this example, I apply a custom hook called "my.hook.name". All callables previously registered for this hook will be invoked.
+
+ $app = new Slim();
+ $app->applyHook('my.hook.name');
+
+When you run the above code, any callables previously assigned to the hook **my.hook.name** will be invoked in order of priority (ascending).
+
+You should register callables to a hook before the hook is applied. Think of it this way: when you invoke the Slim application's `applyHook()` instance method, you are asking Slim to invoke all callables already registered for that hook name.
\ No newline at end of file
diff --git a/docs/hooks-default.markdown b/docs/hooks-default.markdown
new file mode 100644
index 0000000..1419f39
--- /dev/null
+++ b/docs/hooks-default.markdown
@@ -0,0 +1,21 @@
+# Default Hooks [hooks-default] #
+
+These are the default hooks always invoked in a Slim application.
+
+slim.before
+: This hook is invoked before the Slim application is run and before output buffering is turned on. This hook is invoked once during the Slim application lifecycle.
+
+slim.before.router
+: This hook is invoked after output buffering is turned on and before the router is dispatched. This hook is invoked once during the Slim application lifecycle.
+
+slim.before.dispatch
+: This hook is invoked before the current matching route is dispatched. Usually this hook is invoked only once during the Slim application lifecycle; however, this hook may be invoked multiple times if a matching route chooses to pass to a subsequent matching route.
+
+slim.after.dispatch
+: This hook is invoked after the current matching route is dispatched. Usually this hook is invoked only once during the Slim application lifecycle; however, this hook may be invoked multiple times if a matching route chooses to pass to a subsequent matching route.
+
+slim.after.router
+: This hook is invoked after the router is dispatched, before the Response is sent to the client, and after output buffering is turned off. This hook is invoked once during the Slim application lifecycle.
+
+slim.after
+: This hook is invoked after output buffering is turned off and after the Response is sent to the client. This hook is invoked once during the Slim application lifecycle.
\ No newline at end of file
diff --git a/docs/hooks-usage.markdown b/docs/hooks-usage.markdown
new file mode 100644
index 0000000..cd4108a
--- /dev/null
+++ b/docs/hooks-usage.markdown
@@ -0,0 +1,33 @@
+# How to use a hook #
+
+A callable is assigned to a hook using the Slim application's `hook()` instance method:
+
+ $app = new Slim();
+ $app->hook('the.hook.name', function () {
+ //Do something
+ });
+
+The first argument is the hook name, and the second argument is the callable. Each hook maintains a priority list of registered callables. By default, each callable assigned to a hook is given a priority of 10. You can give your callable a different priority by passing an integer as the third parameter of the `hook()` method like this:
+
+ $app = new Slim();
+ $app->hook('the.hook.name', function () {
+ //Do something
+ }, 5);
+
+The example above assigns a priority of 5 to the callable. When the hook is called, it will sort all callables assigned to it by priority (ascending). A callable with priority 1 will be invoked before a callable with priority 10.
+
+Hooks do not pass arguments to their callables. If a callable needs to access the Slim application, you can inject the application into the callback with the `use` keyword or with the static `getInstance()` method:
+
+ $app = new Slim();
+
+ //If using PHP >= 5.3
+ $app->hook('the.hook.name', function () use ($app) {
+ // Do something
+ });
+
+ //If using PHP < 5.3
+ $app->hook('the.hook.name', 'nameOfMyHookFunction');
+ function nameOfMyHookFunction() {
+ $app = Slim::getInstance();
+ //Do something
+ }
\ No newline at end of file
diff --git a/docs/hooks.markdown b/docs/hooks.markdown
new file mode 100644
index 0000000..0aa6b9f
--- /dev/null
+++ b/docs/hooks.markdown
@@ -0,0 +1,7 @@
+# Hooks [hooks] #
+
+A Slim application provides a set of hooks to which you can register your own callbacks.
+
+A “hook” is a moment in the Slim application lifecycle at which a priority list of callables assigned to the hook will be invoked. A hook is identified by a string name.
+
+A “callable” is anything that returns `true` for `is_callable()`. A callable is assigned to a hook and is invoked when the hook is called.
\ No newline at end of file
diff --git a/docs/index.txt b/docs/index.txt
new file mode 100644
index 0000000..570e202
--- /dev/null
+++ b/docs/index.txt
@@ -0,0 +1,144 @@
+# Documentation
+#
+# This documentation is written in MultiMarkdown format and may be consolidated into HTML, PDF,
+# ODF, Latex, or other formats using the `mmd_merge` command line utility.
+#
+# Learn more about MultiMarkdown at
+
+welcome.markdown
+
+system-requirements.markdown
+
+instantiation.markdown
+
+ settings.markdown
+
+ names-and-scopes.markdown
+
+ modes.markdown
+
+routing.markdown
+
+ routing-get.markdown
+
+ routing-post.markdown
+
+ routing-put.markdown
+
+ routing-delete.markdown
+
+ routing-options.markdown
+
+ routing-generic.markdown
+
+ routing-custom.markdown
+
+ routing-parameters.markdown
+
+ routing-names.markdown
+
+ routing-conditions.markdown
+
+ routing-middleware.markdown
+
+ routing-helpers.markdown
+
+ routing-helpers-halt.markdown
+
+ routing-helpers-pass.markdown
+
+ routing-helpers-redirect.markdown
+
+ routing-helpers-stop.markdown
+
+ routing-helpers-urlfor.markdown
+
+ routing-indepth.markdown
+
+ routing-indepth-slashes.markdown
+
+ routing-indepth-with-rewrite.markdown
+
+ routing-indepth-without-rewrite.markdown
+
+environment.markdown
+
+request.markdown
+
+ request-method.markdown
+
+ request-parameters.markdown
+
+ request-headers.markdown
+
+ request-cookies.markdown
+
+ request-xhr.markdown
+
+ request-paths.markdown
+
+ request-helpers.markdown
+
+response.markdown
+
+ response-status.markdown
+
+ response-header.markdown
+
+ response-body.markdown
+
+ response-cookies.markdown
+
+ response-helpers.markdown
+
+views.markdown
+
+ views-rendering.markdown
+
+ views-custom.markdown
+
+ views-data.markdown
+
+ views-settings.markdown
+
+caching.markdown
+
+ caching-etag.markdown
+
+ caching-last-modified.markdown
+
+ caching-expires.markdown
+
+middleware.markdown
+
+ middleware-architecture.markdown
+
+ middleware-implementation.markdown
+
+ middleware-add.markdown
+
+hooks.markdown
+
+ hooks-usage.markdown
+
+ hooks-custom.markdown
+
+ hooks-default.markdown
+
+flash.markdown
+
+sessions.markdown
+
+logging.markdown
+
+errors.markdown
+
+ errors-error-handler.markdown
+
+ errors-notfound-handler.markdown
+
+ errors-debug.markdown
+
+ errors-reporting.markdown
+
+ errors-output.markdown
\ No newline at end of file
diff --git a/docs/instantiation.markdown b/docs/instantiation.markdown
new file mode 100644
index 0000000..42bea26
--- /dev/null
+++ b/docs/instantiation.markdown
@@ -0,0 +1,11 @@
+# Instantiation
+
+First `require()` Slim into your application file. If you move the **Slim/** directory elsewhere on your filesystem, it is important that you keep Slim's dependencies in the same directory as Slim.php. Keeping the files together enables you to only require **Slim.php** and have the other files loaded automatically. Assuming the **Slim/** directory is on your include path, you only need to call:
+
+ require 'Slim/Slim.php';
+
+After you require Slim, instantiate your Slim application like this:
+
+ $app = new Slim();
+
+The Slim constructor accepts an optional associative array of settings to customize the Slim application during instantiation (see [settings]).
\ No newline at end of file
diff --git a/docs/logging.markdown b/docs/logging.markdown
new file mode 100644
index 0000000..23669cd
--- /dev/null
+++ b/docs/logging.markdown
@@ -0,0 +1,104 @@
+# Logging [logging] #
+
+A Slim application provides a `Slim_Log` object that logs data to a specific output via a log writer.
+
+## How to Log Messages ##
+
+To log messages from a Slim application, get a reference to the `Slim_Log` object like this:
+
+ $log = $app->getLog();
+
+The `Slim_Log` object provides the following public interface:
+
+ $log->debug( mixed $object );
+ $log->info( mixed $object );
+ $log->warn( mixed $object );
+ $log->error( mixed $object );
+ $log->fatal( mixed $object );
+
+Each `Slim_Log` method accepts one mixed argument. The argument is usually a string, but the argument can be anything. The argument will be passed to the log writer. It is the log writer's responsibility to log arbitrary input appropriately.
+
+## Log Writers ##
+
+The Slim application's `Slim_Log` instance has a log writer. The log writer is responsible for sending a logged message to the appropriate output (e.g. STDERR, a log file, a remote web service, Twitter, or a database). Out of the box, the Slim application's `Slim_Log` object has a log writer instance of class `Slim_LogFileWriter`; this log writer directs output to the resource handle referenced by the application environment's **slim.errors** key (by default, this is "php://stderr"). You may also define and use a custom log writer.
+
+### How to Use a Custom Log Writer ###
+
+A custom log writer must implement the following public interface:
+
+ public function write( mixed $message );
+
+You must tell the Slim application's `Slim_Log` instance to use your writer. You can do so in your application's settings during instantiation like this:
+
+ $app = new Slim(array(
+ 'log.writer' => new MyLogWriter()
+ ));
+
+You may also set a custom log writer with middleware like this:
+
+ class CustomLogWriterMiddleware extends Slim_Middleware {
+ public function call() {
+ //Set the new log writer
+ $log = $this->app->getLog()->setWriter( new MyLogWriter() );
+
+ //Call next middleware
+ $this->next->call();
+ }
+ }
+
+You can set the log writer similarly in an application [hook](#hooks) or [route](#routing-get) callback like this:
+
+ $app->hook('slim.before', function () use ($app) {
+ $app->getLog()->setWriter( new MyLogWriter() );
+ });
+
+If you only need to redirect error output to a different resource, I recommend you [update the Environment's **slim.errors** element](#errors-output) instead of writing and entirely new LogWriter.
+
+## How to Enable or Disable Logging ##
+
+The Slim application's `Slim_Log` object provides the following public methods to enable or disable logging during runtime.
+
+ //Enable logging
+ $app->getLog()->setEnabled(true);
+
+ //Disable logging
+ $app->getLog()->setEnabled(false);
+
+You may enable or disable the `Slim_Log` object during application instantiation like this:
+
+ $app = new Slim(array(
+ 'log.enabled' => true
+ ));
+
+If logging is disabled, the `Slim_Log` object will ignore all logged messages until it is enabled.
+
+## Log Levels ##
+
+The Slim application's `Slim_Log` object provides the following public methods to define the _level_ of messages it will log. When you invoke the `Slim_Log` objects's `debug()`, `info()`, `warn()`, `error()`, or `fatal()` methods, you are inherently assigning a level to the logged message:
+
+Debug
+: Level 4
+
+Info
+: Level 3
+
+Warn
+: Level 2
+
+Error
+: Level 1
+
+Fatal
+: Level 0
+
+Only messages that have a level _less than_ the current `Slim_Log` object's level will be logged. For example, if the `Slim_Log` object's level is "2", the `Slim_Log` object will ignore debug and info messages but will accept warn, error, and fatal messages.
+
+You can set the `Slim_Log` object's level like this:
+
+ $app->getLog()->setLevel(2);
+
+You can set the `Slim_Log` object's level during application instantiation like this:
+
+ $app = new Slim(array(
+ 'log.level' => 2
+ ));
\ No newline at end of file
diff --git a/docs/middleware-add.markdown b/docs/middleware-add.markdown
new file mode 100644
index 0000000..753faf8
--- /dev/null
+++ b/docs/middleware-add.markdown
@@ -0,0 +1,28 @@
+# Add Middleware [middleware-add] #
+
+Use the Slim application's `add()` instance method to add new middleware to a Slim application. New middleware will surround previously added middleware, or the Slim application itself if no middleware has yet been added.
+
+## Example Middleware ##
+
+ class Secret_Middleware extends Slim_Middleware {
+ public function call() {
+ $app = $this->app;
+ $req = $app->request();
+ $res = $app->response();
+ if ( $req->headers('X-Secret-Request') === 'The sun is shining' ) {
+ $res->header('X-Secret-Response', 'But the ice is slippery');
+ }
+ $this->next->call();
+ }
+ }
+
+## Add Middleware ##
+
+ $app = new Slim();
+ $app->add(new Secret_Middleware());
+ $app->get('/foo', function () use ($app) {
+ //Do something
+ });
+ $app->run();
+
+The Slim application's `add()` method accepts one argument: a middleware instance. If the middleware instance requires special configuration, it may implement its own constructor so that it may be configured before it is added to the Slim application.
\ No newline at end of file
diff --git a/docs/middleware-architecture.markdown b/docs/middleware-architecture.markdown
new file mode 100644
index 0000000..faaa1cd
--- /dev/null
+++ b/docs/middleware-architecture.markdown
@@ -0,0 +1,36 @@
+# Middleware Architecture [middleware-architecture] #
+
+Think of a Slim application as the core of an onion. Each layer of the onion is middleware. When you invoke the Slim application's `run()` method, the outer-most middleware layer is invoked first. When ready, that middleware layer is responsible for optionally invoking the next middleware layer that it surrounds. This process steps deeper into the onion — through each middleware layer — until the core Slim application is invoked. This stepped process is possible because each middleware layer, and the Slim application itself, all implement a public `call()` instance method. When you add new middleware to a Slim application, the added middleware will become a new outer layer and surround the previous outer middleware layer (if available) or the Slim application itself.
+
+## Application Reference ##
+
+The purpose of middleware is to inspect, analyze, or modify the application environment, the application request, and the application response before and/or after the Slim application is invoked. It is easy for each middleware to obtain references to the primary Slim application, the Slim application's environment, the Slim application's request, and the Slim application's response:
+
+ class My_Middleware extends Slim_Middleware {
+ public function call() {
+ //The Slim application
+ $app = $this->app;
+
+ //The Environment object
+ $env = $app->environment();
+
+ //The Request object
+ $req = $app->request();
+
+ //The Response object
+ $res = $app->response();
+ }
+ }
+
+Changes made to the environment, request, and response objects will propagate immediately throughout the application and its other middleware layers. This is possible because every middleware layer is given a reference to the same Slim application object.
+
+## Next Middleware Reference ##
+
+Each middleware layer also has a reference to the _next_ inner middleware layer with `$this->next`. It is each middleware's responsibility to optionally call the next middleware. Doing so will allow the Slim application to complete its full lifecycle. If a middleware layer chooses _not_ to call the next inner middleware layer, further inner middleware and the Slim application itself will not be run, and the application response will be returned to the HTTP client as is.
+
+ class My_Middleware extends Slim_Middleware {
+ public function call() {
+ //Optionally call the next middleware
+ $this->next->call();
+ }
+ }
\ No newline at end of file
diff --git a/docs/middleware-implementation.markdown b/docs/middleware-implementation.markdown
new file mode 100644
index 0000000..c1225ae
--- /dev/null
+++ b/docs/middleware-implementation.markdown
@@ -0,0 +1,11 @@
+# Middleware Implementation [middleware-implementation] #
+
+Middleware **must** extend the `Slim_Middleware` class and implement a public `call()` instance method. The `call()` method does not accept arguments. Otherwise, each middleware may implement its own constructor, properties, and methods. I encourage you to look at Slim's built-in middleware for working examples (e.g. `Slim/Middleware/ContentTypes.php` or `Slim/Middleware/SessionCookie.php`).
+
+This example is the most simple implementation of Slim application middleware. It extends `Slim_Middleware`, implements a public `call()` method, and calls the next inner middleware.
+
+ class My_Middleware extends Slim_Middleware {
+ public function call() {
+ $this->next->call();
+ }
+ }
\ No newline at end of file
diff --git a/docs/middleware.markdown b/docs/middleware.markdown
new file mode 100644
index 0000000..faed9a9
--- /dev/null
+++ b/docs/middleware.markdown
@@ -0,0 +1,3 @@
+# Middleware [middleware] #
+
+The Slim Framework implements a derivation of the Rack protocol. As a result, a Slim application can have middleware that may inspect, analyze, or modify the application environment variables, the application Request object, and the application Response object before and/or after the Slim application is invoked.
\ No newline at end of file
diff --git a/docs/modes.markdown b/docs/modes.markdown
new file mode 100644
index 0000000..f6ae132
--- /dev/null
+++ b/docs/modes.markdown
@@ -0,0 +1,54 @@
+# Application Modes [application-modes]
+
+It is common practice to run web applications in a specific mode depending on the current state of the project. If you are developing the application, you will run the application in "development" mode; if you are testing the application, you will run the application in "test" mode; if you launch the application, you will run the application in "production" mode.
+
+Slim supports the concept of modes in that you may define your own modes and prompt Slim to prepare itself appropriately in the current mode. For example, you may want to enable debugging in "development" mode but not in "production" mode. The examples below demonstrate how to configure Slim differently for a given mode.
+
+## What is a mode? [what-is-a-mode]
+
+Technically, an application mode is merely a string of text — like "development" or "production" — that has an associated callback function used to prepare the Slim application appropriately. The application mode may be anything you like: “testing”, “production”, “development”, or even “foo”.
+
+## How do I set the Slim application mode? [how-to-set-mode]
+
+### Use an environment variable
+
+If Slim sees an environment variable named **SLIM_MODE**, it will set the current application mode to that variable’s value. This lets you define the Slim application mode programmatically if calling the application from the command line.
+
+ $_ENV['SLIM_MODE'] = 'production';
+
+### Use an application setting
+
+If an environment variable is not found, Slim will next look for the mode in the application settings.
+
+ $app = new Slim(array(
+ 'mode' => 'production'
+ ));
+
+### Default mode
+
+If the environment variable and application setting are not found, Slim will set the application mode to “development”.
+
+## Configure Slim for a specific mode [configure-for-mode]
+
+After you instantiate a Slim application, you may configure the Slim application for a specific mode with the `configureMode()` application instance method. This method accepts two arguments: the first is the name of the target mode, and the second is anything that returns `true` for `is_callable()` that will be immediately invoked if the first argument matches the current application mode.
+
+In this example, assume the current application mode is “production”. Only the callable associated with the “production” mode will be invoked. The callable associated with the “development” mode will be ignored until the application mode is changed to "development".
+
+ $app = new Slim(array(
+ 'mode' => 'production'
+ ));
+
+ $app->configureMode('production', function () use ($app) {
+ $app->config(array(
+ 'log.enable' => true,
+ 'log.path' => '../logs',
+ 'debug' => false
+ ));
+ });
+
+ $app->configureMode('development', function () use ($app) {
+ $app->config(array(
+ 'log.enable' => false,
+ 'debug' => true
+ ));
+ });
\ No newline at end of file
diff --git a/docs/names-and-scopes.markdown b/docs/names-and-scopes.markdown
new file mode 100644
index 0000000..83e9c32
--- /dev/null
+++ b/docs/names-and-scopes.markdown
@@ -0,0 +1,58 @@
+# Application Names and Scopes [names-and-scopes]
+
+When you build a Slim application you will enter various scopes in your code (e.g. global scope and function scope). You will likely need a reference to your Slim application in each scope. You can use application names, or `use` with PHP >= 5.3, to obtain a reference to your Slim application.
+
+## Application Names [application-names]
+
+Every Slim application may be given a name. **This is optional**. Names help you get a reference to a Slim application instance in any scope throughout your code. Here is how you set and get an application's name:
+
+ $app = new Slim();
+ $app->setName('foo');
+ $theName = $app->getName(); //returns "foo"
+
+## Scope Resolution [scope-resolution]
+
+So how do you get a reference to your Slim application? The example below demonstrates how to obtain a reference to a Slim application within a route callback function. The Slim `$app` variable is accessed in the global scope to define the GET route. We also need to access the Slim `$app` variable within the route’s callback scope to render a template.
+
+ $app = new Slim();
+ $app->get('/foo', function () {
+ $app->render('foo.php'); //<--ERROR
+ });
+
+This fails because we cannot access the Slim `$app` variable inside of the route callback function. In PHP >= 5.3 we can inject the Slim `$app` variable into the anonymous function scope with the `use` keyword:
+
+ $app = new Slim();
+ $app->get('/foo', function () use ($app) {
+ $app->render('foo.php'); //<--SUCCESS
+ });
+
+Now it works correctly. In PHP < 5.3, you can use `Slim::getInstance()` instead.
+
+ $app = new Slim();
+ $app->get('/foo', 'foo');
+ function foo() {
+ $app = Slim::getInstance();
+ $app->render('foo.php');
+ }
+
+The first instantiated Slim application is automatically assigned the name "default". If you invoke `Slim::getInstance()` without an argument, it will return the Slim application that is named "default".
+
+If you instantiate multiple Slim applications with PHP < 5.3, it is important that you assign each Slim application a name.
+
+ $app1 = new Slim();
+ $app1->setName('myApp1');
+
+ $app2 = new Slim();
+ $app2->setName('myApp2');
+
+ $app1->get('/foo', 'appOneCallback');
+ function appOneCallback() {
+ $app = Slim::getInstance('myApp1');
+ }
+
+ $app2->get('/foo', 'appTwoCallback');
+ function appTwoCallback() {
+ $app = Slim::getInstance('myApp2');
+ }
+
+Invoking `Slim::getInstance()` without an argument will return a reference to `$app1` because that is the first Slim application instantiated.
\ No newline at end of file
diff --git a/docs/request-cookies.markdown b/docs/request-cookies.markdown
new file mode 100644
index 0000000..778fd92
--- /dev/null
+++ b/docs/request-cookies.markdown
@@ -0,0 +1,24 @@
+# Request Cookies [request-cookies] #
+
+## Get Cookies [request-cookies-basic] ##
+
+A Slim application will automatically parse cookies sent with the current HTTP request. You can fetch cookie values with the Slim application's `getCookie()` instance method like this:
+
+ $foo = $app->getCookie('foo');
+
+Only Cookies **sent** with the current HTTP request are accessible with this method. If you set a cookie during the current request, it will not be accessible with this method until the subsequent request. If you want to fetch an array of all cookies sent with the current request, you must use the Request object's `cookies()` instance method like this:
+
+ //Get all cookies as associative array
+ $cookies = $app->request()->cookies();
+
+## Get Encrypted Cookies [request-cookies-encrypted] ##
+
+If you previously set an encrypted cookie, you can fetch its decrypted value with the Slim application's `getEncryptedCookie()` instance method like this:
+
+ $cookieValue = $app->getEncryptedCookie('foo');
+
+If the cookie was modified while with the HTTP client, Slim will automatically destroy the cookie's value and invalidate the cookie in the next HTTP response. You can disable this behavior by passing `false` as the second argument:
+
+ $cookieValue = $app->getEncryptedCookie('foo', false);
+
+Whether you destroy invalid cookies or not, `NULL` is returned if the cookie does not exist or is invalid.
\ No newline at end of file
diff --git a/docs/request-headers.markdown b/docs/request-headers.markdown
new file mode 100644
index 0000000..39f1d41
--- /dev/null
+++ b/docs/request-headers.markdown
@@ -0,0 +1,15 @@
+# Request Headers [request-headers] #
+
+A Slim application will automatically parse all HTTP request headers. You can access the Request headers using the Request `headers()` instance method.
+
+ $app = new Slim();
+
+ //Fetch all request headers as associative array
+ $headers = $app->request()->headers();
+
+ //Fetch only the ACCEPT_CHARSET header
+ $charset = $app->request()->headers('ACCEPT_CHARSET'); //returns string or NULL
+
+In the second example, the `headers()` method will either return a string value or `NULL` if the header with the given name does not exist.
+
+The HTTP specification states that HTTP header names may be uppercase, lowercase, or mixed-case. Slim is smart enough to parse and return header values whether you request a header value using upper, lower, or mixed case header name, with either underscores or dashes. So use the naming convention with which you are most comfortable.
\ No newline at end of file
diff --git a/docs/request-helpers.markdown b/docs/request-helpers.markdown
new file mode 100644
index 0000000..18b847e
--- /dev/null
+++ b/docs/request-helpers.markdown
@@ -0,0 +1,46 @@
+# Request Helpers [request-helpers] #
+
+Slim provides several helper methods that help you fetch common HTTP request information.
+
+ //Fetch HTTP request content type (e.g. "application/json;charset=utf-8"
+ $contentType = $app->request()->getContentType();
+
+ //Fetch HTTP request media type (e.g "application/json")
+ $mediaType = $app->request()->getMediaType();
+
+ //Fetch HTTP request media type params (e.g [charset => "utf-8"])
+ $mediaTypeParams = $app->request()->getMediaTypeParams();
+
+ //Fetch HTTP request content type charset (e.g. "utf-8")
+ $charset = $app->request()->getContentCharset();
+
+ //Fetch HTTP request content length
+ $contentLength = $app->request()->getContentLength();
+
+ //Fetch HTTP request host (e.g. "slimframework.com")
+ $host = $app->request()->getHost();
+
+ //Fetch HTTP request host with port (e.g. "slimframework.com:80")
+ $hostAndPort = $app->request()->getHostWithPort();
+
+ //Fetch HTTP request port (e.g. 80)
+ $port = $app->request()->getPort();
+
+ //Fetch HTTP request scheme (e.g. "http" or "https")
+ $scheme = $app->request()->getScheme();
+
+ //Fetch HTTP request URI path (root URI + resource URI)
+ $path = $app->request()->getPath();
+
+ //Fetch HTTP request URL (scheme + host [ + port if non-standard ])
+ $url = $app->request()->getUrl();
+
+ //Fetch HTTP request IP address
+ $ip = $app->request()->getIp();
+
+ //Fetch HTTP request referer
+ $ref = $app->request()->getReferer();
+ $ref = $app->request()->getReferrer();
+
+ //Fetch HTTP request user agent string
+ $ua = $app->request()->getUserAgent();
\ No newline at end of file
diff --git a/docs/request-method.markdown b/docs/request-method.markdown
new file mode 100644
index 0000000..ed62ea8
--- /dev/null
+++ b/docs/request-method.markdown
@@ -0,0 +1,27 @@
+# Request Method [request-method] #
+
+Every HTTP request has a method (e.g. GET or POST). You can obtain the current HTTP request method via the Slim application's Request object:
+
+ //What is the request method?
+ $method = $app->request()->getMethod(); //returns "GET", "POST", etc.
+
+ //Is this a GET request?
+ $app->request()->isGet(); //true or false
+
+ //Is this a POST request?
+ $app->request()->isPost(); //true or false
+
+ //Is this a PUT request?
+ $app->request()->isPut(); //true or false
+
+ //Is this a DELETE request?
+ $app->request()->isDelete(); //true or false
+
+ //Is this a HEAD request?
+ $app->request()->isHead(); //true or false
+
+ //Is this a OPTIONS request?
+ $app->request()->isOptions(); //true or false
+
+ //Is this a request made with Ajax/XHR?
+ $app->request()->isAjax(); //true or false
\ No newline at end of file
diff --git a/docs/request-parameters.markdown b/docs/request-parameters.markdown
new file mode 100644
index 0000000..3717eba
--- /dev/null
+++ b/docs/request-parameters.markdown
@@ -0,0 +1,24 @@
+# Request Parameters [request-parameters] #
+
+An HTTP request may have associated parameters (not to be confused with [Route parameters](#routing-paramters)). The GET, POST, or PUT parameters sent with the current HTTP request are exposed via the Slim application's Request object.
+
+If you want to quickly fetch a request parameter value without considering its type, use the `params()` Request method:
+
+ $paramValue = $app->request()->params('paramName');
+
+The `params()` Request instance method will first search **PUT** parameters, then **POST** parameters, then **GET** parameters. If no parameter is found, `NULL` is returned. If you only want to search for a specific type of parameter, you can use these Request instance methods instead:
+
+ //GET parameter
+ $paramValue = $app->request()->get('paramName');
+
+ //POST parameter
+ $paramValue = $app->request()->post('paramName');
+
+ //PUT parameter
+ $paramValue = $app->request()->put('paramName');
+
+If a parameter does not exist, each method above will return `NULL`. You can also invoke any of these functions without an argument to obtain an array of all parameters of the given type:
+
+ $allGetParams = $app->request()->get();
+ $allPostParams = $app->request()->post();
+ $allPutParams = $app->request()->put();
\ No newline at end of file
diff --git a/docs/request-paths.markdown b/docs/request-paths.markdown
new file mode 100644
index 0000000..2f5ec1d
--- /dev/null
+++ b/docs/request-paths.markdown
@@ -0,0 +1,19 @@
+# Request Paths [request-paths] #
+
+Every HTTP request received by a Slim application will have a root URI and a resource URI.
+
+The **root URI** is the physical URL path of the directory in which the Slim application is instantiated and run. If a Slim application is instantiated in **index.php** within the top-most directory of the virtual host's document root, the root URI will be an empty string. If a Slim application is instantiated and run in **index.php** within a physical subdirectory of the virtual host's document root, the root URI will be the path to that subdirectory *with* a leading slash and *without* a trailing slash.
+
+The **resource URI** is the virtual URI path of an application resource. The resource URI will be matched to the Slim application's routes.
+
+Here's an example. Assume the Slim application is installed in a physical subdirectory **/foo** beneath your virtual host's document root. Also assume the full HTTP request URL (what you'd see in the browser location bar) is **/foo/books/1**. The root URI is **/foo** (the path to the physical directory in which the Slim application is instantiated) and the resource URI is **/books/1** (the path to the application resource).
+
+You can get the Request’s root URI and resource URI like this:
+
+ $app = new Slim();
+
+ //Get the Request root URI
+ $rootUri = $app->request()->getRootUri();
+
+ //Get the Request resource URI
+ $resourceUri = $app->request()->getResourceUri();
\ No newline at end of file
diff --git a/docs/request-xhr.markdown b/docs/request-xhr.markdown
new file mode 100644
index 0000000..3fac963
--- /dev/null
+++ b/docs/request-xhr.markdown
@@ -0,0 +1,8 @@
+# XMLHttpRequest [request-xhr] #
+
+When using a Javascript framework like MooTools or jQuery to execute an XMLHttpRequest, the XMLHttpRequest will usually be sent with a 'X-Requested-With' HTTP header. The Slim application will detect the HTTP request's ‘X-Requested-With’ header and flag the request as such. If for some reason an XMLHttpRequest cannot be sent with the 'X-Requested-With' HTTP header, you can force the Slim application to assume an HTTP request is an XMLHttpRequest by setting a GET, POST, or PUT parameter in the HTTP request named “isajax” with a truthy value.
+
+Here's how you tell if the current request is an XHR/Ajax request:
+
+ $isXHR = $app->request()->isAjax(); //true or false
+ $isXHR = $app->request()->isXhr(); //true or false; alias of `isAjax()`
\ No newline at end of file
diff --git a/docs/request.markdown b/docs/request.markdown
new file mode 100644
index 0000000..06ca31c
--- /dev/null
+++ b/docs/request.markdown
@@ -0,0 +1,7 @@
+# The Request Object [request] #
+
+Each Slim application instance has one Request object. The Request object is an abstraction of the current HTTP request and allows you to easily interact with the [Environment](#environment) variables. Although each Slim application includes a default Request object, the `Slim_Http_Request` class is idempotent; you may instantiate the class at will (in [Middleware](#middleware) or elsewhere in your Slim application) without affecting the application as a whole.
+
+You can obtain a reference to the Slim application's Request object like this:
+
+ $app->request();
\ No newline at end of file
diff --git a/docs/response-body.markdown b/docs/response-body.markdown
new file mode 100644
index 0000000..739535f
--- /dev/null
+++ b/docs/response-body.markdown
@@ -0,0 +1,16 @@
+# Response Body [response-body] #
+
+The HTTP response returned to the client will have a body. The HTTP body is the actual content of the HTTP response delivered to the client. You can use the Slim application's Response object to set the HTTP response's body like this:
+
+ $response = $app->response();
+ $response->body('Foo'); //The body is now "Foo" (overwrites)
+ $response->write('Bar'); //The body is now "FooBar" (appends)
+
+When you overwrite or append the Response's body, the Response object will automatically set the `Content-Length` header based on the byte size of the new response body.
+
+You can fetch the Response object's body using the same Response `body()` instance method without an argument like this:
+
+ $response = $app->response();
+ $body = $response->body();
+
+Usually, you will never need to manually set the Response body with the Response `body()` or `write()` methods; instead, the Slim application will do this for you. Whenever you `echo()` content from within a route callback, the `echo()`'d content is captured in an output buffer and later appended to the Response body before the HTTP response is returned to the client.
\ No newline at end of file
diff --git a/docs/response-cookies.markdown b/docs/response-cookies.markdown
new file mode 100644
index 0000000..d08220a
--- /dev/null
+++ b/docs/response-cookies.markdown
@@ -0,0 +1,47 @@
+# Response Cookies [response-cookies] #
+
+The Slim application instance provides several helper methods to send cookies with the HTTP response.
+
+## Set Cookie ##
+
+This example demonstrates how to use the Slim application's `setCookie()` instance method to create a Cookie that will be sent with the HTTP response:
+
+ $app->setCookie('foo', 'bar', '2 days');
+
+This creates a cookie with name **foo** and value **bar** that expires two days from now. You may also provide additional cookie properties, including path, domain, secure, and httponly. The Slim application's `setCookie()` method uses the same signature as PHP's native `setCookie()` function.
+
+ $app->setCookie($name, $value, $expiresAt, $path, $domain, $secure, $httponly);
+
+The last argument, `$httpOnly`, was added in PHP 5.2. However, because Slim's underlying cookie implementation does not rely on PHP's native `setCookie()` function, you may use the `$httpOnly` cookie property even with PHP 5.1.
+
+## Set Encrypted Cookie [response-cookies-encrypted] ##
+
+You may also create encrypted cookies using the Slim application's `setEncryptedCookie()` instance method. This method acts the same as the Slim application's `setCookie()` instance method demonstrated above, but it will encrypt the cookie value using the AES-256 cipher and your own secret key. To use encryption, you **must** define your encryption key when you instantiate your Slim application like this:
+
+ $app = new Slim(array(
+ 'cookies.secret_key' => 'my_secret_key'
+ ));
+
+If you prefer, you may also change the default cipher and cipher mode, too:
+
+ $app = new Slim(array(
+ 'cookies.secret_key' => 'my_secret_key',
+ 'cookies.cipher' => MCRYPT_RIJNDAEL_256,
+ 'cookies.cipher_mode' => MCRYPT_MODE_CBC
+ ));
+
+The encrypted cookie value is hashed and later verified to ensure data integrity so that its value is not changed while on the HTTP client.
+
+## Delete Cookie [response-cookies-delete] ##
+
+You can delete a cookie using the Slim application's `deleteCookie()` instance method. This will remove the cookie from the HTTP client before the next HTTP request. This method accepts the same signature as the Slim application's `setCookie()` instance method, just without the `$expires` argument. Only the first argument is required.
+
+ $app->deleteCookie('foo');
+
+Or if you need to also specify the **path** and **domain**:
+
+ $app->deleteCookie('foo', '/', 'foo.com');
+
+You may also further specify the **secure** and **httponly** properties, too:
+
+ $app->deleteCookie('foo', '/', 'foo.com', true, true);
\ No newline at end of file
diff --git a/docs/response-header.markdown b/docs/response-header.markdown
new file mode 100644
index 0000000..12b8c06
--- /dev/null
+++ b/docs/response-header.markdown
@@ -0,0 +1,15 @@
+# Response Header [response-header] #
+
+The HTTP response returned to the HTTP client will have a header. The HTTP header is a list of keys and values that provide metadata about the HTTP response. You can use the Slim application's Response object to set the HTTP response's header. The Response object is special because it acts like an array. Here's an example.
+
+ $response = $app->response();
+ $response['Content-Type'] = 'application/json';
+ $response['X-Powered-By'] = 'Slim';
+
+Just the same, you can also fetch headers from the Response object like this:
+
+ $response = $app->response();
+ $contentType = $response['Content-Type'];
+ $poweredBy = $response['X-Powered-By'];
+
+If a header with the given name does not exist, `NULL` is returned instead. You may specify header names with upper, lower, or mixed case with dashes or underscores. Use the naming convention with which you are most comfortable.
\ No newline at end of file
diff --git a/docs/response-helpers.markdown b/docs/response-helpers.markdown
new file mode 100644
index 0000000..be4b248
--- /dev/null
+++ b/docs/response-helpers.markdown
@@ -0,0 +1,52 @@
+# Response Helpers [response-helpers] #
+
+The Response object provides several instance methods to help you inspect and interact with the underlying HTTP response.
+
+## Finalize [response-helpers-finalize] ##
+
+The Response object's `finalize()` method returns a numeric array of status, header, and body. The status is an integer; the header is an iterable data structure; and the body is a string. Were you to create a new Response object in the Slim application or in middleware, you would call this method to produce the status, header, and body for the underlying response.
+
+ $res = new Slim_Http_Response();
+ $res->status(400);
+ $res->write('You made a bad request');
+ $res['Content-Type'] = 'text/plain';
+ $array = $res->finalize(); //returns [200, ['Content-type' => 'text/plain'], 'You made a bad request']
+
+## Redirect [response-helpers-redirect] ##
+
+The Response object's `redirect()` method will help you quickly set the status and **Location:** header needed to return a **3xx Redirect** response.
+
+ $app->response()->redirect('/foo', 303);
+
+In this example, the Response will now have a **Location:** header with value "/foo" and a 303 status code.
+
+## Status Inspection [response-helpers-inspection] ##
+
+The Response object provides several methods to help you quickly inspect the type of Response based on its status. All return a boolean value. They are:
+
+ //Is this an informational response?
+ $app->response()->isInformational();
+
+ //Is this a 200 OK response?
+ $app->response()->isOk();
+
+ //Is this a 2xx successful response?
+ $app->response()->isSuccessful();
+
+ //Is this a 3xx redirection response?
+ $app->response()->isRedirection();
+
+ //Is this a specific redirect response? (301, 302, 303, 307)
+ $app->response()->isRedirect();
+
+ //Is this a forbidden response?
+ $app->response()->isForbidden();
+
+ //Is this a not found response?
+ $app->response()->isNotFound();
+
+ //Is this a client error response?
+ $app->response()->isClientError();
+
+ //Is this a server error response?
+ $app->response()->isServerError();
\ No newline at end of file
diff --git a/docs/response-status.markdown b/docs/response-status.markdown
new file mode 100644
index 0000000..9eec58a
--- /dev/null
+++ b/docs/response-status.markdown
@@ -0,0 +1,9 @@
+# Response Status [response-status] #
+
+The HTTP response returned to the client will have a status code indicating the response's type (e.g. 200 OK, 400 Bad Request, or 500 Server Error). You can use the Slim application's Response object to set the HTTP response's status like this:
+
+ $app->response()->status(400);
+
+You only need to set the Response object's status if you intend to return an HTTP response that does not have a **200 OK** status. You can just as easily fetch the Response object's current HTTP status by invoking the same method without an argument, like this:
+
+ $status = $app->response()->status(); //returns int
\ No newline at end of file
diff --git a/docs/response.markdown b/docs/response.markdown
new file mode 100644
index 0000000..8d3fb43
--- /dev/null
+++ b/docs/response.markdown
@@ -0,0 +1,9 @@
+# The Response Object [response] #
+
+Each Slim application instance has one Response object. The Response object is a high-level interface that allows you to easily interact with the HTTP response that is returned to the HTTP client. Although each Slim application includes a default Response object, the `Slim_Http_Response` class is idempotent; you may instantiate the class at will (in [Middleware](#middleware) or elsewhere in your Slim application) without affecting the application as a whole.
+
+You can obtain a reference to the Slim application's Response object like this:
+
+ $app->response();
+
+An HTTP response has three primary properties: the status code, the header, and the body. The Response object provides several helper methods, described next, that help you interact with these HTTP response properties in your Slim application. The default Response object in each Slim application will return a **200 OK** HTTP response with the **text/html** content type.
\ No newline at end of file
diff --git a/docs/routing-conditions.markdown b/docs/routing-conditions.markdown
new file mode 100644
index 0000000..4b76f5e
--- /dev/null
+++ b/docs/routing-conditions.markdown
@@ -0,0 +1,30 @@
+# Route Conditions [routing-conditions] #
+
+Slim lets you assign conditions to route parameters. If the specified conditions are not met, the route is not run. For example, if you need a route with a second segment that must be a valid 4-digit year, you could enforce this condition like this:
+
+ $app = new Slim();
+ $app->get('/archive/:year', function ($year) {
+ echo "You are viewing archives from $year";
+ })->conditions(array('year' => '(19|20)\d\d'));
+
+Invoke the `conditions()` Route method. The first and only argument is an associative array with keys that match any of the route’s parameters and values that are regular expressions.
+
+## Application-wide Route Conditions
+
+If many of your Slim application Routes accept the same parameters and use the same conditions, you can define default application-wide Route conditions like this:
+
+ Slim_Route::setDefaultConditions(array(
+ 'firstName' => '[a-zA-Z]{3,}'
+ ));
+
+Define application-wide route conditions before you define application routes. When you define a route, the route will automatically be assigned any application-wide Route conditions defined with `Slim\_Route::setDefaultConditions()`. If for whatever reason you need to get the application-wide default route conditions, you can fetch them with `Slim_Route::getDefaultConditions()`; this static method returns an array exactly as the default route conditions were defined.
+
+You may override a default route condition by redefining the route’s condition when you define the route, like this:
+
+ $app = new Slim();
+ $app->get('/hello/:firstName', $callable)->conditions(array('firstName' => '[a-z]{10,}'));
+
+You may append new conditions to a given route like this:
+
+ $app = new Slim();
+ $app->get('/hello/:firstName/:lastName', $callable)->conditions(array('lastName' => '[a-z]{10,}'));
diff --git a/docs/routing-custom.markdown b/docs/routing-custom.markdown
new file mode 100644
index 0000000..b008b85
--- /dev/null
+++ b/docs/routing-custom.markdown
@@ -0,0 +1,29 @@
+# Custom Routes [routing-custom] #
+
+## One Route, Multiple HTTP Methods ##
+
+Sometimes you may need a route to respond to multiple HTTP methods; sometimes you may need a route to respond to a custom HTTP method. You can accomplish both with the `via()` Route method. This example demonstrates how to map a resource URI to a callback that responds to multiple HTTP methods.
+
+ $app = new Slim();
+ $app->map('/foo/bar', function() {
+ echo "I respond to multiple HTTP methods!";
+ })->via('GET', 'POST');
+ $app->run();
+
+The route defined in this example will respond to both GET and POST requests for the resource identified by "/foo/bar". Specify each appropriate HTTP method as a separate string argument to the `via()` Route method. Like other Route methods (e.g. `name()` and `conditions()`), the `via()` Route method is chainable:
+
+ $app = new Slim();
+ $app->map('/foo/bar', function() {
+ echo "Fancy, huh?";
+ })->via('GET', 'POST')->name('foo');
+ $app->run();
+
+## One Route, Custom HTTP Methods ##
+
+The `via()` Route method is not limited to just GET, POST, PUT, DELETE, and OPTIONS methods. You may also specify your own custom HTTP methods (e.g. if you were responding to WebDAV HTTP requests). You can define a route that responds to a custom "FOO" HTTP method like this:
+
+ $app = new Slim();
+ $app->map('/hello', function() {
+ echo "Hello";
+ })->via('FOO');
+ $app->run();
\ No newline at end of file
diff --git a/docs/routing-delete.markdown b/docs/routing-delete.markdown
new file mode 100644
index 0000000..a231399
--- /dev/null
+++ b/docs/routing-delete.markdown
@@ -0,0 +1,32 @@
+# DELETE Routes [routing-delete] #
+
+Use the `delete()` application instance method to map a callback function to a resource URI that is requested with the HTTP DELETE method.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->delete('/books/:id', function ($id) {
+ //Delete book identified by $id
+ });
+
+ //For PHP < 5.3
+ $app->delete('/books/:id', 'delete_book');
+ function delete_book($id) {
+ //Delete book identified by $id
+ }
+
+In this example, an HTTP DELETE request for “/books/1” will invoke the associated callback function.
+
+The first argument of the `delete()` application instance method is the resource URI. The last argument is anything that returns `true` for `is_callable()`. I encourage you to use PHP >= 5.3 so you may take advantage of anonymous functions.
+
+## Method Override ##
+
+Unfortunately, modern browsers do not provide native support for HTTP DELETE requests. To work around this limitation, ensure your HTML form’s **method** attribute is “post”, then add a method override parameter to your HTML form like this:
+
+
+
+If you are using [Backbone.js](http://documentcloud.github.com/backbone/) or a command-line HTTP client, you may also override the HTTP method by using the `X-HTTP-Method-Override` header.
\ No newline at end of file
diff --git a/docs/routing-generic.markdown b/docs/routing-generic.markdown
new file mode 100644
index 0000000..6232417
--- /dev/null
+++ b/docs/routing-generic.markdown
@@ -0,0 +1,11 @@
+# Generic Routes [routing-generic] #
+
+Slim provides the `map()` application instance method to define generic routes that are not immediately associated with an HTTP method.
+
+ $app = new Slim();
+ $app->map('/generic/route', function () {
+ echo "I'm a generic route!";
+ });
+ $app->run();
+
+This example always returns a **404 Not Found** response because the "/generic/route" route does not respond to any HTTP methods. Use the [via()](#routing-custom) method (available on Route objects), to assign one or many HTTP methods to a generic route.
\ No newline at end of file
diff --git a/docs/routing-get.markdown b/docs/routing-get.markdown
new file mode 100644
index 0000000..b6f0b29
--- /dev/null
+++ b/docs/routing-get.markdown
@@ -0,0 +1,20 @@
+# GET Routes [routing-get] #
+
+Use the `get()` application instance method to map a callback function to a resource URI that is requested with the HTTP GET method.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->get('/books/:id', function ($id) {
+ //Show book identified by $id
+ });
+
+ //For PHP < 5.3
+ $app->get('/books/:id', 'show_book');
+ function show_book($id) {
+ //Show book identified by $id
+ }
+
+In this example, an HTTP GET request for “/books/1” will invoke the associated callback function, passing “1” as the callback function argument.
+
+The first argument of the `get()` application instance method is the resource URI. The last argument is anything that returns `true` for `is_callable()`. I encourage you to use PHP >= 5.3 so you may take advantage of anonymous functions.
\ No newline at end of file
diff --git a/docs/routing-helpers-halt.markdown b/docs/routing-helpers-halt.markdown
new file mode 100644
index 0000000..f3e556e
--- /dev/null
+++ b/docs/routing-helpers-halt.markdown
@@ -0,0 +1,22 @@
+# Halt [routing-helpers-halt] #
+
+The `halt()` application instance method will immediately return an HTTP response with a given status code and body. This method accepts two arguments: the HTTP status code and an optional message. Slim will immediately halt the current application and send an HTTP response to the client with the specified status and optional message (as the response body). This will override the existing Response object.
+
+ $app = new Slim();
+
+ //Send a default 500 error response
+ $app->halt(500);
+
+ //Or if you encounter a Balrog...
+ $app->halt(403, 'You shall not pass!');
+
+If you would like to render a template with a list of error messages, you should use the `render()` application instance method instead.
+
+ $app = new Slim();
+ $app->get('/foo', function () use ($app) {
+ $errorData = array('error' => 'Permission Denied');
+ $app->render('errorTemplate.php', $errorData, 403);
+ });
+ $app->run();
+
+The `halt()` application instance method may send any type of HTTP response to the client: informational, success, redirect, not found, client error, or server error.
\ No newline at end of file
diff --git a/docs/routing-helpers-pass.markdown b/docs/routing-helpers-pass.markdown
new file mode 100644
index 0000000..5603e8f
--- /dev/null
+++ b/docs/routing-helpers-pass.markdown
@@ -0,0 +1,13 @@
+# Pass [routing-helpers-pass] #
+
+A route can tell the Slim application to continue to the next matching route with the `pass()` application instance method. When this method is invoked, the Slim application will immediately stop processing the current matching route and invoke the next matching route. If no subsequent matching route is found, a **404 Not Found** response is sent to the client. Here is an example. Assume an HTTP request for "GET /hello/Frank".
+
+ $app = new Slim();
+ $app->get('/hello/Frank', function () use ($app) {
+ echo "You won't see this...";
+ $app->pass();
+ });
+ $app->get('/hello/:name', function ($name) use ($app) {
+ echo "But you will see this!";
+ });
+ $app->run();
\ No newline at end of file
diff --git a/docs/routing-helpers-redirect.markdown b/docs/routing-helpers-redirect.markdown
new file mode 100644
index 0000000..7bcfa6e
--- /dev/null
+++ b/docs/routing-helpers-redirect.markdown
@@ -0,0 +1,19 @@
+# Redirect [routing-helpers-redirect] #
+
+It is easy to redirect the client to another URL with the `redirect()` application instance method. This method accepts two arguments: the first argument is the URL to which the client will redirect; the second optional argument is the HTTP status code. By default the `redirect()` application instance method will send an **HTTP 302 Temporary Redirect** response.
+
+ $app = new Slim();
+ $app->get('/foo', function () use ($app) {
+ $app->redirect('/bar');
+ });
+ $app->run();
+
+Or if you wish to use a permanent redirect, you must specify the destination URL as the first parameter and the HTTP status code as the second parameter.
+
+ $app = new Slim();
+ $app->get('/old', function () use ($app) {
+ $app->redirect('/new', 301);
+ });
+ $app->run();
+
+This method will automatically set the **Location:** header. The HTTP redirect response will be sent to the HTTP client immediately.
\ No newline at end of file
diff --git a/docs/routing-helpers-stop.markdown b/docs/routing-helpers-stop.markdown
new file mode 100644
index 0000000..08846a8
--- /dev/null
+++ b/docs/routing-helpers-stop.markdown
@@ -0,0 +1,11 @@
+# Stop [routing-helpers-stop] #
+
+The `stop()` application instance method will stop the Slim application and send the current HTTP response to the client as is. No *ifs*, *ands*, or *buts*.
+
+ $app = new Slim();
+ $app->get('/foo', function () use ($app) {
+ echo "You will see this...";
+ $app->stop();
+ echo "But not this";
+ });
+ $app->run();
\ No newline at end of file
diff --git a/docs/routing-helpers-urlfor.markdown b/docs/routing-helpers-urlfor.markdown
new file mode 100644
index 0000000..6c90e99
--- /dev/null
+++ b/docs/routing-helpers-urlfor.markdown
@@ -0,0 +1,15 @@
+# URL For [routing-helpers-urlfor] #
+
+The `urlFor()` application instance method lets you dynamically create URLs *for a named route* so that, were a route pattern to change, your URLs would update automatically without breaking your application. This example demonstrates how to generate URLs for a named route.
+
+ $app = new Slim();
+
+ //Create a named route
+ $app->get('/hello/:name', function ($name) use ($app) {
+ echo "Hello $name";
+ })->name('hello');
+
+ //Generate a URL for the named route
+ $url = $app->urlFor('hello', array('name' => 'Josh'));
+
+In this example, `$url` is "/hello/Josh". To use the `urlFor()` application instance method, you must first assign a name to a route. Next, invoke the `urlFor()` application instance method. The first argument is the name of the route, and the second argument is an associative array used to replace the route’s URL parameters with actual values; the array's keys must match parameters in the route's URI and the values will be used as substitutions.
\ No newline at end of file
diff --git a/docs/routing-helpers.markdown b/docs/routing-helpers.markdown
new file mode 100644
index 0000000..93d7b06
--- /dev/null
+++ b/docs/routing-helpers.markdown
@@ -0,0 +1,3 @@
+# Route Helpers [routing-helpers] #
+
+Slim provides several helper methods (exposed via the `Slim` application instance) that will help you control the flow of your application.
\ No newline at end of file
diff --git a/docs/routing-indepth-slashes.markdown b/docs/routing-indepth-slashes.markdown
new file mode 100644
index 0000000..e89029f
--- /dev/null
+++ b/docs/routing-indepth-slashes.markdown
@@ -0,0 +1,17 @@
+# Trailing Slashes [routing-indepth-slashes] #
+
+Slim routes automatically provide pretty URLs and intelligent redirection — behavior very similar to the Apache web server. Here are two example routes:
+
+ $app = new Slim();
+
+ //This has a trailing slash
+ $app->get('/services/', function () {});
+
+ //This does not have a trailing slash
+ $app->get('/contact', function () {});
+
+At first glance, both routes appear similar. However, in the first route example, the canonical URL for the services route has a trailing slash. It acts the same as a folder; accessing it without a trailing slash will prompt Slim to redirect to the canonical URL with the trailing slash.
+
+In the second example, the URL is defined without a trailing slash. Therefore, it behaves similar to a file. Accessing it with a trailing slash will result with a **404 Not Found** response.
+
+This behavior allows URLs to continue working if users access the page and forget the trailing slash. This is consistent with the Apache web server’s behavior. Because Slim automatically redirects URLs, search engines will always index the canonical URLs rather than index both the correct and incorrect URLs.
\ No newline at end of file
diff --git a/docs/routing-indepth-with-rewrite.markdown b/docs/routing-indepth-with-rewrite.markdown
new file mode 100644
index 0000000..525bc75
--- /dev/null
+++ b/docs/routing-indepth-with-rewrite.markdown
@@ -0,0 +1,30 @@
+# URL Rewriting [routing-indepth-with-rewrite] #
+
+I strongly encourage you to use a web server that supports URL rewriting; this will let you enjoy clean, human-friendly URLs with your Slim application. To enable URL rewriting, you should use the appropriate tools provided by your web server to forward all HTTP requests to the PHP file in which you instantiate and run your Slim application.
+
+I am most familiar with the Apache web server, so my examples below demonstrate how to setup a Slim application with Apache and mod_rewrite.
+
+Here is an example directory structure:
+
+ www.mysite.com/
+ public_html/ <-- Document root!
+ .htaccess
+ index.php <-- I instantiate Slim here!
+ lib/
+ Slim/ <-- I store Slim lib files here!
+
+The **.htaccess** file in the directory structure above contains:
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^(.*)$ index.php [QSA,L]
+
+As a result, Apache will send all requests for non-existent files to my **index.php** script in which I instantiate and run my Slim application. With URL rewriting enabled and assuming the following Slim application is defined in **index.php**, you can access the application route below at "/foo" rather than "/index.php/foo".
+
+ $app = new Slim();
+ $app->get('/foo', function () {
+ echo "Foo!";
+ });
+ $app->run();
+
+This process will act very much the same for nginx, except that you will define the URL rewriting in your nginx configuration file rather than in a **.htaccess** file. I will defer to those more intelligent than me to demonstrate nginx configuration.
\ No newline at end of file
diff --git a/docs/routing-indepth-without-rewrite.markdown b/docs/routing-indepth-without-rewrite.markdown
new file mode 100644
index 0000000..c6515bb
--- /dev/null
+++ b/docs/routing-indepth-without-rewrite.markdown
@@ -0,0 +1,11 @@
+# Without URL Rewriting [routing-indepth-without-rewrite] #
+
+Slim will work without URL rewriting. In this scenario, you must include the name of the PHP file in which you instantiate and run the Slim application in the resource URI. For example, assume the following Slim application is defined in **index.php** at the top level of your virtual host's document root:
+
+ $app = new Slim();
+ $app->get('/foo', function () {
+ echo "Foo!";
+ });
+ $app->run();
+
+You can access the defined route at "/index.php/foo". If the same application is instead defined in **index.php** inside of the physical subdirectory **blog/**, you can access the defined route at **/blog/index.php/foo**.
\ No newline at end of file
diff --git a/docs/routing-indepth.markdown b/docs/routing-indepth.markdown
new file mode 100644
index 0000000..91d29af
--- /dev/null
+++ b/docs/routing-indepth.markdown
@@ -0,0 +1,13 @@
+# Routing In-Depth [routing-indepth] #
+
+Slim's routing methods, described earlier, help you map a resource URI to a callback function for a given HTTP method (or for multiple HTTP methods). This is the simplest explanation of Slim's router. However, there is more than meets the eye.
+
+When you invoke a routing method (e.g `get()`, `post()`, `put()`, etc.) to define a route, you are actually telling Slim to create a Route object that responds to the respective HTTP request method. The Route object will know its resource URI, its callback, and the HTTP methods to which it responds. You may further assign [names](#routing-names) and [conditions](#routing-conditions) to a Route object.
+
+When you invoke your app's `run()` method, Slim will determine the current HTTP request's method and URI. Slim next iterates over each route in your application in the order they were defined. If a route's URI matches the HTTP request's URI, Slim asks the matching route if it answers to the current HTTP request's method. If yes, the route's callback is invoked; if no, the Route is ignored but Slim remembers the HTTP methods to which that Route *does* respond (more on this in a bit).
+
+If a route matches both the current HTTP request's URI and method, Slim will invoke that Route's callback and send the eventual HTTP response to the client. Subsequent route objects will not be iterated.
+
+If Slim finds routes that match the HTTP request URI but not the HTTP request method, Slim will return a **405 Method Not Allowed** response with an **Allow:** header that lists the HTTP methods to which the found routes do respond (as I alluded to above).
+
+If Slim does not find routes that match the HTTP request URI, Slim will return a **404 Not Found** response.
\ No newline at end of file
diff --git a/docs/routing-middleware.markdown b/docs/routing-middleware.markdown
new file mode 100644
index 0000000..4cce224
--- /dev/null
+++ b/docs/routing-middleware.markdown
@@ -0,0 +1,49 @@
+# Route Middleware [routing-middleware] #
+
+Slim enables you to associate middleware with a specific application route. When the given route matches the current HTTP request and is invoked, Slim will first invoke the associated middleware in the order they are defined.
+
+## What is Route Middleware?
+
+Route middleware is anything that returns true for is_callable.
+
+## How do I Add Route Middleware?
+
+When you define a new application route with the Slim application's `get()`, `post()`, `put()`, or `delete()` methods you must define a route pattern and a callable to be invoked when the route matches an HTTP request.
+
+ $app = new Slim();
+ $app->get('/foo', function () {
+ //Do something
+ });
+
+In the example above, the first argument is the route pattern. The last argument is the callable to be invoked when the route matches the current HTTP request. The route pattern must always be the first argument. The route callable must always be the last argument.
+
+You can assign middleware to this route by passing each middleware as a separate interior or... (ahem) middle... argument like this:
+
+ function myMiddleware1() {
+ echo "This is middleware!";
+ }
+ function myMiddleware2() {
+ echo "This is middleware!";
+ }
+ $app = new Slim();
+ $app->get('/foo', 'myMiddleware', 'myMiddleware2', function () {
+ //Do something
+ });
+
+When the **/foo** route matches the current HTTP request, the `myMiddleware1` and `myMiddleware2` functions will be invoked in sequence before the route’s callable.
+
+If you are running PHP >= 5.3, you can get a bit more creative. Suppose you wanted to authenticate the current user against a given role for a specific route. You could use some closure magic like this:
+
+ $authenticateForRole = function ( $role = 'member' ) {
+ return function () use ( $role ) {
+ $user = User::fetchFromDatabaseSomehow();
+ if ( $user->belongsToRole($role) === false ) {
+ Slim::flash('error', 'Login required');
+ Slim::redirect('/login');
+ }
+ }
+ }
+ $app = new Slim();
+ $app->get('/foo', $authenticateForRole('admin'), function () {
+ //Display admin control panel
+ });
\ No newline at end of file
diff --git a/docs/routing-names.markdown b/docs/routing-names.markdown
new file mode 100644
index 0000000..f0a7aa6
--- /dev/null
+++ b/docs/routing-names.markdown
@@ -0,0 +1,15 @@
+# Named Routes [routing-names] #
+
+Slim lets you assign a name to a route. Naming a route enables you to dynamically generate URLs using the [urlFor](#routing-helpers-urlfor) helper method. When you use the `urlFor()` application instance method to create application URLs, you can freely change route patterns without breaking your application. Here is an example of a named route:
+
+ $app = new Slim();
+ $app->get('/hello/:name', function ($name) {
+ echo "Hello, $name!";
+ })->name('hello');
+
+You may now generate URLs for this route using the `urlFor()` application instance method, described later in this documentation. The route `name()` method is also chainable:
+
+ $app = new Slim();
+ $app->get('/hello/:name', function ($name) {
+ echo "Hello, $name!";
+ })->name('hello')->conditions(array('name' => '\w+'));
\ No newline at end of file
diff --git a/docs/routing-options.markdown b/docs/routing-options.markdown
new file mode 100644
index 0000000..b7bccfb
--- /dev/null
+++ b/docs/routing-options.markdown
@@ -0,0 +1,30 @@
+# OPTIONS Routes [routing-options] #
+
+Use the `options()` application instance method to map a callback function to a resource URI that is requested with the HTTP OPTIONS method.
+
+ $app = new Slim();
+
+ //For PHP 5 >= 5.3
+ $app->options('/books/:id', function ($id) {
+ //Provide options for this resource to the client
+ });
+
+ //For PHP 5 < 5.3
+ $app->options('/books/:id', 'options_book');
+ function options_book($id) {
+ //Provide options for this resource to the client
+ }
+
+In this example, an HTTP OPTIONS request for “/books/1” will invoke the associated callback function.
+
+The first argument of the `options()` application instance method is the resource URI. The last argument is anything that returns `true` for `is_callable()`. I encourage you to use PHP >= 5.3 so you may take advantage of anonymous functions.
+
+## Method Override ##
+
+Unfortunately, modern browsers do not provide native support for HTTP OPTIONS requests. To work around this limitation, ensure your HTML form’s **method** attribute is “post”, then add a method override parameter to your HTML form like this:
+
+
\ No newline at end of file
diff --git a/docs/routing-parameters.markdown b/docs/routing-parameters.markdown
new file mode 100644
index 0000000..2e2e052
--- /dev/null
+++ b/docs/routing-parameters.markdown
@@ -0,0 +1,40 @@
+# Route Parameters [routing-parameters] #
+
+You can embed parameters into route resource URIs. In this example, I have two parameters in my route URI, “:one” and “:two”.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->get('/books/:one/:two', function ($one, $two) {
+ echo "The first paramter is " . $one;
+ echo "The second parameter is " . $two;
+ });
+
+ //For PHP < 5.3
+ $app->get('/books/:one/:two', 'callback_name');
+ function callback_name($one, $two) {
+ echo "The first paramter is " . $one;
+ echo "The second parameter is " . $two;
+ }
+
+To create a URL parameter, prepend “:” to the parameter name in the route URI pattern. When the route matches the current HTTP request, the values for each route parameter are extracted from the HTTP request URI and are passed into the associated callback function in order of appearance.
+
+## Optional Route Parameters
+
+You may also have optional route parameters. These are ideal for using one route for a blog archive. To declare optional route parameters, specify your route pattern like this:
+
+ $app = new Slim();
+ $app->get('/archive(/:year(/:month(/:day)))', function ($year = 2010, $month = 12, $day = 05) {
+ echo sprintf('%s-%s-%s', $year, $month, $day);
+ });
+
+Each subsequent route segment is optional. This route will accept HTTP requests for:
+
+ /archive
+ /archive/2010
+ /archive/2010/12
+ /archive/2010/12/05
+
+If an optional route segment is omitted from the HTTP request, the default values in the callback signature are used instead.
+
+Currently, you can only use optional route segments in situations like the example above where each route segment is subsequently optional. You may find this feature unstable when used in scenarios different from the example above.
\ No newline at end of file
diff --git a/docs/routing-post.markdown b/docs/routing-post.markdown
new file mode 100644
index 0000000..a472009
--- /dev/null
+++ b/docs/routing-post.markdown
@@ -0,0 +1,20 @@
+# POST Routes [routing-post] #
+
+Use the `post()` application instance method to map a callback function to a resource URI that is requested with the HTTP POST method.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->post('/books', function () {
+ //Create book
+ });
+
+ //For PHP < 5.3
+ $app->post('/books', 'post_book');
+ function post_book() {
+ //Create book
+ }
+
+In this example, an HTTP POST request for “/books” will invoke the associated callback function.
+
+The first argument of the `post()` application instance method is the resource URI. The last argument is anything that returns `true` for `is_callable()`. I encourage you to use PHP >= 5.3 so you may take advantage of anonymous functions.
\ No newline at end of file
diff --git a/docs/routing-put.markdown b/docs/routing-put.markdown
new file mode 100644
index 0000000..12b1df9
--- /dev/null
+++ b/docs/routing-put.markdown
@@ -0,0 +1,32 @@
+# PUT Routes [routing-put] #
+
+Use the `put()` application instance method to map a callback function to a resource URI that is requested with the HTTP PUT method.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->put('/books/:id', function ($id) {
+ //Update book identified by $id
+ });
+
+ //For PHP < 5.3
+ $app->put('/books/:id', 'put_book');
+ function put_book($id) {
+ //Update book identified by $id
+ }
+
+In this example, an HTTP PUT request for “/books/1” will invoke the associated callback function.
+
+The first argument of the `put()` application instance method is the resource URI. The last argument is anything that returns `true` for `is_callable()`. I encourage you to use PHP >= 5.3 so you may take advantage of anonymous functions.
+
+## Method Override ##
+
+Unfortunately, modern browsers do not provide native support for HTTP PUT requests. To work around this limitation, ensure your HTML form’s **method** attribute is “post”, then add a method override parameter to your HTML form like this:
+
+
+
+If you are using [Backbone.js](http://documentcloud.github.com/backbone/) or a command-line HTTP client, you may also override the HTTP method by using the `X-HTTP-Method-Override` header.
\ No newline at end of file
diff --git a/docs/routing.markdown b/docs/routing.markdown
new file mode 100644
index 0000000..72223ae
--- /dev/null
+++ b/docs/routing.markdown
@@ -0,0 +1,7 @@
+# Routing [routing]
+
+Slim helps you map resource URIs to callback functions for specific HTTP request methods (e.g. GET, POST, PUT, DELETE, OPTIONS or HEAD). Slim will invoke the first route that matches the current HTTP request's URI and method.
+
+If Slim does not find routes with URIs that match the HTTP request URI, Slim will automatically return a **404 Not Found** response.
+
+If Slim finds routes with URIs that match the HTTP request URI *but not* the HTTP request method, Slim will automatically return a **405 Method Not Allowed** response with an **Allow:** header whose value lists HTTP methods that are acceptable for the requested resource.
\ No newline at end of file
diff --git a/docs/sessions.markdown b/docs/sessions.markdown
new file mode 100644
index 0000000..d94933d
--- /dev/null
+++ b/docs/sessions.markdown
@@ -0,0 +1,38 @@
+# Sessions [sessions] #
+
+## Native PHP Sessions ##
+
+A Slim application does not presume anything about sessions. If you prefer to use a PHP session, you must configure and start a native PHP session with `session_start()` before you instantiate the Slim application.
+
+You should also disable PHP's session cache limiter so that PHP does not send conflicting cache expiration headers with the HTTP response. You can disable PHP's session cache limiter with:
+
+ session_cache_limiter(false);
+ session_start();
+
+## Secure Sessions ##
+
+You may also use the `Slim_Middleware_SessionCookie` middleware to persist session data in encrypted, hashed HTTP cookies. To enable the session cookie middleware, add the `Slim_Middleware_SessionCookie` middleware to your Slim application like this:
+
+ $app = new Slim();
+
+ $app->add('Slim_Middleware_SessionCookie', array(
+ 'expires' => '20 minutes',
+ 'path' => '/',
+ 'domain' => null,
+ 'secure' => false,
+ 'httponly' => false,
+ 'name' => 'slim_session',
+ 'secret' => 'CHANGE_ME',
+ 'cipher' => MCRYPT_RIJNDAEL_256,
+ 'cipher_mode' => MCRYPT_MODE_CBC
+ ));
+
+ // Define routes here
+
+ $app->run();
+
+The second argument is optional; it is shown here so you can see the default middleware settings. The session cookie middleware will work seamlessly with the `$_SESSION` superglobal so you can easily migrate to this session storage middleware with zero changes to your application code.
+
+If you use the session cookie middleware, you **DO NOT need to start a native PHP session**. The `$_SESSION` superglobal will still be available, and it will be persisted into an HTTP cookie via the middleware layer rather than with PHP's native session management.
+
+Remember, HTTP cookies are inherently limited to only 4 kilobytes of data. If your encrypted session data will exceed this length, you should instead rely on PHP's native sessions or an alternate session store.
\ No newline at end of file
diff --git a/docs/settings.markdown b/docs/settings.markdown
new file mode 100644
index 0000000..97488df
--- /dev/null
+++ b/docs/settings.markdown
@@ -0,0 +1,192 @@
+# Settings [settings]
+
+Slim's constructor accepts an associative array of settings to customize the Slim application during instantiation. These settings may be retrieved or changed during runtime as demonstrated below. Before I list the available settings, I want to quickly explain how you may define and inspect settings with your Slim application.
+
+To define settings upon instantiation, pass an associative array into the Slim constructor:
+
+ $app = new Slim(array(
+ 'debug' => true,
+ 'templates.path' => '../templates'
+ ));
+
+To define settings after instantiation, use the `config` application instance method; the first argument is the setting name and the second argument is the setting value.
+
+ $app->config('debug', false);
+
+You may also define multiple settings at once using an associative array:
+
+ $app->config(array(
+ 'debug' => true,
+ 'templates.path' => '../templates'
+ ));
+
+To retrieve the value of a setting, you also use the `config` application instance method; however, you only pass one argument — the name of the setting you wish to inspect. If the setting you request does not exist, `NULL` is returned.
+
+ $settingValue = $app->config('templates.path'); //returns "../templates"
+
+You are not limited to the settings shown below; you may also define your own.
+
+## mode [settings-mode] ##
+
+Declare the Slim application mode (e.g. "test", "development", "production"). However, this may be anything you want.
+
+Data Type
+: string
+
+Default
+: development
+
+## debug [settings-debug]
+
+Enable or disable application debugging. If true, Slim will display debugging information for errors and exceptions. If false, Slim will instead invoke the default or custom error handler, passing the exception into the handler as the first and only argument.
+
+Data Type
+: boolean
+
+Default
+: true
+
+## log.writer [settings-log-writer] ##
+
+This sets the application log writer upon instantiation. This is optional. By default the application log will write logged messages to STDERR. If you do specify a custom log writer here, it must be an object that implements a `write()` public instance method that accepts a mixed argument; the `write()` method is responsible for sending the logged object to the appropriate output.
+
+Data Type
+: mixed
+
+Default
+: Slim_LogFileWriter
+
+## log.level [settings-log-level] ##
+
+This sets the application log level upon instantiation to determine which messages are logged.
+
+Data Type
+: int
+
+Default
+: 4
+
+## log.enabled [settings-log-enabled] ##
+
+This enables or disables the application log upon instantiation.
+
+Data Type
+: boolean
+
+Default
+: true
+
+## templates.path [settings-templates-path]
+
+The relative or absolute filesystem path to template files directory. This is referenced by the application View to fetch and render templates.
+
+Data Type
+: string
+
+Default
+: ./templates
+
+## view [settings-view]
+
+Determines the View class used by the Slim application.
+
+Data Type
+: If string, the name of the custom View class;
+ If object, a subclass of `Slim_View`;
+
+Default
+: Slim_View
+
+## cookies.lifetime [settings-cookies-lifetime]
+
+Determines the lifetime of HTTP cookies created by the Slim application.
+
+Data Type
+: If integer, a valid UNIX timestamp at which the cookie expires;
+ If string, a description parsed by `strtotime` to extrapolate a valid UNIX timestamp.
+
+Default
+: 20 minutes
+
+## cookies.path [settings-cookies-path]
+
+Determines the default HTTP cookie path if none specified when invoking the Slim::setCookie or Slim::setEncryptedCookie application instance methods.
+
+Data Type
+: string
+
+Default
+: /
+
+## cookies.domain [settings-cookies-domain]
+
+Determines the default HTTP cookie domain if none specified when invoking the Slim::setCookie or Slim::setEncryptedCookie application instance methods.
+
+Data Type
+: string
+
+Default
+: null
+
+## cookies.secure [settings-cookies-secure]
+
+Should the Slim application transfer HTTP cookies over SSL/HTTPS only?
+
+Data Type
+: boolean
+
+Default
+: false
+
+## cookies.httponly [settings-cookies-httponly]
+
+Should the Slim application transfer HTTP cookies using the HTTP protocol only?
+
+Data Type
+: boolean
+
+Default
+: false
+
+## cookies.secret_key [settings-cookies-secret-key]
+
+The secret key used for HTTP cookie encryption. This field is required if you use encrypted HTTP cookies in your Slim application.
+
+Data Type
+: string
+
+Default
+: CHANGE_ME
+
+## cookies.cipher [settings-cookies-cipher]
+
+The mcrypt cipher used for HTTP cookie encryption. You can see a list of available ciphers at http://php.net/manual/en/mcrypt.ciphers.php.
+
+Data Type
+: PHP constant (see URL above)
+
+Default
+: MCRYPT_RIJNDAEL_256
+
+## cookies.cipher_mode [settings-cookies-cipher-mode]
+
+The mcrypt cipher mode used for HTTP cookie encryption. You can see a list of available cipher modes at http://php.net/manual/en/mcrypt.ciphers.php.
+
+Data Type
+: PHP constant (see URL above)
+
+Default
+: MCRYPT_MODE_CBC
+
+## http.version [settings-http-version]
+
+By default, Slim returns an HTTP/1.1 response to the client. Use this setting if you need to return an HTTP/1.0 response. This is useful if you use PHPFog or an nginx server configuration where you communicate with backend proxies rather than directly with the HTTP client.
+
+Data Type
+: string
+
+Default
+: 1.1
+
+Possible Values
+: "1.1" or "1.0"
\ No newline at end of file
diff --git a/docs/system-requirements.markdown b/docs/system-requirements.markdown
new file mode 100644
index 0000000..d25bf4b
--- /dev/null
+++ b/docs/system-requirements.markdown
@@ -0,0 +1,7 @@
+# System Requirements [system-requirements]
+
+* Web server
+* PHP >= 5.2.0
+* libmcrypt > 2.4.x
+
+I encourage you to choose a web server with a URL rewrite function so you may enjoy clean URLs with your Slim application.
\ No newline at end of file
diff --git a/docs/views-custom.markdown b/docs/views-custom.markdown
new file mode 100644
index 0000000..3f4cd76
--- /dev/null
+++ b/docs/views-custom.markdown
@@ -0,0 +1,47 @@
+# Custom Views [views-custom] #
+
+A custom view is a subclass of `Slim_View` that implements a public `render()` method. When the custom view’s render method is invoked, it is passed the desired template pathname (relative to the app's "templates.path" setting) as its one and only argument.
+
+ class CustomView extends Slim_View {
+ public function render( $template ) {
+ return 'The final rendered template';
+ }
+ }
+
+The custom view can do whatever it wants, so long as it returns the template’s rendered output as a string. A custom view makes it easy to integrate popular PHP template systems like Twig or Smarty.
+
+You can browse ready-to-use custom views that work with popular PHP template engines in the [Slim-Extras](https://github.com/codeguy/Slim-Extras) repository on GitHub.
+
+The custom view may access data passed to it by the Slim application's `render()` instance method. The custom view can access this array of data with `$this->data`. Here is an example.
+
+## The Route
+
+ $app = new Slim();
+ $app->get('/books/:id', function ($id) use ($app) {
+ $app->render('show.php', array('title' => 'Sahara'));
+ });
+
+## The View
+
+ class CustomView extends Slim_View {
+ public function render( $template ) {
+ //$template === 'show.php'
+ //$this->data['title'] === 'Sahara'
+ }
+ }
+
+If the custom view is not discoverable by a registered autoloader, it must be required before the Slim application is instantiated.
+
+ require 'Slim/Slim.php';
+ require 'CustomView.php';
+ $app = new Slim(array(
+ 'view' => 'CustomView'
+ ));
+
+It is also possible to pass a custom view instance into the Slim application’s constructor instead of the custom view class name. This may be helpful if your custom view requires special preparation before application instantiation.
+
+ require 'Slim/Slim.php';
+ require 'CustomView.php';
+ $app = new Slim(array(
+ 'view' => new CustomView()
+ ));
\ No newline at end of file
diff --git a/docs/views-data.markdown b/docs/views-data.markdown
new file mode 100644
index 0000000..c4f1085
--- /dev/null
+++ b/docs/views-data.markdown
@@ -0,0 +1,31 @@
+# View Data [views-data] #
+
+The view's `setData()` and `appendData()` instance methods inject data into the view object; the injected data is available to view templates. View data is stored internally as a key-value array.
+
+## Set Data ##
+
+The view's `setData()` instance method will _overwrite_ existing view data.
+
+You may use this method to set a single variable to a given value like this:
+
+ $view = $app->view();
+ $view->setData('color', 'red');
+
+The view's data will now contain a key "color" with value "red". You may also use the view's `setData()` instance method to batch assign an entire array of data like this:
+
+ $view = $app->view();
+ $view->setData(array(
+ 'color' => 'red',
+ 'size' => 'medium'
+ ));
+
+Remember, the view's `setData()` instance will _replace_ all previous data.
+
+## Append Data ##
+
+The view also has the `appendData()` instance method. This method _appends_ data to the view's existing data. This method accepts an array as its one and only argument like this:
+
+ $app = new Slim();
+ $app->view()->appendData(array(
+ 'foo' => 'bar'
+ ));
\ No newline at end of file
diff --git a/docs/views-rendering.markdown b/docs/views-rendering.markdown
new file mode 100644
index 0000000..6e6422f
--- /dev/null
+++ b/docs/views-rendering.markdown
@@ -0,0 +1,32 @@
+# Rendering [views-rendering] #
+
+You can use the Slim application's `render()` instance method to ask the current View to render a template with a given set of variables. The `render()` method will `echo()` the output returned from the View so that the output is appended automatically to the Response object's body. This assumes nothing about how the template is rendered; that is delegated to the View.
+
+ $app = new Slim();
+
+ //For PHP >= 5.3
+ $app->get('/books/:id', function ($id) use ($app) {
+ $app->render('myTemplate.php', array('id' => $id));
+ });
+
+ //For PHP < 5.3
+ $app->get('/books/:id', 'show_book');
+ function show_book($id) {
+ $app = Slim::getInstance();
+ $app->render('myTemplate.php', array('id' => $id));
+ }
+
+If you need to pass data from the route callback into the View, you must explicitly do so by passing an array as the second argument of the Slim application's `render()` instance method like this:
+
+ $app->render(
+ 'myTemplate.php',
+ array( 'name' => 'Josh' )
+ );
+
+You can also set the HTTP response status when you render a template:
+
+ $app->render(
+ 'myTemplate.php',
+ array( 'name' => 'Josh' ),
+ 404
+ );
\ No newline at end of file
diff --git a/docs/views-settings.markdown b/docs/views-settings.markdown
new file mode 100644
index 0000000..0745acd
--- /dev/null
+++ b/docs/views-settings.markdown
@@ -0,0 +1,15 @@
+# View Settings [views-settings] #
+
+The Slim application's view is configurable with application settings. These settings are described in the [Application Settings](#settings) section of this documentation. Briefly, these settings are:
+
+## view ##
+
+The class name or instance of a custom view. The view class must subclass `Slim_View`.
+
+[More Information](#settings-view)
+
+## templates.path ##
+
+The relative or absolute file system path to the directory that contains view templates.
+
+[More Information](#settings-templates-path)
\ No newline at end of file
diff --git a/docs/views.markdown b/docs/views.markdown
new file mode 100644
index 0000000..3d1c37c
--- /dev/null
+++ b/docs/views.markdown
@@ -0,0 +1,43 @@
+# Views [views] #
+
+A View is a PHP class that **returns** a rendered template as a string.
+
+## Set the View ##
+
+The Slim application View may be set during instantiation like this:
+
+ //During instantiation as instance
+ $app = new Slim(array(
+ 'view' => new TwigView()
+ ));
+
+ //During instantiation as class name
+ $app = new Slim(array(
+ 'view' => 'TwigView'
+ ));
+
+During instantiation, you may pass either an instance of the View or the class name; if you use the class name, the class must be discoverable (either already included or available to a registered autoloader).
+
+The View may be set during runtime, too, like this:
+
+ $app = new Slim();
+ $app->get('/foo', function () use ($app) {
+ //Using an instance of View
+ $app->view(new TwigView());
+
+ //Using a class name
+ $app->view('TwigView');
+ });
+ $app->run();
+
+If you set the View after instantiation, data already set on the previous View will be transferred to the new View.
+
+## Get the View ##
+
+You can just as easily fetch a reference to the current View with the same method but without an argument:
+
+ $theView = $app->view();
+
+## The Default View ##
+
+The Slim application will use a default View unless you specify your own. The default view uses `require()` and `extract()` to pull a template into memory and render its content against a set of variables. The rendered content is captured in an output buffer and returned. It's a fairly naive implementation, but it is easily overridden with [custom Views](#views-custom).
\ No newline at end of file
diff --git a/docs/welcome.markdown b/docs/welcome.markdown
new file mode 100644
index 0000000..41cc5a7
--- /dev/null
+++ b/docs/welcome.markdown
@@ -0,0 +1,6 @@
+# Slim Framework for PHP 5 Documentation [welcome]
+
+Slim is a micro framework for PHP 5 that helps you quickly write simple yet powerful RESTful web applications and APIs. Slim is easy to use for both beginners and professionals. Slim favors cleanliness over terseness and common cases over edge cases. Its interface is simple, intuitive, and extensively documented — both here and within the code itself. If you need further explanation about a certain class or method, don't hesitate to open up the source code. Thank you for choosing Slim for your next project. I think you're going to love it.
+
+Best regards,
+Josh Lockhart
\ No newline at end of file
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..e69de29
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..59bc1a6
--- /dev/null
+++ b/index.php
@@ -0,0 +1,79 @@
+get('/hello/:name', 'myFunction');
+ * function myFunction($name) { echo "Hello, $name"; }
+ *
+ * The routes below work with PHP >= 5.3.
+ */
+
+//GET route
+$app->get('/', function () use($app) {
+ $app->render('home.html');
+});
+$app->get('/about', function () use($app) {
+ $app->render('home.html');
+});
+$app->get('/code', function () use($app) {
+ $app->render('home.html');
+});
+
+$app->notFound(function () use ($app) {
+ $resourceUri = $app->request()->getResourceUri();
+
+ $x = new GopherGetter($resourceUri);
+ if ( $x->isValid() ) {
+ $x->get();
+ echo $x->result;
+ }
+ else {
+ $app->render('404.html');
+ }
+});
+
+//POST route
+$app->post('/post', function () {
+ echo 'This is a POST route';
+});
+
+/**
+ * Step 4: Run the Slim application
+ *
+ * This method should be called last. This is responsible for executing
+ * the Slim application using the settings and routes defined above.
+ */
+$app->run();
diff --git a/jasmine/SpecRunner.html b/jasmine/SpecRunner.html
new file mode 100644
index 0000000..be7ee06
--- /dev/null
+++ b/jasmine/SpecRunner.html
@@ -0,0 +1,54 @@
+
+
+
+ Jasmine Spec Runner
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jasmine/lib/jasmine-1.2.0.rc3/MIT.LICENSE b/jasmine/lib/jasmine-1.2.0.rc3/MIT.LICENSE
new file mode 100644
index 0000000..7c435ba
--- /dev/null
+++ b/jasmine/lib/jasmine-1.2.0.rc3/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/jasmine/lib/jasmine-1.2.0.rc3/jasmine-html.js b/jasmine/lib/jasmine-1.2.0.rc3/jasmine-html.js
new file mode 100644
index 0000000..a0b0639
--- /dev/null
+++ b/jasmine/lib/jasmine-1.2.0.rc3/jasmine-html.js
@@ -0,0 +1,616 @@
+jasmine.HtmlReporterHelpers = {};
+
+jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
+ var results = child.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+
+ return status;
+};
+
+jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
+ var parentDiv = this.dom.summary;
+ var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
+ var parent = child[parentSuite];
+
+ if (parent) {
+ if (typeof this.views.suites[parent.id] == 'undefined') {
+ this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
+ }
+ parentDiv = this.views.suites[parent.id].element;
+ }
+
+ parentDiv.appendChild(childElement);
+};
+
+
+jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
+ for(var fn in jasmine.HtmlReporterHelpers) {
+ ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
+ }
+};
+
+jasmine.HtmlReporter = function(_doc) {
+ var self = this;
+ var doc = _doc || window.document;
+
+ var reporterView;
+
+ var dom = {};
+
+ // Jasmine Reporter Public Interface
+ self.logRunningSpecs = false;
+
+ self.reportRunnerStarting = function(runner) {
+ var specs = runner.specs() || [];
+
+ if (specs.length == 0) {
+ return;
+ }
+
+ createReporterDom(runner.env.versionString());
+ doc.body.appendChild(dom.reporter);
+
+ reporterView = new jasmine.HtmlReporter.ReporterView(dom);
+ reporterView.addSpecs(specs, self.specFilter);
+ };
+
+ self.reportRunnerResults = function(runner) {
+ reporterView && reporterView.complete();
+ };
+
+ self.reportSuiteResults = function(suite) {
+ reporterView.suiteComplete(suite);
+ };
+
+ self.reportSpecStarting = function(spec) {
+ if (self.logRunningSpecs) {
+ self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+ };
+
+ self.reportSpecResults = function(spec) {
+ reporterView.specComplete(spec);
+ };
+
+ self.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+ };
+
+ self.specFilter = function(spec) {
+ if (!focusedSpecName()) {
+ return true;
+ }
+
+ return spec.getFullName().indexOf(focusedSpecName()) === 0;
+ };
+
+ return self;
+
+ function focusedSpecName() {
+ var specName;
+
+ (function memoizeFocusedSpec() {
+ if (specName) {
+ return;
+ }
+
+ var paramMap = [];
+ var params = doc.location.search.substring(1).split('&');
+
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ specName = paramMap.spec;
+ })();
+
+ return specName;
+ }
+
+ function createReporterDom(version) {
+ dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
+ dom.banner = self.createDom('div', { className: 'banner' },
+ self.createDom('span', { className: 'title' }, "Jasmine "),
+ self.createDom('span', { className: 'version' }, version)),
+
+ dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
+ dom.alert = self.createDom('div', {className: 'alert'}),
+ dom.results = self.createDom('div', {className: 'results'},
+ dom.summary = self.createDom('div', { className: 'summary' }),
+ dom.details = self.createDom('div', { id: 'details' }))
+ );
+ }
+};
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
+ this.startedAt = new Date();
+ this.runningSpecCount = 0;
+ this.completeSpecCount = 0;
+ this.passedCount = 0;
+ this.failedCount = 0;
+ this.skippedCount = 0;
+
+ this.createResultsMenu = function() {
+ this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
+ this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
+ ' | ',
+ this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
+
+ this.summaryMenuItem.onclick = function() {
+ dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
+ };
+
+ this.detailsMenuItem.onclick = function() {
+ showDetails();
+ };
+ };
+
+ this.addSpecs = function(specs, specFilter) {
+ this.totalSpecCount = specs.length;
+
+ this.views = {
+ specs: {},
+ suites: {}
+ };
+
+ for (var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
+ if (specFilter(spec)) {
+ this.runningSpecCount++;
+ }
+ }
+ };
+
+ this.specComplete = function(spec) {
+ this.completeSpecCount++;
+
+ if (isUndefined(this.views.specs[spec.id])) {
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
+ }
+
+ var specView = this.views.specs[spec.id];
+
+ switch (specView.status()) {
+ case 'passed':
+ this.passedCount++;
+ break;
+
+ case 'failed':
+ this.failedCount++;
+ break;
+
+ case 'skipped':
+ this.skippedCount++;
+ break;
+ }
+
+ specView.refresh();
+ this.refresh();
+ };
+
+ this.suiteComplete = function(suite) {
+ var suiteView = this.views.suites[suite.id];
+ if (isUndefined(suiteView)) {
+ return;
+ }
+ suiteView.refresh();
+ };
+
+ this.refresh = function() {
+
+ if (isUndefined(this.resultsMenu)) {
+ this.createResultsMenu();
+ }
+
+ // currently running UI
+ if (isUndefined(this.runningAlert)) {
+ this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
+ dom.alert.appendChild(this.runningAlert);
+ }
+ this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
+
+ // skipped specs UI
+ if (isUndefined(this.skippedAlert)) {
+ this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
+ }
+
+ this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.skippedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.skippedAlert);
+ }
+
+ // passing specs UI
+ if (isUndefined(this.passedAlert)) {
+ this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
+ }
+ this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
+
+ // failing specs UI
+ if (isUndefined(this.failedAlert)) {
+ this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
+ }
+ this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
+
+ if (this.failedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.failedAlert);
+ dom.alert.appendChild(this.resultsMenu);
+ }
+
+ // summary info
+ this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
+ this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
+ };
+
+ this.complete = function() {
+ dom.alert.removeChild(this.runningAlert);
+
+ this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.failedCount === 0) {
+ dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
+ } else {
+ showDetails();
+ }
+
+ dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
+ };
+
+ return this;
+
+ function showDetails() {
+ if (dom.reporter.className.search(/showDetails/) === -1) {
+ dom.reporter.className += " showDetails";
+ }
+ }
+
+ function isUndefined(obj) {
+ return typeof obj === 'undefined';
+ }
+
+ function isDefined(obj) {
+ return !isUndefined(obj);
+ }
+
+ function specPluralizedFor(count) {
+ var str = count + " spec";
+ if (count > 1) {
+ str += "s"
+ }
+ return str;
+ }
+
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
+
+
+jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
+ this.spec = spec;
+ this.dom = dom;
+ this.views = views;
+
+ this.symbol = this.createDom('li', { className: 'pending' });
+ this.dom.symbolSummary.appendChild(this.symbol);
+
+ this.summary = this.createDom('div', { className: 'specSummary' },
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.description)
+ );
+
+ this.detail = this.createDom('div', { className: 'specDetail' },
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.getFullName())
+ );
+};
+
+jasmine.HtmlReporter.SpecView.prototype.status = function() {
+ return this.getSpecStatus(this.spec);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
+ this.symbol.className = this.status();
+
+ switch (this.status()) {
+ case 'skipped':
+ break;
+
+ case 'passed':
+ this.appendSummaryToSuiteDiv();
+ break;
+
+ case 'failed':
+ this.appendSummaryToSuiteDiv();
+ this.appendFailureDetail();
+ break;
+ }
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
+ this.summary.className += ' ' + this.status();
+ this.appendToSummary(this.spec, this.summary);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
+ this.detail.className += ' ' + this.status();
+
+ var resultItems = this.spec.results().getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ this.detail.appendChild(messagesDiv);
+ this.dom.details.appendChild(this.detail);
+ }
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
+ this.suite = suite;
+ this.dom = dom;
+ this.views = views;
+
+ this.element = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
+ );
+
+ this.appendToSummary(this.suite, this.element);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.status = function() {
+ return this.getSpecStatus(this.suite);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
+ this.element.className += " " + this.status();
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
+
+/* @deprecated Use jasmine.HtmlReporter instead
+ */
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/jasmine/lib/jasmine-1.2.0.rc3/jasmine.css b/jasmine/lib/jasmine-1.2.0.rc3/jasmine.css
new file mode 100644
index 0000000..826e575
--- /dev/null
+++ b/jasmine/lib/jasmine-1.2.0.rc3/jasmine.css
@@ -0,0 +1,81 @@
+body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
+
+#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
+#HTMLReporter a { text-decoration: none; }
+#HTMLReporter a:hover { text-decoration: underline; }
+#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
+#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
+#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
+#HTMLReporter .version { color: #aaaaaa; }
+#HTMLReporter .banner { margin-top: 14px; }
+#HTMLReporter .duration { color: #aaaaaa; float: right; }
+#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
+#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
+#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
+#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
+#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
+#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
+#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
+#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
+#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
+#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+#HTMLReporter .runningAlert { background-color: #666666; }
+#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
+#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
+#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
+#HTMLReporter .passingAlert { background-color: #a6b779; }
+#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
+#HTMLReporter .failingAlert { background-color: #cf867e; }
+#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
+#HTMLReporter .results { margin-top: 14px; }
+#HTMLReporter #details { display: none; }
+#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
+#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter.showDetails .summary { display: none; }
+#HTMLReporter.showDetails #details { display: block; }
+#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter .summary { margin-top: 14px; }
+#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
+#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
+#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
+#HTMLReporter .description + .suite { margin-top: 0; }
+#HTMLReporter .suite { margin-top: 14px; }
+#HTMLReporter .suite a { color: #333333; }
+#HTMLReporter #details .specDetail { margin-bottom: 28px; }
+#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
+#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
+#HTMLReporter .resultMessage span.result { display: block; }
+#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
+
+#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
+#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
+#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
+#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
+#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
+#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
+#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
+#TrivialReporter .runner.running { background-color: yellow; }
+#TrivialReporter .options { text-align: right; font-size: .8em; }
+#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
+#TrivialReporter .suite .suite { margin: 5px; }
+#TrivialReporter .suite.passed { background-color: #dfd; }
+#TrivialReporter .suite.failed { background-color: #fdd; }
+#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
+#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
+#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
+#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
+#TrivialReporter .spec.skipped { background-color: #bbb; }
+#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
+#TrivialReporter .passed { background-color: #cfc; display: none; }
+#TrivialReporter .failed { background-color: #fbb; }
+#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
+#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
+#TrivialReporter .resultMessage .mismatch { color: black; }
+#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
+#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
+#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
+#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
+#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
diff --git a/jasmine/lib/jasmine-1.2.0.rc3/jasmine.js b/jasmine/lib/jasmine-1.2.0.rc3/jasmine.js
new file mode 100644
index 0000000..5bb904d
--- /dev/null
+++ b/jasmine/lib/jasmine-1.2.0.rc3/jasmine.js
@@ -0,0 +1,2530 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use jasmine.undefined instead of undefined, since undefined is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
+ * attributes on the object.
+ *
+ * @example
+ * // don't care about any other attributes than foo.
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
+ *
+ * @param sample {Object} sample
+ * @returns matchable object for the sample
+ */
+jasmine.objectContaining = function (sample) {
+ return new jasmine.Matchers.ObjectContaining(sample);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a disabled Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate;
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a.jasmineMatches) {
+ return a.jasmineMatches(b);
+ }
+
+ if (b.jasmineMatches) {
+ return b.jasmineMatches(a);
+ }
+
+ if (a instanceof jasmine.Matchers.ObjectContaining) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.ObjectContaining) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount === 0) {
+ // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+ ];
+ } else {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+ ];
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ var multiplier = Math.pow(10, precision);
+ var actual = Math.round(this.actual * multiplier);
+ expected = Math.round(expected * multiplier);
+ return expected == actual;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.jasmineToString = function() {
+ return '';
+};
+
+jasmine.Matchers.ObjectContaining = function (sample) {
+ this.sample = sample;
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ var env = jasmine.getEnv();
+
+ var hasKey = function(obj, keyName) {
+ return obj != null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in this.sample) {
+ if (!hasKey(other, property) && hasKey(this.sample, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
+ }
+ }
+
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
+ return "";
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if everything below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ if (this.ppNestLevel_ > 40) {
+ throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+ }
+
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('');
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+ this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+ this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !this.abort) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 2,
+ "build": 0,
+ "revision": 1333557965,
+ "release_candidate": 3
+};
diff --git a/jasmine/spec/PlayerSpec.js b/jasmine/spec/PlayerSpec.js
new file mode 100644
index 0000000..79f1022
--- /dev/null
+++ b/jasmine/spec/PlayerSpec.js
@@ -0,0 +1,58 @@
+describe("Player", function() {
+ var player;
+ var song;
+
+ beforeEach(function() {
+ player = new Player();
+ song = new Song();
+ });
+
+ it("should be able to play a Song", function() {
+ player.play(song);
+ expect(player.currentlyPlayingSong).toEqual(song);
+
+ //demonstrates use of custom matcher
+ expect(player).toBePlaying(song);
+ });
+
+ describe("when song has been paused", function() {
+ beforeEach(function() {
+ player.play(song);
+ player.pause();
+ });
+
+ it("should indicate that the song is currently paused", function() {
+ expect(player.isPlaying).toBeFalsy();
+
+ // demonstrates use of 'not' with a custom matcher
+ expect(player).not.toBePlaying(song);
+ });
+
+ it("should be possible to resume", function() {
+ player.resume();
+ expect(player.isPlaying).toBeTruthy();
+ expect(player.currentlyPlayingSong).toEqual(song);
+ });
+ });
+
+ // demonstrates use of spies to intercept and test method calls
+ it("tells the current song if the user has made it a favorite", function() {
+ spyOn(song, 'persistFavoriteStatus');
+
+ player.play(song);
+ player.makeFavorite();
+
+ expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);
+ });
+
+ //demonstrates use of expected exceptions
+ describe("#resume", function() {
+ it("should throw an exception if song is already playing", function() {
+ player.play(song);
+
+ expect(function() {
+ player.resume();
+ }).toThrow("song is already playing");
+ });
+ });
+});
\ No newline at end of file
diff --git a/jasmine/spec/SpecHelper.js b/jasmine/spec/SpecHelper.js
new file mode 100644
index 0000000..e9b8284
--- /dev/null
+++ b/jasmine/spec/SpecHelper.js
@@ -0,0 +1,9 @@
+beforeEach(function() {
+ this.addMatchers({
+ toBePlaying: function(expectedSong) {
+ var player = this.actual;
+ return player.currentlyPlayingSong === expectedSong &&
+ player.isPlaying;
+ }
+ });
+});
diff --git a/jasmine/src/Player.js b/jasmine/src/Player.js
new file mode 100644
index 0000000..fcce826
--- /dev/null
+++ b/jasmine/src/Player.js
@@ -0,0 +1,22 @@
+function Player() {
+}
+Player.prototype.play = function(song) {
+ this.currentlyPlayingSong = song;
+ this.isPlaying = true;
+};
+
+Player.prototype.pause = function() {
+ this.isPlaying = false;
+};
+
+Player.prototype.resume = function() {
+ if (this.isPlaying) {
+ throw new Error("song is already playing");
+ }
+
+ this.isPlaying = true;
+};
+
+Player.prototype.makeFavorite = function() {
+ this.currentlyPlayingSong.persistFavoriteStatus(true);
+};
\ No newline at end of file
diff --git a/jasmine/src/Song.js b/jasmine/src/Song.js
new file mode 100644
index 0000000..a8a3f2d
--- /dev/null
+++ b/jasmine/src/Song.js
@@ -0,0 +1,7 @@
+function Song() {
+}
+
+Song.prototype.persistFavoriteStatus = function(value) {
+ // something complicated
+ throw new Error("not yet implemented");
+};
\ No newline at end of file
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..f2ac54a
Binary files /dev/null and b/logo.png differ
diff --git a/router.php b/router.php
new file mode 100644
index 0000000..2c3eea1
--- /dev/null
+++ b/router.php
@@ -0,0 +1,8 @@
+Welcome to PHP
Use this document as a way to quick start any new project. All you get is this message and a barebones HTML document.
+
+
+iWelcome to Floodgap Systems' official gopher server. error.host 1
+iFloodgap has served the gopher community since 1999 error.host 1
+i(formerly gopher.ptloma.edu). ** OVER A DECADE OF SERVICE! ** error.host 1
+i error.host 1
+1Getting started with gopher, software, more /gopher gopher.floodgap.com 70
+
+
+
+1The QuIX Auditorium 1/audio quix.us 70 +
+1The "Bullet-Proof" "Golden Age" Software Repository 1/repo quix.us 70 +
+1Jay's Dangerous Essays 1/essays quix.us 70 +
+0About Autism 0/aboutAutism quix.us 70 +
+0About the Archivist 0/aboutJay quix.us 70 +
+1All the Computers I have Known Before 1/comp quix.us 70 +
+1File Archive for the Quay County MCCH Council (2002-2004) 1/mcch quix.us 70 +
+1The Founding Documents of the United States of America 1/founding quix.us 70 +
+.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/EnvironmentTest.php b/tests/EnvironmentTest.php
new file mode 100644
index 0000000..30d5b7b
--- /dev/null
+++ b/tests/EnvironmentTest.php
@@ -0,0 +1,340 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Environment.php';
+
+class EnvironmentTest extends PHPUnit_Framework_TestCase {
+
+ /**
+ * Default server settings assume the Slim app is installed
+ * in a subdirectory `foo/` directly beneath the public document
+ * root directory; URL rewrite is disabled; requested app
+ * resource is GET `/bar/xyz` with three query params.
+ *
+ * These only provide a common baseline for the following
+ * tests; tests are free to override these values.
+ */
+ public function setUp() {
+ $_SERVER['SERVER_NAME'] = 'slim';
+ $_SERVER['SERVER_PORT'] = '80';
+ $_SERVER['SCRIPT_NAME'] = '/foo/index.php';
+ $_SERVER['REQUEST_URI'] = '/foo/index.php/bar/xyz';
+ $_SERVER['PATH_INFO'] = '/bar/xyz';
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $_SERVER['QUERY_STRING'] = 'one=1&two=2&three=3';
+ $_SERVER['HTTPS'] = '';
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ unset($_SERVER['CONTENT_TYPE'], $_SERVER['CONTENT_LENGTH']);
+ }
+
+ /**
+ * Test mock environment
+ *
+ * This should return the custom values where specified
+ * and the default values otherwise.
+ */
+ public function testMockEnvironment() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'PUT'
+ ));
+ $env2 = Slim_Environment::getInstance();
+ $this->assertSame($env, $env2);
+ $this->assertInstanceOf('Slim_Environment', $env);
+ $this->assertEquals('PUT', $env['REQUEST_METHOD']);
+ $this->assertEquals(80, $env['SERVER_PORT']);
+ $this->assertNull($env['foo']);
+ }
+
+ /**
+ * Test sets HTTP method
+ */
+ public function testSetsHttpMethod() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('GET', $env['REQUEST_METHOD']);
+ }
+
+ /**
+ * Test parses script name and path info
+ *
+ * Pre-conditions:
+ * URL Rewrite is disabled;
+ * App installed in subdirectory;
+ */
+ public function testParsesPathsWithoutUrlRewriteInSubdirectory() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/bar/xyz', $env['PATH_INFO']);
+ $this->assertEquals('/foo/index.php', $env['SCRIPT_NAME']);
+ }
+
+ /**
+ * Test parses script name and path info
+ *
+ * Pre-conditions:
+ * URL Rewrite is disabled;
+ * App installed in root directory;
+ */
+ public function testParsesPathsWithoutUrlRewriteInRootDirectory() {
+ $_SERVER['REQUEST_URI'] = '/index.php/bar/xyz';
+ $_SERVER['SCRIPT_NAME'] = '/index.php';
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/bar/xyz', $env['PATH_INFO']);
+ $this->assertEquals('/index.php', $env['SCRIPT_NAME']);
+ }
+
+ /**
+ * Test parses script name and path info
+ *
+ * Pre-conditions:
+ * URL Rewrite disabled;
+ * App installed in root directory;
+ * Requested resource is "/";
+ */
+ public function testParsesPathsWithoutUrlRewriteInRootDirectoryForAppRootUri() {
+ $_SERVER['REQUEST_URI'] = '/index.php';
+ $_SERVER['SCRIPT_NAME'] = '/index.php';
+ unset($_SERVER['PATH_INFO']);
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/', $env['PATH_INFO']);
+ $this->assertEquals('/index.php', $env['SCRIPT_NAME']);
+ }
+
+ /**
+ * Test parses script name and path info
+ *
+ * Pre-conditions:
+ * URL Rewrite enabled;
+ * App installed in subdirectory;
+ */
+ public function testParsesPathsWithUrlRewriteInSubdirectory() {
+ $_SERVER['SCRIPT_NAME'] = '/foo/index.php';
+ $_SERVER['REQUEST_URI'] = '/foo/bar/xyz';
+ unset($_SERVER['PATH_INFO']);
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/bar/xyz', $env['PATH_INFO']);
+ $this->assertEquals('/foo', $env['SCRIPT_NAME']);
+ }
+
+ /**
+ * Test parses script name and path info
+ *
+ * Pre-conditions:
+ * URL Rewrite enabled;
+ * App installed in root directory;
+ */
+ public function testParsesPathsWithUrlRewriteInRootDirectory() {
+ $_SERVER['SCRIPT_NAME'] = '/index.php';
+ $_SERVER['REQUEST_URI'] = '/bar/xyz';
+ unset($_SERVER['PATH_INFO']);
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/bar/xyz', $env['PATH_INFO']);
+ $this->assertEquals('', $env['SCRIPT_NAME']);
+ }
+
+ /**
+ * Test parses script name and path info
+ *
+ * Pre-conditions:
+ * URL Rewrite enabled;
+ * App installed in root directory;
+ * Requested resource is "/"
+ */
+ public function testParsesPathsWithUrlRewriteInRootDirectoryForAppRootUri() {
+ $_SERVER['REQUEST_URI'] = '/';
+ $_SERVER['SCRIPT_NAME'] = '/index.php';
+ unset($_SERVER['PATH_INFO']);
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/', $env['PATH_INFO']);
+ $this->assertEquals('', $env['SCRIPT_NAME']);
+ }
+
+ /**
+ * Test parses query string
+ *
+ * Pre-conditions:
+ * $_SERVER['QUERY_STRING'] exists and is not empty;
+ */
+ public function testParsesQueryString() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('one=1&two=2&three=3', $env['QUERY_STRING']);
+ }
+
+ /**
+ * Test removes query string from PATH_INFO when using URL Rewrite
+ *
+ * Pre-conditions:
+ * $_SERVER['QUERY_STRING'] exists and is not empty;
+ * URL Rewrite enabled;
+ */
+ public function testRemovesQueryStringFromPathInfo() {
+ $_SERVER['SCRIPT_NAME'] = '/foo/index.php';
+ $_SERVER['REQUEST_URI'] = '/foo/bar/xyz?one=1&two=2&three=3';
+ unset($_SERVER['PATH_INFO']);
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/bar/xyz', $env['PATH_INFO']);
+ }
+
+ /**
+ * Test environment's PATH_INFO retains URL encoded characters (e.g. #)
+ *
+ * In earlier version, Slim_Environment would use PATH_INFO instead
+ * of REQUEST_URI to determine the root URI and resource URI.
+ * Unfortunately, the server would URL decode the PATH_INFO string
+ * before it was handed to PHP. This prevented certain URL-encoded
+ * characters like the octothorpe from being delivered correctly to
+ * the Slim application environment. This test ensures the
+ * REQUEST_URI is used instead and parsed as expected.
+ */
+ public function testPathInfoRetainsUrlEncodedCharacters() {
+ $_SERVER['SCRIPT_NAME'] = '/index.php';
+ $_SERVER['REQUEST_URI'] = '/foo/%23bar'; //<-- URL-encoded "#bar"
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('/foo/%23bar', $env['PATH_INFO']);
+ }
+
+ /**
+ * Test parses query string
+ *
+ * Pre-conditions:
+ * $_SERVER['QUERY_STRING'] does not exist;
+ */
+ public function testParsesQueryStringThatDoesNotExist() {
+ unset($_SERVER['QUERY_STRING']);
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('', $env['QUERY_STRING']);
+ }
+
+ /**
+ * Test SERVER_NAME is not empty
+ */
+ public function testServerNameIsNotEmpty() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertFalse(empty($env['SERVER_NAME']));
+ }
+
+ /**
+ * Test SERVER_PORT is not empty
+ */
+ public function testServerPortIsNotEmpty() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertFalse(empty($env['SERVER_PORT']));
+ }
+
+ /**
+ * Test unsets HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH
+ *
+ * Pre-conditions:
+ * HTTP_CONTENT_TYPE is sent with HTTP request;
+ * HTTP_CONTENT_LENGTH is sent with HTTP request;
+ */
+ public function testUnsetsContentTypeAndContentLength() {
+ $_SERVER['HTTP_CONTENT_TYPE'] = 'text/csv';
+ $_SERVER['HTTP_CONTENT_LENGTH'] = 150;
+ $env = Slim_Environment::getInstance(true);
+ $this->assertFalse(isset($env['HTTP_CONTENT_TYPE']));
+ $this->assertFalse(isset($env['HTTP_CONTENT_LENGTH']));
+ }
+
+ /**
+ * Test sets special request headers if not empty
+ *
+ * Pre-conditions:
+ * CONTENT_TYPE, CONTENT_LENGTH, X_REQUESTED_WITH are sent in client HTTP request;
+ * CONTENT_TYPE, CONTENT_LENGTH, X_REQUESTED_WITH are not empty;
+ */
+ public function testSetsSpecialHeaders() {
+ $_SERVER['CONTENT_TYPE'] = 'text/csv';
+ $_SERVER['CONTENT_LENGTH'] = '100';
+ $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XmlHttpRequest';
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('text/csv', $env['CONTENT_TYPE']);
+ $this->assertEquals('100', $env['CONTENT_LENGTH']);
+ $this->assertEquals('XmlHttpRequest', $env['X_REQUESTED_WITH']);
+ }
+
+ /**
+ * Test detects HTTPS
+ *
+ * Pre-conditions:
+ * $_SERVER['HTTPS'] is set to a non-empty value;
+ */
+ public function testHttps() {
+ $_SERVER['HTTPS'] = 1;
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('https', $env['slim.url_scheme']);
+ }
+
+ /**
+ * Test detects not HTTPS
+ *
+ * Pre-conditions:
+ * $_SERVER['HTTPS'] is set to an empty value;
+ */
+ public function testNotHttps() {
+ $_SERVER['HTTPS'] = '';
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('http', $env['slim.url_scheme']);
+ }
+
+ /**
+ * Test detects not HTTPS on IIS
+ *
+ * Pre-conditions:
+ * $_SERVER['HTTPS'] is set to "off";
+ */
+ public function testNotHttpsIIS() {
+ $_SERVER['HTTPS'] = 'off';
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('http', $env['slim.url_scheme']);
+ }
+
+ /**
+ * Test input is an empty string (and not false)
+ *
+ * Pre-conditions:
+ * Input at php://input may only be read once; subsequent attempts
+ * will return `false`; in which case, use an empty string.
+ */
+ public function testInputIsEmptyString() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertEquals('', $env['slim.input']);
+ }
+
+ /**
+ * Test valid resource handle to php://stdErr
+ */
+ public function testErrorResource() {
+ $env = Slim_Environment::getInstance(true);
+ $this->assertTrue(is_resource($env['slim.errors']));
+ }
+}
diff --git a/tests/Foo.php b/tests/Foo.php
new file mode 100644
index 0000000..5ff331e
--- /dev/null
+++ b/tests/Foo.php
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/tests/Http/HeadersTest.php b/tests/Http/HeadersTest.php
new file mode 100644
index 0000000..a8deec8
--- /dev/null
+++ b/tests/Http/HeadersTest.php
@@ -0,0 +1,137 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Http/Headers.php';
+
+class HeadersTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test constructor without args
+ */
+ public function testConstructorWithoutArgs() {
+ $h = new Slim_Http_Headers();
+ $this->assertEquals(0, count($h));
+ }
+
+ /**
+ * Test constructor with args
+ */
+ public function testConstructorWithArgs() {
+ $h = new Slim_Http_Headers(array('Content-Type' => 'text/html'));
+ $this->assertEquals(1, count($h));
+ }
+
+ /**
+ * Test get and set header
+ */
+ public function testSetAndGetHeader() {
+ $h = new Slim_Http_Headers();
+ $h['Content-Type'] = 'text/html';
+ $this->assertEquals('text/html', $h['Content-Type']);
+ $this->assertEquals('text/html', $h['Content-type']);
+ $this->assertEquals('text/html', $h['content-type']);
+ }
+
+ /**
+ * Test get non-existent header
+ */
+ public function testGetNonExistentHeader() {
+ $h = new Slim_Http_Headers();
+ $this->assertNull($h['foo']);
+ }
+
+ /**
+ * Test isset header
+ */
+ public function testHeaderIsSet() {
+ $h = new Slim_Http_Headers();
+ $h['Content-Type'] = 'text/html';
+ $this->assertTrue(isset($h['Content-Type']));
+ $this->assertTrue(isset($h['Content-type']));
+ $this->assertTrue(isset($h['content-type']));
+ $this->assertFalse(isset($h['foo']));
+ }
+
+ /**
+ * Test unset header
+ */
+ public function testUnsetHeader() {
+ $h = new Slim_Http_Headers();
+ $h['Content-Type'] = 'text/html';
+ $this->assertEquals(1, count($h));
+ unset($h['Content-Type']);
+ $this->assertEquals(0, count($h));
+ }
+
+ /**
+ * Test merge headers
+ */
+ public function testMergeHeaders() {
+ $h = new Slim_Http_Headers();
+ $h['Content-Type'] = 'text/html';
+ $this->assertEquals(1, count($h));
+ $h->merge(array('Content-type' => 'text/csv', 'content-length' => 10));
+ $this->assertEquals(2, count($h));
+ $this->assertEquals('text/csv', $h['content-type']);
+ $this->assertEquals(10, $h['Content-length']);
+ }
+
+ /**
+ * Test iteration
+ */
+ public function testIteration() {
+ $h = new Slim_Http_Headers();
+ $h['One'] = 'Foo';
+ $h['Two'] = 'Bar';
+ $output = '';
+ foreach ( $h as $key => $value ) {
+ $output .= $key . $value;
+ }
+ $this->assertEquals('OneFooTwoBar', $output);
+ }
+
+ /**
+ * Test outputs header name in original form, not canonical form
+ */
+ public function testOutputsOriginalNotCanonicalName() {
+ $h = new Slim_Http_Headers();
+ $h['X-Powered-By'] = 'Slim';
+ $h['Content-Type'] = 'text/csv';
+ $keys = array();
+ foreach ( $h as $name => $value ) {
+ $keys[] = $name;
+ }
+ $this->assertContains('X-Powered-By', $keys);
+ $this->assertContains('Content-Type', $keys);
+ }
+}
\ No newline at end of file
diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php
new file mode 100644
index 0000000..f9ad881
--- /dev/null
+++ b/tests/Http/RequestTest.php
@@ -0,0 +1,789 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Environment.php';
+require_once 'Slim/Http/Util.php';
+require_once 'Slim/Http/Request.php';
+
+class RequestTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test sets HTTP method
+ */
+ public function testGetMethod() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('GET', $req->getMethod());
+ }
+
+ /**
+ * Test HTTP GET method detection
+ */
+ public function testIsGet() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue($req->isGet());
+ $this->assertFalse($req->isPost());
+ $this->assertFalse($req->isPut());
+ $this->assertFalse($req->isDelete());
+ $this->assertFalse($req->isOptions());
+ $this->assertFalse($req->isHead());
+ }
+
+ /**
+ * Test HTTP POST method detection
+ */
+ public function testIsPost() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isGet());
+ $this->assertTrue($req->isPost());
+ $this->assertFalse($req->isPut());
+ $this->assertFalse($req->isDelete());
+ $this->assertFalse($req->isOptions());
+ $this->assertFalse($req->isHead());
+ }
+
+ /**
+ * Test HTTP PUT method detection
+ */
+ public function testIsPut() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'PUT',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isGet());
+ $this->assertFalse($req->isPost());
+ $this->assertTrue($req->isPut());
+ $this->assertFalse($req->isDelete());
+ $this->assertFalse($req->isOptions());
+ $this->assertFalse($req->isHead());
+ }
+
+ /**
+ * Test HTTP DELETE method detection
+ */
+ public function testIsDelete() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'DELETE',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isGet());
+ $this->assertFalse($req->isPost());
+ $this->assertFalse($req->isPut());
+ $this->assertTrue($req->isDelete());
+ $this->assertFalse($req->isOptions());
+ $this->assertFalse($req->isHead());
+ }
+
+ /**
+ * Test HTTP OPTIONS method detection
+ */
+ public function testIsOptions() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'OPTIONS',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isGet());
+ $this->assertFalse($req->isPost());
+ $this->assertFalse($req->isPut());
+ $this->assertFalse($req->isDelete());
+ $this->assertTrue($req->isOptions());
+ $this->assertFalse($req->isHead());
+ }
+
+ /**
+ * Test HTTP HEAD method detection
+ */
+ public function testIsHead() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'HEAD',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isGet());
+ $this->assertFalse($req->isPost());
+ $this->assertFalse($req->isPut());
+ $this->assertFalse($req->isDelete());
+ $this->assertFalse($req->isOptions());
+ $this->assertTrue($req->isHead());
+ }
+
+ /**
+ * Test AJAX method detection w/ header
+ */
+ public function testIsAjaxWithHeader() {
+ $env = Slim_Environment::mock(array(
+ 'X_REQUESTED_WITH' => 'XMLHttpRequest'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue($req->isAjax());
+ $this->assertTrue($req->isXhr());
+ }
+
+ /**
+ * Test AJAX method detection w/ query parameter
+ */
+ public function testIsAjaxWithQueryParameter() {
+ $env = Slim_Environment::mock(array(
+ 'QUERY_STRING' => 'one=1&two=2&three=3&isajax=1',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue($req->isAjax());
+ $this->assertTrue($req->isXhr());
+ }
+
+ /**
+ * Test AJAX method detection wihtout header or query paramter
+ */
+ public function testIsAjaxWithoutHeaderOrQueryParameter() {
+ $env = Slim_Environment::mock();
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isAjax());
+ $this->assertFalse($req->isXhr());
+ }
+
+ /**
+ * Test AJAX method detection with misspelled header
+ */
+ public function testIsAjaxWithMisspelledHeader() {
+ $env = Slim_Environment::mock(array(
+ 'X_REQUESTED_WITH' => 'foo'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isAjax());
+ $this->assertFalse($req->isXhr());
+ }
+
+ /**
+ * Test params from query string
+ */
+ public function testParamsFromQueryString() {
+ $env = Slim_Environment::mock(array(
+ 'QUERY_STRING' => 'one=1&two=2&three=3'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(3, count($req->params()));
+ $this->assertEquals('1', $req->params('one'));
+ $this->assertNull($req->params('foo'));
+ }
+
+ /**
+ * Test params from request body
+ */
+ public function testParamsFromRequestBody() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'slim.input' => 'foo=bar&abc=123',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => 15
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(5, count($req->params())); //Union of GET and POST
+ $this->assertEquals('bar', $req->params('foo'));
+ }
+
+ /**
+ * Test fetch GET params
+ */
+ public function testGet() {
+ $env = Slim_Environment::mock(array(
+ 'QUERY_STRING' => 'one=1&two=2&three=3'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(3, count($req->get()));
+ $this->assertEquals('1', $req->get('one'));
+ $this->assertNull($req->get('foo'));
+ }
+
+ /**
+ * Test fetch GET params without multibyte
+ */
+ public function testGetWithoutMultibyte() {
+ $env = Slim_Environment::mock(array(
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'slim.tests.ignore_multibyte' => true
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(3, count($req->get()));
+ $this->assertEquals('1', $req->get('one'));
+ $this->assertNull($req->get('foo'));
+ }
+
+ /**
+ * Test fetch POST params
+ */
+ public function testPost() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'slim.input' => 'foo=bar&abc=123',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => 15
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(2, count($req->post()));
+ $this->assertEquals('bar', $req->post('foo'));
+ $this->assertNull($req->post('xyz'));
+ }
+
+ /**
+ * Test fetch POST params without multibyte
+ */
+ public function testPostWithoutMultibyte() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'slim.input' => 'foo=bar&abc=123',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => 15,
+ 'slim.tests.ignore_multibyte' => true
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(2, count($req->post()));
+ $this->assertEquals('bar', $req->post('foo'));
+ $this->assertNull($req->post('xyz'));
+ }
+
+ /**
+ * Test fetch POST without slim.input
+ */
+ public function testPostWithoutInput() {
+ $this->setExpectedException('RuntimeException');
+ $env = Slim_Environment::mock();
+ unset($env['slim.input']);
+ $req = new Slim_Http_Request($env);
+ $req->post('foo');
+ }
+
+ /**
+ * Test fetch PUT params
+ */
+ public function testPut() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'PUT',
+ 'slim.input' => 'foo=bar&abc=123',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => 15
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(2, count($req->put()));
+ $this->assertEquals('bar', $req->put('foo'));
+ $this->assertNull($req->put('xyz'));
+ }
+
+ /**
+ * Test fetch COOKIE params
+ */
+ public function testCookies() {
+ $env = Slim_Environment::mock(array(
+ 'COOKIE' => 'foo=bar; abc=123'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(2, count($req->cookies()));
+ $this->assertEquals('bar', $req->cookies('foo'));
+ $this->assertNull($req->cookies('xyz'));
+ }
+
+ /**
+ * Test is form data
+ */
+ public function testIsFormDataContentFormUrlencoded() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'PUT',
+ 'slim.input' => '',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue($req->isFormData());
+ }
+
+ /**
+ * Test is form data
+ */
+ public function testIsFormDataPostContentUnknown() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'slim.input' => '',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue($req->isFormData());
+ }
+
+ /**
+ * Test is form data
+ */
+ public function testIsFormDataPostContentUnknownWithMethodOverride() {
+ $env = Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'PUT',
+ ));
+ $env['slim.method_override.original_method'] = 'POST';
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue($req->isPut());
+ $this->assertTrue($req->isFormData());
+ }
+
+ /**
+ * Test is not form data
+ */
+ public function testIsNotFormData() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => '',
+ 'CONTENT_TYPE' => 'application/json'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertFalse($req->isFormData());
+ }
+
+ /**
+ * Test headers
+ */
+ public function testHeaders() {
+ $env = Slim_Environment::mock(array(
+ 'ACCEPT_ENCODING' => 'gzip'
+ ));
+ $req = new Slim_Http_Request($env);
+ $headers = $req->headers();
+ $this->assertTrue(is_array($headers));
+ $this->assertArrayHasKey('ACCEPT_ENCODING', $headers);
+ $this->assertEquals('gzip', $req->headers('HTTP_ACCEPT_ENCODING'));
+ $this->assertEquals('gzip', $req->headers('HTTP-ACCEPT-ENCODING'));
+ $this->assertEquals('gzip', $req->headers('http_accept_encoding'));
+ $this->assertEquals('gzip', $req->headers('http-accept-encoding'));
+ $this->assertEquals('gzip', $req->headers('ACCEPT_ENCODING'));
+ $this->assertEquals('gzip', $req->headers('ACCEPT-ENCODING'));
+ $this->assertEquals('gzip', $req->headers('accept_encoding'));
+ $this->assertEquals('gzip', $req->headers('accept-encoding'));
+ $this->assertNull($req->headers('foo'));
+ }
+
+ /**
+ * Test accurately removes HTTP_ prefix from input header name
+ */
+ public function testHeaderRemovesHttpPrefix() {
+ $env = Slim_Environment::mock(array(
+ 'X_HTTP_METHOD_OVERRIDE' => 'PUT',
+ 'CONTENT_TYPE' => 'application/json'
+ ));
+ //fwrite(fopen('php://stdout', 'w'), print_r($env, true));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('PUT', $req->headers('X_HTTP_METHOD_OVERRIDE'));
+ $this->assertNull($req->headers('X_METHOD_OVERRIDE')); //<-- Ensures `HTTP_` is not removed if not prefix
+ $this->assertEquals('application/json', $req->headers('HTTP_CONTENT_TYPE')); //<-- Ensures `HTTP_` is removed if prefix
+ }
+
+ /**
+ * Test get body
+ */
+ public function testGetBodyWhenExists() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => 'foo=bar&abc=123',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => 15
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('foo=bar&abc=123', $req->getBody());
+ }
+
+ /**
+ * Test get body
+ */
+ public function testGetBodyWhenNotExists() {
+ $env = Slim_Environment::mock();
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('', $req->getBody());
+ }
+
+ /**
+ * Test get content type
+ */
+ public function testGetContentTypeWhenExists() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => '',
+ 'CONTENT_TYPE' => 'application/json; charset=ISO-8859-4'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('application/json; charset=ISO-8859-4', $req->getContentType());
+ }
+
+ /**
+ * Test get content type
+ */
+ public function testGetContentTypeWhenNotExists() {
+ $env = Slim_Environment::mock();
+ $req = new Slim_Http_Request($env);
+ $this->assertNull($req->getContentType());
+ }
+
+ /**
+ * Test get media type
+ */
+ public function testGetMediaTypeWhenExists() {
+ $env = Slim_Environment::mock(array(
+ 'CONTENT_TYPE' => 'application/json;charset=utf-8'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('application/json', $req->getMediaType());
+ }
+
+ /**
+ * Test get media type
+ */
+ public function testGetMediaTypeWhenNotExists() {
+ $env = Slim_Environment::mock();
+ $req = new Slim_Http_Request($env);
+ $this->assertNull($req->getMediaType());
+ }
+
+ /**
+ * Test get media type
+ */
+ public function testGetMediaTypeWhenNoParamsExist() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => '',
+ 'CONTENT_TYPE' => 'application/json'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('application/json', $req->getMediaType());
+ }
+
+ /**
+ * Test get media type params
+ */
+ public function testGetMediaTypeParams() {
+ $env = Slim_Environment::mock(array(
+ 'CONTENT_TYPE' => 'application/json; charset=ISO-8859-4'
+ ));
+ $req = new Slim_Http_Request($env);
+ $params = $req->getMediaTypeParams();
+ $this->assertEquals(1, count($params));
+ $this->assertArrayHasKey('charset', $params);
+ $this->assertEquals('ISO-8859-4', $params['charset']);
+ }
+
+ /**
+ * Test get media type params
+ */
+ public function testGetMediaTypeParamsWhenNotExists() {
+ $env = Slim_Environment::mock();
+ $req = new Slim_Http_Request($env);
+ $params = $req->getMediaTypeParams();
+ $this->assertTrue(is_array($params));
+ $this->assertEquals(0, count($params));
+ }
+
+ /**
+ * Test get content charset
+ */
+ public function testGetContentCharset() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => '',
+ 'CONTENT_TYPE' => 'application/json; charset=ISO-8859-4'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('ISO-8859-4', $req->getContentCharset());
+ }
+
+ /**
+ * Test get content charset
+ */
+ public function testGetContentCharsetWhenNotExists() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => '',
+ 'CONTENT_TYPE' => 'application/json'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertNull($req->getContentCharset());
+ }
+
+ /**
+ * Test get content length
+ */
+ public function testGetContentLength() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => 'foo=bar&abc=123',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONTENT_LENGTH' => 15
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(15, $req->getContentLength());
+ }
+
+ /**
+ * Test get content length
+ */
+ public function testGetContentLengthWhenNotExists() {
+ $env = Slim_Environment::mock(array(
+ 'slim.input' => '',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals(0, $req->getContentLength());
+ }
+
+ /**
+ * Test get host
+ */
+ public function testGetHost() {
+ $env = Slim_Environment::mock(array(
+ 'SERVER_NAME' => 'slim',
+ 'HOST' => 'slimframework.com'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('slimframework.com', $req->getHost()); //Uses HTTP_HOST if available
+ }
+
+ /**
+ * Test get host
+ */
+ public function testGetHostWhenNotExists() {
+ $env = Slim_Environment::mock(array(
+ 'SERVER_NAME' => 'slim',
+ 'HOST' => 'slimframework.com'
+ ));
+ unset($env['HOST']);
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('slim', $req->getHost()); //Uses SERVER_NAME as backup
+ }
+
+ /**
+ * Test get host with port
+ */
+ public function testGetHostWithPort() {
+ $env = Slim_Environment::mock(array(
+ 'HOST' => 'slimframework.com',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('slimframework.com:80', $req->getHostWithPort());
+ }
+
+ /**
+ * Test get port
+ */
+ public function testGetPort() {
+ $env = Slim_Environment::mock(array(
+ 'SERVER_PORT' => 80
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertTrue(is_integer($req->getPort()));
+ $this->assertEquals(80, $req->getPort());
+ }
+
+ /**
+ * Test get scheme
+ */
+ public function testGetSchemeIfHttp() {
+ $env = Slim_Environment::mock(array(
+ 'slim.url_scheme' => 'http'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('http', $req->getScheme());
+ }
+
+ /**
+ * Test get scheme
+ */
+ public function testGetSchemeIfHttps() {
+ $env = Slim_Environment::mock(array(
+ 'slim.url_scheme' => 'https',
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('https', $req->getScheme());
+ }
+
+ /**
+ * Test get [script name, root uri, path, path info, resource uri] in subdirectory without htaccess
+ */
+ public function testAppPathsInSubdirectoryWithoutHtaccess() {
+ $env = Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo/index.php', //<-- Physical
+ 'PATH_INFO' => '/bar/xyz', //<-- Virtual
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('/foo/index.php', $req->getScriptName());
+ $this->assertEquals('/foo/index.php', $req->getRootUri());
+ $this->assertEquals('/foo/index.php/bar/xyz', $req->getPath());
+ $this->assertEquals('/bar/xyz', $req->getPathInfo());
+ $this->assertEquals('/bar/xyz', $req->getResourceUri());
+ }
+
+ /**
+ * Test get [script name, root uri, path, path info, resource uri] in subdirectory with htaccess
+ */
+ public function testAppPathsInSubdirectoryWithHtaccess() {
+ $env = Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar/xyz', //<-- Virtual
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('/foo', $req->getScriptName());
+ $this->assertEquals('/foo', $req->getRootUri());
+ $this->assertEquals('/foo/bar/xyz', $req->getPath());
+ $this->assertEquals('/bar/xyz', $req->getPathInfo());
+ $this->assertEquals('/bar/xyz', $req->getResourceUri());
+ }
+
+ /**
+ * Test get [script name, root uri, path, path info, resource uri] in root directory without htaccess
+ */
+ public function testAppPathsInRootDirectoryWithoutHtaccess() {
+ $env = Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/index.php', //<-- Physical
+ 'PATH_INFO' => '/bar/xyz', //<-- Virtual
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('/index.php', $req->getScriptName());
+ $this->assertEquals('/index.php', $req->getRootUri());
+ $this->assertEquals('/index.php/bar/xyz', $req->getPath());
+ $this->assertEquals('/bar/xyz', $req->getPathInfo());
+ $this->assertEquals('/bar/xyz', $req->getResourceUri());
+ }
+
+ /**
+ * Test get [script name, root uri, path, path info, resource uri] in root directory with htaccess
+ */
+ public function testAppPathsInRootDirectoryWithHtaccess() {
+ $env = Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/bar/xyz', //<-- Virtual
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('', $req->getScriptName());
+ $this->assertEquals('', $req->getRootUri());
+ $this->assertEquals('/bar/xyz', $req->getPath());
+ $this->assertEquals('/bar/xyz', $req->getPathInfo());
+ $this->assertEquals('/bar/xyz', $req->getResourceUri());
+ }
+
+ /**
+ * Test get URL
+ */
+ public function testGetUrl() {
+ $env = Slim_Environment::mock(array(
+ 'HOST' => 'slimframework.com',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('http://slimframework.com', $req->getUrl());
+ }
+
+ /**
+ * Test get URL
+ */
+ public function testGetUrlWithCustomPort() {
+ $env = Slim_Environment::mock(array(
+ 'HOST' => 'slimframework.com',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 8080,
+ 'slim.url_scheme' => 'http'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('http://slimframework.com:8080', $req->getUrl());
+ }
+
+ /**
+ * Test get URL
+ */
+ public function testGetUrlWithHttps() {
+ $env = Slim_Environment::mock(array(
+ 'HOST' => 'slimframework.com',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 443,
+ 'slim.url_scheme' => 'https'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('https://slimframework.com', $req->getUrl());
+ }
+
+ /**
+ * Test get IP
+ */
+ public function testGetIp() {
+ $env = Slim_Environment::mock(array(
+ 'REMOTE_ADDR' => '127.0.0.1'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('127.0.0.1', $req->getIp());
+ }
+
+ /**
+ * Test get refererer
+ */
+ public function testGetReferrer() {
+ $env = Slim_Environment::mock(array(
+ 'REFERER' => 'http://foo.com'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('http://foo.com', $req->getReferrer());
+ $this->assertEquals('http://foo.com', $req->getReferer());
+ }
+
+ /**
+ * Test get refererer
+ */
+ public function testGetReferrerWhenNotExists() {
+ $env = Slim_Environment::mock();
+ $req = new Slim_Http_Request($env);
+ $this->assertNull($req->getReferrer());
+ $this->assertNull($req->getReferer());
+ }
+
+ /**
+ * Test get user agent string
+ */
+ public function testGetUserAgent() {
+ $env = Slim_Environment::mock(array(
+ 'USER_AGENT' => 'user-agent-string'
+ ));
+ $req = new Slim_Http_Request($env);
+ $this->assertEquals('user-agent-string', $req->getUserAgent());
+ }
+
+ /**
+ * Test get user agent string when not set
+ */
+ public function testGetUserAgentWhenNotExists() {
+ $env = Slim_Environment::mock();
+ unset($env['USER_AGENT']);
+ $req = new Slim_Http_Request($env);
+ $this->assertNull($req->getUserAgent());
+ }
+}
diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php
new file mode 100644
index 0000000..d40b066
--- /dev/null
+++ b/tests/Http/ResponseTest.php
@@ -0,0 +1,534 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Http/Headers.php';
+require_once 'Slim/Http/Util.php';
+require_once 'Slim/Http/Response.php';
+
+class ResponseTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test constructor without args
+ */
+ public function testConstructorWithoutArgs() {
+ $r = new Slim_Http_Response();
+ $this->assertEquals('', $r->body());
+ $this->assertEquals(200, $r->status());
+ $this->assertEquals(0, $r->length());
+ $this->assertEquals('text/html', $r['Content-Type']);
+ }
+
+ /**
+ * Test constructor with args
+ */
+ public function testConstructorWithArgs() {
+ $r = new Slim_Http_Response('Page Not Found', 404, array('Content-Type' => 'application/json', 'X-Created-By' => 'Slim'));
+ $this->assertEquals('Page Not Found', $r->body());
+ $this->assertEquals(404, $r->status());
+ $this->assertEquals(14, $r->length());
+ $this->assertEquals('application/json', $r['Content-Type']);
+ $this->assertEquals('Slim', $r['X-Created-By']);
+ }
+
+ /**
+ * Test get status
+ */
+ public function testGetStatus() {
+ $r = new Slim_Http_Response();
+ $this->assertEquals(200, $r->status());
+ }
+
+ /**
+ * Test set status
+ */
+ public function testSetStatus() {
+ $r = new Slim_Http_Response();
+ $r->status(500);
+ $this->assertEquals(500, $r->status());
+ }
+
+ /**
+ * Test get headers
+ */
+ public function testGetHeaders() {
+ $r = new Slim_Http_Response();
+ $headers = $r->headers();
+ $this->assertEquals(1, count($headers));
+ $this->assertEquals('text/html', $headers['Content-Type']);
+ }
+
+ /**
+ * Test get and set header (without Array Access)
+ */
+ public function testGetAndSetHeader() {
+ $r = new Slim_Http_Response();
+ $r->header('X-Foo', 'Bar');
+ $this->assertEquals('Bar', $r->header('X-Foo'));
+ }
+
+ /**
+ * Test get body
+ */
+ public function testGetBody() {
+ $r = new Slim_Http_Response('Foo');
+ $this->assertEquals('Foo', $r->body());
+ }
+
+ /**
+ * Test set body
+ */
+ public function testSetBody() {
+ $r = new Slim_Http_Response();
+ $r->body('Foo');
+ $this->assertEquals('Foo', $r->body());
+ }
+
+ /**
+ * Test get length
+ */
+ public function testGetLength() {
+ $r = new Slim_Http_Response('Foo');
+ $this->assertEquals(3, $r->length());
+ }
+
+ /**
+ * Test set length
+ */
+ public function testSetLength() {
+ $r = new Slim_Http_Response();
+ $r->length(3);
+ $this->assertEquals(3, $r->length());
+ }
+
+ /**
+ * Test write for appending
+ */
+ public function testWriteAppend() {
+ $r = new Slim_Http_Response('Foo');
+ $r->write('Bar');
+ $this->assertEquals('FooBar', $r->body());
+ }
+
+ /**
+ * Test write for replacing
+ */
+ public function testWriteReplace() {
+ $r = new Slim_Http_Response('Foo');
+ $r->write('Bar', true);
+ $this->assertEquals('Bar', $r->body());
+ }
+
+ /**
+ * Test finalize
+ */
+ public function testFinalize() {
+ $r = new Slim_Http_Response();
+ $r->status(404);
+ $r['Content-Type'] = 'application/json';
+ $r->write('Foo');
+ $result = $r->finalize();
+ $this->assertEquals(3, count($result));
+ $this->assertEquals(404, $result[0]);
+ $this->assertFalse(is_null($result[1]['Content-Type']));
+ }
+
+ /**
+ * Test finalize
+ */
+ public function testFinalizeWithoutBody() {
+ $r = new Slim_Http_Response();
+ $r->status(204);
+ $r['Content-Type'] = 'application/json';
+ $r->write('Foo');
+ $result = $r->finalize();
+ $this->assertEquals(3, count($result));
+ $this->assertEquals('', $result[2]);
+ }
+
+ /**
+ * Test set cookie with only name and value
+ */
+ public function testSetCookieWithNameAndValue() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', 'bar');
+ $this->assertEquals('foo=bar', $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set multiple cookies with only name and value
+ */
+ public function testSetMultipleCookiesWithNameAndValue() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', 'bar');
+ $r->setCookie('abc', '123');
+ $this->assertEquals("foo=bar\nabc=123", $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie only name and value and expires (as int)
+ */
+ public function testSetMultipleCookiesWithNameAndValueAndExpiresAsInt() {
+ $now = time();
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'expires' => $now
+ ));
+ $this->assertEquals("foo=bar; expires=" . gmdate('D, d-M-Y H:i:s e', $now), $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie with only name and value and expires (as string)
+ */
+ public function testSetMultipleCookiesWithNameAndValueAndExpiresAsString() {
+ $expires = 'next Tuesday';
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'expires' => $expires
+ ));
+ $this->assertEquals("foo=bar; expires=" . gmdate('D, d-M-Y H:i:s e', strtotime($expires)), $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie with name, value, domain
+ */
+ public function testSetCookieWithNameAndValueAndDomain() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'domain' => '.slimframework.com'
+ ));
+ $this->assertEquals('foo=bar; domain=.slimframework.com', $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie with name, value, domain, path
+ */
+ public function testSetCookieWithNameAndValueAndDomainAndPath() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'domain' => '.slimframework.com',
+ 'path' => '/foo'
+ ));
+ $this->assertEquals($r['Set-Cookie'], 'foo=bar; domain=.slimframework.com; path=/foo');
+ }
+
+ /**
+ * Test set cookie with only name and value and secure flag
+ */
+ public function testSetCookieWithNameAndValueAndSecureFlag() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'secure' => true
+ ));
+ $this->assertEquals('foo=bar; secure', $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie with only name and value and secure flag (as non-truthy)
+ */
+ public function testSetCookieWithNameAndValueAndSecureFlagAsNonTruthy() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'secure' => 0
+ ));
+ $this->assertEquals('foo=bar', $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie with only name and value and httponly flag
+ */
+ public function testSetCookieWithNameAndValueAndHttpOnlyFlag() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'httponly' => true
+ ));
+ $this->assertEquals('foo=bar; HttpOnly', $r['Set-Cookie']);
+ }
+
+ /**
+ * Test set cookie with only name and value and httponly flag (as non-truthy)
+ */
+ public function testSetCookieWithNameAndValueAndHttpOnlyFlagAsNonTruthy() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'httponly' => 0
+ ));
+ $this->assertEquals('foo=bar', $r['Set-Cookie']);
+ }
+
+ /*
+ * Test delete cookie by name
+ */
+ public function testDeleteCookieByName() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', 'bar');
+ $r->setCookie('abc', '123');
+ $r->deleteCookie('foo');
+ $this->assertEquals(1, preg_match("@abc=123\nfoo=; expires=@", $r['Set-Cookie']));
+ }
+
+ /*
+ * Test delete cookie by name and domain
+ */
+ public function testDeleteCookieByNameAndDomain1() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', 'bar'); //Note: This does not have domain associated with it
+ $r->setCookie('abc', '123');
+ $r->deleteCookie('foo', array('domain' => '.slimframework.com')); //This SHOULD NOT remove the `foo` cookie
+ $this->assertEquals(1, preg_match("@foo=bar\nabc=123\nfoo=; domain=.slimframework.com; expires=@", $r['Set-Cookie']));
+ }
+
+ /*
+ * Test delete cookie by name and domain
+ */
+ public function testDeleteCookieByNameAndDomain2() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', array(
+ 'value' => 'bar',
+ 'domain' => '.slimframework.com' //Note: This does have domain associated with it
+ ));
+ $r->setCookie('abc', '123');
+ $r->deleteCookie('foo', array('domain' => '.slimframework.com')); //This SHOULD remove the `foo` cookie
+ $this->assertEquals(1, preg_match("@abc=123\nfoo=; domain=.slimframework.com; expires=@", $r['Set-Cookie']));
+ }
+
+ /**
+ * Test delete cookie by name and custom props
+ */
+ public function testDeleteCookieByNameAndCustomProps() {
+ $r = new Slim_Http_Response();
+ $r->setCookie('foo', 'bar');
+ $r->setCookie('abc', '123');
+ $r->deleteCookie('foo', array(
+ 'secure' => true,
+ 'httponly' => true
+ ));
+ $this->assertEquals(1, preg_match("@abc=123\nfoo=; expires=.*; secure; HttpOnly@", $r['Set-Cookie']));
+ }
+
+ /**
+ * Test redirect
+ */
+ public function testRedirect() {
+ $r = new Slim_Http_Response();
+ $r->redirect('/foo');
+ $this->assertEquals(302, $r->status());
+ $this->assertEquals('/foo', $r['Location']);
+ }
+
+ /**
+ * Test redirect with custom status
+ */
+ public function testRedirectWithCustomStatus() {
+ $r = new Slim_Http_Response();
+ $r->redirect('/foo', 307);
+ $this->assertEquals(307, $r->status());
+ $this->assertEquals('/foo', $r['Location']);
+ }
+
+ /**
+ * Test isEmpty
+ */
+ public function testIsEmpty() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(404);
+ $r2->status(201);
+ $this->assertFalse($r1->isEmpty());
+ $this->assertTrue($r2->isEmpty());
+ }
+
+ /**
+ * Test isClientError
+ */
+ public function testIsClientError() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(404);
+ $r2->status(500);
+ $this->assertTrue($r1->isClientError());
+ $this->assertFalse($r2->isClientError());
+ }
+
+ /**
+ * Test isForbidden
+ */
+ public function testIsForbidden() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(403);
+ $r2->status(500);
+ $this->assertTrue($r1->isForbidden());
+ $this->assertFalse($r2->isForbidden());
+ }
+
+ /**
+ * Test isInformational
+ */
+ public function testIsInformational() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(100);
+ $r2->status(200);
+ $this->assertTrue($r1->isInformational());
+ $this->assertFalse($r2->isInformational());
+ }
+
+ /**
+ * Test isInformational
+ */
+ public function testIsNotFound() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(404);
+ $r2->status(200);
+ $this->assertTrue($r1->isNotFound());
+ $this->assertFalse($r2->isNotFound());
+ }
+
+ /**
+ * Test isOk
+ */
+ public function testIsOk() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(200);
+ $r2->status(201);
+ $this->assertTrue($r1->isOk());
+ $this->assertFalse($r2->isOk());
+ }
+
+ /**
+ * Test isSuccessful
+ */
+ public function testIsSuccessful() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r3 = new Slim_Http_Response();
+ $r1->status(200);
+ $r2->status(201);
+ $r3->status(302);
+ $this->assertTrue($r1->isSuccessful());
+ $this->assertTrue($r2->isSuccessful());
+ $this->assertFalse($r3->isSuccessful());
+ }
+
+ /**
+ * Test isRedirect
+ */
+ public function testIsRedirect() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(307);
+ $r2->status(304);
+ $this->assertTrue($r1->isRedirect());
+ $this->assertFalse($r2->isRedirect());
+ }
+
+ /**
+ * Test isRedirection
+ */
+ public function testIsRedirection() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r3 = new Slim_Http_Response();
+ $r1->status(307);
+ $r2->status(304);
+ $r3->status(200);
+ $this->assertTrue($r1->isRedirection());
+ $this->assertTrue($r2->isRedirection());
+ $this->assertFalse($r3->isRedirection());
+ }
+
+ /**
+ * Test isServerError
+ */
+ public function testIsServerError() {
+ $r1 = new Slim_Http_Response();
+ $r2 = new Slim_Http_Response();
+ $r1->status(500);
+ $r2->status(400);
+ $this->assertTrue($r1->isServerError());
+ $this->assertFalse($r2->isServerError());
+ }
+
+ /**
+ * Test offset exists and offset get
+ */
+ public function testOffsetExistsAndGet() {
+ $r = new Slim_Http_Response();
+ $this->assertFalse(empty($r['Content-Type']));
+ $this->assertNull($r['foo']);
+ }
+
+ /**
+ * Test iteration
+ */
+ public function testIteration() {
+ $h = new Slim_Http_Response();
+ $output = '';
+ foreach ( $h as $key => $value ) {
+ $output .= $key . $value;
+ }
+ $this->assertEquals('Content-Typetext/html', $output);
+ }
+
+ /**
+ * Test countable
+ */
+ public function testCountable() {
+ $r1 = new Slim_Http_Response();
+ $this->assertEquals(1, count($r1)); //Content-Type
+ }
+
+ /**
+ * Test message for code when message exists
+ */
+ public function testMessageForCode() {
+ $this->assertEquals('200 OK', Slim_Http_Response::getMessageForCode(200));
+ }
+
+ /**
+ * Test message for code when message exists
+ */
+ public function testMessageForCodeWithInvalidCode() {
+ $this->assertNull(Slim_Http_Response::getMessageForCode(600));
+ }
+}
\ No newline at end of file
diff --git a/tests/Http/UtilTest.php b/tests/Http/UtilTest.php
new file mode 100644
index 0000000..3d81961
--- /dev/null
+++ b/tests/Http/UtilTest.php
@@ -0,0 +1,352 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Http/Util.php';
+
+class SlimHttpUtilTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test strip slashes when magic quotes disabled
+ */
+ public function testStripSlashesWithoutMagicQuotes() {
+ $data = "This should have \"quotes\" in it";
+ $stripped = Slim_Http_Util::stripSlashesIfMagicQuotes($data, false);
+ $this->assertEquals($data, $stripped);
+ }
+
+ /**
+ * Test strip slashes from array when magic quotes disabled
+ */
+ public function testStripSlashesFromArrayWithoutMagicQuotes() {
+ $data = array("This should have \"quotes\" in it", "And this \"too\" has quotes");
+ $stripped = Slim_Http_Util::stripSlashesIfMagicQuotes($data, false);
+ $this->assertEquals($data, $stripped);
+ }
+
+ /**
+ * Test strip slashes when magic quotes enabled
+ */
+ public function testStripSlashesWithMagicQuotes() {
+ $data = "This should have \"quotes\" in it";
+ $stripped = Slim_Http_Util::stripSlashesIfMagicQuotes($data, true);
+ $this->assertEquals('This should have "quotes" in it', $stripped);
+ }
+
+ /**
+ * Test strip slashes from array when magic quotes enabled
+ */
+ public function testStripSlashesFromArrayWithMagicQuotes() {
+ $data = array("This should have \"quotes\" in it", "And this \"too\" has quotes");
+ $stripped = Slim_Http_Util::stripSlashesIfMagicQuotes($data, true);
+ $this->assertEquals($data = array('This should have "quotes" in it', 'And this "too" has quotes'), $stripped);
+ }
+
+ /**
+ * Test encrypt and decrypt with valid data
+ */
+ public function testEncryptAndDecryptWithValidData() {
+ $data = 'foo';
+ $key = 'secret';
+ $iv = md5('initializationVector');
+ $encrypted = Slim_Http_Util::encrypt($data, $key, $iv);
+ $decrypted = Slim_Http_Util::decrypt($encrypted, $key, $iv);
+ $this->assertEquals($data, $decrypted);
+ $this->assertTrue($data !== $encrypted);
+ }
+
+ /**
+ * Test encrypt when data is empty string
+ */
+ public function testEncryptWhenDataIsEmptyString() {
+ $data = '';
+ $key = 'secret';
+ $iv = md5('initializationVector');
+ $encrypted = Slim_Http_Util::encrypt($data, $key, $iv);
+ $this->assertEquals('', $encrypted);
+ }
+
+ /**
+ * Test decrypt when data is empty string
+ */
+ public function testDecryptWhenDataIsEmptyString() {
+ $data = '';
+ $key = 'secret';
+ $iv = md5('initializationVector');
+ $decrypted = Slim_Http_Util::decrypt($data, $key, $iv);
+ $this->assertEquals('', $decrypted);
+ }
+
+ /**
+ * Test encrypt when IV and key sizes are too long
+ */
+ public function testEncryptAndDecryptWhenKeyAndIvAreTooLong() {
+ $data = 'foo';
+ $key = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz';
+ $iv = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz';
+ $encrypted = Slim_Http_Util::encrypt($data, $key, $iv);
+ $decrypted = Slim_Http_Util::decrypt($encrypted, $key, $iv);
+ $this->assertEquals($data, $decrypted);
+ $this->assertTrue($data !== $encrypted);
+ }
+
+ public function testEncodeAndDecodeSecureCookieWithValidData() {
+ //Prepare cookie value
+ $value = 'foo';
+ $expires = time() + 86400;
+ $secret = 'password';
+ $algorithm = MCRYPT_RIJNDAEL_256;
+ $mode = MCRYPT_MODE_CBC;
+ $encodedValue = Slim_Http_Util::encodeSecureCookie($value, $expires, $secret, $algorithm, $mode);
+ $decodedValue = Slim_Http_Util::decodeSecureCookie($encodedValue, $secret, $algorithm, $mode);
+
+ //Test secure cookie value
+ $parts = explode('|', $encodedValue);
+ $this->assertEquals(3, count($parts));
+ $this->assertEquals($expires, $parts[0]);
+ $this->assertEquals($value, $decodedValue);
+ }
+
+ /**
+ * Test encode/decode secure cookie with old expiration
+ *
+ * In this test, the expiration date is purposefully set to a time before now.
+ * When decoding the encoded cookie value, FALSE is returned since the cookie
+ * will have expired before it is decoded.
+ */
+ public function testEncodeAndDecodeSecureCookieWithOldExpiration() {
+ $value = 'foo';
+ $expires = time() - 100;
+ $secret = 'password';
+ $algorithm = MCRYPT_RIJNDAEL_256;
+ $mode = MCRYPT_MODE_CBC;
+ $encodedValue = Slim_Http_Util::encodeSecureCookie($value, $expires, $secret, $algorithm, $mode);
+ $decodedValue = Slim_Http_Util::decodeSecureCookie($encodedValue, $secret, $algorithm, $mode);
+ $this->assertFalse($decodedValue);
+ }
+
+ /**
+ * Test encode/decode secure cookie with tampered data
+ *
+ * In this test, the encoded data is purposefully changed to simulate someone
+ * tampering with the client-side cookie data. When decoding the encoded cookie value,
+ * FALSE is returned since the verification key will not match.
+ */
+ public function testEncodeAndDecodeSecureCookieWithTamperedData() {
+ $value = 'foo';
+ $expires = time() + 86400;
+ $secret = 'password';
+ $algorithm = MCRYPT_RIJNDAEL_256;
+ $mode = MCRYPT_MODE_CBC;
+ $encodedValue = Slim_Http_Util::encodeSecureCookie($value, $expires, $secret, $algorithm, $mode);
+ $encodedValueParts = explode('|', $encodedValue);
+ $encodedValueParts[1] = $encodedValueParts[1] . 'changed';
+ $encodedValue = implode('|', $encodedValueParts);
+ $decodedValue = Slim_Http_Util::decodeSecureCookie($encodedValue, $secret, $algorithm, $mode);
+ $this->assertFalse($decodedValue);
+ }
+
+ public function testSetCookieHeaderWithNameAndValue() {
+ $name = 'foo';
+ $value = 'bar';
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, $value);
+ $this->assertEquals('foo=bar', $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueWhenCookieAlreadySet() {
+ $name = 'foo';
+ $value = 'bar';
+ $header = array('Set-Cookie' => 'one=two');
+ Slim_Http_Util::setCookieHeader($header, $name, $value);
+ $this->assertEquals("one=two\nfoo=bar", $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomain() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com', $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomainAndPath() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $path = '/foo';
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain,
+ 'path' => $path
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com; path=/foo', $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomainAndPathAndExpiresAsString() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $path = '/foo';
+ $expires = '2 days';
+ $expiresFormat = gmdate('D, d-M-Y H:i:s e', strtotime($expires));
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain,
+ 'path' => '/foo',
+ 'expires' => $expires
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com; path=/foo; expires=' . $expiresFormat, $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomainAndPathAndExpiresAsInteger() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $path = '/foo';
+ $expires = strtotime('2 days');
+ $expiresFormat = gmdate('D, d-M-Y H:i:s e', $expires);
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain,
+ 'path' => '/foo',
+ 'expires' => $expires
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com; path=/foo; expires=' . $expiresFormat, $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomainAndPathAndExpiresAsZero() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $path = '/foo';
+ $expires = 0;
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain,
+ 'path' => '/foo',
+ 'expires' => $expires
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com; path=/foo', $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomainAndPathAndExpiresAndSecure() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $path = '/foo';
+ $expires = strtotime('2 days');
+ $expiresFormat = gmdate('D, d-M-Y H:i:s e', $expires);
+ $secure = true;
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain,
+ 'path' => '/foo',
+ 'expires' => $expires,
+ 'secure' => $secure
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com; path=/foo; expires=' . $expiresFormat . '; secure', $header['Set-Cookie']);
+ }
+
+ public function testSetCookieHeaderWithNameAndValueAndDomainAndPathAndExpiresAndSecureAndHttpOnly() {
+ $name = 'foo';
+ $value = 'bar';
+ $domain = 'foo.com';
+ $path = '/foo';
+ $expires = strtotime('2 days');
+ $expiresFormat = gmdate('D, d-M-Y H:i:s e', $expires);
+ $secure = true;
+ $httpOnly = true;
+ $header = array();
+ Slim_Http_Util::setCookieHeader($header, $name, array(
+ 'value' => $value,
+ 'domain' => $domain,
+ 'path' => '/foo',
+ 'expires' => $expires,
+ 'secure' => $secure,
+ 'httponly' => $httpOnly
+ ));
+ $this->assertEquals('foo=bar; domain=foo.com; path=/foo; expires=' . $expiresFormat . '; secure; HttpOnly', $header['Set-Cookie']);
+ }
+
+ public function testDeleteCookieHeaderWithSurvivingCookie() {
+ $header = array('Set-Cookie' => "foo=bar\none=two");
+ Slim_Http_Util::deleteCookieHeader($header, 'foo');
+ $this->assertEquals(1, preg_match("@^one=two\nfoo=; expires=@", $header['Set-Cookie']));
+ }
+
+ public function testDeleteCookieHeaderWithoutSurvivingCookie() {
+ $header = array('Set-Cookie' => "foo=bar");
+ Slim_Http_Util::deleteCookieHeader($header, 'foo');
+ $this->assertEquals(1, preg_match("@foo=; expires=@", $header['Set-Cookie']));
+ }
+
+ public function testDeleteCookieHeaderWithMatchingDomain() {
+ $header = array('Set-Cookie' => "foo=bar; domain=foo.com");
+ Slim_Http_Util::deleteCookieHeader($header, 'foo', array(
+ 'domain' => 'foo.com'
+ ));
+ $this->assertEquals(1, preg_match("@foo=; domain=foo.com; expires=@", $header['Set-Cookie']));
+ }
+
+ public function testDeleteCookieHeaderWithoutMatchingDomain() {
+ $header = array('Set-Cookie' => "foo=bar; domain=foo.com");
+ Slim_Http_Util::deleteCookieHeader($header, 'foo', array(
+ 'domain' => 'bar.com'
+ ));
+ $this->assertEquals(1, preg_match("@foo=bar; domain=foo\.com\nfoo=; domain=bar\.com@", $header['Set-Cookie']));
+ }
+
+ /**
+ * Test parses Cookie: HTTP header
+ */
+ public function testParsesCookieHeader() {
+ $header = 'foo=bar; one=two; colors=blue; colors=red; colors=green';
+ $result = Slim_Http_Util::parseCookieHeader($header);
+ $this->assertEquals(3, count($result));
+ $this->assertEquals('bar', $result['foo']);
+ $this->assertEquals('two', $result['one']);
+ $this->assertEquals(3, count($result['colors']));
+ $this->assertEquals('blue', $result['colors'][0]);
+ $this->assertEquals('red', $result['colors'][1]);
+ $this->assertEquals('green', $result['colors'][2]);
+ }
+}
\ No newline at end of file
diff --git a/tests/LogFileWriterTest.php b/tests/LogFileWriterTest.php
new file mode 100644
index 0000000..dad22cf
--- /dev/null
+++ b/tests/LogFileWriterTest.php
@@ -0,0 +1,49 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/LogFileWriter.php';
+
+class LogFileWriterTest extends PHPUnit_Framework_TestCase {
+ public function testInstantiation() {
+ $this->expectOutputString('Hello!' . PHP_EOL);
+ $handle = fopen('php://output', 'w');
+ $fw = new Slim_LogFileWriter($handle);
+ $this->assertTrue($fw->write('Hello!') > 0); //<-- Returns number of bytes written if successful
+ }
+
+ public function testInstantiationWithNonResource() {
+ $this->setExpectedException('InvalidArgumentException');
+ $fw = new Slim_LogFileWriter(@fopen('/foo/bar.txt', 'w'));
+ }
+}
\ No newline at end of file
diff --git a/tests/LogTest.php b/tests/LogTest.php
new file mode 100644
index 0000000..daf0d6c
--- /dev/null
+++ b/tests/LogTest.php
@@ -0,0 +1,138 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require 'Slim/Log.php';
+
+class MyWriter {
+ public function write( $object ) {
+ echo (string)$object;
+ return true;
+ }
+}
+
+class LogTest extends PHPUnit_Framework_TestCase {
+ public function testEnabled() {
+ $log = new Slim_Log(new MyWriter());
+ $this->assertTrue($log->isEnabled()); //<-- Default case
+ $log->setEnabled(true);
+ $this->assertTrue($log->isEnabled());
+ $log->setEnabled(false);
+ $this->assertFalse($log->isEnabled());
+ }
+
+ public function testGetLevel() {
+ $log = new Slim_Log(new MyWriter());
+ $this->assertEquals(4, $log->getLevel());
+ }
+
+ public function testSetLevel() {
+ $log = new Slim_Log(new MyWriter());
+ $log->setLevel(2);
+ $this->assertEquals(2, $log->getLevel());
+ }
+
+ public function testSetInvalidLevel() {
+ $this->setExpectedException('InvalidArgumentException');
+ $log = new Slim_Log(new MyWriter());
+ $log->setLevel(5);
+ }
+
+ public function testLogDebug() {
+ $this->expectOutputString('Debug');
+ $log = new Slim_Log(new MyWriter());
+ $result = $log->debug('Debug');
+ $this->assertTrue($result);
+ }
+
+ public function testLogDebugExcludedByLevel() {
+ $log = new Slim_Log(new MyWriter());
+ $log->setLevel(3);
+ $this->assertFalse($log->debug('Debug'));
+ }
+
+ public function testLogInfo() {
+ $this->expectOutputString('Info');
+ $log = new Slim_Log(new MyWriter());
+ $result = $log->info('Info');
+ $this->assertTrue($result);
+ }
+
+ public function testLogInfoExcludedByLevel() {
+ $log = new Slim_Log(new MyWriter());
+ $log->setLevel(2);
+ $this->assertFalse($log->info('Info'));
+ }
+
+ public function testLogWarn() {
+ $this->expectOutputString('Warn');
+ $log = new Slim_Log(new MyWriter());
+ $result = $log->warn('Warn');
+ $this->assertTrue($result);
+ }
+
+ public function testLogWarnExcludedByLevel() {
+ $log = new Slim_Log(new MyWriter());
+ $log->setLevel(1);
+ $this->assertFalse($log->warn('Warn'));
+ }
+
+ public function testLogError() {
+ $this->expectOutputString('Error');
+ $log = new Slim_Log(new MyWriter());
+ $result = $log->error('Error');
+ $this->assertTrue($result);
+ }
+
+ public function testLogErrorExcludedByLevel() {
+ $log = new Slim_Log(new MyWriter());
+ $log->setLevel(0);
+ $this->assertFalse($log->error('Error'));
+ }
+
+ public function testLogFatal() {
+ $this->expectOutputString('Fatal');
+ $log = new Slim_Log(new MyWriter());
+ $result = $log->fatal('Fatal');
+ $this->assertTrue($result);
+ }
+
+ public function testGetAndSetWriter() {
+ $writer1 = new MyWriter();
+ $writer2 = new MyWriter();
+ $log = new Slim_Log($writer1);
+ $this->assertSame($writer1, $log->getWriter());
+ $log->setWriter($writer2);
+ $this->assertSame($writer2, $log->getWriter());
+ }
+}
\ No newline at end of file
diff --git a/tests/Middleware/ContentTypesTest.php b/tests/Middleware/ContentTypesTest.php
new file mode 100644
index 0000000..5d09711
--- /dev/null
+++ b/tests/Middleware/ContentTypesTest.php
@@ -0,0 +1,138 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Slim.php';
+require_once 'Slim/Environment.php';
+
+class ContentTypesTest extends PHPUnit_Framework_TestCase {
+ public function setUp() {
+ ob_start();
+ }
+
+ public function tearDown() {
+ ob_end_clean();
+ }
+
+ /**
+ * Test parses JSON
+ */
+ public function testParsesJson() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/json',
+ 'CONENT_LENGTH' => 13,
+ 'slim.input' => '{"foo":"bar"}'
+ ));
+ $s = new Slim();
+ $s->add(new Slim_Middleware_ContentTypes());
+ $s->run();
+ $body = $s->request()->getBody();
+ $this->assertTrue(is_array($body));
+ $this->assertEquals('bar', $body['foo']);
+ }
+
+ /**
+ * Test ignores JSON with errors
+ */
+ public function testParsesJsonWithError() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/json',
+ 'CONENT_LENGTH' => 12,
+ 'slim.input' => '{"foo":"bar"' //<-- This should be incorrect!
+ ));
+ $s = new Slim();
+ $s->add(new Slim_Middleware_ContentTypes());
+ $s->run();
+ $body = $s->request()->getBody();
+ $this->assertTrue(is_string($body));
+ $this->assertEquals('{"foo":"bar"', $body);
+ }
+
+ /**
+ * Test parses XML
+ */
+ public function testParsesXml() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/xml',
+ 'CONENT_LENGTH' => 68,
+ 'slim.input' => '1Clive Cussler'
+ ));
+ $s = new Slim();
+ $s->add(new Slim_Middleware_ContentTypes());
+ $s->run();
+ $body = $s->request()->getBody();
+ $this->assertInstanceOf('SimpleXMLElement', $body);
+ $this->assertEquals('Clive Cussler', (string)$body->book->author);
+ }
+
+ /**
+ * Test ignores XML with errors
+ */
+ public function testParsesXmlWithError() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/xml',
+ 'CONENT_LENGTH' => 68,
+ 'slim.input' => '1Clive Cussler' //<-- This should be incorrect!
+ ));
+ $s = new Slim();
+ $s->add(new Slim_Middleware_ContentTypes());
+ $s->run();
+ $body = $s->request()->getBody();
+ $this->assertTrue(is_string($body));
+ $this->assertEquals('1Clive Cussler', $body);
+ }
+
+ /**
+ * Test parses CSV
+ */
+ public function testParsesCsv() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'text/csv',
+ 'CONENT_LENGTH' => 44,
+ 'slim.input' => "John,Doe,000-111-2222\nJane,Doe,111-222-3333"
+ ));
+ $s = new Slim();
+ $s->add(new Slim_Middleware_ContentTypes());
+ $s->run();
+ $body = $s->request()->getBody();
+ $this->assertTrue(is_array($body));
+ $this->assertEquals(2, count($body));
+ $this->assertEquals('000-111-2222', $body[0][2]);
+ $this->assertEquals('Doe', $body[1][1]);
+ }
+}
\ No newline at end of file
diff --git a/tests/Middleware/FlashTest.php b/tests/Middleware/FlashTest.php
new file mode 100644
index 0000000..fb698df
--- /dev/null
+++ b/tests/Middleware/FlashTest.php
@@ -0,0 +1,108 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Middleware.php';
+require_once 'Slim/Middleware/Flash.php';
+
+class SlimFlashTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Setup
+ */
+ public function setUp() {
+ $_SESSION = array();
+ }
+
+ /**
+ * Test set flash message for next request
+ */
+ public function testSetFlashForNextRequest() {
+ $f = new Slim_Middleware_Flash();
+ $f->set('foo', 'bar');
+ $f->save();
+ $this->assertEquals('bar', $_SESSION['slim.flash']['foo']);
+ }
+
+ /**
+ * Test set flash message for current request
+ */
+ public function testSetFlashForCurrentRequest() {
+ $f = new Slim_Middleware_Flash();
+ $f->now('foo', 'bar');
+ $this->assertEquals('bar', $f['foo']);
+ }
+
+ /**
+ * Test loads flash from previous request
+ */
+ public function testLoadsFlashFromPreviousRequest() {
+ $_SESSION['slim.flash'] = array('info' => 'foo');
+ $f = new Slim_Middleware_Flash();
+ $this->assertEquals('foo', $f['info']);
+ }
+
+ /**
+ * Test keep flash message for next request
+ */
+ public function testKeepFlashFromPreviousRequest() {
+ $_SESSION['slim.flash'] = array('info' => 'foo');
+ $f = new Slim_Middleware_Flash();
+ $f->keep();
+ $f->save();
+ $this->assertEquals('foo', $_SESSION['slim.flash']['info']);
+ }
+
+ /**
+ * Test flash messages from preivous request do not persist to next request
+ */
+ public function testFlashMessagesFromPreviousRequestDoNotPersist() {
+ $_SESSION['slim.flash'] = array('info' => 'foo');
+ $f = new Slim_Middleware_Flash();
+ $f->save();
+ $this->assertEmpty($_SESSION['slim.flash']);
+ }
+
+ /**
+ * Test set Flash using array access
+ */
+ public function testFlashArrayAccess() {
+ $_SESSION['slim.flash'] = array('info' => 'foo');
+ $f = new Slim_Middleware_Flash();
+ $f['info'] = 'bar';
+ $f->save();
+ $this->assertTrue(isset($f['info']));
+ $this->assertEquals('bar', $f['info']);
+ unset($f['info']);
+ $this->assertFalse(isset($f['info']));
+ }
+}
diff --git a/tests/Middleware/MethodOverrideTest.php b/tests/Middleware/MethodOverrideTest.php
new file mode 100644
index 0000000..4f913b2
--- /dev/null
+++ b/tests/Middleware/MethodOverrideTest.php
@@ -0,0 +1,145 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Slim.php';
+
+/**
+ * We use a mock application, instead of a Slim application.
+ * so that we may easily test the Method Override middleware
+ * in isolation.
+ */
+class CustomAppMethod {
+ protected $environment;
+
+ public function __construct() {
+ $this->environment = Slim_Environment::getInstance();
+ }
+
+ public function &environment() {
+ return $this->environment;
+ }
+
+ function call() {
+ //Do nothing
+ }
+}
+
+class MethodOverrideTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test overrides method as POST
+ */
+ public function testOverrideMethodAsPost() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
+ 'CONENT_LENGTH' => 11,
+ 'slim.input' => '_METHOD=PUT'
+ ));
+ $app = new CustomAppMethod();
+ $mw = new Slim_Middleware_MethodOverride();
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $env =& $app->environment();
+ $this->assertEquals('PUT', $env['REQUEST_METHOD']);
+ $this->assertTrue(isset($env['slim.method_override.original_method']));
+ $this->assertEquals('POST', $env['slim.method_override.original_method']);
+ }
+
+ /**
+ * Test does not override method if not POST
+ */
+ public function testDoesNotOverrideMethodIfNotPost() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'slim.input' => ''
+ ));
+ $app = new CustomAppMethod();
+ $mw = new Slim_Middleware_MethodOverride();
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $env =& $app->environment();
+ $this->assertEquals('GET', $env['REQUEST_METHOD']);
+ $this->assertFalse(isset($env['slim.method_override.original_method']));
+ }
+
+ /**
+ * Test does not override method if no method ovveride parameter
+ */
+ public function testDoesNotOverrideMethodAsPostWithoutParameter() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '/foo/index.php', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'QUERY_STRING' => 'foo=bar',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w')
+ ));
+ $app = new CustomAppMethod();
+ $mw = new Slim_Middleware_MethodOverride();
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $env =& $app->environment();
+ $this->assertEquals('POST', $env['REQUEST_METHOD']);
+ $this->assertFalse(isset($env['slim.method_override.original_method']));
+ }
+
+ /**
+ * Test overrides method with X-Http-Method-Override header
+ */
+ public function testOverrideMethodAsHeader() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_TYPE' => 'application/json',
+ 'CONENT_LENGTH' => 0,
+ 'slim.input' => '',
+ 'X_HTTP_METHOD_OVERRIDE' => 'DELETE'
+ ));
+ $app = new CustomAppMethod();
+ $mw = new Slim_Middleware_MethodOverride();
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $env =& $app->environment();
+ $this->assertEquals('DELETE', $env['REQUEST_METHOD']);
+ $this->assertTrue(isset($env['slim.method_override.original_method']));
+ $this->assertEquals('POST', $env['slim.method_override.original_method']);
+ }
+}
\ No newline at end of file
diff --git a/tests/Middleware/PrettyExceptionsTest.php b/tests/Middleware/PrettyExceptionsTest.php
new file mode 100644
index 0000000..09f0223
--- /dev/null
+++ b/tests/Middleware/PrettyExceptionsTest.php
@@ -0,0 +1,79 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Slim.php';
+
+class PrettyExceptionsTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Test middleware returns successful response unchanged
+ */
+ public function testReturnsUnchangedSuccessResponse() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/index.php',
+ 'PATH_INFO' => '/foo'
+ ));
+ $app = new Slim();
+ $app->get('/foo', function () {
+ echo "Success";
+ });
+ $mw = new Slim_Middleware_PrettyExceptions();
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $this->assertEquals(200, $app->response()->status());
+ $this->assertEquals('Success', $app->response()->body());
+ }
+
+ /**
+ * Test middleware returns diagnostic screen for error response
+ */
+ public function testReturnsDiagnosticsForErrorResponse() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/index.php',
+ 'PATH_INFO' => '/foo'
+ ));
+ $app = new Slim(array(
+ 'log.enabled' => false
+ ));
+ $app->get('/foo', function () {
+ throw new Exception('Test Message', 100);
+ });
+ $mw = new Slim_Middleware_PrettyExceptions();
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $this->assertEquals(1, preg_match('@Slim Application Error@', $app->response()->body()));
+ $this->assertEquals(500, $app->response()->status());
+ }
+}
\ No newline at end of file
diff --git a/tests/Middleware/SessionCookieTest.php b/tests/Middleware/SessionCookieTest.php
new file mode 100644
index 0000000..4760020
--- /dev/null
+++ b/tests/Middleware/SessionCookieTest.php
@@ -0,0 +1,110 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Slim.php';
+
+class SessionCookieTest extends PHPUnit_Framework_TestCase {
+ public function setUp() {
+ $_SESSION = array();
+ }
+
+ /**
+ * Test session cookie is set and constructed correctly
+ *
+ * We test for two things:
+ * 1) That the HTTP cookie is added to the `Set-Cookie:` response header;
+ * 2) That the HTTP cookie is constructed in the expected format;
+ */
+ public function testSessionCookieIsCreatedAndEncrypted() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/index.php',
+ 'PATH_INFO' => '/foo'
+ ));
+ $app = new Slim();
+ $app->get('/foo', function () {
+ echo "Success";
+ });
+ $mw = new Slim_Middleware_SessionCookie(array('expires' => '10 years'));
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ list($status, $header, $body) = $app->response()->finalize();
+ $matches = array();
+ preg_match_all('@^slim_session=.+|.+|.+; expires=@', $header['Set-Cookie'], $matches, PREG_SET_ORDER);
+ $this->assertEquals(1, count($matches));
+ }
+
+ /**
+ * Test $_SESSION is populated from HTTP cookie
+ *
+ * The HTTP cookie in this test was created using the previous test; the encrypted cookie contains
+ * the serialized array ['foo' => 'bar']. The middleware secret, cipher, and cipher mode are assumed
+ * to be the default values.
+ */
+ public function testSessionIsPopulatedFromCookie() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/index.php',
+ 'PATH_INFO' => '/foo',
+ 'COOKIE' => 'slim_session=1639490378%7CqWbI5R%2Bf%2B%2F1KfHQQ9cANqEEdK5aNhf%2FQy2WX%2FCFOG5Y%3D%7Ce207c55544e1f7889a357ab39700f9cbb3836ea3',
+ ));
+ $app = new Slim();
+ $app->get('/foo', function () {
+ echo "Success";
+ });
+ $mw = new Slim_Middleware_SessionCookie(array('expires' => '10 years'));
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $this->assertEquals(array('foo' => 'bar'), $_SESSION);
+ }
+
+ /**
+ * Test $_SESSION is populated as empty array if no HTTP cookie
+ */
+ public function testSessionIsPopulatedAsEmptyIfNoCookie() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/index.php',
+ 'PATH_INFO' => '/foo'
+ ));
+ $app = new Slim();
+ $app->get('/foo', function () {
+ echo "Success";
+ });
+ $mw = new Slim_Middleware_SessionCookie(array('expires' => '10 years'));
+ $mw->setApplication($app);
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ $this->assertEquals(array(), $_SESSION);
+ }
+}
diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php
new file mode 100644
index 0000000..5d82fcb
--- /dev/null
+++ b/tests/MiddlewareTest.php
@@ -0,0 +1,82 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.5.2
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Middleware.php';
+
+class My_Middleware extends Slim_Middleware {
+ public function call() {
+ echo "Before";
+ $this->next->call();
+ echo "After";
+ }
+}
+
+class My_Application {
+ public function call() {
+ echo "Application";
+ }
+}
+
+class MiddlewareTest extends PHPUnit_Framework_TestCase {
+ /**
+ * Get and set application
+ */
+ public function testGetAndSetApplication() {
+ $app = new My_Application();
+ $mw = new My_Middleware();
+ $mw->setApplication($app);
+ $this->assertSame($app, $mw->getApplication());
+ }
+
+ /**
+ * Get and set next middleware
+ */
+ public function testGetAndSetNextMiddleware() {
+ $mw1 = new My_Middleware();
+ $mw2 = new My_Middleware();
+ $mw1->setNextMiddleware($mw2);
+ $this->assertSame($mw2, $mw1->getNextMiddleware());
+ }
+
+ /**
+ * Test call
+ */
+ public function testCall() {
+ $this->expectOutputString('BeforeApplicationAfter');
+ $app = new My_Application();
+ $mw = new My_Middleware();
+ $mw->setNextMiddleware($app);
+ $mw->call();
+ }
+}
\ No newline at end of file
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..7bc611c
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,18 @@
+Slim Framework Unit Tests
+
+Follow the directions below to run the Slim Framework unit tests. You'll need the latest version of PHPUnit. To save development time, these unit tests require PHP >= 5.3. However, the Slim Framework itself requires only PHP >= 5.2.
+
+1. Install the latest version of PHPUnit
+Visit http://www.phpunit.de/ for installation instructions.
+
+2. Run PHPUnit
+From the filesystem directory that contains the `tests` directory, you may run all unit tests or specific unit tests. Here are several examples. The '$>' in the examples below is your command prompt.
+
+To run all tests:
+$> phpunit tests
+
+To run all HTTP-related tests:
+$> phpunit tests/Http
+
+To run only the HTTP Request tests:
+$> phpunit tests/Http/RequestTest
\ No newline at end of file
diff --git a/tests/RouteTest.php b/tests/RouteTest.php
new file mode 100644
index 0000000..6019286
--- /dev/null
+++ b/tests/RouteTest.php
@@ -0,0 +1,444 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Route.php';
+require_once 'Slim/Router.php';
+require_once 'Slim/Environment.php';
+require_once 'Slim/Http/Headers.php';
+require_once 'Slim/Http/Request.php';
+require_once 'Slim/Http/Response.php';
+require_once 'Slim/Exception/RequestSlash.php';
+
+/**
+ * Router Mock
+ *
+ * This is a mock for the Router class so that it,
+ * A) provides the necessary features for this test and
+ * B) removes dependencies on the Request class.
+ */
+class RouterMock extends Slim_Router {
+
+ public $cache = array();
+
+ public function __construct() {}
+
+ public function addNamedRoute($name, Slim_Route $route) {
+ $this->cache[$name] = $route;
+ }
+
+}
+
+class RouteTest extends PHPUnit_Framework_TestCase {
+
+ /**
+ * Route should set name and be cached by Router
+ */
+ public function testRouteSetsNameAndIsCached() {
+ $router = new RouterMock();
+ $route = new Slim_Route('/foo/bar', function () {});
+ $route->setRouter($router);
+ $route->name('foo');
+ $cacheKeys = array_keys($router->cache);
+ $cacheValues = array_values($router->cache);
+ $this->assertEquals('foo', $route->getName());
+ $this->assertSame($router, $route->getRouter());
+ $this->assertEquals($cacheKeys[0], 'foo');
+ $this->assertSame($cacheValues[0], $route);
+ }
+
+ /**
+ * Route should set pattern
+ */
+ public function testRouteSetsPattern() {
+ $route1 = new Slim_Route('/foo/bar', function () {});
+ $this->assertEquals('/foo/bar', $route1->getPattern());
+ }
+
+ /**
+ * Route should store a reference to the callable
+ * anonymous function.
+ */
+ public function testRouteSetsCallableAsFunction() {
+ $callable = function () { echo "Foo!"; };
+ $route = new Slim_Route('/foo/bar', $callable);
+ $this->assertSame($callable, $route->getCallable());
+ }
+
+ /**
+ * Route should store a reference to the callable
+ * regular function (for PHP 5 < 5.3)
+ */
+ public function testRouteSetsCallableAsString() {
+ $route = new Slim_Route('/foo/bar', 'testCallable');
+ $this->assertEquals('testCallable', $route->getCallable());
+ }
+
+ /**
+ * If route matches a resource URI, param should be extracted.
+ */
+ public function testRouteMatchesAndParamExtracted() {
+ $resource = '/hello/Josh';
+ $route = new Slim_Route('/hello/:name', function () {});
+ $result = $route->matches($resource);
+ $this->assertTrue($result);
+ $this->assertEquals(array('name' => 'Josh'), $route->getParams());
+ }
+
+ /**
+ * If route matches a resource URI, multiple params should be extracted.
+ */
+ public function testRouteMatchesAndMultipleParamsExtracted() {
+ $resource = '/hello/Josh/and/John';
+ $route = new Slim_Route('/hello/:first/and/:second', function () {});
+ $result = $route->matches($resource);
+ $this->assertTrue($result);
+ $this->assertEquals(array('first' => 'Josh', 'second' => 'John'), $route->getParams());
+ }
+
+ /**
+ * If route does not match a resource URI, params remain an empty array
+ */
+ public function testRouteDoesNotMatchAndParamsNotExtracted() {
+ $resource = '/foo/bar';
+ $route = new Slim_Route('/hello/:name', function () {});
+ $result = $route->matches($resource);
+ $this->assertFalse($result);
+ $this->assertEquals(array(), $route->getParams());
+ }
+
+ /**
+ * Route matches URI with trailing slash
+ *
+ */
+ public function testRouteMatchesWithTrailingSlash() {
+ $resource1 = '/foo/bar/';
+ $resource2 = '/foo/bar';
+ $route = new Slim_Route('/foo/:one/', function () {});
+ $this->assertTrue($route->matches($resource1));
+ $this->assertTrue($route->matches($resource2));
+ }
+
+ /**
+ * Route matches URI with conditions
+ */
+ public function testRouteMatchesResourceWithConditions() {
+ $resource = '/hello/Josh/and/John';
+ $route = new Slim_Route('/hello/:first/and/:second', function () {});
+ $route->conditions(array('first' => '[a-zA-Z]{3,}'));
+ $result = $route->matches($resource);
+ $this->assertTrue($result);
+ $this->assertEquals(array('first' => 'Josh', 'second' => 'John'), $route->getParams());
+ }
+
+ /**
+ * Route does not match URI with conditions
+ */
+ public function testRouteDoesNotMatchResourceWithConditions() {
+ $resource = '/hello/Josh/and/John';
+ $route = new Slim_Route('/hello/:first/and/:second', function () {});
+ $route->conditions(array('first' => '[a-z]{3,}'));
+ $result = $route->matches($resource);
+ $this->assertFalse($result);
+ $this->assertEquals(array(), $route->getParams());
+ }
+
+ /*
+ * Route should match URI with valid path component according to rfc2396
+ *
+ * "Uniform Resource Identifiers (URI): Generic Syntax" http://www.ietf.org/rfc/rfc2396.txt
+ *
+ * Excludes "+" which is valid but decodes into a space character
+ */
+ public function testRouteMatchesResourceWithValidRfc2396PathComponent() {
+ $symbols = ':@&=$,';
+ $resource = '/rfc2386/' . $symbols;
+ $route = new Slim_Route('/rfc2386/:symbols', function () {});
+ $result = $route->matches($resource);
+ $this->assertTrue($result);
+ $this->assertEquals(array('symbols' => $symbols), $route->getParams());
+ }
+
+ /*
+ * Route should match URI including unreserved punctuation marks from rfc2396
+ *
+ * "Uniform Resource Identifiers (URI): Generic Syntax" http://www.ietf.org/rfc/rfc2396.txt
+ */
+ public function testRouteMatchesResourceWithUnreservedMarks() {
+ $marks = "-_.!~*'()";
+ $resource = '/marks/' . $marks;
+ $route = new Slim_Route('/marks/:marks', function () {});
+ $result = $route->matches($resource);
+ $this->assertTrue($result);
+ $this->assertEquals(array('marks' => $marks), $route->getParams());
+ }
+
+ /**
+ * Route optional parameters
+ *
+ * Pre-conditions:
+ * Route pattern requires :year, optionally accepts :month and :day
+ *
+ * Post-conditions:
+ * All: Year is 2010
+ * Case A: Month and day default values are used
+ * Case B: Month is "05" and day default value is used
+ * Case C: Month is "05" and day is "13"
+ */
+ public function testRouteOptionalParameters() {
+ $pattern = '/archive/:year(/:month(/:day))';
+
+ //Case A
+ $routeA = new Slim_Route($pattern, function () {});
+ $resourceA = '/archive/2010';
+ $resultA = $routeA->matches($resourceA);
+ $this->assertTrue($resultA);
+ $this->assertEquals(array('year' => '2010'), $routeA->getParams());
+
+ //Case B
+ $routeB = new Slim_Route($pattern, function () {});
+ $resourceB = '/archive/2010/05';
+ $resultB = $routeB->matches($resourceB);
+ $this->assertTrue($resultB);
+ $this->assertEquals(array('year' => '2010', 'month' => '05'), $routeB->getParams());
+
+ //Case C
+ $routeC = new Slim_Route($pattern, function () {});
+ $resourceC = '/archive/2010/05/13';
+ $resultC = $routeC->matches($resourceC);
+ $this->assertTrue($resultC);
+ $this->assertEquals(array('year' => '2010', 'month' => '05', 'day' => '13'), $routeC->getParams());
+ }
+
+ /**
+ * Test route default conditions
+ *
+ * Pre-conditions:
+ * Route class has default conditions;
+ *
+ * Post-conditions:
+ * Case A: Route instance has default conditions;
+ * Case B: Route instance has newly merged conditions;
+ */
+ public function testRouteDefaultConditions() {
+ Slim_Route::setDefaultConditions(array('id' => '\d+'));
+ $r = new Slim_Route('/foo', function () {});
+ //Case A
+ $this->assertEquals(Slim_Route::getDefaultConditions(), $r->getConditions());
+ //Case B
+ $r->conditions(array('name' => '[a-z]{2,5}'));
+ $c = $r->getConditions();
+ $this->assertArrayHasKey('id', $c);
+ $this->assertArrayHasKey('name', $c);
+ }
+
+ /**
+ * Test route sets and gets middleware
+ *
+ * Pre-conditions:
+ * Route instantiated
+ *
+ * Post-conditions:
+ * Case A: Middleware set as callable, not array
+ * Case B: Middleware set after other middleware already set
+ * Case C: Middleware set as array of callables
+ * Case D: Middleware set as a callable array
+ * Case E: Middleware is invalid; throws InvalidArgumentException
+ */
+ public function testRouteMiddleware() {
+ $callable1 = function () {};
+ $callable2 = function () {};
+ //Case A
+ $r1 = new Slim_Route('/foo', function () {});
+ $r1->setMiddleware($callable1);
+ $mw = $r1->getMiddleware();
+ $this->assertInternalType('array', $mw);
+ $this->assertEquals(1, count($mw));
+ //Case B
+ $r1->setMiddleware($callable2);
+ $mw = $r1->getMiddleware();
+ $this->assertEquals(2, count($mw));
+ //Case C
+ $r2 = new Slim_Route('/foo', function () {});
+ $r2->setMiddleware(array($callable1, $callable2));
+ $mw = $r2->getMiddleware();
+ $this->assertInternalType('array', $mw);
+ $this->assertEquals(2, count($mw));
+ //Case D
+ $r3 = new Slim_Route('/foo', function () {});
+ $r3->setMiddleware(array($this, 'callableTestFunction'));
+ $mw = $r3->getMiddleware();
+ $this->assertInternalType('array', $mw);
+ $this->assertEquals(1, count($mw));
+ //Case E
+ try {
+ $r3->setMiddleware('sdjfsoi788');
+ $this->fail('Did not catch InvalidArgumentException when setting invalid route middleware');
+ } catch ( InvalidArgumentException $e ) {}
+ }
+
+ public function callableTestFunction() {}
+
+ /**
+ * Test that a Route manages the HTTP methods that it supports
+ *
+ * Case A: Route initially supports no HTTP methods
+ * Case B: Route can set its supported HTTP methods
+ * Case C: Route can append supported HTTP methods
+ * Case D: Route can test if it supports an HTTP method
+ * Case E: Route can lazily declare supported HTTP methods with `via`
+ */
+ public function testHttpMethods() {
+ //Case A
+ $r = new Slim_Route('/foo', function () {});
+ $this->assertEmpty($r->getHttpMethods());
+ //Case B
+ $r->setHttpMethods('GET');
+ $this->assertEquals(array('GET'), $r->getHttpMethods());
+ //Case C
+ $r->appendHttpMethods('POST', 'PUT');
+ $this->assertEquals(array('GET', 'POST', 'PUT'), $r->getHttpMethods());
+ //Case D
+ $this->assertTrue($r->supportsHttpMethod('GET'));
+ $this->assertFalse($r->supportsHttpMethod('DELETE'));
+ //Case E
+ $viaResult = $r->via('DELETE');
+ $this->assertTrue($viaResult instanceof Slim_Route);
+ $this->assertTrue($r->supportsHttpMethod('DELETE'));
+ }
+
+ public function testDispatch() {
+ $this->expectOutputString('Hello josh');
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/hello/josh', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $env = Slim_Environment::getInstance();
+ $req = new Slim_Http_Request($env);
+ $res = new Slim_Http_Response();
+ $router = new Slim_Router($req, $res);
+ $route = new Slim_Route('/hello/:name', function ($name) { echo "Hello $name"; });
+ $route->setRouter($router);
+ $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
+ $route->dispatch();
+ }
+
+ public function testDispatchWithMiddlware() {
+ $this->expectOutputString('First! Second! Hello josh');
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/hello/josh', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $env = Slim_Environment::getInstance();
+ $req = new Slim_Http_Request($env);
+ $res = new Slim_Http_Response();
+ $router = new Slim_Router($req, $res);
+ $route = new Slim_Route('/hello/:name', function ($name) { echo "Hello $name"; });
+ $route->setMiddleware(function () {
+ echo "First! ";
+ });
+ $route->setMiddleware(function () {
+ echo "Second! ";
+ });
+ $route->setRouter($router);
+ $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
+ $route->dispatch();
+ }
+
+ public function testDispatchWithRequestSlash() {
+ $this->setExpectedException('Slim_Exception_RequestSlash');
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/hello/josh', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $env = Slim_Environment::getInstance();
+ $req = new Slim_Http_Request($env);
+ $res = new Slim_Http_Response();
+ $router = new Slim_Router($req, $res);
+ $route = new Slim_Route('/hello/:name/', function ($name) { echo "Hello $name"; });
+ $route->setRouter($router);
+ $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
+ $route->dispatch();
+ }
+
+ public function testDispatchWithoutCallable() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/hello/josh', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $env = Slim_Environment::getInstance();
+ $req = new Slim_Http_Request($env);
+ $res = new Slim_Http_Response();
+ $router = new Slim_Router($req, $res);
+ $route = new Slim_Route('/hello/:name', 'foo');
+ $route->setRouter($router);
+ $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI
+ $this->assertFalse($route->dispatch());
+ }
+}
\ No newline at end of file
diff --git a/tests/RouterTest.php b/tests/RouterTest.php
new file mode 100644
index 0000000..711fe79
--- /dev/null
+++ b/tests/RouterTest.php
@@ -0,0 +1,329 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Environment.php';
+require_once 'Slim/Http/Headers.php';
+require_once 'Slim/Http/Request.php';
+require_once 'Slim/Http/Response.php';
+require_once 'Slim/Router.php';
+require_once 'Slim/Route.php';
+
+class RouterTest extends PHPUnit_Framework_TestCase {
+
+ protected $env;
+ protected $req;
+ protected $res;
+
+ public function setUp() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $this->env = Slim_Environment::getInstance();
+ $this->req = new Slim_Http_Request($this->env);
+ $this->res = new Slim_Http_Response();
+ }
+
+ /**
+ * Test sets and gets Request and Response
+ */
+ public function testRouterRequestAndResponse() {
+ $router = new Slim_Router($this->req, $this->res);
+ $this->assertSame($this->req, $router->getRequest());
+ $this->assertSame($this->res, $router->getResponse());
+ }
+
+ /**
+ * Router::urlFor should return a full route pattern
+ * even if no params data is provided.
+ */
+ public function testUrlForNamedRouteWithoutParams() {
+ $router = new Slim_Router($this->req, $this->res);
+ $route = $router->map('/foo/bar', function () {})->via('GET');
+ $router->addNamedRoute('foo', $route);
+ $this->assertEquals('/foo/bar', $router->urlFor('foo'));
+ }
+
+ /**
+ * Router::urlFor should return a full route pattern if
+ * param data is provided.
+ */
+ public function testUrlForNamedRouteWithParams() {
+ $router = new Slim_Router($this->req, $this->res);
+ $route = $router->map('/foo/:one/and/:two', function ($one, $two) {})->via('GET');
+ $router->addNamedRoute('foo', $route);
+ $this->assertEquals('/foo/Josh/and/John', $router->urlFor('foo', array('one' => 'Josh', 'two' => 'John')));
+ }
+
+ /**
+ * Router::urlFor should throw an exception if Route with name
+ * does not exist.
+ */
+ public function testUrlForNamedRouteThatDoesNotExist() {
+ $this->setExpectedException('RuntimeException');
+ $router = new Slim_Router($this->req, $this->res);
+ $route = $router->map('/foo/bar', function () {})->via('GET');
+ $router->addNamedRoute('bar', $route);
+ $router->urlFor('foo');
+ }
+
+ /**
+ * Router::addNamedRoute should throw an exception if named Route
+ * with same name already exists.
+ */
+ public function testNamedRouteWithExistingName() {
+ $this->setExpectedException('RuntimeException');
+ $router = new Slim_Router($this->req, $this->res);
+ $route1 = $router->map('/foo/bar', function () {})->via('GET');
+ $route2 = $router->map('/foo/bar/2', function () {})->via('GET');
+ $router->addNamedRoute('bar', $route1);
+ $router->addNamedRoute('bar', $route2);
+ }
+
+ /**
+ * Test if named route exists
+ *
+ * Pre-conditions:
+ * Slim app instantiated;
+ * Named route created;
+ *
+ * Post-conditions:
+ * Named route found to exist;
+ * Non-existant route found not to exist;
+ */
+ public function testHasNamedRoute() {
+ $router = new Slim_Router($this->req, $this->res);
+ $route = $router->map('/foo', function () {})->via('GET');
+ $router->addNamedRoute('foo', $route);
+ $this->assertTrue($router->hasNamedRoute('foo'));
+ $this->assertFalse($router->hasNamedRoute('bar'));
+ }
+
+ /**
+ * Test Router gets named route
+ *
+ * Pre-conditions;
+ * Slim app instantiated;
+ * Named route created;
+ *
+ * Post-conditions:
+ * Named route fetched by named;
+ * NULL is returned if named route does not exist;
+ */
+ public function testGetNamedRoute() {
+ $router = new Slim_Router($this->req, $this->res);
+ $route1 = $router->map('/foo', function () {})->via('GET');
+ $router->addNamedRoute('foo', $route1);
+ $this->assertSame($route1, $router->getNamedRoute('foo'));
+ $this->assertNull($router->getNamedRoute('bar'));
+ }
+
+ /**
+ * Test external iterator for Router's named routes
+ *
+ * Pre-conditions:
+ * Slim app instantiated;
+ * Named routes created;
+ *
+ * Post-conditions:
+ * Array iterator returned for named routes;
+ */
+ public function testGetNamedRoutes() {
+ $router = new Slim_Router($this->req, $this->res);
+ $route1 = $router->map('/foo', function () {})->via('GET');
+ $route2 = $router->map('/bar', function () {})->via('POST');
+ $router->addNamedRoute('foo', $route1);
+ $router->addNamedRoute('bar', $route2);
+ $namedRoutesIterator = $router->getNamedRoutes();
+ $this->assertInstanceOf('ArrayIterator', $namedRoutesIterator);
+ $this->assertEquals(2, $namedRoutesIterator->count());
+ }
+
+ /**
+ * Router should keep reference to a callable NotFound callback
+ */
+ public function testNotFoundHandler() {
+ $router = new Slim_Router($this->req, $this->res);
+ $notFoundCallback = function () { echo "404"; };
+ $callback = $router->notFound($notFoundCallback);
+ $this->assertSame($notFoundCallback, $callback);
+ }
+
+ /**
+ * Router should NOT keep reference to a callback that is not callable
+ */
+ public function testNotFoundHandlerIfNotCallable() {
+ $router = new Slim_Router($this->req, $this->res);
+ $notFoundCallback = 'foo';
+ $callback = $router->notFound($notFoundCallback);
+ $this->assertNull($callback);
+ }
+
+ /**
+ * Router should keep reference to a callable NotFound callback
+ */
+ public function testErrorHandler() {
+ $router = new Slim_Router($this->req, $this->res);
+ $errCallback = function () { echo "404"; };
+ $callback = $router->error($errCallback);
+ $this->assertSame($errCallback, $callback);
+ }
+
+ /**
+ * Router should NOT keep reference to a callback that is not callable
+ */
+ public function testErrorHandlerIfNotCallable() {
+ $router = new Slim_Router($this->req, $this->res);
+ $errCallback = 'foo';
+ $callback = $router->error($errCallback);
+ $this->assertNull($callback);
+ }
+
+ /**
+ * Router considers HEAD requests as GET requests
+ */
+ public function testRouterConsidersHeadAsGet() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'HEAD',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $this->env = Slim_Environment::getInstance();
+ $this->req = new Slim_Http_Request($this->env);
+ $this->res = new Slim_Http_Response();
+ $router = new Slim_Router($this->req, $this->res);
+ $route = $router->map('/bar', function () {})->via('GET', 'HEAD');
+ $numberOfMatchingRoutes = count($router->getMatchedRoutes());
+ $this->assertEquals(1, $numberOfMatchingRoutes);
+ }
+
+ /**
+ * Router::urlFor
+ */
+ public function testRouterUrlFor() {
+ $router = new Slim_Router($this->req, $this->res);
+ $route1 = $router->map('/foo/bar', function () {})->via('GET');
+ $route2 = $router->map('/foo/:one/:two', function () {})->via('GET');
+ $route3 = $router->map('/foo/:one(/:two)', function () {})->via('GET');
+ $route4 = $router->map('/foo/:one/(:two/)', function () {})->via('GET');
+ $route5 = $router->map('/foo/:one/(:two/(:three/))', function () {})->via('GET');
+ $route1->setName('route1');
+ $route2->setName('route2');
+ $route3->setName('route3');
+ $route4->setName('route4');
+ $route5->setName('route5');
+ //Route
+ $this->assertEquals('/foo/bar', $router->urlFor('route1'));
+ //Route with params
+ $this->assertEquals('/foo/foo/bar', $router->urlFor('route2', array('one' => 'foo', 'two' => 'bar')));
+ $this->assertEquals('/foo/foo/:two', $router->urlFor('route2', array('one' => 'foo')));
+ $this->assertEquals('/foo/:one/bar', $router->urlFor('route2', array('two' => 'bar')));
+ //Route with params and optional segments
+ $this->assertEquals('/foo/foo/bar', $router->urlFor('route3', array('one' => 'foo', 'two' => 'bar')));
+ $this->assertEquals('/foo/foo', $router->urlFor('route3', array('one' => 'foo')));
+ $this->assertEquals('/foo/:one/bar', $router->urlFor('route3', array('two' => 'bar')));
+ $this->assertEquals('/foo/:one', $router->urlFor('route3'));
+ //Route with params and optional segments
+ $this->assertEquals('/foo/foo/bar/', $router->urlFor('route4', array('one' => 'foo', 'two' => 'bar')));
+ $this->assertEquals('/foo/foo/', $router->urlFor('route4', array('one' => 'foo')));
+ $this->assertEquals('/foo/:one/bar/', $router->urlFor('route4', array('two' => 'bar')));
+ $this->assertEquals('/foo/:one/', $router->urlFor('route4'));
+ //Route with params and optional segments
+ $this->assertEquals('/foo/foo/bar/what/', $router->urlFor('route5', array('one' => 'foo', 'two' => 'bar', 'three' => 'what')));
+ $this->assertEquals('/foo/foo/', $router->urlFor('route5', array('one' => 'foo')));
+ $this->assertEquals('/foo/:one/bar/', $router->urlFor('route5', array('two' => 'bar')));
+ $this->assertEquals('/foo/:one/bar/what/', $router->urlFor('route5', array('two' => 'bar', 'three' => 'what')));
+ $this->assertEquals('/foo/:one/', $router->urlFor('route5'));
+ }
+
+ /**
+ * Test that router returns matched routes based on URI only, not
+ * based on the HTTP method.
+ */
+ public function testRouterMatchesRoutesByUriOnly() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '', //<-- Physical
+ 'PATH_INFO' => '/foo', //<-- Virtual
+ 'QUERY_STRING' => 'one=1&two=2&three=3',
+ 'SERVER_NAME' => 'slim',
+ 'SERVER_PORT' => 80,
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => fopen('php://stderr', 'w'),
+ 'HTTP_HOST' => 'slim'
+ ));
+ $this->env = Slim_Environment::getInstance();
+ $this->req = new Slim_Http_Request($this->env);
+ $this->res = new Slim_Http_Response();
+ $router = new Slim_Router($this->req, $this->res);
+ $router->map('/foo', function () {})->via('GET');
+ $router->map('/foo', function () {})->via('POST');
+ $router->map('/foo', function () {})->via('PUT');
+ $router->map('/foo/bar/xyz', function () {})->via('DELETE');
+ $this->assertEquals(3, count($router->getMatchedRoutes()));
+ }
+
+ /**
+ * Test that Router implements IteratorAggregate interface
+ */
+ public function testRouterImplementsIteratorAggregate() {
+ $router = new Slim_Router($this->req, $this->res);
+ $router->map('/bar', function () {})->via('GET');
+ $router->map('/foo1', function () {})->via('POST');
+ $router->map('/bar', function () {})->via('PUT');
+ $router->map('/foo/bar/xyz', function () {})->via('DELETE');
+ $iterator = $router->getIterator();
+ $this->assertInstanceOf('ArrayIterator', $iterator);
+ $this->assertEquals(2, $iterator->count());
+ }
+}
\ No newline at end of file
diff --git a/tests/SlimTest.php b/tests/SlimTest.php
new file mode 100644
index 0000000..52ff536
--- /dev/null
+++ b/tests/SlimTest.php
@@ -0,0 +1,1238 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/Slim.php';
+require_once 'Slim/View.php';
+require_once 'Slim/Middleware/Flash.php';
+
+//Register non-Slim autoloader
+function customAutoLoader( $class ) {
+ $file = rtrim(dirname(__FILE__), '/') . '/' . $class . '.php';
+ if ( file_exists($file) ) {
+ require $file;
+ } else {
+ return;
+ }
+}
+spl_autoload_register('customAutoLoader');
+
+//Mock custom view
+class CustomView extends Slim_View {
+ function render($template) { echo "Custom view"; }
+}
+
+//Mock middleware
+class CustomMiddleware extends Slim_Middleware {
+ public function call() {
+ $env = $this->app->environment();
+ $res = $this->app->response();
+ $env['slim.test'] = 'Hello';
+ $this->next->call();
+ $res->header('X-Slim-Test', 'Hello');
+ $res->write('Hello');
+ }
+}
+
+class SlimTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ //Remove environment mode if set
+ unset($_ENV['SLIM_MODE']);
+
+ //Reset session
+ $_SESSION = array();
+
+ //Prepare default environment variables
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'QUERY_STRING' => 'one=foo&two=bar',
+ 'SERVER_NAME' => 'slimframework.com',
+ ));
+ }
+
+ /************************************************
+ * INSTANTIATION
+ ************************************************/
+
+ /**
+ * Test version constant is string
+ */
+ public function testHasVersionConstant() {
+ $this->assertTrue(is_string(Slim::VERSION));
+ }
+
+ /**
+ * Test default instance properties
+ */
+ public function testDefaultInstanceProperties() {
+ $s = new Slim();
+ $this->assertInstanceOf('Slim_Http_Request', $s->request());
+ $this->assertInstanceOf('Slim_Http_Response', $s->response());
+ $this->assertInstanceOf('Slim_Router', $s->router());
+ $this->assertInstanceOf('Slim_View', $s->view());
+ $this->assertInstanceOf('Slim_Log', $s->getLog());
+ $this->assertEquals(4, $s->getLog()->getLevel());
+ $this->assertTrue($s->getLog()->getEnabled());
+ $this->assertInstanceOf('Slim_Environment', $s->environment());
+ }
+
+ /**
+ * Test get default instance
+ */
+ public function testGetDefaultInstance() {
+ $s = new Slim();
+ $s->setName('default'); //We must do this manually since a default app is already set in prev tests
+ $this->assertEquals('default', $s->getName());
+ $this->assertInstanceOf('Slim', Slim::getInstance());
+ $this->assertSame($s, Slim::getInstance());
+ }
+
+ /**
+ * Test get named instance
+ */
+ public function testGetNamedInstance() {
+ $s = new Slim();
+ $s->setName('foo');
+ $this->assertSame($s, Slim::getInstance('foo'));
+ }
+
+ /**
+ * Test Slim autoloader ignores non-Slim classes
+ *
+ * Pre-conditions:
+ * Instantiate a non-Slim class;
+ *
+ * Post-conditions:
+ * Slim autoloader returns without requiring a class file;
+ */
+ public function testSlimAutoloaderIgnoresNonSlimClass() {
+ $foo = new Foo();
+ }
+
+ /************************************************
+ * SETTINGS
+ ************************************************/
+
+ /**
+ * Test get setting that exists
+ */
+ public function testGetSettingThatExists() {
+ $s = new Slim();
+ $this->assertEquals('./templates', $s->config('templates.path'));
+ }
+
+ /**
+ * Test get setting that does not exist
+ */
+ public function testGetSettingThatDoesNotExist() {
+ $s = new Slim();
+ $this->assertNull($s->config('foo'));
+ }
+
+ /**
+ * Test set setting
+ */
+ public function testSetSetting() {
+ $s = new Slim();
+ $this->assertEquals('./templates', $s->config('templates.path'));
+ $s->config('templates.path', './tmpl');
+ $this->assertEquals('./tmpl', $s->config('templates.path'));
+ }
+
+ /**
+ * Test batch set settings
+ */
+ public function testBatchSetSettings() {
+ $s = new Slim();
+ $this->assertEquals('./templates', $s->config('templates.path'));
+ $this->assertTrue($s->config('debug'));
+ $s->config(array(
+ 'templates.path' => './tmpl',
+ 'debug' => false
+ ));
+ $this->assertEquals('./tmpl', $s->config('templates.path'));
+ $this->assertFalse($s->config('debug'));
+ }
+
+ /************************************************
+ * MODES
+ ************************************************/
+
+ /**
+ * Test default mode
+ */
+ public function testGetDefaultMode() {
+ $s = new Slim();
+ $this->assertEquals('development', $s->getMode());
+ }
+
+ /**
+ * Test custom mode from environment
+ */
+ public function testGetModeFromEnvironment() {
+ $_ENV['SLIM_MODE'] = 'production';
+ $s = new Slim();
+ $this->assertEquals('production', $s->getMode());
+ }
+
+ /**
+ * Test custom mode from app settings
+ */
+ public function testGetModeFromSettings() {
+ $s = new Slim(array(
+ 'mode' => 'test'
+ ));
+ $this->assertEquals('test', $s->getMode());
+ }
+
+ /**
+ * Test mode configuration
+ */
+ public function testModeConfiguration() {
+ $flag = 0;
+ $configureTest = function () use (&$flag) {
+ $flag = 'test';
+ };
+ $configureProduction = function () use (&$flag) {
+ $flag = 'production';
+ };
+ $s = new Slim(array('mode' => 'test'));
+ $s->configureMode('test', $configureTest);
+ $s->configureMode('production', $configureProduction);
+ $this->assertEquals('test', $flag);
+ }
+
+ /**
+ * Test mode configuration when mode does not match
+ */
+ public function testModeConfigurationWhenModeDoesNotMatch() {
+ $flag = 0;
+ $configureTest = function () use (&$flag) {
+ $flag = 'test';
+ };
+ $s = new Slim(array('mode' => 'production'));
+ $s->configureMode('test', $configureTest);
+ $this->assertEquals(0, $flag);
+ }
+
+ /**
+ * Test mode configuration when not callable
+ */
+ public function testModeConfigurationWhenNotCallable() {
+ $flag = 0;
+ $s = new Slim(array('mode' => 'production'));
+ $s->configureMode('production', 'foo');
+ $this->assertEquals(0, $flag);
+ }
+
+ /************************************************
+ * ROUTING
+ ************************************************/
+
+ /**
+ * Test GENERIC route
+ */
+ public function testGenericRoute() {
+ $s = new Slim();
+ $callable = function () { echo "foo"; };
+ $route = $s->map('/bar', $callable);
+ $this->assertInstanceOf('Slim_Route', $route);
+ $this->assertEmpty($route->getHttpMethods());
+ }
+
+ /**
+ * Test GET route
+ */
+ public function testGetRoute() {
+ $s = new Slim();
+ $mw1 = function () { echo "foo"; };
+ $mw2 = function () { echo "bar"; };
+ $callable = function () { echo "xyz"; };
+ $route = $s->get('/bar', $mw1, $mw2, $callable);
+ $s->call();
+ $this->assertEquals('foobarxyz', $s->response()->body());
+ $this->assertEquals('/bar', $route->getPattern());
+ $this->assertSame($callable, $route->getCallable());
+ }
+
+ /**
+ * Test POST route
+ */
+ public function testPostRoute() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $mw1 = function () { echo "foo"; };
+ $mw2 = function () { echo "bar"; };
+ $callable = function () { echo "xyz"; };
+ $route = $s->post('/bar', $mw1, $mw2, $callable);
+ $s->call();
+ $this->assertEquals('foobarxyz', $s->response()->body());
+ $this->assertEquals('/bar', $route->getPattern());
+ $this->assertSame($callable, $route->getCallable());
+ }
+
+ /**
+ * Test PUT route
+ */
+ public function testPutRoute() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'PUT',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $mw1 = function () { echo "foo"; };
+ $mw2 = function () { echo "bar"; };
+ $callable = function () { echo "xyz"; };
+ $route = $s->put('/bar', $mw1, $mw2, $callable);
+ $s->call();
+ $this->assertEquals('foobarxyz', $s->response()->body());
+ $this->assertEquals('/bar', $route->getPattern());
+ $this->assertSame($callable, $route->getCallable());
+ }
+
+ /**
+ * Test DELETE route
+ */
+ public function testDeleteRoute() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'DELETE',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $mw1 = function () { echo "foo"; };
+ $mw2 = function () { echo "bar"; };
+ $callable = function () { echo "xyz"; };
+ $route = $s->delete('/bar', $mw1, $mw2, $callable);
+ $s->call();
+ $this->assertEquals('foobarxyz', $s->response()->body());
+ $this->assertEquals('/bar', $route->getPattern());
+ $this->assertSame($callable, $route->getCallable());
+ }
+
+ /**
+ * Test OPTIONS route
+ */
+ public function testOptionsRoute() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'OPTIONS',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $mw1 = function () { echo "foo"; };
+ $mw2 = function () { echo "bar"; };
+ $callable = function () { echo "xyz"; };
+ $route = $s->options('/bar', $mw1, $mw2, $callable);
+ $s->call();
+ $this->assertEquals('foobarxyz', $s->response()->body());
+ $this->assertEquals('/bar', $route->getPattern());
+ $this->assertSame($callable, $route->getCallable());
+ }
+
+ /**
+ * Test if route expects trailing slash and URL does not have one
+ */
+ public function testRouteWithSlashAndUrlWithout() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/bar/', function () { echo "xyz"; });
+ $s->call();
+ $this->assertEquals(301, $s->response()->status());
+ }
+
+ /**
+ * Test 405 Method Not Allowed
+ */
+ public function testMethodNotAllowed() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'POST',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () { echo "xyz"; });
+ $s->call();
+ $this->assertEquals(405, $s->response()->status());
+ }
+
+ /**
+ * Test if route does NOT expect trailing slash and URL has one
+ */
+ public function testRouteWithoutSlashAndUrlWithOne() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar/', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () { echo "xyz"; });
+ $s->call();
+ $this->assertEquals(404, $s->response()->status());
+ }
+
+ /**
+ * Test if route contains URL encoded characters
+ */
+ public function testRouteWithUrlEncodedCharacters() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar/jo%20hn/smi%20th', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/bar/:one/:two', function ($one, $two) { echo $one . $two; });
+ $s->call();
+ $this->assertEquals('jo hnsmi th', $s->response()->body());
+ }
+
+ /************************************************
+ * VIEW
+ ************************************************/
+
+ /**
+ * Test set view with string class name
+ */
+ public function testSetSlimViewFromString() {
+ $s = new Slim();
+ $this->assertInstanceOf('Slim_View', $s->view());
+ $s->view('CustomView');
+ $this->assertInstanceOf('CustomView', $s->view());
+ }
+
+ /**
+ * Test set view with object instance
+ */
+ public function testSetSlimViewFromInstance() {
+ $s = new Slim();
+ $this->assertInstanceOf('Slim_View', $s->view());
+ $s->view(new CustomView());
+ $this->assertInstanceOf('CustomView', $s->view());
+ }
+
+ /**
+ * Test view data is transferred to newer view
+ */
+ public function testViewDataTransfer() {
+ $data = array('foo' => 'bar');
+ $s = new Slim();
+ $s->view()->setData($data);
+ $s->view('CustomView');
+ $this->assertSame($data, $s->view()->getData());
+ }
+
+ /************************************************
+ * RENDERING
+ ************************************************/
+
+ /**
+ * Test render with template and data
+ */
+ public function testRenderTemplateWithData() {
+ $s = new Slim(array('templates.path' => dirname(__FILE__) . '/templates'));
+ $s->get('/bar', function () use ($s) {
+ $s->render('test.php', array('foo' => 'bar'));
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(200, $status);
+ $this->assertEquals('test output bar', $body);
+ }
+
+ /**
+ * Test render with template and data and status
+ */
+ public function testRenderTemplateWithDataAndStatus() {
+ $s = new Slim(array('templates.path' => dirname(__FILE__) . '/templates'));
+ $s->get('/bar', function () use ($s) {
+ $s->render('test.php', array('foo' => 'bar'), 500);
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(500, $status);
+ $this->assertEquals('test output bar', $body);
+ }
+
+ /************************************************
+ * LOG
+ ************************************************/
+
+ /**
+ * Test get log
+ *
+ * This asserts that a Slim app has a default Log
+ * upon instantiation. The Log itself is tested
+ * separately in another file.
+ */
+ public function testGetLog() {
+ $s = new Slim();
+ $this->assertInstanceOf('Slim_Log', $s->getLog());
+ }
+
+ /************************************************
+ * HTTP CACHING
+ ************************************************/
+
+ /**
+ * Test Last-Modified match
+ */
+ public function testLastModifiedMatch() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 17:00:52 -0400',
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->lastModified(1286139652);
+ });
+ $s->call();
+ $this->assertEquals(304, $s->response()->status());
+ }
+
+ /**
+ * Test Last-Modified match
+ */
+ public function testLastModifiedDoesNotMatch() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 17:00:52 -0400',
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->lastModified(1286139250);
+ });
+ $s->call();
+ $this->assertEquals(200, $s->response()->status());
+ }
+
+ public function testLastModifiedOnlyAcceptsIntegers(){
+ $this->setExpectedException('InvalidArgumentException');
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->lastModified('Test');
+ });
+ $s->call();
+ }
+
+ /**
+ * Test ETag matches
+ */
+ public function testEtagMatches() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'IF_NONE_MATCH' => '"abc123"',
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->etag('abc123');
+ });
+ $s->call();
+ $this->assertEquals(304, $s->response()->status());
+ }
+
+ /**
+ * Test ETag does not match
+ */
+ public function testEtagDoesNotMatch() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'IF_NONE_MATCH' => '"abc1234"',
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->etag('abc123');
+ });
+ $s->call();
+ $this->assertEquals(200, $s->response()->status());
+ }
+
+ /**
+ * Test ETag with invalid type
+ */
+ public function testETagWithInvalidType(){
+ $this->setExpectedException('InvalidArgumentException');
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'IF_NONE_MATCH' => '"abc1234"',
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->etag('123','foo');
+ });
+ $s->call();
+ }
+
+ /**
+ * Test Expires
+ */
+ public function testExpiresAsString() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $expectedDate = gmdate('D, d M Y', strtotime('5 days')); //Just the day, month, and year
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->expires('5 days');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertTrue(isset($header['Expires']));
+ $this->assertEquals(0, strpos($header['Expires'], $expectedDate));
+ }
+
+ /**
+ * Test Expires
+ */
+ public function testExpiresAsInteger() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $fiveDaysFromNow = time() + (60 * 60 * 24 * 5);
+ $expectedDate = gmdate('D, d M Y', $fiveDaysFromNow); //Just the day, month, and year
+ $s = new Slim();
+ $s->get('/bar', function () use ($s, $fiveDaysFromNow) {
+ $s->expires($fiveDaysFromNow);
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertTrue(isset($header['Expires']));
+ $this->assertEquals(0, strpos($header['Expires'], $expectedDate));
+ }
+
+ /************************************************
+ * COOKIES
+ ************************************************/
+
+ /**
+ * Set cookie
+ *
+ * This tests that the Slim application instance sets
+ * a cookie in the HTTP response header. This does NOT
+ * test the implementation of setting the cookie; that is
+ * tested in a separate file.
+ */
+ public function testSetCookie() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->setCookie('foo', 'bar', '2 days');
+ $s->setCookie('foo1', 'bar1', '2 days');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $cookies = explode("\n", $header['Set-Cookie']);
+ $this->assertEquals(2, count($cookies));
+ $this->assertEquals(1, preg_match('@foo=bar@', $cookies[0]));
+ $this->assertEquals(1, preg_match('@foo1=bar1@', $cookies[1]));
+ }
+
+ /**
+ * Test get cookie
+ *
+ * This method ensures that the `Cookie:` HTTP request
+ * header is parsed if present, and made accessible via the
+ * Request object.
+ */
+ public function testGetCookie() {
+ Slim_Environment::mock(array(
+ 'REQUEST_METHOD' => 'GET',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'QUERY_STRING' => 'one=foo&two=bar',
+ 'SERVER_NAME' => 'slimframework.com',
+ 'SERVER_PORT' => 80,
+ 'COOKIE' => 'foo=bar; foo2=bar2',
+ 'slim.url_scheme' => 'http',
+ 'slim.input' => '',
+ 'slim.errors' => @fopen('php://stderr', 'w')
+ ));
+ $s = new Slim();
+ $this->assertEquals('bar', $s->getCookie('foo'));
+ $this->assertEquals('bar2', $s->getCookie('foo2'));
+ }
+
+ /**
+ * Test get cookie when cookie does not exist
+ */
+ public function testGetCookieThatDoesNotExist() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $this->assertNull($s->getCookie('foo'));
+ }
+
+ /**
+ * Test delete cookie
+ *
+ * This method ensures that the `Set-Cookie:` HTTP response
+ * header is set. The implementation of setting the response
+ * cookie is tested separately in another file.
+ */
+ public function testDeleteCookie() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ 'COOKIE' => 'foo=bar; foo2=bar2',
+ ));
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->setCookie('foo', 'bar');
+ $s->deleteCookie('foo');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $cookies = explode("\n", $header['Set-Cookie']);
+ $this->assertEquals(1, count($cookies));
+ $this->assertEquals(1, preg_match('@^foo=;@', $cookies[0]));
+ }
+
+ /**
+ * Test set encrypted cookie
+ *
+ * This method ensures that the `Set-Cookie:` HTTP request
+ * header is set. The implementation is tested in a separate file.
+ */
+ public function testSetEncryptedCookie() {
+ $s = new Slim();
+ $s->setEncryptedCookie('foo', 'bar');
+ $r = $s->response();
+ $this->assertEquals(1, preg_match("@^foo=.+%7C.+%7C.+@", $r['Set-Cookie'])); //<-- %7C is a url-encoded pipe
+ }
+
+ /**
+ * Test get encrypted cookie
+ *
+ * This only tests that this method runs without error. The implementation of
+ * fetching the encrypted cookie is tested separately.
+ */
+ public function testGetEncryptedCookieAndDeletingIt() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $r = $s->response();
+ $this->assertFalse($s->getEncryptedCookie('foo'));
+ $this->assertEquals(1, preg_match("@foo=;.*@", $r['Set-Cookie']));
+ }
+
+ /**
+ * Test get encrypted cookie WITHOUT deleting it
+ *
+ * This only tests that this method runs without error. The implementation of
+ * fetching the encrypted cookie is tested separately.
+ */
+ public function testGetEncryptedCookieWithoutDeletingIt() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/bar', //<-- Virtual
+ ));
+ $s = new Slim();
+ $r = $s->response();
+ $this->assertFalse($s->getEncryptedCookie('foo', false));
+ $this->assertEquals(0, preg_match("@foo=;.*@", $r['Set-Cookie']));
+ }
+
+ /************************************************
+ * HELPERS
+ ************************************************/
+
+ /**
+ * Test get filesystem path to Slim app root directory
+ */
+ public function testGetRoot() {
+ $_SERVER['DOCUMENT_ROOT'] = dirname(__FILE__); //<-- No trailing slash
+ $s = new Slim();
+ $this->assertEquals($_SERVER['DOCUMENT_ROOT'] . '/foo/', $s->root()); //<-- Appends physical app path with trailing slash
+ }
+
+ /**
+ * Test stop
+ */
+ public function testStop() {
+ $this->setExpectedException('Slim_Exception_Stop');
+ $s = new Slim();
+ $s->stop();
+ }
+
+ /**
+ * Test stop with subsequent output
+ */
+ public function testStopWithSubsequentOutput() {
+ $this->expectOutputString('Foo');
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ echo "Foo"; //<-- Should be in response body!
+ $s->stop();
+ echo "Bar"; //<-- Should not be in response body!
+ });
+ $s->call();
+ $this->assertEquals('Foo', $s->response()->body());
+ }
+
+ /**
+ * Test halt
+ */
+ public function testHalt() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ echo "Foo!"; //<-- Should not be in response body!
+ $s->halt(500, 'Something broke');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(500, $status);
+ $this->assertEquals('Something broke', $body);
+ }
+
+ /**
+ * Test pass cleans buffer and throws exception
+ */
+ public function testPass() {
+ ob_start();
+ $s = new Slim();
+ echo "Foo";
+ try {
+ $s->pass();
+ $this->fail('Did not catch Slim_Exception_Pass');
+ } catch ( Slim_Exception_Pass $e ) {}
+ $output = ob_get_clean();
+ $this->assertEquals('', $output);
+ }
+
+ /**
+ * Test pass when there is a subsequent fallback route
+ */
+ public function testPassWithSubsequentRoute() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/name/Frank', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/name/Frank', function () use ($s) {
+ echo "Fail"; //<-- Should not be in response body!
+ $s->pass();
+ });
+ $s->get('/name/:name', function ($name) {
+ echo $name; //<-- Should be in response body!
+ });
+ $s->call();
+ $this->assertEquals('Frank', $s->response()->body());
+ }
+
+ /**
+ * Test pass when there is not a subsequent fallback route
+ */
+ public function testPassWithoutSubsequentRoute() {
+ Slim_Environment::mock(array(
+ 'SCRIPT_NAME' => '/foo', //<-- Physical
+ 'PATH_INFO' => '/name/Frank', //<-- Virtual
+ ));
+ $s = new Slim();
+ $s->get('/name/Frank', function () use ($s) {
+ echo "Fail"; //<-- Should not be in response body!
+ $s->pass();
+ });
+ $s->call();
+ $this->assertEquals(404, $s->response()->status());
+ }
+
+ /**
+ * Test content type
+ */
+ public function testContentType() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->contentType('application/json');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals('application/json', $header['Content-Type']);
+ }
+
+ /**
+ * Test status
+ */
+ public function testStatus() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->status(403);
+ });
+ $s->call();
+ $this->assertEquals(403, $s->response()->status());
+ }
+
+ /**
+ * Test URL for
+ */
+ public function testSlimUrlFor(){
+ $s = new Slim();
+ $s->get('/hello/:name', function () {})->name('hello');
+ $this->assertEquals('/foo/hello/Josh', $s->urlFor('hello', array('name' => 'Josh'))); //<-- Prepends physical path!
+ }
+
+ /**
+ * Test redirect sets status and header
+ */
+ public function testRedirect() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ echo "Foo"; //<-- Should not be in response body!
+ $s->redirect('/somewhere/else', 303);
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(303, $status);
+ $this->assertEquals('/somewhere/else', $header['Location']);
+ $this->assertEquals('/somewhere/else', $body);
+ }
+
+ /************************************************
+ * RUNNER
+ ************************************************/
+
+ /**
+ * Test that runner sends headers and body
+ */
+ public function testRun() {
+ $this->expectOutputString('Foo');
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ echo "Foo";
+ });
+ $s->run();
+ }
+
+ /************************************************
+ * MIDDLEWARE
+ ************************************************/
+
+ /**
+ * Test add middleware
+ *
+ * This asserts that middleware are queued and called
+ * in sequence. This also asserts that the environment
+ * variables are passed by reference.
+ */
+ public function testAddMiddleware() {
+ $this->expectOutputString('FooHello');
+ $s = new Slim();
+ $s->add(new CustomMiddleware()); //<-- See top of this file for class definition
+ $s->get('/bar', function () {
+ echo 'Foo';
+ });
+ $s->run();
+ $this->assertEquals('Hello', $s->response()->header('X-Slim-Test'));
+ }
+
+ /************************************************
+ * FLASH MESSAGING
+ ************************************************/
+
+ public function testSetFlashForNextRequest() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->flash('info', 'bar');
+ });
+ $this->assertFalse(isset($_SESSION['slim.flash']));
+ $s->run();
+ $this->assertEquals('bar', $_SESSION['slim.flash']['info']);
+ }
+
+ public function testSetFlashForCurrentRequest() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->flashNow('info', 'bar');
+ });
+ $s->run();
+ $env = $s->environment();
+ $this->assertEquals('bar', $env['slim.flash']['info']);
+ }
+
+ public function testKeepFlashForNextRequest() {
+ $_SESSION['slim.flash'] = array('info' => 'Foo');
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->flashKeep();
+ });
+ $s->run();
+ $this->assertEquals('Foo', $_SESSION['slim.flash']['info']);
+ }
+
+ /************************************************
+ * NOT FOUND HANDLING
+ ************************************************/
+
+ /**
+ * Test custom Not Found handler
+ */
+ public function testNotFound() {
+ $s = new Slim();
+ $s->notFound(function () {
+ echo "Not Found";
+ });
+ $s->get('/foo', function () {});
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(404, $status);
+ $this->assertEquals('Not Found', $body);
+ }
+
+ /************************************************
+ * ERROR HANDLING
+ ************************************************/
+
+ /**
+ * Test default and custom error handlers
+ *
+ * Pre-conditions:
+ * Invoked app route calls default error handler;
+ *
+ * Post-conditions:
+ * Response status code is 500;
+ */
+ public function testSlimError() {
+ $s = new Slim();
+ $s->get('/bar', function () use ($s) {
+ $s->error();
+ });
+ $s->call();
+ $this->assertEquals(500, $s->response()->status());
+ }
+
+ /**
+ * Test triggered errors are converted to ErrorExceptions
+ *
+ * Pre-conditions:
+ * Custom error handler defined;
+ * Invoked app route triggers error;
+ *
+ * Post-conditions:
+ * Response status is 500;
+ * Response body is equal to triggered error message;
+ * Error handler's argument is ErrorException instance;
+ */
+ public function DISABLEDtestTriggeredErrorsAreConvertedToErrorExceptions() {
+ $s = new Slim(array(
+ 'debug' => false
+ ));
+ $s->error(function ( $e ) {
+ if ( $e instanceof ErrorException ) {
+ echo $e->getMessage();
+ }
+ });
+ $s->get('/bar', function () {
+ trigger_error('Foo I say!');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(500, $status);
+ $this->assertEquals('Foo I say!', $body);
+ }
+
+ /**
+ * Test error triggered with multiple applications
+ *
+ * Pre-conditions:
+ * Multiple Slim apps are instantiated;
+ * Both apps are run;
+ * One app returns 200 OK;
+ * One app triggers an error;
+ *
+ * Post-conditions:
+ * One app returns 200 OK with no Exceptions;
+ * One app returns 500 Error;
+ * Error triggered does not affect other app;
+ */
+ public function testErrorWithMultipleApps() {
+ $s1 = new Slim(array(
+ 'debug' => false
+ ));
+ $s2 = new Slim();
+ $s1->get('/bar', function () {
+ trigger_error('error');
+ });
+ $s2->get('/bar', function () {
+ echo 'success';
+ });
+ $s1->call();
+ $s2->call();
+ $this->assertEquals(500, $s1->response()->status());
+ $this->assertEquals(200, $s2->response()->status());
+ }
+
+ /**
+ * Test custom error handler uses existing Response object
+ */
+ public function testErrorHandlerUsesCurrentResponseObject() {
+ $s = new Slim(array(
+ 'debug' => false
+ ));
+ $s->error(function ( Exception $e ) use ($s) {
+ $r = $s->response();
+ $r->status(503);
+ $r->write('Foo');
+ $r['X-Powered-By'] = 'Slim';
+ echo 'Bar';
+ });
+ $s->get('/bar', function () {
+ throw new Exception('Foo');
+ });
+ $s->call();
+ list($status, $header, $body) = $s->response()->finalize();
+ $this->assertEquals(503, $status);
+ $this->assertEquals('FooBar', $body);
+ $this->assertEquals('Slim', $header['X-Powered-By']);
+ }
+
+ /************************************************
+ * HOOKS
+ ************************************************/
+
+ /**
+ * Test hook listener
+ *
+ * Pre-conditions:
+ * Slim app instantiated;
+ * Hook name does not exist;
+ * Listeners are callable objects;
+ *
+ * Post-conditions:
+ * Callables are invoked in expected order;
+ */
+ public function testRegistersAndCallsHooksByPriority() {
+ $this->expectOutputString('barfoo');
+ $app = new Slim();
+ $callable1 = function () { echo "foo"; };
+ $callable2 = function () { echo "bar"; };
+ $app->hook('test.hook.one', $callable1); //default is 10
+ $app->hook('test.hook.one', $callable2, 8);
+ $hooks = $app->getHooks();
+ $this->assertEquals(7, count($hooks)); //6 default, 1 custom
+ $app->applyHook('test.hook.one');
+ }
+
+ /**
+ * Test hook listener if listener is not callable
+ *
+ * Pre-conditions:
+ * Slim app instantiated;
+ * Hook name does not exist;
+ * Listener is NOT a callable object;
+ *
+ * Post-conditions:
+ * Hook is created;
+ * Callable is NOT assigned to hook;
+ */
+ public function testHookInvalidCallable() {
+ $app = new Slim();
+ $callable = 'test'; //NOT callable
+ $app->hook('test.hook.one', $callable);
+ $this->assertEquals(array(array()), $app->getHooks('test.hook.one'));
+ }
+
+ /**
+ * Test hook invocation if hook does not exist
+ *
+ * Pre-conditions:
+ * Slim app instantiated;
+ * Hook name does not exist;
+ *
+ * Post-conditions:
+ * Hook is created;
+ * Hook initialized with empty array;
+ */
+ public function testHookInvocationIfNotExists() {
+ $app = new Slim();
+ $app->applyHook('test.hook.one');
+ $this->assertEquals(array(array()), $app->getHooks('test.hook.one'));
+ }
+
+ /**
+ * Test clear hooks
+ *
+ * Pre-conditions:
+ * Slim app instantiated;
+ * Two hooks exist, each with one listener;
+ *
+ * Post-conditions:
+ * Case A: Listeners for 'test.hook.one' are cleared;
+ * Case B: Listeners for all hooks are cleared;
+ */
+ public function testHookClear() {
+ $app = new Slim();
+ $app->hook('test.hook.one', function () {});
+ $app->hook('test.hook.two', function () {});
+ $app->clearHooks('test.hook.two');
+ $this->assertEquals(array(array()), $app->getHooks('test.hook.two'));
+ $hookOne = $app->getHooks('test.hook.one');
+ $this->assertTrue(count($hookOne[10]) === 1);
+ $app->clearHooks();
+ $this->assertEquals(array(array()), $app->getHooks('test.hook.one'));
+ }
+
+ /**
+ * Test hook filter behavior
+ */
+ public function testHookFilterBehavior() {
+ $app = new Slim();
+ $app->hook('test.hook', function ($arg) { return $arg . 'foo'; });
+ $this->assertEquals('barfoo', $app->applyHook('test.hook', 'bar'));
+ }
+}
diff --git a/tests/ViewTest.php b/tests/ViewTest.php
new file mode 100644
index 0000000..d19466b
--- /dev/null
+++ b/tests/ViewTest.php
@@ -0,0 +1,180 @@
+
+ * @copyright 2011 Josh Lockhart
+ * @link http://www.slimframework.com
+ * @license http://www.slimframework.com/license
+ * @version 1.6.0
+ *
+ * MIT LICENSE
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+set_include_path(dirname(__FILE__) . '/../' . PATH_SEPARATOR . get_include_path());
+
+require_once 'Slim/View.php';
+
+class ViewTest extends PHPUnit_Framework_TestCase {
+
+ public function setUp() {
+ $this->view = new Slim_View();
+ }
+
+ public function generateTestData() {
+ return array('a' => 1, 'b' => 2, 'c' => 3);
+ }
+
+ /**
+ * Test initial View data is an empty array
+ *
+ * Pre-conditions:
+ * None
+ *
+ * Post-conditions:
+ * The View object's data attribute is an empty array
+ */
+ public function testViewIsConstructedWithDataArray() {
+ $this->assertEquals(array(), $this->view->getData());
+ }
+
+ /**
+ * Test View sets and gets data
+ *
+ * Pre-conditions:
+ * Case A: Set view data key/value
+ * Case B: Set view data as array
+ * Case C: Set view data with one argument that is not an array
+ *
+ * Post-conditions:
+ * Case A: Data key/value are set
+ * Case B: Data is set to array
+ * Case C: An InvalidArgumentException is thrown
+ */
+ public function testViewSetAndGetData() {
+ //Case A
+ $this->view->setData('one', 1);
+ $this->assertEquals(1, $this->view->getData('one'));
+
+ //Case B
+ $data = array('foo' => 'bar', 'a' => 'A');
+ $this->view->setData($data);
+ $this->assertSame($data, $this->view->getData());
+
+ //Case C
+ try {
+ $this->view->setData('foo');
+ $this->fail('Setting View data with non-array single argument did not throw exception');
+ } catch ( InvalidArgumentException $e ) {}
+ }
+
+ /**
+ * Test View appends data
+ *
+ * Pre-conditions:
+ * Append data to View several times
+ *
+ * Post-conditions:
+ * The View data contains all appended data
+ */
+ public function testViewAppendsData(){
+ $this->view->appendData(array('a' => 'A'));
+ $this->view->appendData(array('b' => 'B'));
+ $this->assertEquals(array('a' => 'A', 'b' => 'B'), $this->view->getData());
+ }
+
+ /**
+ * Test View templates directory
+ *
+ * Pre-conditions:
+ * View templates directory is set to an existing directory
+ *
+ * Post-conditions:
+ * The templates directory is set correctly.
+ */
+ public function testSetsTemplatesDirectory() {
+ $templatesDirectory = dirname(__FILE__) . '/templates';
+ $this->view->setTemplatesDirectory($templatesDirectory);
+ $this->assertEquals($templatesDirectory, $this->view->getTemplatesDirectory());
+ }
+
+ /**
+ * Test View templates directory may have a trailing slash when set
+ *
+ * Pre-conditions:
+ * View templates directory is set to an existing directory with a trailing slash
+ *
+ * Post-conditions:
+ * The View templates directory is set correctly without a trailing slash
+ */
+ public function testTemplatesDirectoryWithTrailingSlash() {
+ $this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates/');
+ $this->assertEquals(dirname(__FILE__) . '/templates', $this->view->getTemplatesDirectory());
+ }
+
+ /**
+ * Test View renders template
+ *
+ * Pre-conditions:
+ * View templates directory is set to an existing directory.
+ * View data is set without errors
+ * Case A: View renders an existing template
+ * Case B: View renders a non-existing template
+ *
+ * Post-conditions:
+ * Case A: The rendered template is returned as a string
+ * Case B: A RuntimeException is thrown
+ */
+ public function testRendersTemplateWithData() {
+ $this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates');
+ $this->view->setData(array('foo' => 'bar'));
+
+ //Case A
+ $output = $this->view->render('test.php');
+ $this->assertEquals('test output bar', $output);
+
+ //Case B
+ try {
+ $output = $this->view->render('foo.php');
+ $this->fail('Rendering non-existent template did not throw exception');
+ } catch ( RuntimeException $e ) {}
+ }
+
+ /**
+ * Test View displays template
+ *
+ * Pre-conditions:
+ * View templates directory is set to an existing directory.
+ * View data is set without errors
+ * View is displayed
+ *
+ * Post-conditions:
+ * The output buffer contains the rendered template
+ */
+ public function testDisplaysTemplateWithData() {
+ $this->expectOutputString('test output bar');
+ $this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates');
+ $this->view->setData(array('foo' => 'bar'));
+ $this->view->display('test.php');
+ }
+
+}
\ No newline at end of file
diff --git a/tests/templates/test.php b/tests/templates/test.php
new file mode 100644
index 0000000..2bdcdf8
--- /dev/null
+++ b/tests/templates/test.php
@@ -0,0 +1 @@
+test output
\ No newline at end of file