diff --git a/Slim/Exception/Pass.php b/Slim/Exception/Pass.php index 897e74e..bdefea0 100644 --- a/Slim/Exception/Pass.php +++ b/Slim/Exception/Pass.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,7 +29,6 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim\Exception; /** * Pass Exception @@ -41,10 +39,7 @@ namespace Slim\Exception; * HTTP 404 Not Found response will be sent to the client. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class Pass extends \Exception -{ - -} +class Slim_Exception_Pass extends Exception {} \ No newline at end of file diff --git a/Slim/Exception/RequestSlash.php b/Slim/Exception/RequestSlash.php index a4afd45..1e11b91 100644 --- a/Slim/Exception/RequestSlash.php +++ b/Slim/Exception/RequestSlash.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,7 +29,6 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim\Exception; /** * Request Slash Exception @@ -42,10 +40,7 @@ namespace Slim\Exception; * to the same resource URI with a trailing slash. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class RequestSlash extends \Exception -{ - -} +class Slim_Exception_RequestSlash extends Exception {} \ No newline at end of file diff --git a/Slim/Exception/Stop.php b/Slim/Exception/Stop.php index cad3cc5..086adb3 100644 --- a/Slim/Exception/Stop.php +++ b/Slim/Exception/Stop.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,7 +29,6 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim\Exception; /** * Stop Exception @@ -39,10 +37,7 @@ namespace Slim\Exception; * processing and return control flow to the outer PHP script. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class Stop extends \Exception -{ - -} +class Slim_Exception_Stop extends Exception {} \ No newline at end of file diff --git a/Slim/Http/Cookie.php b/Slim/Http/Cookie.php new file mode 100644 index 0000000..ffb2881 --- /dev/null +++ b/Slim/Http/Cookie.php @@ -0,0 +1,222 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.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. + */ + +/** + * Cookie + * + * Object-oriented representation of a Cookie to be sent in an HTTP response + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.0 + */ +class Slim_Http_Cookie { + + /** + * @var string + */ + protected $name; + + /** + * @var string + */ + protected $value; + + /** + * @var int UNIX timestamp + */ + protected $expires; + + /** + * @var string + */ + protected $path; + + /** + * @var string + */ + protected $domain; + + /** + * @var bool + */ + protected $secure; + + /** + * @var bool + */ + protected $httponly; + + /** + * Constructor + * @param string $name The cookie name + * @param string $value The cookie value + * @param mixed $time The duration of the cookie; + * If integer, should be a 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 __construct( $name, $value = null, $expires = 0, $path = null, $domain = null, $secure = false, $httponly = false ) { + $this->setName($name); + $this->setValue($value); + $this->setExpires($expires); + $this->setPath($path); + $this->setDomain($domain); + $this->setSecure($secure); + $this->setHttpOnly($httponly); + } + + /** + * Get cookie name + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Set cookie name + * @param string $name + * @return void + */ + public function setName( $name ) { + $this->name = (string)$name; + } + + /** + * Get cookie value + * @return string + */ + public function getValue() { + return $this->value; + } + + /** + * Set cookie value + * @param string $value + * @return void + */ + public function setValue( $value ) { + $this->value = (string)$value; + } + + /** + * Get cookie expiration time + * @return int UNIX timestamp + */ + public function getExpires() { + return $this->expires; + } + + /** + * Set cookie expiration time + * @param string|int Cookie expiration time + * @return void + */ + public function setExpires( $time ) { + $this->expires = is_string($time) ? strtotime($time) : (int)$time; + } + + /** + * Get cookie path + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * Set cookie path + * @param string $path + * @return void + */ + public function setPath( $path ) { + $this->path = (string)$path; + } + + /** + * Get cookie domain + * @return string + */ + public function getDomain() { + return $this->domain; + } + + /** + * Set cookie domain + * @param string $domain + * @return void + */ + public function setDomain( $domain ) { + $this->domain = (string)$domain; + } + + /** + * Is cookie sent only if SSL/HTTPS is used? + * @return bool + */ + public function getSecure() { + return $this->secure; + } + + /** + * Set whether cookie is sent only if SSL/HTTPS is used + * @param bool $secure + * @return void + */ + public function setSecure( $secure ) { + $this->secure = (bool)$secure; + } + + /** + * Is cookie sent with HTTP protocol only? + * @return bool + */ + public function getHttpOnly() { + return $this->httponly; + } + + /** + * Set whether cookie is sent with HTTP protocol only + * @param bool $httponly + * @return void + */ + public function setHttpOnly( $httponly ) { + $this->httponly = (bool)$httponly; + } + +} \ No newline at end of file diff --git a/Slim/Http/CookieJar.php b/Slim/Http/CookieJar.php new file mode 100644 index 0000000..f2f0305 --- /dev/null +++ b/Slim/Http/CookieJar.php @@ -0,0 +1,401 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.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. + */ + +/** + * Cooke Jar + * + * Used to manage signed, encrypted Cookies. Provides: + * + * - Cookie integrity and authenticity with HMAC + * - Confidentiality with symmetric encryption + * - Protection from replay attack if using SSL or TLS + * - Protection from interception if using SSL or TLS + * + * This code was originally called "BigOrNot_CookieManager" and written by + * Matthieu Huguet released under "CopyLeft" license. I have cleaned up the + * code formatting to conform with Slim Framework contributor guidelines and + * added additional code where necessary to play nice with Slim Cookie objects. + * + * Requirements: + * + * - libmcrypt > 2.4.x + * + * @author Matthies Huguet + */ +class Slim_Http_CookieJar { + + /** + * @var string Server secret key + */ + protected $_secret = ''; + + /** + * @var int Cryptographic algorithm used to encrypt cookies data + */ + protected $_algorithm = MCRYPT_RIJNDAEL_256; + + /** + * @var int Cryptographic mode (CBC, CFB ...) + */ + protected $_mode = MCRYPT_MODE_CBC; + + /** + * @var resource mcrypt module resource + */ + protected $_cryptModule = null; + + /** + * @var bool Enable high confidentiality for cookie value (symmetric encryption) + */ + protected $_highConfidentiality = true; + + /** + * @var bool Enable SSL support + */ + protected $_ssl = false; + + /** + * @var array[Cookie] Cookie objects + */ + protected $_cookies = array(); + + /** + * Constructor + * + * Initialize cookie manager and mcrypt module. + * + * @param string $secret Server's secret key + * @param array $config + * @throws Exception If secret key is empty + * @throws Exception If unable to open mcypt module + */ + public function __construct( $secret, $config = null ) { + if ( empty($secret) ) { + throw new Exception('You must provide a secret key'); + } + $this->_secret = $secret; + if ( $config !== null && !is_array($config) ) { + throw new Exception('Config must be an array'); + } + if ( is_array($config) ) { + if ( isset($config['high_confidentiality']) ) { + $this->_highConfidentiality = $config['high_confidentiality']; + } + if ( isset($config['mcrypt_algorithm']) ) { + $this->_algorithm = $config['mcrypt_algorithm']; + } + if ( isset($config['mcrypt_mode']) ) { + $this->_mode = $config['mcrypt_mode']; + } + if ( isset($config['enable_ssl']) ) { + $this->_ssl = $config['enable_ssl']; + } + } + if ( extension_loaded('mcrypt') ) { + $this->_cryptModule = mcrypt_module_open($this->_algorithm, '', $this->_mode, ''); + if ( $this->_cryptModule === false ) { + throw new Exception('Error while loading mcrypt module'); + } + } + } + + /** + * Get the high confidentiality mode + * + * @return bool TRUE if cookie data encryption is enabled, or FALSE if it isn't + */ + public function getHighConfidentiality() { + return $this->_highConfidentiality; + } + + /** + * Enable or disable cookie data encryption + * + * @param bool $enable TRUE to enable, FALSE to disable + * @return CookieJar + */ + public function setHighConfidentiality( $enable ) { + $this->_highConfidentiality = (bool)$enable; + return $this; + } + + /** + * Get the SSL status (enabled or disabled?) + * + * @return bool TRUE if SSL support is enabled, or FALSE if it isn't + */ + public function getSSL() { + return $this->_ssl; + } + + /** + * Enable SSL support (not enabled by default) + * + * Pro: Protect against replay attack + * Con: Cookie's lifetime is limited to SSL session's lifetime + * + * @param bool $enable TRUE to enable, FALSE to disable + * @return CookieJar + */ + public function setSSL( $enable ) { + $this->_ssl = (bool)$enable; + return $this; + } + + /** + * Get Cookies for Response + * + * @author Josh Lockhart + * @return array[Cookie] + */ + public function getResponseCookies() { + return $this->_cookies; + } + + /** + * Get Cookie with name for Response + * + * @author Josh Lockhart + * @param string $cookiename The name of the Cookie + * @return Cookie|null Cookie, or NULL if Cookie with name not found + */ + public function getResponseCookie( $cookiename ) { + return isset($this->_cookies[$cookiename]) ? $this->_cookies[$cookiename] : null; + } + + /** + * Set a secure cookie + * + * @param string $name Cookie name + * @param string $value Cookie value + * @param string $username User identifier + * @param integer $expire Expiration time + * @param string $path Cookie path + * @param string $domain Cookie domain + * @param bool $secure When TRUE, send the cookie only on a secure connection + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + */ + public function setCookie( $cookiename, $value, $username, $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = null ) { + $secureValue = extension_loaded('mcrypt') ? $this->_secureCookieValue($value, $username, $expire) : $value; + $this->setClassicCookie($cookiename, $secureValue, $expire, $path, $domain, $secure, $httponly); + } + + /** + * Delete a cookie + * + * @param string $name Cookie name + * @param string $path Cookie path + * @param string $domain Cookie domain + * @param bool $secure When TRUE, send the cookie only on a secure connection + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + */ + public function deleteCookie( $name, $path = '/', $domain = '', $secure = false, $httponly = null ) { + $expire = 315554400; /* 1980-01-01 */ + $this->_cookies[$name] = new Slim_Http_Cookie($name, '', $expire, $path, $domain, $secure, $httponly); + //setcookie($name, '', $expire, $path, $domain, $secure, $httponly); + } + + /** + * Get a secure cookie value + * + * Verify the integrity of cookie data and decrypt it. If the cookie + * is invalid, it can be automatically destroyed (default behaviour) + * + * @param string $cookiename Cookie name + * @param bool $delete Destroy the cookie if invalid? + * @return string|false The Cookie value, or FALSE if Cookie invalid + */ + public function getCookieValue( $cookiename, $deleteIfInvalid = true ) { + if ( $this->cookieExists($cookiename) ) { + if ( extension_loaded('mcrypt') ) { + $cookieValues = explode('|', $_COOKIE[$cookiename]); + if ( (count($cookieValues) === 4) && ($cookieValues[1] == 0 || $cookieValues[1] >= time()) ) { + $key = hash_hmac('sha1', $cookieValues[0] . $cookieValues[1], $this->_secret); + $cookieData = base64_decode($cookieValues[2]); + if ( $cookieData !== '' && $this->getHighConfidentiality() ) { + $data = $this->_decrypt($cookieData, $key, md5($cookieValues[1])); + } else { + $data = $cookieData; + } + if ( $this->_ssl && isset($_SERVER['SSL_SESSION_ID']) ) { + $verifKey = hash_hmac('sha1', $cookieValues[0] . $cookieValues[1] . $data . $_SERVER['SSL_SESSION_ID'], $key); + } else { + $verifKey = hash_hmac('sha1', $cookieValues[0] . $cookieValues[1] . $data, $key); + } + if ( $verifKey == $cookieValues[3] ) { + return $data; + } + } + } else { + return $_COOKIE[$cookiename]; + } + } + if ( $deleteIfInvalid ) { + $this->deleteCookie($cookiename); + } + return false; + } + + /** + * Send a classic (unsecure) cookie + * + * @param string $name Cookie name + * @param string $value Cookie value + * @param integer $expire Expiration time + * @param string $path Cookie path + * @param string $domain Cookie domain + * @param bool $secure When TRUE, send the cookie only on a secure connection + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + */ + public function setClassicCookie( $cookiename, $value, $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = null ) { + /* httponly option is only available for PHP version >= 5.2 */ + if ( $httponly === null ) { + $this->_cookies[$cookiename] = new Slim_Http_Cookie($cookiename, $value, $expire, $path, $domain, $secure); + //setcookie($cookiename, $value, $expire, $path, $domain, $secure); + } else { + $this->_cookies[$cookiename] = new Slim_Http_Cookie($cookiename, $value, $expire, $path, $domain, $secure, $httponly); + //setcookie($cookiename, $value, $expire, $path, $domain, $secure, $httponly); + } + } + + /** + * Verify if a cookie exists + * + * @param string $cookiename + * @return bool TRUE if cookie exist, or FALSE if not + */ + public function cookieExists($cookiename) { + return isset($_COOKIE[$cookiename]); + } + + /** + * Secure a cookie value + * + * The initial value is transformed with this protocol: + * + * secureValue = username|expire|base64((value)k,expire)|HMAC(user|expire|value,k) + * where k = HMAC(user|expire, sk) + * and sk is server's secret key + * (value)k,md5(expire) is the result an cryptographic function (ex: AES256) on "value" with key k and initialisation vector = md5(expire) + * + * @param string $value Unsecure value + * @param string $username User identifier + * @param integer $expire Expiration time + * @return string Secured value + */ + protected function _secureCookieValue( $value, $username, $expire ) { + if ( is_string($expire) ) { + $expire = strtotime($expire); + } + $key = hash_hmac('sha1', $username . $expire, $this->_secret); + if ( $value !== '' && $this->getHighConfidentiality() ) { + $encryptedValue = base64_encode($this->_encrypt($value, $key, md5($expire))); + } else { + $encryptedValue = base64_encode($value); + } + if ( $this->_ssl && isset($_SERVER['SSL_SESSION_ID']) ) { + $verifKey = hash_hmac('sha1', $username . $expire . $value . $_SERVER['SSL_SESSION_ID'], $key); + } else { + $verifKey = hash_hmac('sha1', $username . $expire . $value, $key); + } + $result = array($username, $expire, $encryptedValue, $verifKey); + return implode('|', $result); + } + + /** + * Encrypt a given data with a given key and a given initialisation vector + * + * @param string $data Data to crypt + * @param string $key Secret key + * @param string $iv Initialisation vector + * @return string Encrypted data + */ + protected function _encrypt( $data, $key, $iv ) { + $iv = $this->_validateIv($iv); + $key = $this->_validateKey($key); + mcrypt_generic_init($this->_cryptModule, $key, $iv); + $res = @mcrypt_generic($this->_cryptModule, $data); + mcrypt_generic_deinit($this->_cryptModule); + return $res; + } + + /** + * Decrypt a given data with a given key and a given initialisation vector + * + * @param string $data Data to crypt + * @param string $key Secret key + * @param string $iv Initialisation vector + * @return string Encrypted data + */ + protected function _decrypt( $data, $key, $iv ) { + $iv = $this->_validateIv($iv); + $key = $this->_validateKey($key); + mcrypt_generic_init($this->_cryptModule, $key, $iv); + $decryptedData = mdecrypt_generic($this->_cryptModule, $data); + $res = str_replace("\x0", '', $decryptedData); + mcrypt_generic_deinit($this->_cryptModule); + return $res; + } + + /** + * Validate Initialization vector + * + * If given IV is too long for the selected mcrypt algorithm, it will be truncated + * + * @param string $iv Initialization vector + * @return string + */ + protected function _validateIv($iv) { + $ivSize = mcrypt_enc_get_iv_size($this->_cryptModule); + if ( strlen($iv) > $ivSize ) { + $iv = substr($iv, 0, $ivSize); + } + return $iv; + } + + /** + * Validate key + * + * If given key is too long for the selected mcrypt algorithm, it will be truncated + * + * @param string $key key + * @param string + */ + protected function _validateKey($key) { + $keySize = mcrypt_enc_get_key_size($this->_cryptModule); + if ( strlen($key) > $keySize ) { + $key = substr($key, 0, $keySize); + } + return $key; + } + +} diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php index 7fbf7ae..3880a53 100644 --- a/Slim/Http/Request.php +++ b/Slim/Http/Request.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,20 +29,24 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim\Http; /** - * Slim HTTP Request + * Request * - * This class provides a human-friendly interface to the Slim environment variables; - * environment variables are passed by reference and will be modified directly. + * Object-oriented representation of an HTTP request. This class + * is responsible for parsing the raw HTTP request into a format + * usable by the Slim application. + * + * This class will automatically remove slashes from GET, POST, PUT, + * and Cookie data if magic quotes are enabled. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @author Kris Jordan + * @since Version 1.0 */ -class Request -{ +class Slim_Http_Request { + const METHOD_HEAD = 'HEAD'; const METHOD_GET = 'GET'; const METHOD_POST = 'POST'; @@ -53,533 +56,350 @@ class Request const METHOD_OVERRIDE = '_METHOD'; /** - * @var array + * @var string Request method (ie. "GET", "POST", "PUT", "DELETE", "HEAD") */ - protected static $formDataMediaTypes = array('application/x-www-form-urlencoded'); + protected $method; /** - * @var array + * @var array Key-value array of HTTP request headers */ - protected $env; + protected $headers; + + /** + * @var array Names of additional headers to parse from the current + * HTTP request that are not prefixed with "HTTP_" + */ + protected $additionalHeaders = array('content-type', 'content-length', 'php-auth-user', 'php-auth-pw', 'auth-type', 'x-requested-with'); + + /** + * @var array Key-value array of cookies sent with the + * current HTTP request + */ + protected $cookies; + + /** + * @var array Key-value array of HTTP GET parameters + */ + protected $get; + + /** + * @var array Key-value array of HTTP POST parameters + */ + protected $post; + + /** + * @var array Key-value array of HTTP PUT parameters + */ + protected $put; + + /** + * @var string Raw body of HTTP request + */ + protected $body; + + /** + * @var string Content type of HTTP request + */ + protected $contentType; + + /** + * @var string Resource URI (ie. "/person/1") + */ + protected $resource; + + /** + * @var string The root URI of the Slim application without trailing slash. + * This will be "" if the app is installed at the web + * document root. If the app is installed in a + * sub-directory "/foo", this will be "/foo". + */ + protected $root; /** * 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']; + public function __construct() { + $this->method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : false; + $this->headers = $this->loadHttpHeaders(); + $this->body = @file_get_contents('php://input'); + $this->get = self::stripSlashesIfMagicQuotes($_GET); + $this->post = self::stripSlashesIfMagicQuotes($_POST); + $this->put = self::stripSlashesIfMagicQuotes($this->loadPutParameters()); + $this->cookies = self::stripSlashesIfMagicQuotes($_COOKIE); + $this->root = Slim_Http_Uri::getBaseUri(true); + $this->resource = Slim_Http_Uri::getUri(true); + $this->checkForHttpMethodOverride(); } /** * Is this a GET request? * @return bool */ - public function isGet() - { - return $this->getMethod() === self::METHOD_GET; + public function isGet() { + return $this->method === self::METHOD_GET; } /** * Is this a POST request? * @return bool */ - public function isPost() - { - return $this->getMethod() === self::METHOD_POST; + public function isPost() { + return $this->method === self::METHOD_POST; } /** * Is this a PUT request? * @return bool */ - public function isPut() - { - return $this->getMethod() === self::METHOD_PUT; + public function isPut() { + return $this->method === self::METHOD_PUT; } /** * Is this a DELETE request? * @return bool */ - public function isDelete() - { - return $this->getMethod() === self::METHOD_DELETE; + public function isDelete() { + return $this->method === self::METHOD_DELETE; } /** * Is this a HEAD request? * @return bool */ - public function isHead() - { - return $this->getMethod() === self::METHOD_HEAD; + public function isHead() { + return $this->method === self::METHOD_HEAD; } /** * Is this a OPTIONS request? * @return bool */ - public function isOptions() - { - return $this->getMethod() === self::METHOD_OPTIONS; + public function isOptions() { + return $this->method === self::METHOD_OPTIONS; } /** - * Is this an AJAX request? + * Is this a XHR request? * @return bool */ - public function isAjax() - { - if ($this->params('isajax')) { - return true; - } elseif (isset($this->env['X_REQUESTED_WITH']) && $this->env['X_REQUESTED_WITH'] === 'XMLHttpRequest') { - return true; + public function isAjax() { + return ( $this->params('isajax') || $this->headers('X_REQUESTED_WITH') === 'XMLHttpRequest' ); + } + + /** + * Fetch a PUT|POST|GET parameter value + * + * The preferred method to fetch the value of a + * PUT, POST, or GET parameter (searched in that order). + * + * @param string $key The paramter name + * @return string|null The value of parameter, or NULL if parameter not found + */ + public function params( $key ) { + foreach ( array('put', 'post', 'get') as $dataSource ) { + $source = $this->$dataSource; + if ( isset($source[(string)$key]) ) { + return $source[(string)$key]; + } + } + return null; + } + + /** + * Fetch GET parameter(s) + * @param string $key Name of parameter + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function get( $key = null ) { + return $this->arrayOrArrayValue($this->get, $key); + } + + /** + * Fetch POST parameter(s) + * @param string $key Name of parameter + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function post( $key = null ) { + return $this->arrayOrArrayValue($this->post, $key); + } + + /** + * Fetch PUT parameter(s) + * @param string $key Name of parameter + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function put( $key = null ) { + return $this->arrayOrArrayValue($this->put, $key); + } + + /** + * Fetch COOKIE value(s) + * @param string $key The cookie name + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function cookies( $key = null ) { + return $this->arrayOrArrayValue($this->cookies, $key); + } + + /** + * Get HTTP request header + * @param string $key The header name + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function headers( $key = null ) { + return is_null($key) ? $this->headers : $this->arrayOrArrayValue($this->headers, $this->convertHttpHeaderName($key)); + } + + /** + * Get HTTP request body + * @return string|false String, or FALSE if body could not be read + */ + public function getBody() { + return $this->body; + } + + /** + * Get HTTP method + * @return string + */ + public function getMethod() { + return $this->method; + } + + /** + * Get HTTP request content type + * @return string + */ + public function getContentType() { + if ( !isset($this->contentType) ) { + $contentType = 'application/x-www-form-urlencoded'; + $header = $this->headers('CONTENT_TYPE'); + if ( !is_null($header) ) { + $headerParts = preg_split('/\s*;\s*/', $header); + $contentType = $headerParts[0]; + } + $this->contentType = $contentType; + } + return $this->contentType; + } + + /** + * Get HTTP request resource URI + * @return string + */ + public function getResourceUri() { + return $this->resource; + } + + /** + * Get HTTP request root URI + * @return string + */ + public function getRootUri() { + return $this->root; + } + + /** + * Fetch array or array value + * @param array $array + * @param string $key + * @return array|mixed Array if key is null, else array value + */ + protected function arrayOrArrayValue( array &$array, $key = null ) { + return is_null($key) ? $array : $this->arrayValueForKey($array, $key); + } + + /** + * Fetch value from array + * @return mixed|null + */ + protected function arrayValueForKey( array &$array, $key ) { + return isset($array[(string)$key]) ? $array[(string)$key] : null; + } + + /** + * Strip slashes from string or array of strings + * @param array|string $rawData + * @return array|string + */ + public static function stripSlashesIfMagicQuotes( $rawData ) { + if ( get_magic_quotes_gpc() ) { + return is_array($rawData) ? array_map(array('self', 'stripSlashesIfMagicQuotes'), $rawData) : stripslashes($rawData); } else { - return false; + return $rawData; } } /** - * Is this an XHR request? (alias of Slim_Http_Request::isAjax) - * @return bool + * Get PUT parameters + * @return array Key-value array of HTTP request PUT parameters */ - 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]; + protected function loadPutParameters() { + if ( $this->getContentType() === 'application/x-www-form-urlencoded' ) { + $input = is_string($this->body) ? $this->body : ''; + if ( function_exists('mb_parse_str') ) { + mb_parse_str($input, $output); } else { - return null; + parse_str($input, $output); } + return $output; } else { - return $union; + return array(); } } /** - * Fetch GET data + * Get HTTP request headers + * @return array Key-value array of HTTP request headers + */ + protected function loadHttpHeaders() { + $headers = array(); + foreach ( $_SERVER as $key => $value ) { + $key = $this->convertHttpHeaderName($key); + if ( strpos($key, 'http-') === 0 || in_array($key, $this->additionalHeaders) ) { + $name = str_replace('http-', '', $key); + $headers[$name] = $value; + } + } + return $headers; + } + + /** + * Convert HTTP header name + * @return string + */ + protected function convertHttpHeaderName( $name ) { + return str_replace('_', '-', strtolower($name)); + } + + /** + * Check for HTTP request method override * - * 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. + * Because traditional web browsers do not support PUT and DELETE + * HTTP methods, we use a hidden form input field to + * mimic PUT and DELETE requests. We check for this override here. * - * @param string $key - * @return array|mixed|null + * @return void */ - 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'] = 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() && is_string($this->env['slim.input'])) { - $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'] = Util::stripSlashesIfMagicQuotes($output); - } else { - $this->env['slim.request.form_hash'] = Util::stripSlashesIfMagicQuotes($_POST); + protected function checkForHttpMethodOverride() { + if ( isset($this->post[self::METHOD_OVERRIDE]) ) { + $this->method = $this->post[self::METHOD_OVERRIDE]; + unset($this->post[self::METHOD_OVERRIDE]); + if ( $this->isPut() ) { + $this->put = $this->post; } } - 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 DELETE data (alias for \Slim\Http\Request::post) - * @param string $key - * @return array|mixed|null - */ - public function delete($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'] = 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'])) { - if (strpos($this->env['HOST'], ':') !== false) { - $hostParts = explode(':', $this->env['HOST']); - - return $hostParts[0]; - } - - 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() - { - if (isset($this->env['X_FORWARDED_FOR'])) { - return $this->env['X_FORWARDED_FOR']; - } elseif (isset($this->env['CLIENT_IP'])) { - return $this->env['CLIENT_IP']; - } - - 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; - } - } -} +} \ No newline at end of file diff --git a/Slim/Http/Response.php b/Slim/Http/Response.php index 55b2bea..5414edb 100644 --- a/Slim/Http/Response.php +++ b/Slim/Http/Response.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,40 +29,54 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim\Http; /** * 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. + * Object-oriented representation of an HTTP response that is + * returned to the client. This class is responsible for: + * + * - HTTP response status + * - HTTP response body + * - HTTP response headers + * - HTTP response cookies * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @author Kris Jordan + * @since Version 1.0 */ -class Response implements \ArrayAccess, \Countable, \IteratorAggregate -{ +class Slim_Http_Response { + + /** + * @var Slim_Http_Request + */ + protected $request; + + /** + * @var string + */ + protected $httpVersion = '1.1'; + /** * @var int HTTP status code */ - protected $status; + protected $status = 200; /** - * @var \Slim\Http\Headers List of HTTP response headers + * @var array Key-value array of HTTP response headers */ - protected $header; + protected $headers = array(); /** * @var string HTTP response body */ - protected $body; + protected $body = ''; /** * @var int Length of HTTP response body */ - protected $length; + protected $length = 0; /** * @var array HTTP response codes and messages @@ -120,340 +133,189 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate ); /** - * 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 + * @var CookieJar Manages Cookies to be sent with this Response */ - 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 Headers(array_merge(array('Content-Type' => 'text/html'), $headers)); - $this->body = ''; - $this->write($body); + protected $cookieJar; + + /** + * Constructor + */ + public function __construct( Slim_Http_Request $req ) { + $this->request = $req; + $this->header('Content-Type', 'text/html'); } /** - * Get and set status - * @param int|null $status - * @return int + * Set and/or get the HTTP response version + * @param string $version + * @return void + * @throws InvalidArgumentException If argument is not a valid HTTP version */ - public function status($status = null) - { - if (!is_null($status)) { - $this->status = (int) $status; + public function httpVersion( $version = null ) { + if ( $version ) { + $version = (string)$version; + if ( $version === '1.0' || $version === '1.1' ) { + $this->httpVersion = $version; + } else { + throw new InvalidArgumentException('Invalid HTTP version in Response object'); + } } + return $this->httpVersion; + } + /** + * Set and/or get the HTTP response status code + * @param int $status + * @return int + * @throws InvalidArgumentException If argument is not a valid HTTP status code + */ + public function status( $status = null ) { + if ( !is_null($status) ) { + if ( !in_array(intval($status), array_keys(self::$messages)) ) { + throw new InvalidArgumentException('Cannot set Response status. Provided status code "' . $status . '" is not a valid HTTP response code.'); + } + $this->status = intval($status); + } return $this->status; } /** - * Get and set header - * @param string $name Header name - * @param string|null $value Header value - * @return string Header value + * Get HTTP response headers + * @return array */ - public function header($name, $value = null) - { - if (!is_null($value)) { - $this[$name] = $value; - } - - return $this[$name]; + public function headers() { + return $this->headers; } /** - * Get headers - * @return \Slim\Http\Headers + * Get and/or set an HTTP response header + * @param string $key The header name + * @param string $value The header value + * @return string|null The header value, or NULL if header not set */ - public function headers() - { - return $this->header; + public function header( $key, $value = null ) { + if ( !is_null($value) ) { + $this->headers[$key] = $value; + } + return isset($this->headers[$key]) ? $this->headers[$key] : null; } /** - * Get and set body - * @param string|null $body Content of HTTP response body - * @return string + * Set the HTTP response body + * @param string $body The new HTTP response body + * @return string The new HTTP response body */ - public function body($body = null) - { - if (!is_null($body)) { - $this->write($body, true); + public function body( $body = null ) { + if ( !is_null($body) ) { + $this->body = ''; + $this->length = 0; + $this->write($body); } - return $this->body; } /** - * Get and set length - * @param int|null $length - * @return int + * Append the HTTP response body + * @param string $body Content to append to the current HTTP response body + * @return string The updated HTTP response body */ - public function length($length = null) - { - if (!is_null($length)) { - $this->length = (int) $length; + public function write( $body ) { + $body = (string)$body; + $this->length += strlen($body); + $this->body .= $body; + $this->header('Content-Length', $this->length); + return $body; + } + + /** + * Set cookie jar + * @param Slim_Http_CookieJar $cookieJar + * @return void + */ + public function setCookieJar( Slim_Http_CookieJar $cookieJar ) { + $this->cookieJar = $cookieJar; + } + + /** + * Get cookie jar + * @return Slim_Http_CookieJar + */ + public function getCookieJar() { + return $this->cookieJar; + } + + /** + * Finalize response headers before response is sent + * @return void + */ + public function finalize() { + if ( in_array($this->status, array(204, 304)) ) { + $this->body(''); + unset($this->headers['Content-Type']); } - - 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) - { - 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()) - { - 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]; + public static function getMessageForCode( $status ) { + return isset(self::$messages[$status]) ? self::$messages[$status] : null; + } + + /** + * Can this HTTP response have a body? + * @return bool + */ + public function canHaveBody() { + return ( $this->status < 100 || $this->status >= 200 ) && $this->status != 204 && $this->status != 304; + } + + /** + * Send headers for HTTP response + * @return void + */ + protected function sendHeaders() { + //Finalize response + $this->finalize(); + + if ( substr(PHP_SAPI, 0, 3) === 'cgi') { + //Send Status header if running with fastcgi + header('Status: ' . self::getMessageForCode($this->status())); } else { - return null; + //Else send HTTP message + header(sprintf('HTTP/%s %s', $this->httpVersion, self::getMessageForCode($this->status()))); + } + + //Send headers + foreach ( $this->headers() as $name => $value ) { + header("$name: $value"); + } + + //Send cookies + foreach ( $this->getCookieJar()->getResponseCookies() as $name => $cookie ) { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpires(), $cookie->getPath(), $cookie->getDomain(), $cookie->getSecure(), $cookie->getHttpOnly()); + } + + //Flush all output to client + flush(); + } + + /** + * Send HTTP response + * + * This method will set Response headers, set Response cookies, + * and `echo` the Response body to the current output buffer. + * + * @return void + */ + public function send() { + if ( !headers_sent() ) { + $this->sendHeaders(); + } + if ( $this->canHaveBody() && $this->request->isHead() === false ) { + echo $this->body; } } -} + +} \ No newline at end of file diff --git a/Slim/Http/Uri.php b/Slim/Http/Uri.php new file mode 100644 index 0000000..dfe3448 --- /dev/null +++ b/Slim/Http/Uri.php @@ -0,0 +1,131 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.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. + */ + +/** + * Uri + * + * Parses base uri and application uri from Request. + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.0 + */ +class Slim_Http_Uri { + + /** + * @var string "https" or "http" + */ + protected static $scheme; + + /** + * @var string + */ + protected static $baseUri; + + /** + * @var string + */ + protected static $uri; + + /** + * @var string The URI query string, excluding leading "?" + */ + protected static $queryString; + + /** + * Get Base URI without trailing slash + * @param bool $reload Force reparse the base URI? + * @return string + */ + public static function getBaseUri( $reload = false ) { + if ( $reload || is_null(self::$baseUri) ) { + $requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF']; //Full Request URI + $scriptName = $_SERVER['SCRIPT_NAME']; //Script path from docroot + $baseUri = strpos($requestUri, $scriptName) === 0 ? $scriptName : str_replace('\\', '/', dirname($scriptName)); + self::$baseUri = rtrim($baseUri, '/'); + } + return self::$baseUri; + } + + /** + * Get URI with leading slash + * @param bool $reload Force reparse the URI? + * @return string + * @throws RuntimeException If unable if unable to determine URI + */ + public static function getUri( $reload = false ) { + if ( $reload || is_null(self::$uri) ) { + $uri = ''; + if ( !empty($_SERVER['PATH_INFO']) ) { + $uri = $_SERVER['PATH_INFO']; + } else { + if ( isset($_SERVER['REQUEST_URI']) ) { + $uri = parse_url(self::getScheme() . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], PHP_URL_PATH); + } else if ( isset($_SERVER['PHP_SELF']) ) { + $uri = $_SERVER['PHP_SELF']; + } else { + throw new RuntimeException('Unable to detect request URI'); + } + } + if ( self::getBaseUri() !== '' && strpos($uri, self::getBaseUri()) === 0 ) { + $uri = substr($uri, strlen(self::getBaseUri())); + } + self::$uri = '/' . ltrim($uri, '/'); + } + return self::$uri; + } + + /** + * Get URI Scheme + * @param bool $reload For reparse the URL scheme? + * @return string "https" or "http" + */ + public static function getScheme( $reload = false ) { + if ( $reload || is_null(self::$scheme) ) { + self::$scheme = ( empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ) ? 'http' : 'https'; + } + return self::$scheme; + } + + /** + * Get URI Query String + * @param bool $reload For reparse the URL query string? + * @return string + */ + public static function getQueryString( $reload = false ) { + if ( $reload || is_null(self::$queryString) ) { + self::$queryString = $_SERVER['QUERY_STRING']; + } + return self::$queryString; + } + +} \ No newline at end of file diff --git a/Slim/Log.php b/Slim/Log.php index fc13355..b958890 100644 --- a/Slim/Log.php +++ b/Slim/Log.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,14 +29,12 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim; /** - * Log + * Log Adapter * - * 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: + * This is an adapter for your own custom Logger. This adapter assumes + * your custom Logger provides the following public instance methods: * * debug( mixed $object ) * info( mixed $object ) @@ -45,193 +42,114 @@ namespace Slim; * 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. + * This class assumes nothing else about your custom Logger, so you are free + * to use Apache's Log4PHP logger or any other log class that, at the + * very least, implements the five public instance methods shown above. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class Log -{ - const FATAL = 0; - const ERROR = 1; - const WARN = 2; - const INFO = 3; - const DEBUG = 4; +class Slim_Log { /** - * @var array + * @var mixed An object that implements expected Logger interface */ - protected static $levels = array( - self::FATAL => 'FATAL', - self::ERROR => 'ERROR', - self::WARN => 'WARN', - self::INFO => 'INFO', - self::DEBUG => 'DEBUG' - ); + protected $logger; /** - * @var mixed - */ - protected $writer; - - /** - * @var bool + * @var bool Enable logging? */ protected $enabled; - /** - * @var int - */ - protected $level; - /** * Constructor - * @param mixed $writer */ - public function __construct($writer) - { - $this->writer = $writer; + public function __construct() { $this->enabled = true; - $this->level = self::DEBUG; - } - - /** - * Is logging enabled? - * @return bool - */ - public function getEnabled() - { - return $this->enabled; } /** * Enable or disable logging - * @param bool $enabled + * @param bool $enabled + * @return void */ - public function setEnabled($enabled) - { - if ($enabled) { + public function setEnabled( $enabled ) { + if ( $enabled ) { $this->enabled = true; } else { $this->enabled = false; } } - /** - * Set level - * @param int $level - * @throws \InvalidArgumentException If invalid log level specified - */ - 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 - */ - 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() - { + 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 + * @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, self::DEBUG); + public function debug( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->debug($object) : false; } /** * Log info message - * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @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, self::INFO); + public function info( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->info($object) : false; } /** * Log warn message - * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @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, self::WARN); + public function warn( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->warn($object) : false; } /** * Log error message - * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @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, self::ERROR); + public function error( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->error($object) : false; } /** * Log fatal message - * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @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, self::FATAL); + public function fatal( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->fatal($object) : false; } /** - * Log message - * @param mixed The object to log - * @param int The message level - * @return int|false + * Set Logger + * @param mixed $logger + * @return void */ - protected function log($object, $level) - { - if ($this->enabled && $this->writer && $level <= $this->level) { - return $this->writer->write($object, $level); - } else { - return false; - } + public function setLogger( $logger ) { + $this->logger = $logger; } -} + + /** + * Get Logger + * @return mixed + */ + public function getLogger() { + return $this->logger; + } + +} \ No newline at end of file diff --git a/Slim/Logger.php b/Slim/Logger.php new file mode 100644 index 0000000..b55eaa2 --- /dev/null +++ b/Slim/Logger.php @@ -0,0 +1,200 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.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. + */ + +/** + * Logger + * + * A simple Logger that writes to a daily-unique log file in + * a user-specified directory. By default, this class will write log + * messages for all log levels; the log level may be changed to filter + * unwanted log messages from the log file. + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.0 + */ +class Slim_Logger { + + /** + * @var array Log levels + */ + protected $levels = array( + 0 => 'FATAL', + 1 => 'ERROR', + 2 => 'WARN', + 3 => 'INFO', + 4 => 'DEBUG' + ); + + /** + * @var string Absolute path to log directory with trailing slash + */ + protected $directory; + + /** + * Constructor + * @param string $directory Absolute or relative path to log directory + * @param int $level The maximum log level reported by this Logger + */ + public function __construct( $directory, $level = 4 ) { + $this->setDirectory($directory); + $this->setLevel($level); + } + + /** + * Set log directory + * @param string $directory Absolute or relative path to log directory + * @return void + */ + public function setDirectory( $directory ) { + $realPath = realpath($directory); + if ( $realPath ) { + $this->directory = rtrim($realPath, '/') . '/'; + } else { + $this->directory = false; + } + } + + /** + * Get log directory + * @return string|false Absolute path to log directory with trailing slash + */ + public function getDirectory() { + return $this->directory; + } + + /** + * Set log level + * @param int The maximum log level reported by this Logger + * @return void + * @throws InvalidArgumentException If level specified is not 0, 1, 2, 3, 4 + */ + public function setLevel( $level ) { + $theLevel = (int)$level; + if ( $theLevel >= 0 && $theLevel <= 4 ) { + $this->level = $theLevel; + } else { + throw new InvalidArgumentException('Invalid Log Level. Must be one of: 0, 1, 2, 3, 4.'); + } + } + + /** + * Get log level + * @return int + */ + public function getLevel() { + return $this->level; + } + + /** + * Log debug data + * @param mixed $data + * @return void + */ + public function debug( $data ) { + $this->log($data, 4); + } + + /** + * Log info data + * @param mixed $data + * @return void + */ + public function info( $data ) { + $this->log($data, 3); + } + + /** + * Log warn data + * @param mixed $data + * @return void + */ + public function warn( $data ) { + $this->log($data, 2); + } + + /** + * Log error data + * @param mixed $data + * @return void + */ + public function error( $data ) { + $this->log($data, 1); + } + + /** + * Log fatal data + * @param mixed $data + * @return void + */ + public function fatal( $data ) { + $this->log($data, 0); + } + + /** + * Get absolute path to current daily log file + * @return string + */ + public function getFile() { + return $this->getDirectory() . strftime('%Y-%m-%d') . '.log'; + } + + /** + * Log data to file + * @param mixed $data + * @param int $level + * @return void + * @throws RuntimeException If log directory not found or not writable + */ + protected function log( $data, $level ) { + $dir = $this->getDirectory(); + if ( $dir == false || !is_dir($dir) ) { + throw new RuntimeException("Log directory '$dir' invalid."); + } + if ( !is_writable($dir) ) { + throw new RuntimeException("Log directory '$dir' not writable."); + } + if ( $level <= $this->getLevel() ) { + $this->write(sprintf("[%s] %s - %s\r\n", $this->levels[$level], date('c'), (string)$data)); + } + } + + /** + * Persist data to log + * @param string Log message + * @return void + */ + protected function write( $data ) { + @file_put_contents($this->getFile(), $data, FILE_APPEND | LOCK_EX); + } + +} \ No newline at end of file diff --git a/Slim/Route.php b/Slim/Route.php index 1a05594..69923d4 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,23 +29,23 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim; /** * Route + * * @package Slim - * @author Josh Lockhart, Thomas Bley - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class Route -{ +class Slim_Route { + /** - * @var string The route pattern (e.g. "/books/:id") + * @var string The route pattern (ie. "/books/:id") */ protected $pattern; /** - * @var mixed The route callable + * @var mixed The callable associated with this route */ protected $callable; @@ -56,7 +55,7 @@ class Route protected $conditions = array(); /** - * @var array Default conditions applied to all route instances + * @var array Default conditions applied to all Route instances */ protected static $defaultConditions = array(); @@ -70,33 +69,27 @@ class Route */ protected $params = array(); - /** - * @var array value array of URL parameter names - */ - protected $paramNames = array(); - - /** - * @var array key array of URL parameter names with + at the end - */ - protected $paramNamesPath = array(); - /** * @var array HTTP methods supported by this Route */ protected $methods = array(); /** - * @var array[Callable] Middleware to be run before only this route instance + * @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 (e.g. "/books/:id") - * @param mixed $callable Anything that returns TRUE for is_callable() + * @param string $pattern The URL pattern (ie. "/books/:id") + * @param mixed $callable Anything that returns TRUE for is_callable() */ - public function __construct($pattern, $callable) - { + public function __construct( $pattern, $callable ) { $this->setPattern($pattern); $this->setCallable($callable); $this->setConditions(self::getDefaultConditions()); @@ -104,10 +97,10 @@ class Route /** * Set default route conditions for all instances - * @param array $defaultConditions + * @param array $defaultConditions + * @return void */ - public static function setDefaultConditions(array $defaultConditions) - { + public static function setDefaultConditions( array $defaultConditions ) { self::$defaultConditions = $defaultConditions; } @@ -115,8 +108,7 @@ class Route * Get default route conditions for all instances * @return array */ - public static function getDefaultConditions() - { + public static function getDefaultConditions() { return self::$defaultConditions; } @@ -124,35 +116,33 @@ class Route * Get route pattern * @return string */ - public function getPattern() - { + public function getPattern() { return $this->pattern; } /** * Set route pattern - * @param string $pattern + * @param string $pattern + * @return void */ - public function setPattern($pattern) - { - $this->pattern = $pattern; + public function setPattern( $pattern ) { + $this->pattern = str_replace(')', ')?', (string)$pattern); } /** * Get route callable * @return mixed */ - public function getCallable() - { + public function getCallable() { return $this->callable; } /** * Set route callable - * @param mixed $callable + * @param mixed $callable + * @return void */ - public function setCallable($callable) - { + public function setCallable($callable) { $this->callable = $callable; } @@ -160,17 +150,16 @@ class Route * Get route conditions * @return array */ - public function getConditions() - { + public function getConditions() { return $this->conditions; } /** * Set route conditions - * @param array $conditions + * @param array $conditions + * @return void */ - public function setConditions(array $conditions) - { + public function setConditions( array $conditions ) { $this->conditions = $conditions; } @@ -178,72 +167,33 @@ class Route * Get route name * @return string|null */ - public function getName() - { + public function getName() { return $this->name; } /** * Set route name - * @param string $name + * @param string $name + * @return void */ - public function setName($name) - { - $this->name = (string) $name; + public function setName( $name ) { + $this->name = (string)$name; + $this->router->cacheNamedRoute($this->name, $this); } /** * Get route parameters * @return array */ - public function getParams() - { + public function getParams() { return $this->params; } - /** - * Set route parameters - * @param array $params - */ - public function setParams($params) - { - $this->params = $params; - } - - /** - * Get route parameter value - * @param string $index Name of URL parameter - * @return string - * @throws \InvalidArgumentException If route parameter does not exist at index - */ - public function getParam($index) - { - if (!isset($this->params[$index])) { - throw new \InvalidArgumentException('Route parameter does not exist at specified index'); - } - - return $this->params[$index]; - } - - /** - * Set route parameter value - * @param string $index Name of URL parameter - * @param mixed $value The new parameter value - * @throws \InvalidArgumentException If route parameter does not exist at index - */ - public function setParam($index, $value) - { - if (!isset($this->params[$index])) { - throw new \InvalidArgumentException('Route parameter does not exist at specified index'); - } - $this->params[$index] = $value; - } - /** * Add supported HTTP method(s) + * @return void */ - public function setHttpMethods() - { + public function setHttpMethods() { $args = func_get_args(); $this->methods = $args; } @@ -252,29 +202,26 @@ class Route * Get supported HTTP methods * @return array */ - public function getHttpMethods() - { + public function getHttpMethods() { return $this->methods; } /** * Append supported HTTP methods + * @return void */ - public function appendHttpMethods() - { + 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 + * @return Slim_Route */ - public function via() - { + public function via() { $args = func_get_args(); $this->methods = array_merge($this->methods, $args); - return $this; } @@ -282,17 +229,32 @@ class Route * Detect support for an HTTP method * @return bool */ - public function supportsHttpMethod($method) - { + 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() - { + public function getMiddleware() { return $this->middleware; } @@ -305,22 +267,20 @@ class Route * 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 Router::dispatch(). + * against `is_callable` during Route::dispatch(). * - * @param Callable|array[Callable] - * @return \Slim\Route - * @throws \InvalidArgumentException If argument is not callable or not an array + * @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)) { + public function setMiddleware( $middleware ) { + if ( is_callable($middleware) ) { $this->middleware[] = $middleware; - } elseif (is_array($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'); + throw new InvalidArgumentException('Route middleware must be callable or an array of callables'); } - return $this; } @@ -332,76 +292,107 @@ class Route * * http://blog.sosedoff.com/2009/09/20/rails-like-php-url-router/ * - * @param string $resourceUri A Request URI - * @return bool + * @param string $resourceUri A Request URI + * @return bool */ - public function matches($resourceUri) - { - //Convert URL params into regex patterns, construct a regex for this route, init params - $patternAsRegex = preg_replace_callback('#:([\w]+)\+?#', array($this, 'matchesCallback'), - str_replace(')', ')?', (string) $this->pattern)); - if (substr($this->pattern, -1) === '/') { - $patternAsRegex .= '?'; + 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)) { - return false; - } - foreach ($this->paramNames as $name) { - if (isset($paramValues[$name])) { - if (isset($this->paramNamesPath[ $name ])) { - $this->params[$name] = explode('/', urldecode($paramValues[$name])); - } else { - $this->params[$name] = urldecode($paramValues[$name]); + 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; } - - return true; } /** - * Convert a URL parameter (e.g. ":id", ":id+") into a regular expression - * @param array URL parameters - * @return string Regular expression for URL parameter + * Convert a URL parameter (ie. ":id") into a regular expression + * @param array URL parameters + * @return string Regular expression for URL parameter */ - protected function matchesCallback($m) - { - $this->paramNames[] = $m[1]; - if (isset($this->conditions[ $m[1] ])) { - return '(?P<' . $m[1] . '>' . $this->conditions[ $m[1] ] . ')'; + 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_\-\.\!\~\*\\\'\(\)\:\@\&\=\$\+,%]+)'; } - if (substr($m[0], -1) === '+') { - $this->paramNamesPath[ $m[1] ] = 1; - - return '(?P<' . $m[1] . '>.+)'; - } - - return '(?P<' . $m[1] . '>[^/]+)'; } /** * Set route name - * @param string $name The name of the route - * @return \Slim\Route + * @param string $name The name of the route + * @return Slim_Route */ - public function name($name) - { + 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 + * @param array $conditions Key-value array of URL parameter conditions + * @return Slim_Route */ - public function conditions(array $conditions) - { + 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 + foreach ( $this->middleware as $mw ) { + if ( is_callable($mw) ) { + call_user_func($mw); + } + } + //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 index 6161c6a..af41eb9 100644 --- a/Slim/Router.php +++ b/Slim/Router.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,41 +29,43 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim; /** * Router * - * This class organizes, iterates, and dispatches \Slim\Route objects. + * Responsible for registering route paths with associated callables. + * When a Slim application is run, the Router finds a matching Route for + * the current HTTP request, and if a matching route is found, executes + * the Route's associated callable passing it parameters from the Request URI. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class Router implements \Iterator -{ - /** - * @var string Request URI - */ - protected $resourceUri; +class Slim_Router implements IteratorAggregate { /** - * @var array Lookup hash of all route objects + * @var Slim_Http_Request + */ + protected $request; + + /** + * @var array Lookup hash of routes, keyed by Request method */ protected $routes; /** - * @var array Lookup hash of named route objects, keyed by route name (lazy-loaded) + * @var array Lookup hash of named routes, keyed by route name */ protected $namedRoutes; /** - * @var array Array of route objects that match the request URI (lazy-loaded) + * @var array Array of routes that match the Request method and URL */ protected $matchedRoutes; /** - * @var mixed Callable to be invoked if no matching route objects are found + * @var mixed Callable to be invoked if no matching routes are found */ protected $notFound; @@ -75,263 +76,128 @@ class Router implements \Iterator /** * Constructor + * @param Slim_Http_Request $request The HTTP request object */ - public function __construct() - { + public function __construct( Slim_Http_Request $request ) { + $this->request = $request; $this->routes = array(); } /** - * Set Resource URI - * - * This method injects the current request's resource URI. This method should be invoked - * only immediately before router iteration. - * - * @param string $uri The request URI + * Get Iterator + * @return ArrayIterator */ - public function setResourceUri($uri) - { - $this->resourceUri = $uri; + public function getIterator() { + return new ArrayIterator($this->getMatchedRoutes()); } /** - * Get Current Route object - * @return \Slim\Route|false + * Get Request + * @return Slim_Http_Request */ - public function getCurrentRoute() - { - $this->getMatchedRoutes(); // <-- Parse if not already parsed - - return $this->current(); + public function getRequest() { + return $this->request; } /** - * Return route objects that match the current request URI - * @param bool $reload Should matching routes be re-parsed? - * @return array[\Slim\Route] + * Set Request + * @param Slim_Http_Request $req + * @return void */ - public function getMatchedRoutes($reload = false) - { - if ($reload || is_null($this->matchedRoutes)) { + public function setRequest( Slim_Http_Request $req ) { + $this->request = $req; + } + + /** + * 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->resourceUri)) { + foreach ( $this->routes as $route ) { + if ( $route->matches($this->request->getResourceUri()) ) { $this->matchedRoutes[] = $route; } } } - return $this->matchedRoutes; } /** - * Map a route object 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 + * 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); + public function map( $pattern, $callable ) { + $route = new Slim_Route($pattern, $callable); + $route->setRouter($this); $this->routes[] = $route; - return $route; } + /** + * Cache 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 cacheNamedRoute( $name, Slim_Route $route ) { + if ( isset($this->namedRoutes[(string)$name]) ) { + throw new RuntimeException('Named route already exists with name: ' . $name); + } + $this->namedRoutes[$name] = $route; + } + /** * Get URL for named route - * @param string $name The name of the route - * @param array Associative array of URL parameter names and replacement values - * @throws RuntimeException If named route not found - * @return string The URL for the given route populated with provided replacement values + * @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); + public function urlFor( $name, $params = array() ) { + if ( !isset($this->namedRoutes[(string)$name]) ) { + throw new RuntimeException('Named route not found for name: ' . $name); } - $search = array(); - foreach (array_keys($params) as $key) { - $search[] = '#:' . $key . '\+?(?!\w)#'; + $pattern = $this->namedRoutes[(string)$name]->getPattern(); + $search = $replace = array(); + foreach ( $params as $key => $value ) { + $search[] = ':' . $key; + $replace[] = $value; } - $pattern = preg_replace($search, $params, $this->getNamedRoute($name)->getPattern()); - + $pattern = str_replace($search, $replace, $pattern); //Remove remnants of unpopulated, trailing optional pattern segments - return preg_replace('#\(/?:.+\)|\(|\)#', '', $pattern); - } - - /** - * Dispatch route - * - * This method invokes the route object's callable. If middleware is - * registered for the route, each callable middleware is invoked in - * the order specified. - * - * This method is smart about trailing slashes on the route pattern. - * If the route's pattern is defined with a trailing slash, and if the - * current request URI does not have a trailing slash but otherwise - * matches the 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::call` loop. If the route's pattern is defined without a - * trailing slash, and if the current request URI does have a trailing - * slash, the route will not be matched and a 404 Not Found - * response will be sent if no subsequent matching routes are found. - * - * @param \Slim\Route $route The route object - * @return bool Was route callable invoked successfully? - * @throws \Slim\Exception\RequestSlash - */ - public function dispatch(\Slim\Route $route) - { - if (substr($route->getPattern(), -1) === '/' && substr($this->resourceUri, -1) !== '/') { - throw new Exception\RequestSlash(); - } - - //Invoke middleware - foreach ($route->getMiddleware() as $mw) { - if (is_callable($mw)) { - call_user_func_array($mw, array($route)); - } - } - - //Invoke callable - if (is_callable($route->getCallable())) { - call_user_func_array($route->getCallable(), array_values($route->getParams())); - - return true; - } - - return false; - } - - /** - * 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 - */ - 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) - { - $this->getNamedRoutes(); - - return isset($this->namedRoutes[(string) $name]); - } - - /** - * Get named route - * @param string $name - * @return \Slim\Route|null - */ - public function getNamedRoute($name) - { - $this->getNamedRoutes(); - if ($this->hasNamedRoute($name)) { - return $this->namedRoutes[(string) $name]; - } else { - return null; - } - } - - /** - * Get named routes - * @return \ArrayIterator - */ - public function getNamedRoutes() - { - if (is_null($this->namedRoutes)) { - $this->namedRoutes = array(); - foreach ($this->routes as $route) { - if ($route->getName() !== null) { - $this->addNamedRoute($route->getName(), $route); - } - } - } - - return new \ArrayIterator($this->namedRoutes); + return preg_replace(array( + '@\(\/?:.+\/??\)\??@', + '@\?|\(|\)@' + ), '', $this->request->getRootUri() . $pattern); } /** * Register a 404 Not Found callback - * @param mixed $callable Anything that returns TRUE for is_callable() - * @return mixed + * @param mixed $callable Anything that returns TRUE for is_callable() + * @return mixed */ - public function notFound($callable = null) - { - if (is_callable($callable)) { + 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 + * @param mixed $callable Anything that returns TRUE for is_callable() + * @return mixed */ - public function error($callable = null) - { - if (is_callable($callable)) { + public function error( $callable = null ) { + if ( is_callable($callable) ) { $this->error = $callable; } - return $this->error; } - /** - * Iterator Interface: Rewind - */ - public function rewind() - { - reset($this->matchedRoutes); - } - - /** - * Iterator Interface: Current - * @return \Slim\Route|false - */ - public function current() - { - return current($this->matchedRoutes); - } - - /** - * Iterator Interface: Key - * @return int|null - */ - public function key() - { - return key($this->matchedRoutes); - } - - /** - * Iterator Interface: Next - */ - public function next() - { - next($this->matchedRoutes); - } - - /** - * Iterator Interface: Valid - * @return boolean - */ - public function valid() - { - return $this->current(); - } -} +} \ No newline at end of file diff --git a/Slim/Session/Flash.php b/Slim/Session/Flash.php new file mode 100644 index 0000000..2f71deb --- /dev/null +++ b/Slim/Session/Flash.php @@ -0,0 +1,192 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.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. + */ + +/** + * Flash Messaging + * + * This class enables Flash messaging. Messages are persisted in $_SESSION + * with a user-defined key. + * + * USAGE: + * + * 1. Set Flash message to be shown on the next request + * + * Slim::flash('error', 'The object could not be saved'); + * + * 2. Set Flash message to be shown on the current request + * + * Slim::flashNow('error', 'The object could not be saved'); + * + * 3. Keep old Flash messages for the next request + * + * Slim::flashKeep(); + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.3 + */ +class Slim_Session_Flash implements ArrayAccess { + + /** + * @var string Key used to identify flash information in $_SESSION array + */ + protected $sessionKey = 'flash'; + + /** + * @var array[array] Storage for flash messages + */ + protected $messages = array( + 'prev' => array(), //flash messages from prev request + 'next' => array(), //flash messages for next request + 'now' => array() //flash messages for current request + ); + + /** + * Constructor + * + * Establishes Flash session key and loads existing + * Flash messages from the $_SESSION. + * + * @param string $sessionKey + * @return void + */ + public function __construct( $sessionKey = null ) { + if ( !is_null($sessionKey) ) { + $this->setSessionKey($sessionKey); + } + $this->load(); + } + + /** + * Set the $_SESSION key used to access Flash messages + * @param string $key + * @throws RuntimeException If session key is null + * @return Slim_Session_Flash + */ + public function setSessionKey( $key ) { + if ( is_null($key) ) { + throw new RuntimeException('Session key cannot be null'); + } + $this->sessionKey = (string)$key; + return $this; + } + + /** + * Get the $_SESSION key used to access Flash messages + * @return string + */ + public function getSessionKey() { + return $this->sessionKey; + } + + /** + * Set a Flash message for the current request + * @param string $key + * @param string $value + * @return Slim_Session_Flash + */ + public function now( $key, $value ) { + $this->messages['now'][(string)$key] = $value; + return $this->save(); + } + + /** + * Set a Flash message for the next request + * @param string $key + * @param string $value + * @return Slim_Session_Flash + */ + public function set( $key, $value ) { + $this->messages['next'][(string)$key] = $value; + return $this->save(); + } + + /** + * Get Flash messages intended for the current request's View + * @return array[String] + */ + public function getMessages() { + return array_merge($this->messages['prev'], $this->messages['now']); + } + + /** + * Load Flash messages from $_SESSION + * @return Slim_Session_Flash + */ + public function load() { + $this->messages['prev'] = isset($_SESSION[$this->sessionKey]) ? $_SESSION[$this->sessionKey] : array(); + return $this; + } + + /** + * Transfer Flash messages from the previous request + * so they are available to the next request. + * @return Slim_Session_Flash + */ + public function keep() { + foreach ( $this->messages['prev'] as $key => $val ) { + $this->messages['next'][$key] = $val; + } + return $this->save(); + } + + /** + * Save Flash messages to $_SESSION + * @return Slim_Session_Flash + */ + public function save() { + $_SESSION[$this->sessionKey] = $this->messages['next']; + return $this; + } + + /***** ARRAY ACCESS INTERFACE *****/ + + public function offsetExists( $offset ) { + $messages = $this->getMessages(); + return isset($messages[$offset]); + } + + public function offsetGet( $offset ) { + $messages = $this->getMessages(); + return isset($messages[$offset]) ? $messages[$offset] : null; + } + + public function offsetSet( $offset, $value ) { + $this->now($offset, $value); + } + + public function offsetUnset( $offset ) { + unset($this->messages['prev'][$offset]); + unset($this->messages['now'][$offset]); + } + +} \ No newline at end of file diff --git a/Slim/Session/Handler.php b/Slim/Session/Handler.php new file mode 100644 index 0000000..1e76c3f --- /dev/null +++ b/Slim/Session/Handler.php @@ -0,0 +1,125 @@ +app = $app; + return session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'read'), + array($this, 'write'), + array($this, 'destroy'), + array($this, 'gc') + ); + } + + /** + * Open session + * + * @param string $savePath + * @param string $sessionName + * @return mixed + */ + abstract public function open( $savePath, $sessionName ); + + /** + * Close session + * + * @return mixed + */ + abstract public function close(); + + /** + * Read session data with ID + * + * @param string $id The session identifier + * @return string + */ + abstract public function read( $id ); + + /** + * Write session data with ID + * + * The "write" handler is not executed until after the output stream is + * closed. Thus, output from debugging statements in the "write" handler + * will never be seen in the browser. If debugging output is necessary, it + * is suggested that the debug output be written to a file instead. + * + * @param string $id The session identifier + * @param mixed $sessionData The session data + * @return mixed + */ + abstract public function write( $id, $sessionData ); + + /** + * Destroy session with ID + * + * @param string $id The session identifier + * @return mixed + */ + abstract public function destroy( $id ); + + /** + * Session garbage collection + * + * Executed when the PHP session garbage collector is invoked; should + * remove all session data older than the `$maxLifetime`. + * + * @param int $maxLifetime + * @return mixed + */ + abstract public function gc( $maxLifetime ); + +} \ No newline at end of file diff --git a/Slim/Session/Handler/Cookies.php b/Slim/Session/Handler/Cookies.php new file mode 100644 index 0000000..2358540 --- /dev/null +++ b/Slim/Session/Handler/Cookies.php @@ -0,0 +1,71 @@ +app->getEncryptedCookie($id); + } + + public function write( $id, $sessionData ) { + $this->app->setEncryptedCookie($id, $sessionData, 0); + } + + public function destroy( $id ) { + $this->app->deleteCookie($id); + } + + public function gc( $maxLifetime ) { + return true; //Not used + } + +} \ No newline at end of file diff --git a/Slim/Slim.php b/Slim/Slim.php index 6a36723..0268a76 100644 --- a/Slim/Slim.php +++ b/Slim/Slim.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,29 +29,49 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim; -// Ensure mcrypt constants are defined even if mcrypt extension is not loaded -if (!extension_loaded('mcrypt')) { - define('MCRYPT_MODE_CBC', 0); +//Ensure PHP session IDs only use the characters [a-z0-9] +ini_set('session.hash_bits_per_character', 4); +ini_set('session.hash_function', 0); + +//Slim's Encryted Cookies rely on libmcyrpt and these two constants. +//If libmycrpt is unavailable, we ensure the expected constants +//are available to avoid errors. +if ( !defined('MCRYPT_RIJNDAEL_256') ) { define('MCRYPT_RIJNDAEL_256', 0); } +if ( !defined('MCRYPT_MODE_CBC') ) { + define('MCRYPT_MODE_CBC', 0); +} + +//This determines which errors are reported by PHP. By default, all +//errors (including E_STRICT) are reported. +error_reporting(E_ALL | E_STRICT); + +//This tells PHP to auto-load classes using Slim's autoloader; this will +//only auto-load a class file located in the same directory as Slim.php +//whose file name (excluding the final dot and extension) is the same +//as its class name (case-sensitive). For example, "View.php" will be +//loaded when Slim uses the "View" class for the first time. +spl_autoload_register(array('Slim', 'autoload')); + +//PHP 5.3 will complain if you don't set a timezone. If you do not +//specify your own timezone before requiring Slim, this tells PHP to use UTC. +if ( @date_default_timezone_set(date_default_timezone_get()) === false ) { + date_default_timezone_set('UTC'); +} /** * Slim + * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class Slim -{ - /** - * @const string - */ - const VERSION = '2.0.0'; +class Slim { /** - * @var array[\Slim] + * @var array[Slim] */ protected static $apps = array(); @@ -62,47 +81,42 @@ class Slim protected $name; /** - * @var array - */ - protected $environment; - - /** - * @var \Slim\Http\Request + * @var Slim_Http_Request */ protected $request; /** - * @var \Slim\Http\Response + * @var Slim_Http_Response */ protected $response; /** - * @var \Slim\Router + * @var Slim_Router */ protected $router; /** - * @var \Slim\View + * @var Slim_View */ protected $view; /** - * @var array + * @var Slim_Log + */ + protected $log; + + /** + * @var array Key-value array of application settings */ protected $settings; /** - * @var string + * @var string The application mode */ protected $mode; /** - * @var array - */ - protected $middleware; - - /** - * @var array + * @var array Plugin hooks */ protected $hooks = array( 'slim.before' => array(array()), @@ -113,106 +127,140 @@ class Slim 'slim.after' => array(array()) ); - /******************************************************************************** - * PSR-0 Autoloader - * - * Do not use if you are using Composer to autoload dependencies. - *******************************************************************************/ - /** - * Slim PSR-0 autoloader + * Slim auto-loader + * + * This method lazy-loads class files when a given class if first used. + * 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($className) - { - $thisClass = str_replace(__NAMESPACE__.'\\', '', __CLASS__); - - $baseDir = __DIR__; - - if (substr($baseDir, -strlen($thisClass)) === $thisClass) { - $baseDir = substr($baseDir, 0, -strlen($thisClass)); + public static function autoload( $class ) { + if ( strpos($class, 'Slim') !== 0 ) { + return; } - - $className = ltrim($className, '\\'); - $fileName = $baseDir; - $namespace = ''; - if ($lastNsPos = strripos($className, '\\')) { - $namespace = substr($className, 0, $lastNsPos); - $className = substr($className, $lastNsPos + 1); - $fileName .= str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; - } - $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; - - if (file_exists($fileName)) { - require $fileName; + $file = dirname(__FILE__) . '/' . str_replace('_', DIRECTORY_SEPARATOR, substr($class,5)) . '.php'; + if ( file_exists($file) ) { + require $file; } } - /** - * Register Slim's PSR-0 autoloader - */ - public static function registerAutoloader() - { - spl_autoload_register(__NAMESPACE__ . "\\Slim::autoload"); - } - - /******************************************************************************** - * Instantiation and Configuration - *******************************************************************************/ + /***** INITIALIZATION *****/ /** * Constructor - * @param array $userSettings Associative array of application settings + * @param array $userSettings + * @return void */ - public function __construct($userSettings = array()) - { - // Setup Slim application - $this->settings = array_merge(self::getDefaultSettings(), $userSettings); - $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->getResourceUri()); - $this->middleware = array($this); - $this->add(new \Slim\Middleware\Flash()); - $this->add(new \Slim\Middleware\MethodOverride()); + public function __construct( $userSettings = array() ) { + //Merge application settings + $this->settings = array_merge(array( + //Mode + 'mode' => 'development', + //Logging + 'log.enable' => false, + 'log.logger' => null, + 'log.path' => './logs', + 'log.level' => 4, + //Debugging + 'debug' => true, + //View + 'templates.path' => './templates', + 'view' => 'Slim_View', + //Settings for all cookies + 'cookies.lifetime' => '20 minutes', + 'cookies.path' => '/', + 'cookies.domain' => '', + 'cookies.secure' => false, + 'cookies.httponly' => false, + //Settings for encrypted cookies + 'cookies.secret_key' => 'CHANGE_ME', + 'cookies.cipher' => MCRYPT_RIJNDAEL_256, + 'cookies.cipher_mode' => MCRYPT_MODE_CBC, + 'cookies.encrypt' => true, + 'cookies.user_id' => 'DEFAULT', + //Session handler + 'session.handler' => new Slim_Session_Handler_Cookies(), + 'session.flash_key' => 'flash', + //HTTP + 'http.version' => null + ), $userSettings); - // Determine application mode + //Determine application mode $this->getMode(); - // Setup view - $this->view($this->config('view')); + //Setup HTTP request and response handling + $this->request = new Slim_Http_Request(); + $this->response = new Slim_Http_Response($this->request); + $this->response->setCookieJar(new Slim_Http_CookieJar($this->settings['cookies.secret_key'], array( + 'high_confidentiality' => $this->settings['cookies.encrypt'], + 'mcrypt_algorithm' => $this->settings['cookies.cipher'], + 'mcrypt_mode' => $this->settings['cookies.cipher_mode'], + 'enable_ssl' => $this->settings['cookies.secure'] + ))); + $this->response->httpVersion($this->settings['http.version']); + $this->router = new Slim_Router($this->request); - // Make default if first instance - if (is_null(self::getInstance())) { + //Start session if not already started + if ( session_id() === '' ) { + $sessionHandler = $this->config('session.handler'); + if ( $sessionHandler instanceof Slim_Session_Handler ) { + $sessionHandler->register($this); + } + session_cache_limiter(false); + session_start(); + } + + //Setup view with flash messaging + $this->view($this->config('view'))->setData('flash', new Slim_Session_Flash($this->config('session.flash_key'))); + + //Set app name + if ( !isset(self::$apps['default']) ) { $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\LogWriter($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; + //Set global Error handler after Slim app instantiated + set_error_handler(array('Slim', 'handleErrors')); } /** - * Get application instance by name - * @param string $name The name of the Slim application - * @return \Slim|null + * Get application mode + * @return string */ - public static function getInstance($name = 'default') - { - return isset(self::$apps[$name]) ? self::$apps[$name] : null; + public function getMode() { + if ( !isset($this->mode) ) { + if ( isset($_ENV['SLIM_MODE']) ) { + $this->mode = (string)$_ENV['SLIM_MODE']; + } else { + $envMode = getenv('SLIM_MODE'); + if ( $envMode !== false ) { + $this->mode = $envMode; + } else { + $this->mode = (string)$this->config('mode'); + } + } + } + return $this->mode; + } + + /***** NAMING *****/ + + /** + * Get Slim application with 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[(string)$name]) ? self::$apps[(string)$name] : null; } /** * Set Slim application name - * @param string $name The name of this Slim application + * @param string $name The name of this Slim application + * @return void */ - public function setName($name) - { + public function setName( $name ) { $this->name = $name; self::$apps[$name] = $this; } @@ -221,42 +269,48 @@ class Slim * Get Slim application name * @return string|null */ - public function getName() - { + public function getName() { return $this->name; } + /***** LOGGING *****/ + /** - * Get default application settings - * @return array + * Get application Log (lazy-loaded) + * @return Slim_Log */ - public static function getDefaultSettings() - { - return array( - // Application - 'mode' => 'development', - // Debugging - 'debug' => true, - // Logging - 'log.writer' => null, - 'log.level' => \Slim\Log::DEBUG, - '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' - ); + public function getLog() { + if ( !isset($this->log) ) { + $this->log = new Slim_Log(); + $this->log->setEnabled($this->config('log.enable')); + $logger = $this->config('log.logger'); + if ( $logger ) { + $this->log->setLogger($logger); + } else { + $this->log->setLogger(new Slim_Logger($this->config('log.path'), $this->config('log.level'))); + } + } + return $this->log; + } + + /***** CONFIGURATION *****/ + + /** + * 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); + } } /** @@ -274,89 +328,23 @@ class Slim * 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 + * @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)) { + 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; + return in_array($name, array_keys($this->settings)) ? $this->settings[$name] : null; } } else { $this->settings[$name] = $value; } } - /******************************************************************************** - * Application 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 - *******************************************************************************/ + /***** ROUTING *****/ /** * Add GET|POST|PUT|DELETE route @@ -386,90 +374,76 @@ class Slim * Slim::get('/foo'[, middleware, middleware, ...], callable); * * @param array (See notes above) - * @return \Slim\Route + * @return Slim_Route */ - protected function mapRoute($args) - { + protected function mapRoute($args) { $pattern = array_shift($args); $callable = array_pop($args); $route = $this->router->map($pattern, $callable); - if (count($args) > 0) { + if ( count($args) > 0 ) { $route->setMiddleware($args); } - return $route; } /** * Add generic route without associated HTTP method - * @see mapRoute() - * @return \Slim\Route + * @see Slim::mapRoute + * @return Slim_Route */ - public function map() - { + public function map() { $args = func_get_args(); - return $this->mapRoute($args); } /** * Add GET route - * @see mapRoute() - * @return \Slim\Route + * @see Slim::mapRoute + * @return Slim_Route */ - public function get() - { + public function get() { $args = func_get_args(); - - return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_GET, \Slim\Http\Request::METHOD_HEAD); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_GET, Slim_Http_Request::METHOD_HEAD); } /** * Add POST route - * @see mapRoute() - * @return \Slim\Route + * @see Slim::mapRoute + * @return Slim_Route */ - public function post() - { + public function post() { $args = func_get_args(); - - return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_POST); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_POST); } /** * Add PUT route - * @see mapRoute() - * @return \Slim\Route + * @see Slim::mapRoute + * @return Slim_Route */ - public function put() - { + public function put() { $args = func_get_args(); - - return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PUT); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_PUT); } /** * Add DELETE route - * @see mapRoute() - * @return \Slim\Route + * @see Slim::mapRoute + * @return Slim_Route */ - public function delete() - { + public function delete() { $args = func_get_args(); - - return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_DELETE); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_DELETE); } /** * Add OPTIONS route - * @see mapRoute() - * @return \Slim\Route + * @see Slim::mapRoute + * @return Slim_Route */ - public function options() - { + public function options() { $args = func_get_args(); - - return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_OPTIONS); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_OPTIONS); } /** @@ -491,16 +465,16 @@ class Slim * 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() + * @param mixed $callable Anything that returns true for is_callable() + * @return void */ - public function notFound($callable = null) - { - if (!is_null($callable)) { + public function notFound( $callable = null ) { + if ( !is_null($callable) ) { $this->router->notFound($callable); } else { ob_start(); $customNotFoundHandler = $this->router->notFound(); - if (is_callable($customNotFoundHandler)) { + if ( is_callable($customNotFoundHandler) ) { call_user_func($customNotFoundHandler); } else { call_user_func(array($this, 'defaultNotFound')); @@ -530,81 +504,49 @@ class Slim * 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 + * @param mixed $argument Callable|Exception + * @return void */ - public function error($argument = null) - { - if (is_callable($argument)) { + 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(); + 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)); + } + $this->halt(500, ob_get_clean()); } } - /** - * 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(); - } - - /******************************************************************************** - * Application Accessors - *******************************************************************************/ - - /** - * Get a reference to the Environment object - * @return \Slim\Environment - */ - public function environment() - { - return $this->environment; - } + /***** ACCESSORS *****/ /** * Get the Request object - * @return \Slim\Http\Request + * @return Slim_Http_Request */ - public function request() - { + public function request() { return $this->request; } /** * Get the Response object - * @return \Slim\Http\Response + * @return Slim_Http_Response */ - public function response() - { + public function response() { return $this->response; } /** * Get the Router object - * @return \Slim\Router + * @return Slim_Router */ - public function router() - { + public function router() { return $this->router; } @@ -620,28 +562,24 @@ class Slim * 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 subclass - * @return \Slim\View + * @param string|Slim_View $viewClass The name of a Slim_View class; + * An instance of Slim_View; + * @return Slim_View */ - public function view($viewClass = null) - { - if (!is_null($viewClass)) { + public function view( $viewClass = null ) { + if ( !is_null($viewClass) ) { $existingData = is_null($this->view) ? array() : $this->view->getData(); - if ($viewClass instanceOf \Slim\View) { + 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 - *******************************************************************************/ + /***** RENDERING *****/ /** * Render a template @@ -651,23 +589,26 @@ class Slim * 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's 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) + * @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)) { + public function render( $template, $data = array(), $status = null ) { + $templatesPath = $this->config('templates.path'); + //Legacy support + if ( is_null($templatesPath) ) { + $templatesPath = $this->config('templates_dir'); + } + $this->view->setTemplatesDirectory($templatesPath); + 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 - *******************************************************************************/ + /***** HTTP CACHING *****/ /** * Set Last-Modified HTTP Response Header @@ -679,18 +620,17 @@ class Slim * 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 \InvalidArgumentException If provided timestamp is not an integer + * @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); - } + public function lastModified( $time ) { + if ( is_integer($time) ) { + $this->response->header('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.'); + throw new InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.'); } } @@ -706,154 +646,113 @@ class Slim * 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 + * @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') - { + 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".'); + 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; + if ( $type === 'weak' ) $value = 'W/'.$value; + $this->response->header('ETag', $value); //Check conditional GET - if ($etagsHeader = $this->request->headers('IF_NONE_MATCH')) { + 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); - } + 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; - */ - public function expires($time) - { - if (is_string($time)) { - $time = strtotime($time); - } - $this->response['Expires'] = gmdate(DATE_RFC1123, $time); - } - - /******************************************************************************** - * HTTP Cookies - *******************************************************************************/ + /***** COOKIES *****/ /** - * Set unencrypted HTTP cookie + * 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 + * @param string $name The cookie name + * @param mixed $value The cookie value + * @param mixed $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 + * @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 - )); + public function setCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) { + $time = 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; + $this->response->getCookieJar()->setClassicCookie($name, $value, $time, $path, $domain, $secure, $httponly); } /** - * Get value of unencrypted HTTP cookie + * 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 + * @param string $name + * @return string|null */ - public function getCookie($name) - { + public function getCookie( $name ) { return $this->request->cookies($name); } /** - * Set encrypted HTTP cookie + * 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 + * @param string $name The cookie name + * @param mixed $value The cookie value + * @param mixed $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 from the client - * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + * @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); + public function setEncryptedCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) { + $time = 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; + $userId = $this->config('cookies.user_id'); + $this->response->getCookieJar()->setCookie($name, $value, $userId, $time, $path, $domain, $secure, $httponly); } /** - * Get value of encrypted HTTP cookie + * 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 + * @param string $name + * @return string|null */ - 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; + public function getEncryptedCookie( $name ) { + $value = $this->response->getCookieJar()->getCookieValue($name); + return ($value === false) ? null : $value; } /** - * Delete HTTP cookie (encrypted or unencrypted) + * 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 @@ -861,29 +760,26 @@ class Slim * 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 + * @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 + * @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 - )); + public function deleteCookie( $name, $path = null, $domain = null, $secure = null, $httponly = null ) { + $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; + $this->response->getCookieJar()->deleteCookie( $name, $path, $domain, $secure, $httponly ); } - /******************************************************************************** - * Helper Methods - *******************************************************************************/ + /***** HELPERS *****/ /** - * Get the absolute path to this Slim application's root directory + * Get the Slim application's absolute directory path * * This method returns the absolute path to the Slim application's * directory. If the Slim application is installed in a public-accessible @@ -892,49 +788,47 @@ class Slim * * @return string */ - public function root() - { + public function root() { return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/'; } - /** - * Clean current output buffer - */ - protected function cleanBuffer() - { - if (ob_get_level() !== 0) { - ob_clean(); - } - } - /** * Stop * - * The thrown exception will be caught in application's `call()` method - * and the response will be sent as is to the HTTP client. + * Send the current Response as is and stop executing the Slim + * application. The thrown exception will be caught by the Slim + * custom exception handler which exits this script. * - * @throws \Slim\Exception\Stop + * @throws Slim_Exception_Stop + * @return void */ - public function stop() - { - throw new \Slim\Exception\Stop(); + public function stop() { + $flash = $this->view->getData('flash'); + if ( $flash ) { + $flash->save(); + } + session_write_close(); + $this->response->send(); + 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. + * Halt the application and immediately send an HTTP response with a + * specific status code and body. This may be used to 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 the application's `render()` method instead. + * you should use Slim::render() instead. * - * @param int $status The HTTP response status - * @param string $message The HTTP response body + * @param int $status The HTTP response status + * @param string $message The HTTP response body + * @return void */ - public function halt($status, $message = '') - { - $this->cleanBuffer(); + public function halt( $status, $message = '') { + if ( ob_get_level() !== 0 ) { + ob_clean(); + } $this->response->status($status); $this->response->body($message); $this->stop(); @@ -943,46 +837,48 @@ class Slim /** * Pass * - * The thrown exception is caught in the application's `call()` method causing - * the router's current iteration to stop and continue to the subsequent route if available. - * If no subsequent matching routes are found, a 404 response will be sent to the client. + * This method will cause the Router::dispatch method to ignore + * the current route and continue to the next matching route in the + * dispatch loop. If no subsequent mathing routes are found, + * a 404 Not Found response will be sent to the client. * - * @throws \Slim\Exception\Pass + * @throws Slim_Exception_Pass + * @return void */ - public function pass() - { - $this->cleanBuffer(); - throw new \Slim\Exception\Pass(); + public function pass() { + if ( ob_get_level() !== 0 ) { + ob_clean(); + } + throw new Slim_Exception_Pass(); } /** * Set the HTTP response Content-Type - * @param string $type The Content-Type for the Response (ie. text/html) + * @param string $type The Content-Type for the Response (ie. text/html) + * @return void */ - public function contentType($type) - { - $this->response['Content-Type'] = $type; + public function contentType( $type ) { + $this->response->header('Content-Type', $type); } /** * Set the HTTP response status code - * @param int $status The HTTP response status code + * @param int $status The HTTP response status code + * @return void */ - public function status($code) - { + public function status( $code ) { $this->response->status($code); } /** - * Get the URL for a named route - * @param string $name The route name - * @param array $params Associative array of URL parameters and replacement values - * @throws \RuntimeException If named route does not exist - * @return string + * 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->request->getRootUri() . $this->router->urlFor($name, $params); + public function urlFor( $name, $params = array() ) { + return $this->router->urlFor($name, $params); } /** @@ -992,97 +888,94 @@ class Slim * 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. + * 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) + * @param string $url The destination URL + * @param int $status The HTTP redirect status code (Optional) + * @throws InvalidArgumentException If status parameter is not a valid 3xx status code + * @return void */ - public function redirect($url, $status = 302) - { - $this->response->redirect($url, $status); - $this->halt($status); + public function redirect( $url, $status = 302 ) { + if ( $status >= 300 && $status <= 307 ) { + $this->response->header('Location', (string)$url); + $this->halt($status, (string)$url); + } else { + throw new InvalidArgumentException('Slim::redirect only accepts HTTP 300-307 status codes.'); + } } - /******************************************************************************** - * Flash Messages - *******************************************************************************/ + /***** FLASH *****/ /** * Set flash message for subsequent request - * @param string $key - * @param mixed $value + * @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); - } + public function flash( $key, $value ) { + $this->view->getData('flash')->set($key, $value); } /** * Set flash message for current request - * @param string $key - * @param mixed $value + * @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); - } + public function flashNow( $key, $value ) { + $this->view->getData('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(); - } + public function flashKeep() { + $this->view->getData('flash')->keep(); } - /******************************************************************************** - * Hooks - *******************************************************************************/ + /***** 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 + * @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])) { + 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; + 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 + * @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])) { + public function applyHook( $name, $hookArg = null ) { + if ( !isset($this->hooks[$name]) ) { $this->hooks[$name] = array(array()); } - if (!empty($this->hooks[$name])) { + if( !empty($this->hooks[$name]) ) { // Sort by priority, low to high, if there's more than one priority - if (count($this->hooks[$name]) > 1) { + if ( count($this->hooks[$name]) > 1 ) { ksort($this->hooks[$name]); } - foreach ($this->hooks[$name] as $priority) { - if (!empty($priority)) { - foreach ($priority as $callable) { - call_user_func($callable, $hookArg); + foreach( $this->hooks[$name] as $priority ) { + if( !empty($priority) ) { + foreach($priority as $callable) { + $hookArg = call_user_func($callable, $hookArg); } } } + return $hookArg; } } @@ -1094,13 +987,12 @@ class Slim * 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 + * @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; + public function getHooks( $name = null ) { + if ( !is_null($name) ) { + return isset($this->hooks[(string)$name]) ? $this->hooks[(string)$name] : null; } else { return $this->hooks; } @@ -1113,202 +1005,170 @@ class Slim * a valid hook name, only the listeners attached * to that hook will be cleared. * - * @param string $name A hook name (Optional) + * @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()); + 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) { + foreach( $this->hooks as $key => $value ) { $this->hooks[$key] = array(array()); } } } - /******************************************************************************** - * Middleware - *******************************************************************************/ + /***** RUN SLIM *****/ /** - * Add middleware + * Run the Slim application * - * This method prepends new middleware to the application middleware stack. - * The argument must be an instance that subclasses Slim_Middleware. + * This method is the "meat and potatoes" of Slim and should be the last + * method called. This fires up Slim, invokes the Route that matches + * the current request, and returns the response to the client. * - * @param \Slim\Middleware + * This method will invoke the Not Found handler if no matching + * routes are found. + * + * This method will also catch any unexpected Exceptions thrown by this + * application; the Exceptions will be logged to this application's log + * and rethrown to the global Exception handler. + * + * @return void */ - public function add(\Slim\Middleware $newMiddleware) - { - $newMiddleware->setApplication($this); - $newMiddleware->setNextMiddleware($this->middleware[0]); - array_unshift($this->middleware, $newMiddleware); - } - - /******************************************************************************** - * Runner - *******************************************************************************/ - - /** - * 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. - */ - public function run() - { - set_error_handler(array('\Slim\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 - echo $body; - - restore_error_handler(); - } - - /** - * Call - * - * This method finds and iterates all route objects that match the current request URI. - */ - public function call() - { + public function run() { 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(); - $this->router->setResourceUri($this->request->getResourceUri()); - $this->router->getMatchedRoutes(); - foreach ($this->router as $route) { - if ($route->supportsHttpMethod($this->environment['REQUEST_METHOD'])) { - try { - $this->applyHook('slim.before.dispatch'); - $dispatched = $this->router->dispatch($route); - $this->applyHook('slim.after.dispatch'); - if ($dispatched) { - break; + try { + $this->applyHook('slim.before'); + ob_start(); + $this->applyHook('slim.before.router'); + $dispatched = false; + $httpMethod = $this->request()->getMethod(); + $httpMethodsAllowed = array(); + foreach ( $this->router as $route ) { + if ( $route->supportsHttpMethod($httpMethod) ) { + try { + $this->applyHook('slim.before.dispatch'); + $dispatched = $route->dispatch(); + $this->applyHook('slim.after.dispatch'); + if ( $dispatched ) { + break; + } + } catch ( Slim_Exception_Pass $e ) { + continue; } - } catch (\Slim\Exception\Pass $e) { - continue; + } else { + $httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods()); } - } 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(); + if ( !$dispatched ) { + if ( $httpMethodsAllowed ) { + $this->response()->header('Allow', implode(' ', $httpMethodsAllowed)); + $this->halt(405); + } else { + $this->notFound(); + } } - } - $this->applyHook('slim.after.router'); - $this->stop(); - } catch (\Slim\Exception\Stop $e) { - $this->response()->write(ob_get_clean()); - $this->applyHook('slim.after'); - } catch (\Slim\Exception\RequestSlash $e) { - $this->response->redirect($this->request->getPath() . '/', 301); - } catch (\Exception $e) { - if ($this->config('debug')) { - throw $e; - } else { - try { + $this->response()->write(ob_get_clean()); + $this->applyHook('slim.after.router'); + $this->view->getData('flash')->save(); + session_write_close(); + $this->response->send(); + $this->applyHook('slim.after'); + } catch ( Slim_Exception_RequestSlash $e ) { + $this->redirect($this->request->getRootUri() . $this->request->getResourceUri() . '/', 301); + } catch ( Exception $e ) { + if ( $e instanceof Slim_Exception_Stop ) throw $e; + $this->getLog()->error($e); + if ( $this->config('debug') === true ) { + $this->halt(500, self::generateErrorMarkup($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString())); + } else { $this->error($e); - } catch (\Slim\Exception\Stop $e) { - // Do nothing } } + } catch ( Slim_Exception_Stop $e ) { + //Exit application context } } - /******************************************************************************** - * Error Handling and Debugging - *******************************************************************************/ + /***** EXCEPTION AND ERROR HANDLING *****/ /** - * Convert errors into ErrorException objects + * Handle errors * - * This method catches PHP errors and converts them into \ErrorException objects; - * these \ErrorException objects are then thrown and caught by Slim's - * built-in or custom error handlers. + * 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 + * @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); + public static function handleErrors( $errno, $errstr = '', $errfile = '', $errline = '' ) { + if ( error_reporting() & $errno ) { + throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } - return true; } /** - * Generate diagnostic template markup + * Generate markup for error message * - * This method accepts a title and body content to generate an HTML document layout. + * This method accepts details about an error or exception and + * generates HTML markup for the 500 response body that will + * be sent to the client. * - * @param string $title The title of the HTML template - * @param string $body The body content of the HTML template - * @return string + * @param string $message The error message + * @param string $file The absolute file path to the affected file + * @param int $line The line number in the affected file + * @param string $trace A stack trace of the error + * @return string */ - protected static function generateTemplateMarkup($title, $body) - { - return sprintf("%s

%s

%s", $title, $title, $body); + protected static function generateErrorMarkup( $message, $file = '', $line = '', $trace = '' ) { + $body = '

The application could not run because of the following error:

'; + $body .= "

Details:

Message: $message
"; + if ( $file !== '' ) $body .= "File: $file
"; + if ( $line !== '' ) $body .= "Line: $line
"; + if ( $trace !== '' ) $body .= '

Stack Trace:

' . nl2br($trace); + return self::generateTemplateMarkup('Slim Application Error', $body); + } + + /** + * 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 ) { + $html = "$title"; + $html .= "

$title

"; + $html .= $body; + $html .= ''; + return $html; } /** * 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'); + 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() - { + 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.

'); } -} + +} \ No newline at end of file diff --git a/Slim/View.php b/Slim/View.php index 39b1a5e..b72472e 100644 --- a/Slim/View.php +++ b/Slim/View.php @@ -2,12 +2,11 @@ /** * Slim - a micro PHP 5 framework * - * @author Josh Lockhart + * @author Josh Lockhart * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.0.0 - * @package Slim + * @version 1.5.0 * * MIT LICENSE * @@ -30,63 +29,48 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace Slim; /** - * View + * Slim View * - * The view is responsible for rendering a template. The view - * should subclass \Slim\View and implement this interface: - * - * public render(string $template); - * - * This method should render the specified template and return - * the resultant string. + * The View is responsible for rendering and/or displaying a template. + * It is recommended that you subclass View and re-implement the + * `View::render` method to use a custom templating engine such as + * Smarty, Twig, Mustache, etc. It is important that `View::render` + * `return` the final template output. Do not `echo` the output. * * @package Slim - * @author Josh Lockhart - * @since 1.0.0 + * @author Josh Lockhart + * @since Version 1.0 */ -class View -{ - /** - * @var string Absolute or relative filesystem path to a specific template - * - * DEPRECATION WARNING! - * This variable will be removed in the near future - */ - protected $templatePath = ''; +class Slim_View { /** - * @var array Associative array of template variables + * @var array Key-value array of data available to the template */ protected $data = array(); /** - * @var string Absolute or relative path to the application's templates directory + * @var string Absolute or relative path to the templates directory */ protected $templatesDirectory; /** * Constructor * - * This is empty but may be implemented in a subclass + * This is empty but may be overridden in a subclass */ - public function __construct() - { - - } + public function __construct() {} /** * Get data - * @param string|null $key - * @return mixed If key is null, array of template data; - * If key exists, value of datum with key; - * If key does not exist, null; + * @param string $key + * @return array|mixed|null All View data if no $key, value of datum + * if $key, or NULL if $key but datum + * does not exist. */ - public function getData($key = null) - { - if (!is_null($key)) { + public function getData( $key = null ) { + if ( !is_null($key) ) { return isset($this->data[$key]) ? $this->data[$key] : null; } else { return $this->data; @@ -96,78 +80,59 @@ class View /** * Set data * - * If two arguments: - * A single datum with key is assigned value; + * This method is overloaded to accept two different method signatures. + * You may use this to set a specific key with a specfic value, + * or you may use this to set all data to a specific array. * - * $view->setData('color', 'red'); + * USAGE: * - * If one argument: - * Replace all data with provided array keys and values; + * View::setData('color', 'red'); + * View::setData(array('color' => 'red', 'number' => 1)); * - * $view->setData(array('color' => 'red', 'number' => 1)); - * - * @param mixed - * @param mixed - * @throws InvalidArgumentException If incorrect method signature + * @param string|array + * @param mixed Optional. Only use if first argument is a string. + * @return void + * @throws InvalidArgumentException If incorrect method signature */ - public function setData() - { + public function setData() { $args = func_get_args(); - if (count($args) === 1 && is_array($args[0])) { + if ( count($args) === 1 && is_array($args[0]) ) { $this->data = $args[0]; - } elseif (count($args) === 2) { - $this->data[(string) $args[0]] = $args[1]; + } else if ( count($args) === 2 ) { + $this->data[(string)$args[0]] = $args[1]; } else { - throw new \InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`'); + throw new InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`'); } } /** - * Append new data to existing template data - * @param array - * @throws InvalidArgumentException If not given an array argument + * Append data to existing View data + * @param array $data + * @return void */ - public function appendData($data) - { - if (!is_array($data)) { - throw new \InvalidArgumentException('Cannot append view data. Expected array argument.'); - } + public function appendData( array $data ) { $this->data = array_merge($this->data, $data); } /** * Get templates directory - * @return string|null Path to templates directory without trailing slash; - * Returns null if templates directory not set; + * @return string|null Path to templates directory without trailing slash */ - public function getTemplatesDirectory() - { + public function getTemplatesDirectory() { return $this->templatesDirectory; } /** * Set templates directory - * @param string $dir + * @param string $dir + * @return void + * @throws RuntimeException If directory is not a directory or does not exist */ - public function setTemplatesDirectory($dir) - { - $this->templatesDirectory = rtrim($dir, '/'); - } - - /** - * Set template - * @param string $template - * @throws RuntimeException If template file does not exist - * - * DEPRECATION WARNING! - * This method will be removed in the near future. - */ - public function setTemplate($template) - { - $this->templatePath = $this->getTemplatesDirectory() . '/' . ltrim($template, '/'); - if (!file_exists($this->templatePath)) { - throw new \RuntimeException('View cannot render template `' . $this->templatePath . '`. Template does not exist.'); + public function setTemplatesDirectory( $dir ) { + if ( !is_dir($dir) ) { + throw new RuntimeException('Cannot set View templates directory to: ' . $dir . '. Directory does not exist.'); } + $this->templatesDirectory = rtrim($dir, '/'); } /** @@ -175,42 +140,28 @@ class View * * This method echoes the rendered template to the current output buffer * - * @param string $template Pathname of template file relative to templates directoy + * @param string $template Path to template file relative to templates directoy + * @return void */ - public function display($template) - { - echo $this->fetch($template); - } - - /** - * Fetch rendered template - * - * This method returns the rendered template - * - * @param string $template Pathname of template file relative to templates directory - * @return string - */ - public function fetch($template) - { - return $this->render($template); + public function display( $template ) { + echo $this->render($template); } /** * Render template - * - * @param string $template Pathname of template file relative to templates directory - * @return string - * - * DEPRECATION WARNING! - * Use `\Slim\View::fetch` to return a rendered template instead of `\Slim\View::render`. + * @param string $template Path to template file relative to templates directory + * @return string Rendered template + * @throws RuntimeException If template does not exist */ - public function render($template) - { - $this->setTemplate($template); + public function render( $template ) { extract($this->data); + $templatePath = $this->getTemplatesDirectory() . '/' . ltrim($template, '/'); + if ( !file_exists($templatePath) ) { + throw new RuntimeException('View cannot render template `' . $templatePath . '`. Template does not exist.'); + } ob_start(); - require $this->templatePath; - + require $templatePath; return ob_get_clean(); } -} + +} \ No newline at end of file diff --git a/aprs_func.php b/aprs_func.php new file mode 100644 index 0000000..728c39e --- /dev/null +++ b/aprs_func.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..d43cfa7 --- /dev/null +++ b/index.php @@ -0,0 +1,24 @@ +get('/', function () { + // Require HTML Template + require('templates/main.php'); + }); + + $app->post('/passcode', function () { + require('aprs_func.php'); + echo aprspass($_POST['callsign']); + }); + + + // Run slim + $app->run(); + +?> \ No newline at end of file diff --git a/templates/main.php b/templates/main.php new file mode 100644 index 0000000..eb780dc --- /dev/null +++ b/templates/main.php @@ -0,0 +1,41 @@ + + + + APRS Passcode Generator + + + + +
+ +

APRS Passcode Generator

+ +
+ + + +
+ +

Techical Example of Passcode Generation using PHP

+

Source code available on Github

+
+ + + \ No newline at end of file diff --git a/templates/passcode.php b/templates/passcode.php new file mode 100644 index 0000000..e69de29