├── .gitignore ├── README.md ├── Slim ├── Environment.php ├── Exception │ ├── Pass.php │ └── Stop.php ├── Http │ ├── Headers.php │ ├── Request.php │ ├── Response.php │ └── Util.php ├── Log.php ├── LogWriter.php ├── Middleware.php ├── Middleware │ ├── ContentTypes.php │ ├── Flash.php │ ├── MethodOverride.php │ ├── PrettyExceptions.php │ └── SessionCookie.php ├── Route.php ├── Router.php ├── Slim.php └── View.php ├── api ├── .htaccess ├── db.sqlite3 └── index.php ├── css ├── app.css └── bootstrap.min.css ├── icons └── .gitignore ├── img ├── glyphicons-halflings-white.png └── glyphicons-halflings.png ├── index.html ├── js └── app.js └── partials ├── add.html ├── edit.html └── list.html /.gitignore: -------------------------------------------------------------------------------- 1 | api/db.sqlite3 2 | icons/*.ico 3 | idea/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJSTutorialApp 2 | ==================== 3 | 4 | Simple AngluarJS bookmark app tutorial with a Slim php backend. -------------------------------------------------------------------------------- /Slim/Environment.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim; 34 | 35 | /** 36 | * Environment 37 | * 38 | * This class creates and returns a key/value array of common 39 | * environment variables for the current HTTP request. 40 | * 41 | * This is a singleton class; derived environment variables will 42 | * be common across multiple Slim applications. 43 | * 44 | * This class matches the Rack (Ruby) specification as closely 45 | * as possible. More information available below. 46 | * 47 | * @package Slim 48 | * @author Josh Lockhart 49 | * @since 1.6.0 50 | */ 51 | class Environment implements \ArrayAccess, \IteratorAggregate 52 | { 53 | /** 54 | * @var array 55 | */ 56 | protected $properties; 57 | 58 | /** 59 | * @var \Slim\Environment 60 | */ 61 | protected static $environment; 62 | 63 | /** 64 | * Get environment instance (singleton) 65 | * 66 | * This creates and/or returns an environment instance (singleton) 67 | * derived from $_SERVER variables. You may override the global server 68 | * variables by using `\Slim\Environment::mock()` instead. 69 | * 70 | * @param bool $refresh Refresh properties using global server variables? 71 | * @return \Slim\Environment 72 | */ 73 | public static function getInstance($refresh = false) 74 | { 75 | if (is_null(self::$environment) || $refresh) { 76 | self::$environment = new self(); 77 | } 78 | 79 | return self::$environment; 80 | } 81 | 82 | /** 83 | * Get mock environment instance 84 | * 85 | * @param array $userSettings 86 | * @return \Slim\Environment 87 | */ 88 | public static function mock($userSettings = array()) 89 | { 90 | self::$environment = new self(array_merge(array( 91 | 'REQUEST_METHOD' => 'GET', 92 | 'SCRIPT_NAME' => '', 93 | 'PATH_INFO' => '', 94 | 'QUERY_STRING' => '', 95 | 'SERVER_NAME' => 'localhost', 96 | 'SERVER_PORT' => 80, 97 | 'ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 98 | 'ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', 99 | 'ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', 100 | 'USER_AGENT' => 'Slim Framework', 101 | 'REMOTE_ADDR' => '127.0.0.1', 102 | 'slim.url_scheme' => 'http', 103 | 'slim.input' => '', 104 | 'slim.errors' => @fopen('php://stderr', 'w') 105 | ), $userSettings)); 106 | 107 | return self::$environment; 108 | } 109 | 110 | /** 111 | * Constructor (private access) 112 | * 113 | * @param array|null $settings If present, these are used instead of global server variables 114 | */ 115 | private function __construct($settings = null) 116 | { 117 | if ($settings) { 118 | $this->properties = $settings; 119 | } else { 120 | $env = array(); 121 | 122 | //The HTTP request method 123 | $env['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD']; 124 | 125 | //The IP 126 | $env['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR']; 127 | 128 | /** 129 | * Application paths 130 | * 131 | * This derives two paths: SCRIPT_NAME and PATH_INFO. The SCRIPT_NAME 132 | * is the real, physical path to the application, be it in the root 133 | * directory or a subdirectory of the public document root. The PATH_INFO is the 134 | * virtual path to the requested resource within the application context. 135 | * 136 | * With htaccess, the SCRIPT_NAME will be an absolute path (without file name); 137 | * if not using htaccess, it will also include the file name. If it is "/", 138 | * it is set to an empty string (since it cannot have a trailing slash). 139 | * 140 | * The PATH_INFO will be an absolute path with a leading slash; this will be 141 | * used for application routing. 142 | */ 143 | if (strpos($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']) === 0) { 144 | $env['SCRIPT_NAME'] = $_SERVER['SCRIPT_NAME']; //Without URL rewrite 145 | } else { 146 | $env['SCRIPT_NAME'] = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])); //With URL rewrite 147 | } 148 | $env['PATH_INFO'] = substr_replace($_SERVER['REQUEST_URI'], '', 0, strlen($env['SCRIPT_NAME'])); 149 | if (strpos($env['PATH_INFO'], '?') !== false) { 150 | $env['PATH_INFO'] = substr_replace($env['PATH_INFO'], '', strpos($env['PATH_INFO'], '?')); //query string is not removed automatically 151 | } 152 | $env['SCRIPT_NAME'] = rtrim($env['SCRIPT_NAME'], '/'); 153 | $env['PATH_INFO'] = '/' . ltrim($env['PATH_INFO'], '/'); 154 | 155 | //The portion of the request URI following the '?' 156 | $env['QUERY_STRING'] = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; 157 | 158 | //Name of server host that is running the script 159 | $env['SERVER_NAME'] = $_SERVER['SERVER_NAME']; 160 | 161 | //Number of server port that is running the script 162 | $env['SERVER_PORT'] = $_SERVER['SERVER_PORT']; 163 | 164 | //HTTP request headers 165 | $specialHeaders = array('CONTENT_TYPE', 'CONTENT_LENGTH', 'PHP_AUTH_USER', 'PHP_AUTH_PW', 'PHP_AUTH_DIGEST', 'AUTH_TYPE'); 166 | foreach ($_SERVER as $key => $value) { 167 | $value = is_string($value) ? trim($value) : $value; 168 | if (strpos($key, 'HTTP_') === 0) { 169 | $env[substr($key, 5)] = $value; 170 | } elseif (strpos($key, 'X_') === 0 || in_array($key, $specialHeaders)) { 171 | $env[$key] = $value; 172 | } 173 | } 174 | 175 | //Is the application running under HTTPS or HTTP protocol? 176 | $env['slim.url_scheme'] = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https'; 177 | 178 | //Input stream (readable one time only; not available for mutipart/form-data requests) 179 | $rawInput = @file_get_contents('php://input'); 180 | if (!$rawInput) { 181 | $rawInput = ''; 182 | } 183 | $env['slim.input'] = $rawInput; 184 | 185 | //Error stream 186 | $env['slim.errors'] = fopen('php://stderr', 'w'); 187 | 188 | $this->properties = $env; 189 | } 190 | } 191 | 192 | /** 193 | * Array Access: Offset Exists 194 | */ 195 | public function offsetExists($offset) 196 | { 197 | return isset($this->properties[$offset]); 198 | } 199 | 200 | /** 201 | * Array Access: Offset Get 202 | */ 203 | public function offsetGet($offset) 204 | { 205 | if (isset($this->properties[$offset])) { 206 | return $this->properties[$offset]; 207 | } else { 208 | return null; 209 | } 210 | } 211 | 212 | /** 213 | * Array Access: Offset Set 214 | */ 215 | public function offsetSet($offset, $value) 216 | { 217 | $this->properties[$offset] = $value; 218 | } 219 | 220 | /** 221 | * Array Access: Offset Unset 222 | */ 223 | public function offsetUnset($offset) 224 | { 225 | unset($this->properties[$offset]); 226 | } 227 | 228 | /** 229 | * IteratorAggregate 230 | * 231 | * @return \ArrayIterator 232 | */ 233 | public function getIterator() 234 | { 235 | return new \ArrayIterator($this->properties); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Slim/Exception/Pass.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Exception; 34 | 35 | /** 36 | * Pass Exception 37 | * 38 | * This Exception will cause the Router::dispatch method 39 | * to skip the current matching route and continue to the next 40 | * matching route. If no subsequent routes are found, a 41 | * HTTP 404 Not Found response will be sent to the client. 42 | * 43 | * @package Slim 44 | * @author Josh Lockhart 45 | * @since 1.0.0 46 | */ 47 | class Pass extends \Exception 48 | { 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Slim/Exception/Stop.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Exception; 34 | 35 | /** 36 | * Stop Exception 37 | * 38 | * This Exception is thrown when the Slim application needs to abort 39 | * processing and return control flow to the outer PHP script. 40 | * 41 | * @package Slim 42 | * @author Josh Lockhart 43 | * @since 1.0.0 44 | */ 45 | class Stop extends \Exception 46 | { 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Slim/Http/Headers.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Http; 34 | 35 | /** 36 | * HTTP Headers 37 | * 38 | * This class is an abstraction of the HTTP response headers and 39 | * provides array access to the header list while automatically 40 | * stores and retrieves headers with lowercase canonical keys regardless 41 | * of the input format. 42 | * 43 | * This class also implements the `Iterator` and `Countable` 44 | * interfaces for even more convenient usage. 45 | * 46 | * @package Slim 47 | * @author Josh Lockhart 48 | * @since 1.6.0 49 | */ 50 | class Headers implements \ArrayAccess, \Iterator, \Countable 51 | { 52 | /** 53 | * @var array HTTP headers 54 | */ 55 | protected $headers; 56 | 57 | /** 58 | * @var array Map canonical header name to original header name 59 | */ 60 | protected $map; 61 | 62 | /** 63 | * Constructor 64 | * @param array $headers 65 | */ 66 | public function __construct($headers = array()) 67 | { 68 | $this->merge($headers); 69 | } 70 | 71 | /** 72 | * Merge Headers 73 | * @param array $headers 74 | */ 75 | public function merge($headers) 76 | { 77 | foreach ($headers as $name => $value) { 78 | $this[$name] = $value; 79 | } 80 | } 81 | 82 | /** 83 | * Transform header name into canonical form 84 | * @param string $name 85 | * @return string 86 | */ 87 | protected function canonical($name) 88 | { 89 | return strtolower(trim($name)); 90 | } 91 | 92 | /** 93 | * Array Access: Offset Exists 94 | */ 95 | public function offsetExists($offset) 96 | { 97 | return isset($this->headers[$this->canonical($offset)]); 98 | } 99 | 100 | /** 101 | * Array Access: Offset Get 102 | */ 103 | public function offsetGet($offset) 104 | { 105 | $canonical = $this->canonical($offset); 106 | if (isset($this->headers[$canonical])) { 107 | return $this->headers[$canonical]; 108 | } else { 109 | return null; 110 | } 111 | } 112 | 113 | /** 114 | * Array Access: Offset Set 115 | */ 116 | public function offsetSet($offset, $value) 117 | { 118 | $canonical = $this->canonical($offset); 119 | $this->headers[$canonical] = $value; 120 | $this->map[$canonical] = $offset; 121 | } 122 | 123 | /** 124 | * Array Access: Offset Unset 125 | */ 126 | public function offsetUnset($offset) 127 | { 128 | $canonical = $this->canonical($offset); 129 | unset($this->headers[$canonical], $this->map[$canonical]); 130 | } 131 | 132 | /** 133 | * Countable: Count 134 | */ 135 | public function count() 136 | { 137 | return count($this->headers); 138 | } 139 | 140 | /** 141 | * Iterator: Rewind 142 | */ 143 | public function rewind() 144 | { 145 | reset($this->headers); 146 | } 147 | 148 | /** 149 | * Iterator: Current 150 | */ 151 | public function current() 152 | { 153 | return current($this->headers); 154 | } 155 | 156 | /** 157 | * Iterator: Key 158 | */ 159 | public function key() 160 | { 161 | $key = key($this->headers); 162 | 163 | return $this->map[$key]; 164 | } 165 | 166 | /** 167 | * Iterator: Next 168 | */ 169 | public function next() 170 | { 171 | return next($this->headers); 172 | } 173 | 174 | /** 175 | * Iterator: Valid 176 | */ 177 | public function valid() 178 | { 179 | return current($this->headers) !== false; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Slim/Http/Request.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Http; 34 | 35 | /** 36 | * Slim HTTP Request 37 | * 38 | * This class provides a human-friendly interface to the Slim environment variables; 39 | * environment variables are passed by reference and will be modified directly. 40 | * 41 | * @package Slim 42 | * @author Josh Lockhart 43 | * @since 1.0.0 44 | */ 45 | class Request 46 | { 47 | const METHOD_HEAD = 'HEAD'; 48 | const METHOD_GET = 'GET'; 49 | const METHOD_POST = 'POST'; 50 | const METHOD_PUT = 'PUT'; 51 | const METHOD_DELETE = 'DELETE'; 52 | const METHOD_OPTIONS = 'OPTIONS'; 53 | const METHOD_OVERRIDE = '_METHOD'; 54 | 55 | /** 56 | * @var array 57 | */ 58 | protected static $formDataMediaTypes = array('application/x-www-form-urlencoded'); 59 | 60 | /** 61 | * @var array 62 | */ 63 | protected $env; 64 | 65 | /** 66 | * Constructor 67 | * @param array $env 68 | * @see \Slim\Environment 69 | */ 70 | public function __construct($env) 71 | { 72 | $this->env = $env; 73 | } 74 | 75 | /** 76 | * Get HTTP method 77 | * @return string 78 | */ 79 | public function getMethod() 80 | { 81 | return $this->env['REQUEST_METHOD']; 82 | } 83 | 84 | /** 85 | * Is this a GET request? 86 | * @return bool 87 | */ 88 | public function isGet() 89 | { 90 | return $this->getMethod() === self::METHOD_GET; 91 | } 92 | 93 | /** 94 | * Is this a POST request? 95 | * @return bool 96 | */ 97 | public function isPost() 98 | { 99 | return $this->getMethod() === self::METHOD_POST; 100 | } 101 | 102 | /** 103 | * Is this a PUT request? 104 | * @return bool 105 | */ 106 | public function isPut() 107 | { 108 | return $this->getMethod() === self::METHOD_PUT; 109 | } 110 | 111 | /** 112 | * Is this a DELETE request? 113 | * @return bool 114 | */ 115 | public function isDelete() 116 | { 117 | return $this->getMethod() === self::METHOD_DELETE; 118 | } 119 | 120 | /** 121 | * Is this a HEAD request? 122 | * @return bool 123 | */ 124 | public function isHead() 125 | { 126 | return $this->getMethod() === self::METHOD_HEAD; 127 | } 128 | 129 | /** 130 | * Is this a OPTIONS request? 131 | * @return bool 132 | */ 133 | public function isOptions() 134 | { 135 | return $this->getMethod() === self::METHOD_OPTIONS; 136 | } 137 | 138 | /** 139 | * Is this an AJAX request? 140 | * @return bool 141 | */ 142 | public function isAjax() 143 | { 144 | if ($this->params('isajax')) { 145 | return true; 146 | } elseif (isset($this->env['X_REQUESTED_WITH']) && $this->env['X_REQUESTED_WITH'] === 'XMLHttpRequest') { 147 | return true; 148 | } else { 149 | return false; 150 | } 151 | } 152 | 153 | /** 154 | * Is this an XHR request? (alias of Slim_Http_Request::isAjax) 155 | * @return bool 156 | */ 157 | public function isXhr() 158 | { 159 | return $this->isAjax(); 160 | } 161 | 162 | /** 163 | * Fetch GET and POST data 164 | * 165 | * This method returns a union of GET and POST data as a key-value array, or the value 166 | * of the array key if requested; if the array key does not exist, NULL is returned. 167 | * 168 | * @param string $key 169 | * @return array|mixed|null 170 | */ 171 | public function params($key = null) 172 | { 173 | $union = array_merge($this->get(), $this->post()); 174 | if ($key) { 175 | if (isset($union[$key])) { 176 | return $union[$key]; 177 | } else { 178 | return null; 179 | } 180 | } else { 181 | return $union; 182 | } 183 | } 184 | 185 | /** 186 | * Fetch GET data 187 | * 188 | * This method returns a key-value array of data sent in the HTTP request query string, or 189 | * the value of the array key if requested; if the array key does not exist, NULL is returned. 190 | * 191 | * @param string $key 192 | * @return array|mixed|null 193 | */ 194 | public function get($key = null) 195 | { 196 | if (!isset($this->env['slim.request.query_hash'])) { 197 | $output = array(); 198 | if (function_exists('mb_parse_str') && !isset($this->env['slim.tests.ignore_multibyte'])) { 199 | mb_parse_str($this->env['QUERY_STRING'], $output); 200 | } else { 201 | parse_str($this->env['QUERY_STRING'], $output); 202 | } 203 | $this->env['slim.request.query_hash'] = Util::stripSlashesIfMagicQuotes($output); 204 | } 205 | if ($key) { 206 | if (isset($this->env['slim.request.query_hash'][$key])) { 207 | return $this->env['slim.request.query_hash'][$key]; 208 | } else { 209 | return null; 210 | } 211 | } else { 212 | return $this->env['slim.request.query_hash']; 213 | } 214 | } 215 | 216 | /** 217 | * Fetch POST data 218 | * 219 | * This method returns a key-value array of data sent in the HTTP request body, or 220 | * the value of a hash key if requested; if the array key does not exist, NULL is returned. 221 | * 222 | * @param string $key 223 | * @return array|mixed|null 224 | * @throws \RuntimeException If environment input is not available 225 | */ 226 | public function post($key = null) 227 | { 228 | if (!isset($this->env['slim.input'])) { 229 | throw new \RuntimeException('Missing slim.input in environment variables'); 230 | } 231 | if (!isset($this->env['slim.request.form_hash'])) { 232 | $this->env['slim.request.form_hash'] = array(); 233 | if ($this->isFormData() && is_string($this->env['slim.input'])) { 234 | $output = array(); 235 | if (function_exists('mb_parse_str') && !isset($this->env['slim.tests.ignore_multibyte'])) { 236 | mb_parse_str($this->env['slim.input'], $output); 237 | } else { 238 | parse_str($this->env['slim.input'], $output); 239 | } 240 | $this->env['slim.request.form_hash'] = Util::stripSlashesIfMagicQuotes($output); 241 | } else { 242 | $this->env['slim.request.form_hash'] = Util::stripSlashesIfMagicQuotes($_POST); 243 | } 244 | } 245 | if ($key) { 246 | if (isset($this->env['slim.request.form_hash'][$key])) { 247 | return $this->env['slim.request.form_hash'][$key]; 248 | } else { 249 | return null; 250 | } 251 | } else { 252 | return $this->env['slim.request.form_hash']; 253 | } 254 | } 255 | 256 | /** 257 | * Fetch PUT data (alias for \Slim\Http\Request::post) 258 | * @param string $key 259 | * @return array|mixed|null 260 | */ 261 | public function put($key = null) 262 | { 263 | return $this->post($key); 264 | } 265 | 266 | /** 267 | * Fetch DELETE data (alias for \Slim\Http\Request::post) 268 | * @param string $key 269 | * @return array|mixed|null 270 | */ 271 | public function delete($key = null) 272 | { 273 | return $this->post($key); 274 | } 275 | 276 | /** 277 | * Fetch COOKIE data 278 | * 279 | * This method returns a key-value array of Cookie data sent in the HTTP request, or 280 | * the value of a array key if requested; if the array key does not exist, NULL is returned. 281 | * 282 | * @param string $key 283 | * @return array|string|null 284 | */ 285 | public function cookies($key = null) 286 | { 287 | if (!isset($this->env['slim.request.cookie_hash'])) { 288 | $cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : ''; 289 | $this->env['slim.request.cookie_hash'] = Util::parseCookieHeader($cookieHeader); 290 | } 291 | if ($key) { 292 | if (isset($this->env['slim.request.cookie_hash'][$key])) { 293 | return $this->env['slim.request.cookie_hash'][$key]; 294 | } else { 295 | return null; 296 | } 297 | } else { 298 | return $this->env['slim.request.cookie_hash']; 299 | } 300 | } 301 | 302 | /** 303 | * Does the Request body contain parseable form data? 304 | * @return bool 305 | */ 306 | public function isFormData() 307 | { 308 | $method = isset($this->env['slim.method_override.original_method']) ? $this->env['slim.method_override.original_method'] : $this->getMethod(); 309 | 310 | return ($method === self::METHOD_POST && is_null($this->getContentType())) || in_array($this->getMediaType(), self::$formDataMediaTypes); 311 | } 312 | 313 | /** 314 | * Get Headers 315 | * 316 | * This method returns a key-value array of headers sent in the HTTP request, or 317 | * the value of a hash key if requested; if the array key does not exist, NULL is returned. 318 | * 319 | * @param string $key 320 | * @param mixed $default The default value returned if the requested header is not available 321 | * @return mixed 322 | */ 323 | public function headers($key = null, $default = null) 324 | { 325 | if ($key) { 326 | $key = strtoupper($key); 327 | $key = str_replace('-', '_', $key); 328 | $key = preg_replace('@^HTTP_@', '', $key); 329 | if (isset($this->env[$key])) { 330 | return $this->env[$key]; 331 | } else { 332 | return $default; 333 | } 334 | } else { 335 | $headers = array(); 336 | foreach ($this->env as $key => $value) { 337 | if (strpos($key, 'slim.') !== 0) { 338 | $headers[$key] = $value; 339 | } 340 | } 341 | 342 | return $headers; 343 | } 344 | } 345 | 346 | /** 347 | * Get Body 348 | * @return string 349 | */ 350 | public function getBody() 351 | { 352 | return $this->env['slim.input']; 353 | } 354 | 355 | /** 356 | * Get Content Type 357 | * @return string 358 | */ 359 | public function getContentType() 360 | { 361 | if (isset($this->env['CONTENT_TYPE'])) { 362 | return $this->env['CONTENT_TYPE']; 363 | } else { 364 | return null; 365 | } 366 | } 367 | 368 | /** 369 | * Get Media Type (type/subtype within Content Type header) 370 | * @return string|null 371 | */ 372 | public function getMediaType() 373 | { 374 | $contentType = $this->getContentType(); 375 | if ($contentType) { 376 | $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); 377 | 378 | return strtolower($contentTypeParts[0]); 379 | } else { 380 | return null; 381 | } 382 | } 383 | 384 | /** 385 | * Get Media Type Params 386 | * @return array 387 | */ 388 | public function getMediaTypeParams() 389 | { 390 | $contentType = $this->getContentType(); 391 | $contentTypeParams = array(); 392 | if ($contentType) { 393 | $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); 394 | $contentTypePartsLength = count($contentTypeParts); 395 | for ($i = 1; $i < $contentTypePartsLength; $i++) { 396 | $paramParts = explode('=', $contentTypeParts[$i]); 397 | $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; 398 | } 399 | } 400 | 401 | return $contentTypeParams; 402 | } 403 | 404 | /** 405 | * Get Content Charset 406 | * @return string|null 407 | */ 408 | public function getContentCharset() 409 | { 410 | $mediaTypeParams = $this->getMediaTypeParams(); 411 | if (isset($mediaTypeParams['charset'])) { 412 | return $mediaTypeParams['charset']; 413 | } else { 414 | return null; 415 | } 416 | } 417 | 418 | /** 419 | * Get Content-Length 420 | * @return int 421 | */ 422 | public function getContentLength() 423 | { 424 | if (isset($this->env['CONTENT_LENGTH'])) { 425 | return (int)$this->env['CONTENT_LENGTH']; 426 | } else { 427 | return 0; 428 | } 429 | } 430 | 431 | /** 432 | * Get Host 433 | * @return string 434 | */ 435 | public function getHost() 436 | { 437 | if (isset($this->env['HOST'])) { 438 | if (strpos($this->env['HOST'], ':') !== false) { 439 | $hostParts = explode(':', $this->env['HOST']); 440 | 441 | return $hostParts[0]; 442 | } 443 | 444 | return $this->env['HOST']; 445 | } else { 446 | return $this->env['SERVER_NAME']; 447 | } 448 | } 449 | 450 | /** 451 | * Get Host with Port 452 | * @return string 453 | */ 454 | public function getHostWithPort() 455 | { 456 | return sprintf('%s:%s', $this->getHost(), $this->getPort()); 457 | } 458 | 459 | /** 460 | * Get Port 461 | * @return int 462 | */ 463 | public function getPort() 464 | { 465 | return (int)$this->env['SERVER_PORT']; 466 | } 467 | 468 | /** 469 | * Get Scheme (https or http) 470 | * @return string 471 | */ 472 | public function getScheme() 473 | { 474 | return $this->env['slim.url_scheme']; 475 | } 476 | 477 | /** 478 | * Get Script Name (physical path) 479 | * @return string 480 | */ 481 | public function getScriptName() 482 | { 483 | return $this->env['SCRIPT_NAME']; 484 | } 485 | 486 | /** 487 | * LEGACY: Get Root URI (alias for Slim_Http_Request::getScriptName) 488 | * @return string 489 | */ 490 | public function getRootUri() 491 | { 492 | return $this->getScriptName(); 493 | } 494 | 495 | /** 496 | * Get Path (physical path + virtual path) 497 | * @return string 498 | */ 499 | public function getPath() 500 | { 501 | return $this->getScriptName() . $this->getPathInfo(); 502 | } 503 | 504 | /** 505 | * Get Path Info (virtual path) 506 | * @return string 507 | */ 508 | public function getPathInfo() 509 | { 510 | return $this->env['PATH_INFO']; 511 | } 512 | 513 | /** 514 | * LEGACY: Get Resource URI (alias for Slim_Http_Request::getPathInfo) 515 | * @return string 516 | */ 517 | public function getResourceUri() 518 | { 519 | return $this->getPathInfo(); 520 | } 521 | 522 | /** 523 | * Get URL (scheme + host [ + port if non-standard ]) 524 | * @return string 525 | */ 526 | public function getUrl() 527 | { 528 | $url = $this->getScheme() . '://' . $this->getHost(); 529 | if (($this->getScheme() === 'https' && $this->getPort() !== 443) || ($this->getScheme() === 'http' && $this->getPort() !== 80)) { 530 | $url .= sprintf(':%s', $this->getPort()); 531 | } 532 | 533 | return $url; 534 | } 535 | 536 | /** 537 | * Get IP 538 | * @return string 539 | */ 540 | public function getIp() 541 | { 542 | if (isset($this->env['X_FORWARDED_FOR'])) { 543 | return $this->env['X_FORWARDED_FOR']; 544 | } elseif (isset($this->env['CLIENT_IP'])) { 545 | return $this->env['CLIENT_IP']; 546 | } 547 | 548 | return $this->env['REMOTE_ADDR']; 549 | } 550 | 551 | /** 552 | * Get Referrer 553 | * @return string|null 554 | */ 555 | public function getReferrer() 556 | { 557 | if (isset($this->env['REFERER'])) { 558 | return $this->env['REFERER']; 559 | } else { 560 | return null; 561 | } 562 | } 563 | 564 | /** 565 | * Get Referer (for those who can't spell) 566 | * @return string|null 567 | */ 568 | public function getReferer() 569 | { 570 | return $this->getReferrer(); 571 | } 572 | 573 | /** 574 | * Get User Agent 575 | * @return string|null 576 | */ 577 | public function getUserAgent() 578 | { 579 | if (isset($this->env['USER_AGENT'])) { 580 | return $this->env['USER_AGENT']; 581 | } else { 582 | return null; 583 | } 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /Slim/Http/Response.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Http; 34 | 35 | /** 36 | * Response 37 | * 38 | * This is a simple abstraction over top an HTTP response. This 39 | * provides methods to set the HTTP status, the HTTP headers, 40 | * and the HTTP body. 41 | * 42 | * @package Slim 43 | * @author Josh Lockhart 44 | * @since 1.0.0 45 | */ 46 | class Response implements \ArrayAccess, \Countable, \IteratorAggregate 47 | { 48 | /** 49 | * @var int HTTP status code 50 | */ 51 | protected $status; 52 | 53 | /** 54 | * @var \Slim\Http\Headers List of HTTP response headers 55 | */ 56 | protected $header; 57 | 58 | /** 59 | * @var string HTTP response body 60 | */ 61 | protected $body; 62 | 63 | /** 64 | * @var int Length of HTTP response body 65 | */ 66 | protected $length; 67 | 68 | /** 69 | * @var array HTTP response codes and messages 70 | */ 71 | protected static $messages = array( 72 | //Informational 1xx 73 | 100 => '100 Continue', 74 | 101 => '101 Switching Protocols', 75 | //Successful 2xx 76 | 200 => '200 OK', 77 | 201 => '201 Created', 78 | 202 => '202 Accepted', 79 | 203 => '203 Non-Authoritative Information', 80 | 204 => '204 No Content', 81 | 205 => '205 Reset Content', 82 | 206 => '206 Partial Content', 83 | //Redirection 3xx 84 | 300 => '300 Multiple Choices', 85 | 301 => '301 Moved Permanently', 86 | 302 => '302 Found', 87 | 303 => '303 See Other', 88 | 304 => '304 Not Modified', 89 | 305 => '305 Use Proxy', 90 | 306 => '306 (Unused)', 91 | 307 => '307 Temporary Redirect', 92 | //Client Error 4xx 93 | 400 => '400 Bad Request', 94 | 401 => '401 Unauthorized', 95 | 402 => '402 Payment Required', 96 | 403 => '403 Forbidden', 97 | 404 => '404 Not Found', 98 | 405 => '405 Method Not Allowed', 99 | 406 => '406 Not Acceptable', 100 | 407 => '407 Proxy Authentication Required', 101 | 408 => '408 Request Timeout', 102 | 409 => '409 Conflict', 103 | 410 => '410 Gone', 104 | 411 => '411 Length Required', 105 | 412 => '412 Precondition Failed', 106 | 413 => '413 Request Entity Too Large', 107 | 414 => '414 Request-URI Too Long', 108 | 415 => '415 Unsupported Media Type', 109 | 416 => '416 Requested Range Not Satisfiable', 110 | 417 => '417 Expectation Failed', 111 | 422 => '422 Unprocessable Entity', 112 | 423 => '423 Locked', 113 | //Server Error 5xx 114 | 500 => '500 Internal Server Error', 115 | 501 => '501 Not Implemented', 116 | 502 => '502 Bad Gateway', 117 | 503 => '503 Service Unavailable', 118 | 504 => '504 Gateway Timeout', 119 | 505 => '505 HTTP Version Not Supported' 120 | ); 121 | 122 | /** 123 | * Constructor 124 | * @param string $body The HTTP response body 125 | * @param int $status The HTTP response status 126 | * @param \Slim\Http\Headers|array $header The HTTP response headers 127 | */ 128 | public function __construct($body = '', $status = 200, $header = array()) 129 | { 130 | $this->status = (int)$status; 131 | $headers = array(); 132 | foreach ($header as $key => $value) { 133 | $headers[$key] = $value; 134 | } 135 | $this->header = new Headers(array_merge(array('Content-Type' => 'text/html'), $headers)); 136 | $this->body = ''; 137 | $this->write($body); 138 | } 139 | 140 | /** 141 | * Get and set status 142 | * @param int|null $status 143 | * @return int 144 | */ 145 | public function status($status = null) 146 | { 147 | if (!is_null($status)) { 148 | $this->status = (int)$status; 149 | } 150 | 151 | return $this->status; 152 | } 153 | 154 | /** 155 | * Get and set header 156 | * @param string $name Header name 157 | * @param string|null $value Header value 158 | * @return string Header value 159 | */ 160 | public function header($name, $value = null) 161 | { 162 | if (!is_null($value)) { 163 | $this[$name] = $value; 164 | } 165 | 166 | return $this[$name]; 167 | } 168 | 169 | /** 170 | * Get headers 171 | * @return \Slim\Http\Headers 172 | */ 173 | public function headers() 174 | { 175 | return $this->header; 176 | } 177 | 178 | /** 179 | * Get and set body 180 | * @param string|null $body Content of HTTP response body 181 | * @return string 182 | */ 183 | public function body($body = null) 184 | { 185 | if (!is_null($body)) { 186 | $this->write($body, true); 187 | } 188 | 189 | return $this->body; 190 | } 191 | 192 | /** 193 | * Get and set length 194 | * @param int|null $length 195 | * @return int 196 | */ 197 | public function length($length = null) 198 | { 199 | if (!is_null($length)) { 200 | $this->length = (int)$length; 201 | } 202 | 203 | return $this->length; 204 | } 205 | 206 | /** 207 | * Append HTTP response body 208 | * @param string $body Content to append to the current HTTP response body 209 | * @param bool $replace Overwrite existing response body? 210 | * @return string The updated HTTP response body 211 | */ 212 | public function write($body, $replace = false) 213 | { 214 | if ($replace) { 215 | $this->body = $body; 216 | } else { 217 | $this->body .= (string)$body; 218 | } 219 | $this->length = strlen($this->body); 220 | 221 | return $this->body; 222 | } 223 | 224 | /** 225 | * Finalize 226 | * 227 | * This prepares this response and returns an array 228 | * of [status, headers, body]. This array is passed to outer middleware 229 | * if available or directly to the Slim run method. 230 | * 231 | * @return array[int status, array headers, string body] 232 | */ 233 | public function finalize() 234 | { 235 | if (in_array($this->status, array(204, 304))) { 236 | unset($this['Content-Type'], $this['Content-Length']); 237 | 238 | return array($this->status, $this->header, ''); 239 | } else { 240 | return array($this->status, $this->header, $this->body); 241 | } 242 | } 243 | 244 | /** 245 | * Set cookie 246 | * 247 | * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie` 248 | * header on its own and delegates this responsibility to the `Slim_Http_Util` class. This 249 | * response's header is passed by reference to the utility class and is directly modified. By not 250 | * relying on PHP's native implementation, Slim allows middleware the opportunity to massage or 251 | * analyze the raw header before the response is ultimately delivered to the HTTP client. 252 | * 253 | * @param string $name The name of the cookie 254 | * @param string|array $value If string, the value of cookie; if array, properties for 255 | * cookie including: value, expire, path, domain, secure, httponly 256 | */ 257 | public function setCookie($name, $value) 258 | { 259 | Util::setCookieHeader($this->header, $name, $value); 260 | } 261 | 262 | /** 263 | * Delete cookie 264 | * 265 | * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie` 266 | * header on its own and delegates this responsibility to the `Slim_Http_Util` class. This 267 | * response's header is passed by reference to the utility class and is directly modified. By not 268 | * relying on PHP's native implementation, Slim allows middleware the opportunity to massage or 269 | * analyze the raw header before the response is ultimately delivered to the HTTP client. 270 | * 271 | * This method will set a cookie with the given name that has an expiration time in the past; this will 272 | * prompt the HTTP client to invalidate and remove the client-side cookie. Optionally, you may 273 | * also pass a key/value array as the second argument. If the "domain" key is present in this 274 | * array, only the Cookie with the given name AND domain will be removed. The invalidating cookie 275 | * sent with this response will adopt all properties of the second argument. 276 | * 277 | * @param string $name The name of the cookie 278 | * @param array $value Properties for cookie including: value, expire, path, domain, secure, httponly 279 | */ 280 | public function deleteCookie($name, $value = array()) 281 | { 282 | Util::deleteCookieHeader($this->header, $name, $value); 283 | } 284 | 285 | /** 286 | * Redirect 287 | * 288 | * This method prepares this response to return an HTTP Redirect response 289 | * to the HTTP client. 290 | * 291 | * @param string $url The redirect destination 292 | * @param int $status The redirect HTTP status code 293 | */ 294 | public function redirect($url, $status = 302) 295 | { 296 | $this->status = $status; 297 | $this['Location'] = $url; 298 | } 299 | 300 | /** 301 | * Helpers: Empty? 302 | * @return bool 303 | */ 304 | public function isEmpty() 305 | { 306 | return in_array($this->status, array(201, 204, 304)); 307 | } 308 | 309 | /** 310 | * Helpers: Informational? 311 | * @return bool 312 | */ 313 | public function isInformational() 314 | { 315 | return $this->status >= 100 && $this->status < 200; 316 | } 317 | 318 | /** 319 | * Helpers: OK? 320 | * @return bool 321 | */ 322 | public function isOk() 323 | { 324 | return $this->status === 200; 325 | } 326 | 327 | /** 328 | * Helpers: Successful? 329 | * @return bool 330 | */ 331 | public function isSuccessful() 332 | { 333 | return $this->status >= 200 && $this->status < 300; 334 | } 335 | 336 | /** 337 | * Helpers: Redirect? 338 | * @return bool 339 | */ 340 | public function isRedirect() 341 | { 342 | return in_array($this->status, array(301, 302, 303, 307)); 343 | } 344 | 345 | /** 346 | * Helpers: Redirection? 347 | * @return bool 348 | */ 349 | public function isRedirection() 350 | { 351 | return $this->status >= 300 && $this->status < 400; 352 | } 353 | 354 | /** 355 | * Helpers: Forbidden? 356 | * @return bool 357 | */ 358 | public function isForbidden() 359 | { 360 | return $this->status === 403; 361 | } 362 | 363 | /** 364 | * Helpers: Not Found? 365 | * @return bool 366 | */ 367 | public function isNotFound() 368 | { 369 | return $this->status === 404; 370 | } 371 | 372 | /** 373 | * Helpers: Client error? 374 | * @return bool 375 | */ 376 | public function isClientError() 377 | { 378 | return $this->status >= 400 && $this->status < 500; 379 | } 380 | 381 | /** 382 | * Helpers: Server Error? 383 | * @return bool 384 | */ 385 | public function isServerError() 386 | { 387 | return $this->status >= 500 && $this->status < 600; 388 | } 389 | 390 | /** 391 | * Array Access: Offset Exists 392 | */ 393 | public function offsetExists($offset) 394 | { 395 | return isset($this->header[$offset]); 396 | } 397 | 398 | /** 399 | * Array Access: Offset Get 400 | */ 401 | public function offsetGet($offset) 402 | { 403 | if (isset($this->header[$offset])) { 404 | return $this->header[$offset]; 405 | } else { 406 | return null; 407 | } 408 | } 409 | 410 | /** 411 | * Array Access: Offset Set 412 | */ 413 | public function offsetSet($offset, $value) 414 | { 415 | $this->header[$offset] = $value; 416 | } 417 | 418 | /** 419 | * Array Access: Offset Unset 420 | */ 421 | public function offsetUnset($offset) 422 | { 423 | unset($this->header[$offset]); 424 | } 425 | 426 | /** 427 | * Countable: Count 428 | */ 429 | public function count() 430 | { 431 | return count($this->header); 432 | } 433 | 434 | /** 435 | * Get Iterator 436 | * 437 | * This returns the contained `\Slim\Http\Headers` instance which 438 | * is itself iterable. 439 | * 440 | * @return \Slim\Http\Headers 441 | */ 442 | public function getIterator() 443 | { 444 | return $this->header; 445 | } 446 | 447 | /** 448 | * Get message for HTTP status code 449 | * @return string|null 450 | */ 451 | public static function getMessageForCode($status) 452 | { 453 | if (isset(self::$messages[$status])) { 454 | return self::$messages[$status]; 455 | } else { 456 | return null; 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /Slim/Http/Util.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Http; 34 | 35 | /** 36 | * Slim HTTP Utilities 37 | * 38 | * This class provides useful methods for handling HTTP requests. 39 | * 40 | * @package Slim 41 | * @author Josh Lockhart 42 | * @since 1.0.0 43 | */ 44 | class Util 45 | { 46 | /** 47 | * Strip slashes from string or array 48 | * 49 | * This method strips slashes from its input. By default, this method will only 50 | * strip slashes from its input if magic quotes are enabled. Otherwise, you may 51 | * override the magic quotes setting with either TRUE or FALSE as the send argument 52 | * to force this method to strip or not strip slashes from its input. 53 | * 54 | * @var array|string $rawData 55 | * @return array|string 56 | */ 57 | public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null) 58 | { 59 | $strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes; 60 | if ($strip) { 61 | return self::_stripSlashes($rawData); 62 | } else { 63 | return $rawData; 64 | } 65 | } 66 | 67 | /** 68 | * Strip slashes from string or array 69 | * @param array|string $rawData 70 | * @return array|string 71 | */ 72 | protected static function _stripSlashes($rawData) 73 | { 74 | return is_array($rawData) ? array_map(array('self', '_stripSlashes'), $rawData) : stripslashes($rawData); 75 | } 76 | 77 | /** 78 | * Encrypt data 79 | * 80 | * This method will encrypt data using a given key, vector, and cipher. 81 | * By default, this will encrypt data using the RIJNDAEL/AES 256 bit cipher. You 82 | * may override the default cipher and cipher mode by passing your own desired 83 | * cipher and cipher mode as the final key-value array argument. 84 | * 85 | * @param string $data The unencrypted data 86 | * @param string $key The encryption key 87 | * @param string $iv The encryption initialization vector 88 | * @param array $settings Optional key-value array with custom algorithm and mode 89 | * @return string 90 | */ 91 | public static function encrypt($data, $key, $iv, $settings = array()) 92 | { 93 | if ($data === '' || !extension_loaded('mcrypt')) { 94 | return $data; 95 | } 96 | 97 | //Merge settings with defaults 98 | $settings = array_merge(array( 99 | 'algorithm' => MCRYPT_RIJNDAEL_256, 100 | 'mode' => MCRYPT_MODE_CBC 101 | ), $settings); 102 | 103 | //Get module 104 | $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], ''); 105 | 106 | //Validate IV 107 | $ivSize = mcrypt_enc_get_iv_size($module); 108 | if (strlen($iv) > $ivSize) { 109 | $iv = substr($iv, 0, $ivSize); 110 | } 111 | 112 | //Validate key 113 | $keySize = mcrypt_enc_get_key_size($module); 114 | if (strlen($key) > $keySize) { 115 | $key = substr($key, 0, $keySize); 116 | } 117 | 118 | //Encrypt value 119 | mcrypt_generic_init($module, $key, $iv); 120 | $res = @mcrypt_generic($module, $data); 121 | mcrypt_generic_deinit($module); 122 | 123 | return $res; 124 | } 125 | 126 | /** 127 | * Decrypt data 128 | * 129 | * This method will decrypt data using a given key, vector, and cipher. 130 | * By default, this will decrypt data using the RIJNDAEL/AES 256 bit cipher. You 131 | * may override the default cipher and cipher mode by passing your own desired 132 | * cipher and cipher mode as the final key-value array argument. 133 | * 134 | * @param string $data The encrypted data 135 | * @param string $key The encryption key 136 | * @param string $iv The encryption initialization vector 137 | * @param array $settings Optional key-value array with custom algorithm and mode 138 | * @return string 139 | */ 140 | public static function decrypt($data, $key, $iv, $settings = array()) 141 | { 142 | if ($data === '' || !extension_loaded('mcrypt')) { 143 | return $data; 144 | } 145 | 146 | //Merge settings with defaults 147 | $settings = array_merge(array( 148 | 'algorithm' => MCRYPT_RIJNDAEL_256, 149 | 'mode' => MCRYPT_MODE_CBC 150 | ), $settings); 151 | 152 | //Get module 153 | $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], ''); 154 | 155 | //Validate IV 156 | $ivSize = mcrypt_enc_get_iv_size($module); 157 | if (strlen($iv) > $ivSize) { 158 | $iv = substr($iv, 0, $ivSize); 159 | } 160 | 161 | //Validate key 162 | $keySize = mcrypt_enc_get_key_size($module); 163 | if (strlen($key) > $keySize) { 164 | $key = substr($key, 0, $keySize); 165 | } 166 | 167 | //Decrypt value 168 | mcrypt_generic_init($module, $key, $iv); 169 | $decryptedData = @mdecrypt_generic($module, $data); 170 | $res = str_replace("\x0", '', $decryptedData); 171 | mcrypt_generic_deinit($module); 172 | 173 | return $res; 174 | } 175 | 176 | /** 177 | * Encode secure cookie value 178 | * 179 | * This method will create the secure value of an HTTP cookie. The 180 | * cookie value is encrypted and hashed so that its value is 181 | * secure and checked for integrity when read in subsequent requests. 182 | * 183 | * @param string $value The unsecure HTTP cookie value 184 | * @param int $expires The UNIX timestamp at which this cookie will expire 185 | * @param string $secret The secret key used to hash the cookie value 186 | * @param int $algorithm The algorithm to use for encryption 187 | * @param int $mode The algorithm mode to use for encryption 188 | * @param string 189 | */ 190 | public static function encodeSecureCookie($value, $expires, $secret, $algorithm, $mode) 191 | { 192 | $key = hash_hmac('sha1', $expires, $secret); 193 | $iv = self::get_iv($expires, $secret); 194 | $secureString = base64_encode(self::encrypt($value, $key, $iv, array( 195 | 'algorithm' => $algorithm, 196 | 'mode' => $mode 197 | ))); 198 | $verificationString = hash_hmac('sha1', $expires . $value, $key); 199 | 200 | return implode('|', array($expires, $secureString, $verificationString)); 201 | } 202 | 203 | /** 204 | * Decode secure cookie value 205 | * 206 | * This method will decode the secure value of an HTTP cookie. The 207 | * cookie value is encrypted and hashed so that its value is 208 | * secure and checked for integrity when read in subsequent requests. 209 | * 210 | * @param string $value The secure HTTP cookie value 211 | * @param int $expires The UNIX timestamp at which this cookie will expire 212 | * @param string $secret The secret key used to hash the cookie value 213 | * @param int $algorithm The algorithm to use for encryption 214 | * @param int $mode The algorithm mode to use for encryption 215 | * @param string 216 | */ 217 | public static function decodeSecureCookie($value, $secret, $algorithm, $mode) 218 | { 219 | if ($value) { 220 | $value = explode('|', $value); 221 | if (count($value) === 3 && ((int)$value[0] === 0 || (int)$value[0] > time())) { 222 | $key = hash_hmac('sha1', $value[0], $secret); 223 | $iv = self::get_iv($value[0], $secret); 224 | $data = self::decrypt(base64_decode($value[1]), $key, $iv, array( 225 | 'algorithm' => $algorithm, 226 | 'mode' => $mode 227 | )); 228 | $verificationString = hash_hmac('sha1', $value[0] . $data, $key); 229 | if ($verificationString === $value[2]) { 230 | return $data; 231 | } 232 | } 233 | } 234 | 235 | return false; 236 | } 237 | 238 | /** 239 | * Set HTTP cookie header 240 | * 241 | * This method will construct and set the HTTP `Set-Cookie` header. Slim 242 | * uses this method instead of PHP's native `setcookie` method. This allows 243 | * more control of the HTTP header irrespective of the native implementation's 244 | * dependency on PHP versions. 245 | * 246 | * This method accepts the Slim_Http_Headers object by reference as its 247 | * first argument; this method directly modifies this object instead of 248 | * returning a value. 249 | * 250 | * @param array $header 251 | * @param string $name 252 | * @param string $value 253 | */ 254 | public static function setCookieHeader(&$header, $name, $value) 255 | { 256 | //Build cookie header 257 | if (is_array($value)) { 258 | $domain = ''; 259 | $path = ''; 260 | $expires = ''; 261 | $secure = ''; 262 | $httponly = ''; 263 | if (isset($value['domain']) && $value['domain']) { 264 | $domain = '; domain=' . $value['domain']; 265 | } 266 | if (isset($value['path']) && $value['path']) { 267 | $path = '; path=' . $value['path']; 268 | } 269 | if (isset($value['expires'])) { 270 | if (is_string($value['expires'])) { 271 | $timestamp = strtotime($value['expires']); 272 | } else { 273 | $timestamp = (int)$value['expires']; 274 | } 275 | if ($timestamp !== 0) { 276 | $expires = '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp); 277 | } 278 | } 279 | if (isset($value['secure']) && $value['secure']) { 280 | $secure = '; secure'; 281 | } 282 | if (isset($value['httponly']) && $value['httponly']) { 283 | $httponly = '; HttpOnly'; 284 | } 285 | $cookie = sprintf('%s=%s%s', urlencode($name), urlencode((string)$value['value']), $domain . $path . $expires . $secure . $httponly); 286 | } else { 287 | $cookie = sprintf('%s=%s', urlencode($name), urlencode((string)$value)); 288 | } 289 | 290 | //Set cookie header 291 | if (!isset($header['Set-Cookie']) || $header['Set-Cookie'] === '') { 292 | $header['Set-Cookie'] = $cookie; 293 | } else { 294 | $header['Set-Cookie'] = implode("\n", array($header['Set-Cookie'], $cookie)); 295 | } 296 | } 297 | 298 | /** 299 | * Delete HTTP cookie header 300 | * 301 | * This method will construct and set the HTTP `Set-Cookie` header to invalidate 302 | * a client-side HTTP cookie. If a cookie with the same name (and, optionally, domain) 303 | * is already set in the HTTP response, it will also be removed. Slim uses this method 304 | * instead of PHP's native `setcookie` method. This allows more control of the HTTP header 305 | * irrespective of PHP's native implementation's dependency on PHP versions. 306 | * 307 | * This method accepts the Slim_Http_Headers object by reference as its 308 | * first argument; this method directly modifies this object instead of 309 | * returning a value. 310 | * 311 | * @param array $header 312 | * @param string $name 313 | * @param string $value 314 | */ 315 | public static function deleteCookieHeader(&$header, $name, $value = array()) 316 | { 317 | //Remove affected cookies from current response header 318 | $cookiesOld = array(); 319 | $cookiesNew = array(); 320 | if (isset($header['Set-Cookie'])) { 321 | $cookiesOld = explode("\n", $header['Set-Cookie']); 322 | } 323 | foreach ($cookiesOld as $c) { 324 | if (isset($value['domain']) && $value['domain']) { 325 | $regex = sprintf('@%s=.*domain=%s@', urlencode($name), preg_quote($value['domain'])); 326 | } else { 327 | $regex = sprintf('@%s=@', urlencode($name)); 328 | } 329 | if (preg_match($regex, $c) === 0) { 330 | $cookiesNew[] = $c; 331 | } 332 | } 333 | if ($cookiesNew) { 334 | $header['Set-Cookie'] = implode("\n", $cookiesNew); 335 | } else { 336 | unset($header['Set-Cookie']); 337 | } 338 | 339 | //Set invalidating cookie to clear client-side cookie 340 | self::setCookieHeader($header, $name, array_merge(array('value' => '', 'path' => null, 'domain' => null, 'expires' => time() - 100), $value)); 341 | } 342 | 343 | /** 344 | * Parse cookie header 345 | * 346 | * This method will parse the HTTP requst's `Cookie` header 347 | * and extract cookies into an associative array. 348 | * 349 | * @param string 350 | * @return array 351 | */ 352 | public static function parseCookieHeader($header) 353 | { 354 | $cookies = array(); 355 | $header = rtrim($header, "\r\n"); 356 | $headerPieces = preg_split('@\s*[;,]\s*@', $header); 357 | foreach ($headerPieces as $c) { 358 | $cParts = explode('=', $c); 359 | if (count($cParts) === 2) { 360 | $key = urldecode($cParts[0]); 361 | $value = urldecode($cParts[1]); 362 | if (!isset($cookies[$key])) { 363 | $cookies[$key] = $value; 364 | } 365 | } 366 | } 367 | 368 | return $cookies; 369 | } 370 | 371 | /** 372 | * Generate a random IV 373 | * 374 | * This method will generate a non-predictable IV for use with 375 | * the cookie encryption 376 | * 377 | * @param int $expires The UNIX timestamp at which this cookie will expire 378 | * @param string $secret The secret key used to hash the cookie value 379 | * @return binary string with length 40 380 | */ 381 | private static function get_iv($expires, $secret) 382 | { 383 | $data1 = hash_hmac('sha1', 'a' . $expires . 'b', $secret); 384 | $data2 = hash_hmac('sha1', 'z' . $expires . 'y', $secret); 385 | 386 | return pack("h*", $data1 . $data2); 387 | } 388 | 389 | } 390 | -------------------------------------------------------------------------------- /Slim/Log.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim; 34 | 35 | /** 36 | * Log 37 | * 38 | * This is the primary logger for a Slim application. You may provide 39 | * a Log Writer in conjunction with this Log to write to various output 40 | * destinations (e.g. a file). This class provides this interface: 41 | * 42 | * debug( mixed $object ) 43 | * info( mixed $object ) 44 | * warn( mixed $object ) 45 | * error( mixed $object ) 46 | * fatal( mixed $object ) 47 | * 48 | * This class assumes only that your Log Writer has a public `write()` method 49 | * that accepts any object as its one and only argument. The Log Writer 50 | * class may write or send its argument anywhere: a file, STDERR, 51 | * a remote web API, etc. The possibilities are endless. 52 | * 53 | * @package Slim 54 | * @author Josh Lockhart 55 | * @since 1.0.0 56 | */ 57 | class Log 58 | { 59 | const FATAL = 0; 60 | const ERROR = 1; 61 | const WARN = 2; 62 | const INFO = 3; 63 | const DEBUG = 4; 64 | 65 | /** 66 | * @var array 67 | */ 68 | protected static $levels = array( 69 | self::FATAL => 'FATAL', 70 | self::ERROR => 'ERROR', 71 | self::WARN => 'WARN', 72 | self::INFO => 'INFO', 73 | self::DEBUG => 'DEBUG' 74 | ); 75 | 76 | /** 77 | * @var mixed 78 | */ 79 | protected $writer; 80 | 81 | /** 82 | * @var bool 83 | */ 84 | protected $enabled; 85 | 86 | /** 87 | * @var int 88 | */ 89 | protected $level; 90 | 91 | /** 92 | * Constructor 93 | * @param mixed $writer 94 | */ 95 | public function __construct($writer) 96 | { 97 | $this->writer = $writer; 98 | $this->enabled = true; 99 | $this->level = self::DEBUG; 100 | } 101 | 102 | /** 103 | * Is logging enabled? 104 | * @return bool 105 | */ 106 | public function getEnabled() 107 | { 108 | return $this->enabled; 109 | } 110 | 111 | /** 112 | * Enable or disable logging 113 | * @param bool $enabled 114 | */ 115 | public function setEnabled($enabled) 116 | { 117 | if ($enabled) { 118 | $this->enabled = true; 119 | } else { 120 | $this->enabled = false; 121 | } 122 | } 123 | 124 | /** 125 | * Set level 126 | * @param int $level 127 | * @throws \InvalidArgumentException If invalid log level specified 128 | */ 129 | public function setLevel($level) 130 | { 131 | if (!isset(self::$levels[$level])) { 132 | throw new \InvalidArgumentException('Invalid log level'); 133 | } 134 | $this->level = $level; 135 | } 136 | 137 | /** 138 | * Get level 139 | * @return int 140 | */ 141 | public function getLevel() 142 | { 143 | return $this->level; 144 | } 145 | 146 | /** 147 | * Set writer 148 | * @param mixed $writer 149 | */ 150 | public function setWriter($writer) 151 | { 152 | $this->writer = $writer; 153 | } 154 | 155 | /** 156 | * Get writer 157 | * @return mixed 158 | */ 159 | public function getWriter() 160 | { 161 | return $this->writer; 162 | } 163 | 164 | /** 165 | * Is logging enabled? 166 | * @return bool 167 | */ 168 | public function isEnabled() 169 | { 170 | return $this->enabled; 171 | } 172 | 173 | /** 174 | * Log debug message 175 | * @param mixed $object 176 | * @return mixed|false What the Logger returns, or false if Logger not set or not enabled 177 | */ 178 | public function debug($object) 179 | { 180 | return $this->write($object, self::DEBUG); 181 | } 182 | 183 | /** 184 | * Log info message 185 | * @param mixed $object 186 | * @return mixed|false What the Logger returns, or false if Logger not set or not enabled 187 | */ 188 | public function info($object) 189 | { 190 | return $this->write($object, self::INFO); 191 | } 192 | 193 | /** 194 | * Log warn message 195 | * @param mixed $object 196 | * @return mixed|false What the Logger returns, or false if Logger not set or not enabled 197 | */ 198 | public function warn($object) 199 | { 200 | return $this->write($object, self::WARN); 201 | } 202 | 203 | /** 204 | * Log error message 205 | * @param mixed $object 206 | * @return mixed|false What the Logger returns, or false if Logger not set or not enabled 207 | */ 208 | public function error($object) 209 | { 210 | return $this->write($object, self::ERROR); 211 | } 212 | 213 | /** 214 | * Log fatal message 215 | * @param mixed $object 216 | * @return mixed|false What the Logger returns, or false if Logger not set or not enabled 217 | */ 218 | public function fatal($object) 219 | { 220 | return $this->write($object, self::FATAL); 221 | } 222 | 223 | /** 224 | * Log message 225 | * @param mixed The object to log 226 | * @param int The message level 227 | * @return int|false 228 | */ 229 | protected function write($object, $level) 230 | { 231 | if ($this->enabled && $this->writer && $level <= $this->level) { 232 | return $this->writer->write($object, $level); 233 | } else { 234 | return false; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Slim/LogWriter.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim; 34 | 35 | /** 36 | * Log Writer 37 | * 38 | * This class is used by Slim_Log to write log messages to a valid, writable 39 | * resource handle (e.g. a file or STDERR). 40 | * 41 | * @package Slim 42 | * @author Josh Lockhart 43 | * @since 1.6.0 44 | */ 45 | class LogWriter 46 | { 47 | /** 48 | * @var resource 49 | */ 50 | protected $resource; 51 | 52 | /** 53 | * Constructor 54 | * @param resource $resource 55 | * @throws \InvalidArgumentException If invalid resource 56 | */ 57 | public function __construct($resource) 58 | { 59 | if (!is_resource($resource)) { 60 | throw new \InvalidArgumentException('Cannot create LogWriter. Invalid resource handle.'); 61 | } 62 | $this->resource = $resource; 63 | } 64 | 65 | /** 66 | * Write message 67 | * @param mixed $message 68 | * @param int $level 69 | * @return int|false 70 | */ 71 | public function write($message, $level = null) 72 | { 73 | return fwrite($this->resource, (string)$message . PHP_EOL); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Slim/Middleware.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim; 34 | 35 | /** 36 | * Middleware 37 | * 38 | * @package Slim 39 | * @author Josh Lockhart 40 | * @since 1.6.0 41 | */ 42 | abstract class Middleware 43 | { 44 | /** 45 | * @var \Slim Reference to the primary application instance 46 | */ 47 | protected $app; 48 | 49 | /** 50 | * @var mixed Reference to the next downstream middleware 51 | */ 52 | protected $next; 53 | 54 | /** 55 | * Set application 56 | * 57 | * This method injects the primary Slim application instance into 58 | * this middleware. 59 | * 60 | * @param \Slim $application 61 | */ 62 | final public function setApplication($application) 63 | { 64 | $this->app = $application; 65 | } 66 | 67 | /** 68 | * Get application 69 | * 70 | * This method retrieves the application previously injected 71 | * into this middleware. 72 | * 73 | * @return \Slim 74 | */ 75 | final public function getApplication() 76 | { 77 | return $this->app; 78 | } 79 | 80 | /** 81 | * Set next middleware 82 | * 83 | * This method injects the next downstream middleware into 84 | * this middleware so that it may optionally be called 85 | * when appropriate. 86 | * 87 | * @param \Slim|\Slim\Middleware 88 | */ 89 | final public function setNextMiddleware($nextMiddleware) 90 | { 91 | $this->next = $nextMiddleware; 92 | } 93 | 94 | /** 95 | * Get next middleware 96 | * 97 | * This method retrieves the next downstream middleware 98 | * previously injected into this middleware. 99 | * 100 | * @return \Slim|\Slim\Middleware 101 | */ 102 | final public function getNextMiddleware() 103 | { 104 | return $this->next; 105 | } 106 | 107 | /** 108 | * Call 109 | * 110 | * Perform actions specific to this middleware and optionally 111 | * call the next downstream middleware. 112 | */ 113 | abstract public function call(); 114 | } 115 | -------------------------------------------------------------------------------- /Slim/Middleware/ContentTypes.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Middleware; 34 | 35 | /** 36 | * Content Types 37 | * 38 | * This is middleware for a Slim application that intercepts 39 | * the HTTP request body and parses it into the appropriate 40 | * PHP data structure if possible; else it returns the HTTP 41 | * request body unchanged. This is particularly useful 42 | * for preparing the HTTP request body for an XML or JSON API. 43 | * 44 | * @package Slim 45 | * @author Josh Lockhart 46 | * @since 1.6.0 47 | */ 48 | class ContentTypes extends \Slim\Middleware 49 | { 50 | /** 51 | * @var array 52 | */ 53 | protected $contentTypes; 54 | 55 | /** 56 | * Constructor 57 | * @param array $settings 58 | */ 59 | public function __construct($settings = array()) 60 | { 61 | $this->contentTypes = array_merge(array( 62 | 'application/json' => array($this, 'parseJson'), 63 | 'application/xml' => array($this, 'parseXml'), 64 | 'text/xml' => array($this, 'parseXml'), 65 | 'text/csv' => array($this, 'parseCsv') 66 | ), $settings); 67 | } 68 | 69 | /** 70 | * Call 71 | */ 72 | public function call() 73 | { 74 | $mediaType = $this->app->request()->getMediaType(); 75 | if ($mediaType) { 76 | $env = $this->app->environment(); 77 | $env['slim.input_original'] = $env['slim.input']; 78 | $env['slim.input'] = $this->parse($env['slim.input'], $mediaType); 79 | } 80 | $this->next->call(); 81 | } 82 | 83 | /** 84 | * Parse input 85 | * 86 | * This method will attempt to parse the request body 87 | * based on its content type if available. 88 | * 89 | * @param string $input 90 | * @param string $contentType 91 | * @return mixed 92 | */ 93 | protected function parse($input, $contentType) 94 | { 95 | if (isset($this->contentTypes[$contentType]) && is_callable($this->contentTypes[$contentType])) { 96 | $result = call_user_func($this->contentTypes[$contentType], $input); 97 | if ($result) { 98 | return $result; 99 | } 100 | } 101 | 102 | return $input; 103 | } 104 | 105 | /** 106 | * Parse JSON 107 | * 108 | * This method converts the raw JSON input 109 | * into an associative array. 110 | * 111 | * @param string $input 112 | * @return array|string 113 | */ 114 | protected function parseJson($input) 115 | { 116 | if (function_exists('json_decode')) { 117 | $result = json_decode($input, true); 118 | if ($result) { 119 | return $result; 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * Parse XML 126 | * 127 | * This method creates a SimpleXMLElement 128 | * based upon the XML input. If the SimpleXML 129 | * extension is not available, the raw input 130 | * will be returned unchanged. 131 | * 132 | * @param string $input 133 | * @return \SimpleXMLElement|string 134 | */ 135 | protected function parseXml($input) 136 | { 137 | if (class_exists('SimpleXMLElement')) { 138 | try { 139 | return new \SimpleXMLElement($input); 140 | } catch (\Exception $e) { 141 | // Do nothing 142 | } 143 | } 144 | 145 | return $input; 146 | } 147 | 148 | /** 149 | * Parse CSV 150 | * 151 | * This method parses CSV content into a numeric array 152 | * containing an array of data for each CSV line. 153 | * 154 | * @param string $input 155 | * @return array 156 | */ 157 | protected function parseCsv($input) 158 | { 159 | $temp = fopen('php://memory', 'rw'); 160 | fwrite($temp, $input); 161 | fseek($temp, 0); 162 | $res = array(); 163 | while (($data = fgetcsv($temp)) !== false) { 164 | $res[] = $data; 165 | } 166 | fclose($temp); 167 | 168 | return $res; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Slim/Middleware/Flash.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Middleware; 34 | 35 | /** 36 | * Flash 37 | * 38 | * This is middleware for a Slim application that enables 39 | * Flash messaging between HTTP requests. This allows you 40 | * set Flash messages for the current request, for the next request, 41 | * or to retain messages from the previous request through to 42 | * the next request. 43 | * 44 | * @package Slim 45 | * @author Josh Lockhart 46 | * @since 1.6.0 47 | */ 48 | class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate 49 | { 50 | /** 51 | * @var array 52 | */ 53 | protected $settings; 54 | 55 | /** 56 | * @var array 57 | */ 58 | protected $messages; 59 | 60 | /** 61 | * Constructor 62 | * @param \Slim $app 63 | * @param array $settings 64 | */ 65 | public function __construct($settings = array()) 66 | { 67 | $this->settings = array_merge(array('key' => 'slim.flash'), $settings); 68 | $this->messages = array( 69 | 'prev' => array(), //flash messages from prev request (loaded when middleware called) 70 | 'next' => array(), //flash messages for next request 71 | 'now' => array() //flash messages for current request 72 | ); 73 | } 74 | 75 | /** 76 | * Call 77 | */ 78 | public function call() 79 | { 80 | //Read flash messaging from previous request if available 81 | $this->loadMessages(); 82 | 83 | //Prepare flash messaging for current request 84 | $env = $this->app->environment(); 85 | $env['slim.flash'] = $this; 86 | $this->next->call(); 87 | $this->save(); 88 | } 89 | 90 | /** 91 | * Now 92 | * 93 | * Specify a flash message for a given key to be shown for the current request 94 | * 95 | * @param string $key 96 | * @param string $value 97 | */ 98 | public function now($key, $value) 99 | { 100 | $this->messages['now'][(string)$key] = $value; 101 | } 102 | 103 | /** 104 | * Set 105 | * 106 | * Specify a flash message for a given key to be shown for the next request 107 | * 108 | * @param string $key 109 | * @param string $value 110 | */ 111 | public function set($key, $value) 112 | { 113 | $this->messages['next'][(string)$key] = $value; 114 | } 115 | 116 | /** 117 | * Keep 118 | * 119 | * Retain flash messages from the previous request for the next request 120 | */ 121 | public function keep() 122 | { 123 | foreach ($this->messages['prev'] as $key => $val) { 124 | $this->messages['next'][$key] = $val; 125 | } 126 | } 127 | 128 | /** 129 | * Save 130 | */ 131 | public function save() 132 | { 133 | $_SESSION[$this->settings['key']] = $this->messages['next']; 134 | } 135 | 136 | /** 137 | * Load messages from previous request if available 138 | */ 139 | public function loadMessages() 140 | { 141 | if (isset($_SESSION[$this->settings['key']])) { 142 | $this->messages['prev'] = $_SESSION[$this->settings['key']]; 143 | } 144 | } 145 | 146 | /** 147 | * Return array of flash messages to be shown for the current request 148 | * 149 | * @return array 150 | */ 151 | public function getMessages() 152 | { 153 | return array_merge($this->messages['prev'], $this->messages['now']); 154 | } 155 | 156 | /** 157 | * Array Access: Offset Exists 158 | */ 159 | public function offsetExists($offset) 160 | { 161 | $messages = $this->getMessages(); 162 | 163 | return isset($messages[$offset]); 164 | } 165 | 166 | /** 167 | * Array Access: Offset Get 168 | */ 169 | public function offsetGet($offset) 170 | { 171 | $messages = $this->getMessages(); 172 | 173 | return isset($messages[$offset]) ? $messages[$offset] : null; 174 | } 175 | 176 | /** 177 | * Array Access: Offset Set 178 | */ 179 | public function offsetSet($offset, $value) 180 | { 181 | $this->now($offset, $value); 182 | } 183 | 184 | /** 185 | * Array Access: Offset Unset 186 | */ 187 | public function offsetUnset($offset) 188 | { 189 | unset($this->messages['prev'][$offset], $this->messages['now'][$offset]); 190 | } 191 | 192 | /** 193 | * Iterator Aggregate: Get Iterator 194 | * @return \ArrayIterator 195 | */ 196 | public function getIterator() 197 | { 198 | $messages = $this->getMessages(); 199 | 200 | return new \ArrayIterator($messages); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Slim/Middleware/MethodOverride.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Middleware; 34 | 35 | /** 36 | * HTTP Method Override 37 | * 38 | * This is middleware for a Slim application that allows traditional 39 | * desktop browsers to submit psuedo PUT and DELETE requests by relying 40 | * on a pre-determined request parameter. Without this middleware, 41 | * desktop browsers are only able to submit GET and POST requests. 42 | * 43 | * This middleware is included automatically! 44 | * 45 | * @package Slim 46 | * @author Josh Lockhart 47 | * @since 1.6.0 48 | */ 49 | class MethodOverride extends \Slim\Middleware 50 | { 51 | /** 52 | * @var array 53 | */ 54 | protected $settings; 55 | 56 | /** 57 | * Constructor 58 | * @param \Slim $app 59 | * @param array $settings 60 | */ 61 | public function __construct($settings = array()) 62 | { 63 | $this->settings = array_merge(array('key' => '_METHOD'), $settings); 64 | } 65 | 66 | /** 67 | * Call 68 | * 69 | * Implements Slim middleware interface. This method is invoked and passed 70 | * an array of environment variables. This middleware inspects the environment 71 | * variables for the HTTP method override parameter; if found, this middleware 72 | * modifies the environment settings so downstream middleware and/or the Slim 73 | * application will treat the request with the desired HTTP method. 74 | * 75 | * @param array $env 76 | * @return array[status, header, body] 77 | */ 78 | public function call() 79 | { 80 | $env = $this->app->environment(); 81 | if (isset($env['X_HTTP_METHOD_OVERRIDE'])) { 82 | // Header commonly used by Backbone.js and others 83 | $env['slim.method_override.original_method'] = $env['REQUEST_METHOD']; 84 | $env['REQUEST_METHOD'] = strtoupper($env['X_HTTP_METHOD_OVERRIDE']); 85 | } elseif (isset($env['REQUEST_METHOD']) && $env['REQUEST_METHOD'] === 'POST') { 86 | // HTML Form Override 87 | $req = new \Slim\Http\Request($env); 88 | $method = $req->post($this->settings['key']); 89 | if ($method) { 90 | $env['slim.method_override.original_method'] = $env['REQUEST_METHOD']; 91 | $env['REQUEST_METHOD'] = strtoupper($method); 92 | } 93 | } 94 | $this->next->call(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Slim/Middleware/PrettyExceptions.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim\Middleware; 34 | 35 | /** 36 | * Pretty Exceptions 37 | * 38 | * This middleware catches any Exception thrown by the surrounded 39 | * application and displays a developer-friendly diagnostic screen. 40 | * 41 | * @package Slim 42 | * @author Josh Lockhart 43 | * @since 1.0.0 44 | */ 45 | class PrettyExceptions extends \Slim\Middleware 46 | { 47 | /** 48 | * @var array 49 | */ 50 | protected $settings; 51 | 52 | /** 53 | * Constructor 54 | * @param array $settings 55 | */ 56 | public function __construct($settings = array()) 57 | { 58 | $this->settings = $settings; 59 | } 60 | 61 | /** 62 | * Call 63 | */ 64 | public function call() 65 | { 66 | try { 67 | $this->next->call(); 68 | } catch (\Exception $e) { 69 | $env = $this->app->environment(); 70 | $env['slim.log']->error($e); 71 | $this->app->contentType('text/html'); 72 | $this->app->response()->status(500); 73 | $this->app->response()->body($this->renderBody($env, $e)); 74 | } 75 | } 76 | 77 | /** 78 | * Render response body 79 | * @param array $env 80 | * @param \Exception $exception 81 | * @return string 82 | */ 83 | protected function renderBody(&$env, $exception) 84 | { 85 | $title = 'Slim Application Error'; 86 | $code = $exception->getCode(); 87 | $message = $exception->getMessage(); 88 | $file = $exception->getFile(); 89 | $line = $exception->getLine(); 90 | $trace = $exception->getTraceAsString(); 91 | $html = sprintf('

%s

', $title); 92 | $html .= '

The application could not run because of the following error:

'; 93 | $html .= '

Details

'; 94 | $html .= sprintf('
Type: %s
', get_class($exception)); 95 | if ($code) { 96 | $html .= sprintf('
Code: %s
', $code); 97 | } 98 | if ($message) { 99 | $html .= sprintf('
Message: %s
', $message); 100 | } 101 | if ($file) { 102 | $html .= sprintf('
File: %s
', $file); 103 | } 104 | if ($line) { 105 | $html .= sprintf('
Line: %s
', $line); 106 | } 107 | if ($trace) { 108 | $html .= '

Trace

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

%s

%s", $title, $title, $body); 1292 | } 1293 | 1294 | /** 1295 | * Default Not Found handler 1296 | */ 1297 | protected function defaultNotFound() 1298 | { 1299 | echo static::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'); 1300 | } 1301 | 1302 | /** 1303 | * Default Error handler 1304 | */ 1305 | protected function defaultError($e) 1306 | { 1307 | $this->getLog()->error($e); 1308 | echo self::generateTemplateMarkup('Error', '

A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.

'); 1309 | } 1310 | } 1311 | -------------------------------------------------------------------------------- /Slim/View.php: -------------------------------------------------------------------------------- 1 | 6 | * @copyright 2011 Josh Lockhart 7 | * @link http://www.slimframework.com 8 | * @license http://www.slimframework.com/license 9 | * @version 2.2.0 10 | * @package Slim 11 | * 12 | * MIT LICENSE 13 | * 14 | * Permission is hereby granted, free of charge, to any person obtaining 15 | * a copy of this software and associated documentation files (the 16 | * "Software"), to deal in the Software without restriction, including 17 | * without limitation the rights to use, copy, modify, merge, publish, 18 | * distribute, sublicense, and/or sell copies of the Software, and to 19 | * permit persons to whom the Software is furnished to do so, subject to 20 | * the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | */ 33 | namespace Slim; 34 | 35 | /** 36 | * View 37 | * 38 | * The view is responsible for rendering a template. The view 39 | * should subclass \Slim\View and implement this interface: 40 | * 41 | * public render(string $template); 42 | * 43 | * This method should render the specified template and return 44 | * the resultant string. 45 | * 46 | * @package Slim 47 | * @author Josh Lockhart 48 | * @since 1.0.0 49 | */ 50 | class View 51 | { 52 | /** 53 | * @var string Absolute or relative filesystem path to a specific template 54 | * 55 | * DEPRECATION WARNING! 56 | * This variable will be removed in the near future 57 | */ 58 | protected $templatePath = ''; 59 | 60 | /** 61 | * @var array Associative array of template variables 62 | */ 63 | protected $data = array(); 64 | 65 | /** 66 | * @var string Absolute or relative path to the application's templates directory 67 | */ 68 | protected $templatesDirectory; 69 | 70 | /** 71 | * Constructor 72 | * 73 | * This is empty but may be implemented in a subclass 74 | */ 75 | public function __construct() 76 | { 77 | 78 | } 79 | 80 | /** 81 | * Get data 82 | * @param string|null $key 83 | * @return mixed If key is null, array of template data; 84 | * If key exists, value of datum with key; 85 | * If key does not exist, null; 86 | */ 87 | public function getData($key = null) 88 | { 89 | if (!is_null($key)) { 90 | return isset($this->data[$key]) ? $this->data[$key] : null; 91 | } else { 92 | return $this->data; 93 | } 94 | } 95 | 96 | /** 97 | * Set data 98 | * 99 | * If two arguments: 100 | * A single datum with key is assigned value; 101 | * 102 | * $view->setData('color', 'red'); 103 | * 104 | * If one argument: 105 | * Replace all data with provided array keys and values; 106 | * 107 | * $view->setData(array('color' => 'red', 'number' => 1)); 108 | * 109 | * @param mixed 110 | * @param mixed 111 | * @throws InvalidArgumentException If incorrect method signature 112 | */ 113 | public function setData() 114 | { 115 | $args = func_get_args(); 116 | if (count($args) === 1 && is_array($args[0])) { 117 | $this->data = $args[0]; 118 | } elseif (count($args) === 2) { 119 | $this->data[(string)$args[0]] = $args[1]; 120 | } else { 121 | throw new \InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`'); 122 | } 123 | } 124 | 125 | /** 126 | * Append new data to existing template data 127 | * @param array 128 | * @throws InvalidArgumentException If not given an array argument 129 | */ 130 | public function appendData($data) 131 | { 132 | if (!is_array($data)) { 133 | throw new \InvalidArgumentException('Cannot append view data. Expected array argument.'); 134 | } 135 | $this->data = array_merge($this->data, $data); 136 | } 137 | 138 | /** 139 | * Get templates directory 140 | * @return string|null Path to templates directory without trailing slash; 141 | * Returns null if templates directory not set; 142 | */ 143 | public function getTemplatesDirectory() 144 | { 145 | return $this->templatesDirectory; 146 | } 147 | 148 | /** 149 | * Set templates directory 150 | * @param string $dir 151 | */ 152 | public function setTemplatesDirectory($dir) 153 | { 154 | $this->templatesDirectory = rtrim($dir, '/'); 155 | } 156 | 157 | /** 158 | * Set template 159 | * @param string $template 160 | * @throws RuntimeException If template file does not exist 161 | * 162 | * DEPRECATION WARNING! 163 | * This method will be removed in the near future. 164 | */ 165 | public function setTemplate($template) 166 | { 167 | $this->templatePath = $this->getTemplatesDirectory() . '/' . ltrim($template, '/'); 168 | if (!file_exists($this->templatePath)) { 169 | throw new \RuntimeException('View cannot render template `' . $this->templatePath . '`. Template does not exist.'); 170 | } 171 | } 172 | 173 | /** 174 | * Display template 175 | * 176 | * This method echoes the rendered template to the current output buffer 177 | * 178 | * @param string $template Pathname of template file relative to templates directoy 179 | */ 180 | public function display($template) 181 | { 182 | echo $this->fetch($template); 183 | } 184 | 185 | /** 186 | * Fetch rendered template 187 | * 188 | * This method returns the rendered template 189 | * 190 | * @param string $template Pathname of template file relative to templates directory 191 | * @return string 192 | */ 193 | public function fetch($template) 194 | { 195 | return $this->render($template); 196 | } 197 | 198 | /** 199 | * Render template 200 | * 201 | * @param string $template Pathname of template file relative to templates directory 202 | * @return string 203 | * 204 | * DEPRECATION WARNING! 205 | * Use `\Slim\View::fetch` to return a rendered template instead of `\Slim\View::render`. 206 | */ 207 | public function render($template) 208 | { 209 | $this->setTemplate($template); 210 | extract($this->data); 211 | ob_start(); 212 | require $this->templatePath; 213 | 214 | return ob_get_clean(); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /api/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteBase /bookmark/api/ 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /api/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/AngularJSTutorialApp/98107d2c9d708fe33c547553a2b29b8f55cd88d6/api/db.sqlite3 -------------------------------------------------------------------------------- /api/index.php: -------------------------------------------------------------------------------- 1 | contentType('application/json'); 7 | $app->expires('-1000000'); 8 | $db = new PDO('sqlite:db.sqlite3'); 9 | 10 | function getTitleFromUrl($url) 11 | { 12 | preg_match('/(.+)<\/title>/', file_get_contents($url), $matches); 13 | 14 | return mb_convert_encoding($matches[1], 'UTF-8', 'UTF-8'); 15 | } 16 | 17 | function getFaviconFromUrl($url) 18 | { 19 | $url = parse_url($url); 20 | $url = urlencode(sprintf('%s://%s', 21 | isset($url['scheme']) ? $url['scheme'] : 'http', 22 | isset($url['host']) ? $url['host'] : strtolower($url['path']))); 23 | 24 | return "http://g.etfv.co/$url"; 25 | } 26 | 27 | function saveFavicon($url, $id) 28 | { 29 | file_put_contents("../icons/$id.ico", file_get_contents(getFaviconFromUrl($url))); 30 | } 31 | 32 | function returnResult($action, $success = true, $id = 0) 33 | { 34 | echo json_encode([ 35 | 'action' => $action, 36 | 'success' => $success, 37 | 'id' => intval($id), 38 | ]); 39 | } 40 | 41 | $app->get('/bookmark', function () use ($db, $app) { 42 | $sth = $db->query('SELECT * FROM bookmark;'); 43 | echo json_encode($sth->fetchAll(PDO::FETCH_CLASS)); 44 | }); 45 | 46 | $app->get('/bookmark/:id', function ($id) use ($db, $app) { 47 | $sth = $db->prepare('SELECT * FROM bookmark WHERE id = ? LIMIT 1;'); 48 | $sth->execute([intval($id)]); 49 | echo json_encode($sth->fetchAll(PDO::FETCH_CLASS)[0]); 50 | }); 51 | 52 | $app->post('/bookmark', function () use ($db, $app) { 53 | $title = $app->request()->post('title'); 54 | $sth = $db->prepare('INSERT INTO bookmark (url, title) VALUES (?, ?);'); 55 | $sth->execute([ 56 | $url = $app->request()->post('url'), 57 | empty($title) ? getTitleFromUrl($url) : $title, 58 | ]); 59 | saveFavicon($url, $id = $db->lastInsertId()); 60 | 61 | returnResult('add', $sth->rowCount() == 1, $id); 62 | }); 63 | 64 | $app->put('/bookmark/:id', function ($id) use ($db, $app) { 65 | $sth = $db->prepare('UPDATE bookmark SET title = ?, url = ? WHERE id = ?;'); 66 | $sth->execute([ 67 | $app->request()->post('title'), 68 | $url = $app->request()->post('url'), 69 | intval($id), 70 | ]); 71 | saveFavicon($url, $id); 72 | 73 | returnResult('add', $sth->rowCount() == 1, $id); 74 | }); 75 | 76 | $app->delete('/bookmark/:id', function ($id) use ($db) { 77 | $sth = $db->prepare('DELETE FROM bookmark WHERE id = ?;'); 78 | $sth->execute([intval($id)]); 79 | 80 | unlink("../icons/$id.ico"); 81 | 82 | returnResult('delete', $sth->rowCount() == 1, $id); 83 | }); 84 | 85 | $app->get('/install', function () use ($db) { 86 | $db->exec(' CREATE TABLE IF NOT EXISTS bookmark ( 87 | id INTEGER PRIMARY KEY, 88 | title TEXT, 89 | url TEXT UNIQUE);'); 90 | 91 | returnResult('install'); 92 | }); 93 | 94 | $app->run(); -------------------------------------------------------------------------------- /css/app.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding-top: 80px; 3 | } 4 | 5 | .fade-enter-setup{ 6 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 7 | -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 8 | -ms-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 9 | -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 10 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 11 | opacity: 0; 12 | } 13 | 14 | .fade-enter-start{ 15 | opacity: 1; 16 | } -------------------------------------------------------------------------------- /icons/.gitignore: -------------------------------------------------------------------------------- 1 | *.ico -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/AngularJSTutorialApp/98107d2c9d708fe33c547553a2b29b8f55cd88d6/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacodaimon/AngularJSTutorialApp/98107d2c9d708fe33c547553a2b29b8f55cd88d6/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en" ng-app="cacoBookMark"> 3 | <head> 4 | <meta charset="utf-8"> 5 | <title>caco[miniBookMark] 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | angular.module('cacoBookMark.service', ['ngResource']).factory('Rest', function ($resource) { 2 | return $resource('api/bookmark/:id', {}, { 3 | query: {method: 'GET', isArray: true}, 4 | get: {method: 'GET'}, 5 | remove: {method: 'DELETE'}, 6 | edit: {method: 'PUT'}, 7 | add: {method: 'POST'} 8 | }); 9 | }); 10 | 11 | angular.module('cacoBookMark', ['cacoBookMark.service']).config(function ($httpProvider) { 12 | $httpProvider.defaults.transformRequest = function (data) { 13 | var str = []; 14 | for (var p in data) { 15 | data[p] !== undefined && str.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p])); 16 | } 17 | return str.join('&'); 18 | }; 19 | $httpProvider.defaults.headers.put['Content-Type'] = $httpProvider.defaults.headers.post['Content-Type'] = 20 | 'application/x-www-form-urlencoded; charset=UTF-8'; 21 | }); 22 | 23 | angular.module('cacoBookMark').config(['$routeProvider', function ($routeProvider) { 24 | $routeProvider.when('/add', {templateUrl: 'partials/add.html', controller: BookMarkCtrl}) 25 | .when('/edit/:id', {templateUrl: 'partials/edit.html', controller: BookMarkCtrl}) 26 | .when('/', {templateUrl: 'partials/list.html', controller: BookMarkCtrl}); 27 | }]); 28 | 29 | var BookMarkCtrl = function ($scope, $routeParams, $location, Rest) { 30 | if ($routeParams.id) { 31 | $scope.bookmark = Rest.get({id: $routeParams.id}); 32 | } 33 | if ($location.path() === '/') { 34 | $scope.bookmarks = Rest.query(); 35 | } 36 | 37 | $scope.add = function () { 38 | Rest.add({}, $scope.newBookMark, function (data) { 39 | $location.path('/'); 40 | }); 41 | }; 42 | 43 | $scope.delete = function (id) { 44 | if (!confirm('Confirm delete')) { 45 | return; 46 | } 47 | 48 | Rest.remove({id: id}, {}, function (data) { 49 | $location.path('/'); 50 | }); 51 | }; 52 | 53 | $scope.save = function () { 54 | Rest.edit({id: $scope.bookmark.id}, $scope.bookmark, function (data) { 55 | $location.path('/'); 56 | }); 57 | }; 58 | }; -------------------------------------------------------------------------------- /partials/add.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Add a bookmark 4 |
5 | 6 | 7 |
8 | 10 |
11 |
12 |
13 | 14 | 15 |
16 | 18 |
19 |
20 |
21 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /partials/edit.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | Edit a bookmark 5 |
6 | 7 | 8 |
9 | 10 |
11 |
12 |
13 | 14 | 15 |
16 | 18 | {{message}} 19 |
20 |
21 |
22 | 23 |
24 |
25 |
-------------------------------------------------------------------------------- /partials/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 21 | 29 | 30 | 31 |
5 |
6 | 8 |
9 |
15 | 16 | 17 | 18 | {{bookmark.url}} 19 | 20 | 22 |
23 | 24 | 25 | 27 |
28 |
--------------------------------------------------------------------------------