├── .gitignore ├── README.md ├── composer.json ├── composer.lock ├── readme.txt ├── vendor ├── autoload.php ├── composer │ ├── ClassLoader.php │ ├── ClassLoader52.php │ ├── autoload_classmap.php │ ├── autoload_files.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ └── autoload_real_52.php └── lucatume │ └── klein52 │ └── wp_klein.php └── wp-routes.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Composer template 3 | composer.phar 4 | /vendor 5 | # Do not ignore klein_52 main file 6 | !vendor/lucatume/klein52/wp_klein.php 7 | 8 | .idea 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Routes 2 | 3 | Easy WordPress routing plugin with [klein](https://github.com/lucatume/klein52). 4 | 5 | ## Code Example 6 | In a plugin file or a theme `functions.php` file: 7 | 8 | ```php 9 | function my_rest_api(){ 10 | klein_with( '/my-plugin/api', 'src/routes.php' ); 11 | } 12 | 13 | function my_plugin_say_hi( klein_Request $request ){ 14 | if( !empty( $request->name ) ){ 15 | echo "Hi {$request->name}!j"; 16 | } else { 17 | echo "Hi there!"; 18 | } 19 | } 20 | 21 | function my_plugin_operation( klein_Request $request ){ 22 | echo $request->first + $request->second; 23 | } 24 | 25 | function my_plugin_login( klein_Request $request, klein_Response $response ){ 26 | $response->redirect( wp_login_url() ); 27 | } 28 | 29 | add_filter( 'wp-routes/register_routes', 'my_rest_api' ); 30 | ``` 31 | 32 | In the `src/routes.php` file: 33 | 34 | ```php 35 | // just a redirection 36 | klein_respond( 'GET', '/login', 'my_plugin_login' ); 37 | 38 | // API handling 39 | klein_respond( 'GET\', '/my-plugin/api/say-hi', 'my_plugin_say_hi' ); 40 | klein_respond( 'GET\', '/my-plugin/api/say-hi/[a:name]', 'my_plugin_say_hi' ); 41 | klein_respond( 'GET', '/my-plugin/api/add/[i:first]/[i:second]', 'my_plugin_operation' ); 42 | ``` 43 | 44 | While the example above uses PHP 5.2 compatible code route handlers can be defined using closures; see [examples on klein52 library README file](https://github.com/lucatume/klein52). 45 | 46 | ## Thanks Klein 47 | The possibilities above are possible thanks to the [klein.php library](https://github.com/chriso/klein.php) by Chris O'Hara; I've merely ported v. 1.2.0 of the library to be back compatible with PHP 5.2 and used the goodness. 48 | 49 | ## Installation 50 | Download the `.zip` file and put in WordPress plugin folder. 51 | 52 | ## Usage 53 | The plugin packs the [klein52 library](https://github.com/lucatume/klein52) and allow plugins and themes developers to use any of its methods. 54 | An action, `wp-routes/register_routes`, is fired before WordPress parses and handles the current request, see the code example above. 55 | If a route echoes something and it's not a catch-all route, one that matches on "*", then WordPress normal handling flow will be interrupted and the request will be responsibility of the route. 56 | The routing happens when WordPress is fully loaded along with its plugins and the current theme so any WordPress defined function will be available. 57 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lucatume/wp-routes", 3 | "description": "Easy WordPress routing.", 4 | "type": "wordpress-plugin", 5 | "license": "GPL-2.0", 6 | "authors": [ 7 | { 8 | "name": "Luca Tumedei", 9 | "email": "luca@theaveragedev.com" 10 | } 11 | ], 12 | "require": { 13 | "lucatume/klein52": "~1.1.0" 14 | }, 15 | "scripts": { 16 | "post-install-cmd": [ 17 | "xrstf\\Composer52\\Generator::onPostInstallCmd" 18 | ], 19 | "post-update-cmd": [ 20 | "xrstf\\Composer52\\Generator::onPostInstallCmd" 21 | ], 22 | "post-autoload-dump": [ 23 | "xrstf\\Composer52\\Generator::onPostInstallCmd" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "bbbc42a408dc41d6fee7a9b5b89f5e8c", 8 | "packages": [ 9 | { 10 | "name": "lucatume/klein52", 11 | "version": "1.1.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/lucatume/klein52.git", 15 | "reference": "c35ceff82d1907cfac27c51ec01d2efb14e51675" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/lucatume/klein52/zipball/c35ceff82d1907cfac27c51ec01d2efb14e51675", 20 | "reference": "c35ceff82d1907cfac27c51ec01d2efb14e51675", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.2.17", 25 | "xrstf/composer-php52": "1.*" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit-mock-objects-php52": "dev-1.1.0-php52", 29 | "phpunit/phpunit-php52": "dev-3.6.12-php52" 30 | }, 31 | "type": "library", 32 | "autoload": { 33 | "files": [ 34 | "klein_load.php" 35 | ] 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "Chris O'Hara", 44 | "email": "cohara87@gmail.com", 45 | "homepage": "http://chris6f.com/", 46 | "role": "Developer" 47 | }, 48 | { 49 | "name": "Luca Tumedei", 50 | "email": "luca@theaveragedev.com", 51 | "homepage": "http://theaveragedev.com", 52 | "role": "PHP 5.2 adaptation" 53 | } 54 | ], 55 | "description": "A lightning fast router for PHP 5.2 (fork of chriso/klein.php) ", 56 | "homepage": "https://github.com/lucatume/klein.php", 57 | "keywords": [ 58 | "boilerplate", 59 | "router", 60 | "routing", 61 | "sinatra" 62 | ], 63 | "time": "2017-10-03T11:09:29+00:00" 64 | }, 65 | { 66 | "name": "xrstf/composer-php52", 67 | "version": "v1.0.20", 68 | "source": { 69 | "type": "git", 70 | "url": "https://github.com/composer-php52/composer-php52.git", 71 | "reference": "bd41459d5e27df8d33057842b32377c39e97a5a8" 72 | }, 73 | "dist": { 74 | "type": "zip", 75 | "url": "https://api.github.com/repos/composer-php52/composer-php52/zipball/bd41459d5e27df8d33057842b32377c39e97a5a8", 76 | "reference": "bd41459d5e27df8d33057842b32377c39e97a5a8", 77 | "shasum": "" 78 | }, 79 | "type": "library", 80 | "extra": { 81 | "branch-alias": { 82 | "dev-default": "1.x-dev" 83 | } 84 | }, 85 | "autoload": { 86 | "psr-0": { 87 | "xrstf\\Composer52": "lib/" 88 | } 89 | }, 90 | "notification-url": "https://packagist.org/downloads/", 91 | "license": [ 92 | "MIT" 93 | ], 94 | "time": "2016-04-16T21:52:24+00:00" 95 | } 96 | ], 97 | "packages-dev": [], 98 | "aliases": [], 99 | "minimum-stability": "stable", 100 | "stability-flags": [], 101 | "prefer-stable": false, 102 | "prefer-lowest": false, 103 | "platform": [], 104 | "platform-dev": [] 105 | } 106 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WP Routes === 2 | Contributors: lucatume 3 | Tags: routing 4 | Requires at least: 3.5.0 5 | Tested up to: 4.8.2 6 | Requires PHP: 5.2.17 7 | License: GPL-2.0 8 | License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 9 | 10 | Eeasy PHP 5.2 compatible custom routing thanks to klein.php library. 11 | No need to tamper with rewrite rules and Apache .htaccess files to add custom routes. 12 | 13 | 14 | == Description == 15 | In a plugin file or a theme `functions.php` file: 16 | 17 | ```php 18 | function my_rest_api(){ 19 | klein_with( \'/my-plugin/api\', \'src/routes.php\' ); 20 | } 21 | 22 | function my_plugin_say_hi( klein_Request $request ){ 23 | if( !empty( $request->name ) ){ 24 | echo \"Hi {$request->name}!j\"; 25 | } else { 26 | echo \"Hi there!\"; 27 | } 28 | } 29 | 30 | function my_plugin_operation( klein_Request $request ){ 31 | echo $request->first + $request->second; 32 | } 33 | 34 | function my_plugin_login( klein_Request $request, klein_Response $response ){ 35 | $response->redirect( wp_login_url() ); 36 | } 37 | 38 | add_filter( \'wp-routes/register_routes\', \'my_rest_api\' ); 39 | ``` 40 | 41 | In the `src/routes.php` file: 42 | 43 | ```php 44 | // just a redirection 45 | klein_respond( \'GET\', \'/login\', \'my_plugin_login\' ); 46 | 47 | // API handling 48 | klein_respond( \'GET\', \'/my-plugin/api/say-hi\', \'my_plugin_say_hi\' ); 49 | klein_respond( \'GET\', \'/my-plugin/api/say-hi/[a:name]\', \'my_plugin_say_hi\' ); 50 | klein_respond( \'GET\', \'/my-plugin/api/add/[i:first]/[i:second]\', \'my_plugin_operation\' ); 51 | ``` 52 | 53 | While the example above uses PHP 5.2 compatible code route handlers can be defined using closures; see [examples on klein52 library README file](https://github.com/lucatume/klein52). 54 | 55 | == Installation == 56 | Download the plugin zip file and install via the Plugin management screen. 57 | 58 | == Frequently Asked Questions == 59 | *How to prevent \'die\' being called after a matching route was found?* 60 | 61 | You can filter the \'klein_die_handler\' filter to instruct klein52 to simply echo the response output and continue returning \'echo\' or to use a custom callback to handle the output; see [here]() 62 | 63 | == Changelog == 64 | 65 | = 1.0 = 66 | * Initial version. -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 383 | $length = $this->prefixLengthsPsr4[$first][$search]; 384 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /vendor/composer/ClassLoader52.php: -------------------------------------------------------------------------------- 1 | 16 | * - Jordi Boggiano 17 | */ 18 | 19 | class xrstf_Composer52_ClassLoader { 20 | private $prefixes = array(); 21 | private $fallbackDirs = array(); 22 | private $useIncludePath = false; 23 | private $classMap = array(); 24 | private $classMapAuthoratative = false; 25 | private $allowUnderscore = false; 26 | 27 | /** 28 | * @param boolean $flag true to allow class names with a leading underscore, false to disable 29 | */ 30 | public function setAllowUnderscore($flag) { 31 | $this->allowUnderscore = (boolean) $flag; 32 | } 33 | 34 | /** 35 | * @return array 36 | */ 37 | public function getPrefixes() { 38 | return $this->prefixes; 39 | } 40 | 41 | /** 42 | * Turns off searching the prefix and fallback directories for classes 43 | * that have not been registered with the class map. 44 | * 45 | * @param bool $classMapAuthoratative 46 | */ 47 | public function setClassMapAuthoritative($classMapAuthoratative) { 48 | $this->classMapAuthoratative = $classMapAuthoratative; 49 | } 50 | 51 | /** 52 | * Should class lookup fail if not found in the current class map? 53 | * 54 | * @return bool 55 | */ 56 | public function getClassMapAuthoratative() { 57 | return $this->classMapAuthoratative; 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function getFallbackDirs() { 64 | return $this->fallbackDirs; 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function getClassMap() { 71 | return $this->classMap; 72 | } 73 | 74 | /** 75 | * @param array $classMap class to filename map 76 | */ 77 | public function addClassMap(array $classMap) { 78 | if ($this->classMap) { 79 | $this->classMap = array_merge($this->classMap, $classMap); 80 | } 81 | else { 82 | $this->classMap = $classMap; 83 | } 84 | } 85 | 86 | /** 87 | * Registers a set of classes, merging with any others previously set. 88 | * 89 | * @param string $prefix the classes prefix 90 | * @param array|string $paths the location(s) of the classes 91 | * @param bool $prepend prepend the location(s) 92 | */ 93 | public function add($prefix, $paths, $prepend = false) { 94 | if (!$prefix) { 95 | if ($prepend) { 96 | $this->fallbackDirs = array_merge( 97 | (array) $paths, 98 | $this->fallbackDirs 99 | ); 100 | } 101 | else { 102 | $this->fallbackDirs = array_merge( 103 | $this->fallbackDirs, 104 | (array) $paths 105 | ); 106 | } 107 | 108 | return; 109 | } 110 | 111 | if (!isset($this->prefixes[$prefix])) { 112 | $this->prefixes[$prefix] = (array) $paths; 113 | return; 114 | } 115 | 116 | if ($prepend) { 117 | $this->prefixes[$prefix] = array_merge( 118 | (array) $paths, 119 | $this->prefixes[$prefix] 120 | ); 121 | } 122 | else { 123 | $this->prefixes[$prefix] = array_merge( 124 | $this->prefixes[$prefix], 125 | (array) $paths 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * Registers a set of classes, replacing any others previously set. 132 | * 133 | * @param string $prefix the classes prefix 134 | * @param array|string $paths the location(s) of the classes 135 | */ 136 | public function set($prefix, $paths) { 137 | if (!$prefix) { 138 | $this->fallbackDirs = (array) $paths; 139 | return; 140 | } 141 | 142 | $this->prefixes[$prefix] = (array) $paths; 143 | } 144 | 145 | /** 146 | * Turns on searching the include path for class files. 147 | * 148 | * @param bool $useIncludePath 149 | */ 150 | public function setUseIncludePath($useIncludePath) { 151 | $this->useIncludePath = $useIncludePath; 152 | } 153 | 154 | /** 155 | * Can be used to check if the autoloader uses the include path to check 156 | * for classes. 157 | * 158 | * @return bool 159 | */ 160 | public function getUseIncludePath() { 161 | return $this->useIncludePath; 162 | } 163 | 164 | /** 165 | * Registers this instance as an autoloader. 166 | */ 167 | public function register() { 168 | spl_autoload_register(array($this, 'loadClass'), true); 169 | } 170 | 171 | /** 172 | * Unregisters this instance as an autoloader. 173 | */ 174 | public function unregister() { 175 | spl_autoload_unregister(array($this, 'loadClass')); 176 | } 177 | 178 | /** 179 | * Loads the given class or interface. 180 | * 181 | * @param string $class the name of the class 182 | * @return bool|null true, if loaded 183 | */ 184 | public function loadClass($class) { 185 | if ($file = $this->findFile($class)) { 186 | include $file; 187 | return true; 188 | } 189 | } 190 | 191 | /** 192 | * Finds the path to the file where the class is defined. 193 | * 194 | * @param string $class the name of the class 195 | * @return string|null the path, if found 196 | */ 197 | public function findFile($class) { 198 | if ('\\' === $class[0]) { 199 | $class = substr($class, 1); 200 | } 201 | 202 | if (isset($this->classMap[$class])) { 203 | return $this->classMap[$class]; 204 | } 205 | elseif ($this->classMapAuthoratative) { 206 | return false; 207 | } 208 | 209 | $classPath = $this->getClassPath($class); 210 | 211 | foreach ($this->prefixes as $prefix => $dirs) { 212 | if (0 === strpos($class, $prefix)) { 213 | foreach ($dirs as $dir) { 214 | if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { 215 | return $dir.DIRECTORY_SEPARATOR.$classPath; 216 | } 217 | } 218 | } 219 | } 220 | 221 | foreach ($this->fallbackDirs as $dir) { 222 | if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) { 223 | return $dir.DIRECTORY_SEPARATOR.$classPath; 224 | } 225 | } 226 | 227 | if ($this->useIncludePath && $file = self::resolveIncludePath($classPath)) { 228 | return $file; 229 | } 230 | 231 | return $this->classMap[$class] = false; 232 | } 233 | 234 | private function getClassPath($class) { 235 | if (false !== $pos = strrpos($class, '\\')) { 236 | // namespaced class name 237 | $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)).DIRECTORY_SEPARATOR; 238 | $className = substr($class, $pos + 1); 239 | } 240 | else { 241 | // PEAR-like class name 242 | $classPath = null; 243 | $className = $class; 244 | } 245 | 246 | $className = str_replace('_', DIRECTORY_SEPARATOR, $className); 247 | 248 | // restore the prefix 249 | if ($this->allowUnderscore && DIRECTORY_SEPARATOR === $className[0]) { 250 | $className[0] = '_'; 251 | } 252 | 253 | $classPath .= $className.'.php'; 254 | 255 | return $classPath; 256 | } 257 | 258 | public static function resolveIncludePath($classPath) { 259 | $paths = explode(PATH_SEPARATOR, get_include_path()); 260 | 261 | foreach ($paths as $path) { 262 | $path = rtrim($path, '/\\'); 263 | 264 | if ($file = file_exists($path.DIRECTORY_SEPARATOR.$file)) { 265 | return $file; 266 | } 267 | } 268 | 269 | return false; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/lucatume/klein52/klein_load.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/xrstf/composer-php52/lib'), 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticIniteecda31895e552d61deb37f249eca134::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | if ($useStaticLoader) { 51 | $includeFiles = Composer\Autoload\ComposerStaticIniteecda31895e552d61deb37f249eca134::$files; 52 | } else { 53 | $includeFiles = require __DIR__ . '/autoload_files.php'; 54 | } 55 | foreach ($includeFiles as $fileIdentifier => $file) { 56 | composerRequireeecda31895e552d61deb37f249eca134($fileIdentifier, $file); 57 | } 58 | 59 | return $loader; 60 | } 61 | } 62 | 63 | function composerRequireeecda31895e552d61deb37f249eca134($fileIdentifier, $file) 64 | { 65 | if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { 66 | require $file; 67 | 68 | $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real_52.php: -------------------------------------------------------------------------------- 1 | $path) { 32 | $loader->add($namespace, $path); 33 | } 34 | 35 | $classMap = require $dir.'/autoload_classmap.php'; 36 | if ($classMap) { 37 | $loader->addClassMap($classMap); 38 | } 39 | 40 | $loader->register(true); 41 | 42 | require $vendorDir . '/lucatume/klein52/klein_load.php'; 43 | 44 | return $loader; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/lucatume/klein52/wp_klein.php: -------------------------------------------------------------------------------- 1 | (MIT License) 4 | * http://github.com/chriso/klein.php 5 | * Modified to work in PHP 5.2 by Luca Tumedei 6 | * https://github.com/lucatume/klein52 7 | */ 8 | 9 | $__klein_routes = array(); 10 | $__klein_namespace = null; 11 | 12 | /** 13 | * Registers a route as one handled by klein. 14 | * 15 | * @since 1.0.3 16 | * 17 | * @param string $method A supported HTTP method, e.g. `GET`, `POST`, `DELETE` and so on. 18 | * @param string $route The route matching pattern. 19 | * @param callable $callback The callback that should be used to handle the request. 20 | * 21 | * @return mixed|null 22 | */ 23 | function klein_respond( $method, $route = '*', $callback = null ) { 24 | global $__klein_routes, $__klein_namespace; 25 | 26 | $args = func_get_args(); 27 | $callback = array_pop( $args ); 28 | $route = array_pop( $args ); 29 | $method = array_pop( $args ); 30 | 31 | if ( null === $route ) { 32 | $route = '*'; 33 | } 34 | 35 | // only consider a request to be matched when not using matchall 36 | $count_match = ( $route !== '*' ); 37 | 38 | if ( $__klein_namespace && $route[0] === '@' || ( $route[0] === '!' && $route[1] === '@' ) ) { 39 | if ( $route[0] === '!' ) { 40 | $negate = true; 41 | $route = substr( $route, 2 ); 42 | } else { 43 | $negate = false; 44 | $route = substr( $route, 1 ); 45 | } 46 | 47 | // regex anchored to front of string 48 | if ( $route[0] === '^' ) { 49 | $route = substr( $route, 1 ); 50 | } else { 51 | $route = '.*' . $route; 52 | } 53 | 54 | if ( $negate ) { 55 | $route = '@^' . $__klein_namespace . '(?!' . $route . ')'; 56 | } else { 57 | $route = '@^' . $__klein_namespace . $route; 58 | } 59 | } // empty route with namespace is a match-all 60 | elseif ( $__klein_namespace && ( '*' === $route ) ) { 61 | $route = '@^' . $__klein_namespace . '(/|$)'; 62 | } else { 63 | $route = $__klein_namespace . $route; 64 | } 65 | 66 | $__klein_routes[] = array( $method, $route, $callback, $count_match ); 67 | 68 | return $callback; 69 | } 70 | 71 | /** 72 | * Registers a group of routes with a common root namespace. 73 | * 74 | * @since 1.0.3 75 | * 76 | * @param string $namespace The namespace that for a group of routes, e.g. `/users` or `/api/admin` 77 | * @param string|callable $routes Either the path to a file defining a group of routes or a callable 78 | * registering a group of routes. 79 | */ 80 | function klein_with( $namespace, $routes ) { 81 | global $__klein_namespace; 82 | $previous = $__klein_namespace; 83 | $__klein_namespace .= $namespace; 84 | if ( is_callable( $routes ) ) { 85 | $routes(); 86 | } else { 87 | require $routes; 88 | } 89 | $__klein_namespace = $previous; 90 | } 91 | 92 | /** 93 | * Starts the session. 94 | * 95 | * @since 1.0.3 96 | */ 97 | function klein_start_session() { 98 | if ( session_id() === '' ) { 99 | session_start(); 100 | } 101 | } 102 | 103 | /** 104 | * Dispatches a request to the appropriate route. 105 | * 106 | * @since 1.0.3 107 | * 108 | * @param null|string $uri The request URI 109 | * @param null|string $req_method The request method, e.g. `GET`, `POST` or `DELETE` 110 | * @param array|null $params An array of parameters for the request 111 | * @param bool $capture Whether the matching route response output should be printed or not 112 | * @param bool $passthru If `capture` is set to `true` when no route is matched the function will 113 | * return `false` 114 | * 115 | * @return bool|string `false` if the route was not matched and `capture` and `passthru` were `true`; the matched 116 | * route output otherwise. 117 | */ 118 | function klein_dispatch( $uri = null, $req_method = null, array $params = null, $capture = false, $passthru = false ) { 119 | global $__klein_routes; 120 | 121 | // Pass $request, $response, and a blank object for sharing scope through each callback 122 | $request = new klein_Request; 123 | $response = new klein_Response; 124 | $app = new klein_App; 125 | 126 | // Get/parse the request URI and method 127 | if ( null === $uri ) { 128 | $uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/'; 129 | } 130 | if ( false !== strpos( $uri, '?' ) ) { 131 | $uri = str_replace( stristr( $uri, "?" ), "", $uri ); 132 | } 133 | if ( null === $req_method ) { 134 | $req_method = isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 135 | 136 | // For legacy servers, override the HTTP method with the X-HTTP-Method-Override 137 | // header or _method parameter 138 | if ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { 139 | $req_method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; 140 | } elseif ( isset( $_REQUEST['_method'] ) ) { 141 | $req_method = $_REQUEST['_method']; 142 | } 143 | } 144 | 145 | // Force request_order to be GP 146 | // http://www.mail-archive.com/internals@lists.php.net/msg33119.html 147 | $_REQUEST = array_merge( $_GET, $_POST ); 148 | if ( null !== $params ) { 149 | $_REQUEST = array_merge( $_REQUEST, $params ); 150 | } 151 | 152 | $matched = 0; 153 | $methods_matched = array(); 154 | 155 | ob_start(); 156 | 157 | foreach ( $__klein_routes as $handler ) { 158 | list( $method, $_route, $callback, $count_match ) = $handler; 159 | 160 | $method_match = null; 161 | // Was a method specified? If so, check it against the current request method 162 | if ( is_array( $method ) ) { 163 | foreach ( $method as $test ) { 164 | if ( strcasecmp( $req_method, $test ) === 0 ) { 165 | $method_match = true; 166 | } 167 | } 168 | if ( null === $method_match ) { 169 | $method_match = false; 170 | } 171 | } elseif ( null !== $method && strcasecmp( $req_method, $method ) !== 0 ) { 172 | $method_match = false; 173 | } elseif ( null !== $method && strcasecmp( $req_method, $method ) === 0 ) { 174 | $method_match = true; 175 | } 176 | 177 | // If the method was matched or if it wasn't even passed (in the route callback) 178 | $possible_match = is_null( $method_match ) || $method_match; 179 | 180 | // ! is used to negate a match 181 | if ( isset( $_route[0] ) && $_route[0] === '!' ) { 182 | $negate = true; 183 | $i = 1; 184 | } else { 185 | $negate = false; 186 | $i = 0; 187 | } 188 | 189 | // Check for a wildcard (match all) 190 | if ( $_route === '*' ) { 191 | $match = true; 192 | 193 | // Easily handle 404's 194 | } elseif ( $_route === '404' && ! $matched && count( $methods_matched ) <= 0 ) { 195 | try { 196 | call_user_func( $callback, $request, $response, $app, $matched, $methods_matched ); 197 | } 198 | catch ( Exception $e ) { 199 | $response->error( $e ); 200 | } 201 | 202 | ++ $matched; 203 | continue; 204 | 205 | // Easily handle 405's 206 | } elseif ( $_route === '405' && ! $matched && count( $methods_matched ) > 0 ) { 207 | try { 208 | call_user_func( $callback, $request, $response, $app, $matched, $methods_matched ); 209 | } 210 | catch ( Exception $e ) { 211 | $response->error( $e ); 212 | } 213 | 214 | ++ $matched; 215 | continue; 216 | 217 | // @ is used to specify custom regex 218 | } elseif ( isset( $_route[ $i ] ) && $_route[ $i ] === '@' ) { 219 | $match = preg_match( '`' . substr( $_route, $i + 1 ) . '`', $uri, $params ); 220 | 221 | // Compiling and matching regular expressions is relatively 222 | // expensive, so try and match by a substring first 223 | } else { 224 | $route = null; 225 | $regex = false; 226 | $j = 0; 227 | $n = isset( $_route[ $i ] ) ? $_route[ $i ] : null; 228 | 229 | // Find the longest non-regex substring and match it against the URI 230 | while ( true ) { 231 | if ( ! isset( $_route[ $i ] ) ) { 232 | break; 233 | } elseif ( false === $regex ) { 234 | $c = $n; 235 | $regex = $c === '[' || $c === '(' || $c === '.'; 236 | if ( false === $regex && false !== isset( $_route[ $i + 1 ] ) ) { 237 | $n = $_route[ $i + 1 ]; 238 | $regex = $n === '?' || $n === '+' || $n === '*' || $n === '{'; 239 | } 240 | if ( false === $regex && $c !== '/' && ( ! isset( $uri[ $j ] ) || $c !== $uri[ $j ] ) ) { 241 | continue 2; 242 | } 243 | $j ++; 244 | } 245 | $route .= $_route[ $i ++ ]; 246 | } 247 | 248 | // Check if there's a cached regex string 249 | $regex = wp_cache_get( "route:$route", 'klein' ); 250 | if ( false === $regex ) { 251 | $regex = klein_compile_route( $route ); 252 | wp_cache_set( "route:$route", $regex,'klein' ); 253 | } 254 | 255 | $match = preg_match( $regex, $uri, $params ); 256 | } 257 | 258 | if ( isset( $match ) && $match ^ $negate ) { 259 | // Keep track of possibly matched methods 260 | $methods_matched = array_merge( $methods_matched, (array) $method ); 261 | $methods_matched = array_filter( $methods_matched ); 262 | $methods_matched = array_unique( $methods_matched ); 263 | 264 | if ( $possible_match ) { 265 | if ( null !== $params ) { 266 | $_REQUEST = array_merge( $_REQUEST, $params ); 267 | } 268 | try { 269 | call_user_func( $callback, $request, $response, $app, $matched, $methods_matched ); 270 | } 271 | catch ( Exception $e ) { 272 | $response->error( $e ); 273 | } 274 | if ( $_route !== '*' ) { 275 | $count_match && ++ $matched; 276 | } 277 | } 278 | } 279 | } 280 | 281 | if ( ! $matched && count( $methods_matched ) > 0 ) { 282 | $response->code( 405 ); 283 | $response->header( 'Allow', implode( ', ', $methods_matched ) ); 284 | } elseif ( ! $matched ) { 285 | $response->code( 404 ); 286 | } 287 | 288 | if ( $capture ) { 289 | if ( $passthru && $matched == 0 ) { 290 | ob_end_clean(); 291 | 292 | return false; 293 | } 294 | 295 | return ob_get_clean(); 296 | } elseif ( $response->chunked ) { 297 | $response->chunk(); 298 | } else { 299 | ob_end_flush(); 300 | } 301 | } 302 | 303 | /** 304 | * Dispatches the request to the first available matching routes and dies, or continues the script execution. 305 | * 306 | * @since 1.0.3 307 | * 308 | * @param null|string $uri The request URI 309 | * @param null|string $req_method The request method, e.g. `GET`, `POST` or `DELETE` 310 | * @param array|null $params An array of parameters for the request 311 | * 312 | * @return string|void|mixed|bool The route output if one was matched and the `dieCallback` is set to `true`; 313 | * otherwise the route output will be output before `die`ing; `false` if no matching route 314 | * was found. 315 | */ 316 | function klein_dispatch_or_continue( $uri = null, $req_method = null, array $params = null ) { 317 | $found = klein_dispatch( $uri, $req_method, $params, true, true ); 318 | if ( $found ) { 319 | $dieCallback = null; 320 | 321 | if ( function_exists( 'apply_filters' ) ) { 322 | /** 323 | * Filters the die (output) handler to use when a route is matched. 324 | * 325 | * @since 1.0.3 326 | * 327 | * Use the special value "echo "To just echo the result and return, e.g.: 328 | * 329 | * add_filter('klein_die_handler', function(){ 330 | * return 'echo'; 331 | * }); 332 | * 333 | * @params callabe $handler The function that will be called to output the request; default `die` 334 | */ 335 | $dieCallback = apply_filters( 'klein_die_handler', null ); 336 | } 337 | 338 | switch ( $dieCallback ) { 339 | case 'echo': 340 | echo $found; 341 | 342 | return; 343 | case null: 344 | die ( $found ); 345 | default: 346 | return $dieCallback( $found ); 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * Compiles a route from the format used by klein to a regular expression. 353 | * 354 | * @since 1.0.3 355 | * 356 | * @param string $route The route in the format supported by klein. 357 | * 358 | * @return string The regular expression corresponding to the input route. 359 | */ 360 | function klein_compile_route( $route ) { 361 | if ( preg_match_all( '`(/|\.|)\[([^:\]]*+)(?::([^:\]]*+))?\](\?|)`', $route, $matches, PREG_SET_ORDER ) ) { 362 | $match_types = array( 363 | 'i' => '[0-9]++', 364 | 'a' => '[0-9A-Za-z]++', 365 | 'h' => '[0-9A-Fa-f]++', 366 | '*' => '.+?', 367 | '**' => '.++', 368 | '' => '[^/]+?', 369 | ); 370 | foreach ( $matches as $match ) { 371 | list( $block, $pre, $type, $param, $optional ) = $match; 372 | 373 | if ( isset( $match_types[ $type ] ) ) { 374 | $type = $match_types[ $type ]; 375 | } 376 | if ( $pre === '.' ) { 377 | $pre = '\.'; 378 | } 379 | // Older versions of PCRE require the 'P' in (?P) 380 | $pattern = '(?:' . ( $pre !== '' ? $pre : null ) . '(' . ( $param !== '' ? "?P<$param>" : null ) . $type . '))' . ( $optional !== '' ? '?' : null ); 381 | 382 | $route = str_replace( $block, $pattern, $route ); 383 | } 384 | } 385 | 386 | return "`^$route$`"; 387 | } 388 | 389 | /** 390 | * Class klein_Request 391 | * 392 | * @since 1.0.3 393 | */ 394 | class klein_Request { 395 | 396 | public static $_headers = null; 397 | 398 | // HTTP headers helper 399 | protected $_id = null; 400 | protected $_body = null; 401 | 402 | // Returns all parameters (GET, POST, named) that match the mask 403 | public function params( $mask = null ) { 404 | $params = $_REQUEST; 405 | if ( null !== $mask ) { 406 | if ( ! is_array( $mask ) ) { 407 | $mask = func_get_args(); 408 | } 409 | $params = array_intersect_key( $params, array_flip( $mask ) ); 410 | // Make sure each key in $mask has at least a null value 411 | foreach ( $mask as $key ) { 412 | if ( ! isset( $params[ $key ] ) ) { 413 | $params[ $key ] = null; 414 | } 415 | } 416 | } 417 | 418 | return $params; 419 | } 420 | 421 | // Return a request parameter, or $default if it doesn't exist 422 | 423 | public function __isset( $param ) { 424 | return isset( $_REQUEST[ $param ] ); 425 | } 426 | 427 | public function __get( $param ) { 428 | return isset( $_REQUEST[ $param ] ) ? $_REQUEST[ $param ] : null; 429 | } 430 | 431 | public function __set( $param, $value ) { 432 | $_REQUEST[ $param ] = $value; 433 | } 434 | 435 | public function __unset( $param ) { 436 | unset( $_REQUEST[ $param ] ); 437 | } 438 | 439 | public function isSecure( $required = false ) { 440 | $secure = isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS']; 441 | if ( ! $secure && $required ) { 442 | $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; 443 | self::$_headers->header( 'Location: ' . $url ); 444 | } 445 | 446 | return $secure; 447 | } 448 | 449 | // Is the request secure? If $required then redirect to the secure version of the URL 450 | 451 | public function header( $key, $default = null ) { 452 | $key = 'HTTP_' . strtoupper( str_replace( '-', '_', $key ) ); 453 | 454 | return isset( $_SERVER[ $key ] ) ? $_SERVER[ $key ] : $default; 455 | } 456 | 457 | // Gets a request header 458 | 459 | public function cookie( $key, $default = null ) { 460 | return isset( $_COOKIE[ $key ] ) ? $_COOKIE[ $key ] : $default; 461 | } 462 | 463 | // Gets a request cookie 464 | 465 | public function method( $is = null ) { 466 | $method = isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 467 | if ( null !== $is ) { 468 | return strcasecmp( $method, $is ) === 0; 469 | } 470 | 471 | return $method; 472 | } 473 | 474 | // Gets the request method, or checks it against $is - e.g. method('post') => true 475 | 476 | public function validate( $param, $err = null ) { 477 | return new klein_Validator( $this->param( $param ), $err ); 478 | } 479 | 480 | // Start a validator chain for the specified parameter 481 | 482 | public function param( $key, $default = null ) { 483 | return isset( $_REQUEST[ $key ] ) && $_REQUEST[ $key ] !== '' ? $_REQUEST[ $key ] : $default; 484 | } 485 | 486 | // Gets a unique ID for the request 487 | 488 | public function id() { 489 | if ( null === $this->_id ) { 490 | $this->_id = sha1( mt_rand() . microtime( true ) . mt_rand() ); 491 | } 492 | 493 | return $this->_id; 494 | } 495 | 496 | // Gets a session variable associated with the request 497 | public function session( $key, $default = null ) { 498 | klein_start_session(); 499 | 500 | return isset( $_SESSION[ $key ] ) ? $_SESSION[ $key ] : $default; 501 | } 502 | 503 | // Gets the request IP address 504 | public function ip() { 505 | return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null; 506 | } 507 | 508 | // Gets the request user agent 509 | public function userAgent() { 510 | return isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null; 511 | } 512 | 513 | // Gets the request URI 514 | public function uri() { 515 | return isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/'; 516 | } 517 | 518 | // Gets the request body 519 | public function body() { 520 | if ( null === $this->_body ) { 521 | $this->_body = @file_get_contents( 'php://input' ); 522 | } 523 | 524 | return $this->_body; 525 | } 526 | } 527 | 528 | /** 529 | * Class klein_Response 530 | * 531 | * @since 1.0.3 532 | */ 533 | class klein_Response extends StdClass { 534 | 535 | public static $_headers = null; 536 | public $chunked = false; 537 | protected $_errorCallbacks = array(); 538 | protected $_layout = null; 539 | protected $_view = null; 540 | protected $_code = 200; 541 | 542 | // Enable response chunking. See: http://bit.ly/hg3gHb 543 | 544 | public function cookie( $key, $value = '', $expiry = null, $path = '/', $domain = null, $secure = false, $httponly = false ) { 545 | if ( null === $expiry ) { 546 | $expiry = time() + ( 3600 * 24 * 30 ); 547 | } 548 | 549 | return setcookie( $key, $value, $expiry, $path, $domain, $secure, $httponly ); 550 | } 551 | 552 | // Sets a response header 553 | 554 | public function file( $path, $filename = null, $mimetype = null ) { 555 | $this->discard(); 556 | $this->noCache(); 557 | set_time_limit( 1200 ); 558 | if ( null === $filename ) { 559 | $filename = basename( $path ); 560 | } 561 | if ( null === $mimetype ) { 562 | $mimetype = finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $path ); 563 | } 564 | $this->header( 'Content-type: ' . $mimetype ); 565 | $this->header( 'Content-length: ' . filesize( $path ) ); 566 | $this->header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); 567 | readfile( $path ); 568 | } 569 | 570 | // Sets a response cookie 571 | 572 | public function discard( $restart_buffer = false ) { 573 | $cleaned = ob_end_clean(); 574 | 575 | if ( $restart_buffer ) { 576 | ob_start(); 577 | } 578 | 579 | return $cleaned; 580 | } 581 | 582 | // Stores a flash message of $type 583 | 584 | public function noCache() { 585 | $this->header( "Pragma: no-cache" ); 586 | $this->header( 'Cache-Control: no-store, no-cache' ); 587 | } 588 | 589 | // Support basic markdown syntax 590 | 591 | public function header( $key, $value = null ) { 592 | self::$_headers->header( $key, $value ); 593 | } 594 | 595 | // Tell the browser not to cache the response 596 | 597 | public function json( $object, $jsonp_prefix = null ) { 598 | $this->discard( true ); 599 | $this->noCache(); 600 | set_time_limit( 1200 ); 601 | $json = json_encode( $object ); 602 | if ( null !== $jsonp_prefix ) { 603 | $this->header( 'Content-Type: text/javascript' ); // should ideally be application/json-p once adopted 604 | echo "$jsonp_prefix($json);"; 605 | } else { 606 | $this->header( 'Content-Type: application/json' ); 607 | echo $json; 608 | } 609 | } 610 | 611 | // Sends a file 612 | 613 | public function back() { 614 | if ( isset( $_SERVER['HTTP_REFERER'] ) ) { 615 | $this->redirect( $_SERVER['HTTP_REFERER'] ); 616 | } 617 | $this->refresh(); 618 | } 619 | 620 | // Sends an object as json or jsonp by providing the padding prefix 621 | 622 | public function redirect( $url, $code = 302, $exit_after_redirect = true ) { 623 | $this->code( $code ); 624 | $this->header( "Location: $url" ); 625 | if ( $exit_after_redirect ) { 626 | exit; 627 | } 628 | } 629 | 630 | // Sends a HTTP response code 631 | 632 | public function code( $code = null ) { 633 | if ( null !== $code ) { 634 | $this->_code = $code; 635 | 636 | // Do we have the PHP 5.4 "http_response_code" function? 637 | if ( function_exists( 'http_response_code' ) ) { 638 | // Have PHP automatically create our HTTP Status header from our code 639 | http_response_code( $code ); 640 | } else { 641 | // Manually create the HTTP Status header 642 | $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; 643 | $this->header( "$protocol $code" ); 644 | } 645 | } 646 | 647 | return $this->_code; 648 | } 649 | 650 | // Redirects the request to another URL 651 | 652 | public function refresh() { 653 | $this->redirect( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/' ); 654 | } 655 | 656 | // Redirects the request to the current URL 657 | 658 | public function query( $key, $value = null ) { 659 | $query = array(); 660 | if ( isset( $_SERVER['QUERY_STRING'] ) ) { 661 | parse_str( $_SERVER['QUERY_STRING'], $query ); 662 | } 663 | if ( is_array( $key ) ) { 664 | $query = array_merge( $query, $key ); 665 | } else { 666 | $query[ $key ] = $value; 667 | } 668 | 669 | $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/'; 670 | if ( strpos( $request_uri, '?' ) !== false ) { 671 | $request_uri = str_replace( stristr( $request_uri, "?" ), "", $request_uri ); 672 | } 673 | 674 | return $request_uri . ( ! empty( $query ) ? '?' . http_build_query( $query ) : null ); 675 | } 676 | 677 | // Redirects the request back to the referrer 678 | 679 | public function layout( $layout ) { 680 | $this->_layout = $layout; 681 | } 682 | 683 | // Sets response properties/helpers 684 | 685 | public function partial( $view, array $data = array() ) { 686 | $layout = $this->_layout; 687 | $this->_layout = null; 688 | $this->render( $view, $data ); 689 | $this->_layout = $layout; 690 | } 691 | 692 | // Adds to or modifies the current query string 693 | 694 | public function render( $view, array $data = array() ) { 695 | $original_view = $this->_view; 696 | 697 | if ( ! empty( $data ) ) { 698 | $this->set( $data ); 699 | } 700 | $this->_view = $view; 701 | if ( null === $this->_layout ) { 702 | $this->yieldView(); 703 | } else { 704 | require $this->_layout; 705 | } 706 | if ( false !== $this->chunked ) { 707 | $this->chunk(); 708 | } 709 | 710 | // restore state for parent render() 711 | $this->_view = $original_view; 712 | } 713 | 714 | // Set the view layout 715 | 716 | public function set( $key, $value = null ) { 717 | if ( ! is_array( $key ) ) { 718 | return $this->$key = $value; 719 | } 720 | foreach ( $key as $k => $value ) { 721 | $this->$k = $value; 722 | } 723 | } 724 | 725 | // Renders the current view 726 | 727 | public function yieldView() { 728 | require $this->_view; 729 | } 730 | 731 | // Renders a view + optional layout 732 | 733 | public function chunk( $str = null ) { 734 | if ( false === $this->chunked ) { 735 | $this->chunked = true; 736 | self::$_headers->header( 'Transfer-encoding: chunked' ); 737 | flush(); 738 | } 739 | if ( null !== $str ) { 740 | printf( "%x\r\n", strlen( $str ) ); 741 | echo "$str\r\n"; 742 | flush(); 743 | } elseif ( ( $ob_length = ob_get_length() ) > 0 ) { 744 | printf( "%x\r\n", $ob_length ); 745 | ob_flush(); 746 | echo "\r\n"; 747 | flush(); 748 | } 749 | } 750 | 751 | // Renders a view without a layout 752 | 753 | public function session( $key, $value = null ) { 754 | klein_start_session(); 755 | 756 | return $_SESSION[ $key ] = $value; 757 | } 758 | 759 | // Sets a session variable 760 | 761 | public function onError( $callback ) { 762 | $this->_errorCallbacks[] = $callback; 763 | } 764 | 765 | // Adds an error callback to the stack of error handlers 766 | 767 | public function error( Exception $err ) { 768 | $type = get_class( $err ); 769 | $msg = $err->getMessage(); 770 | 771 | if ( count( $this->_errorCallbacks ) > 0 ) { 772 | foreach ( array_reverse( $this->_errorCallbacks ) as $callback ) { 773 | if ( is_callable( $callback ) ) { 774 | if ( $callback( $this, $msg, $type, $err ) ) { 775 | return; 776 | } 777 | } else { 778 | $this->flash( $err ); 779 | $this->redirect( $callback ); 780 | } 781 | } 782 | } else { 783 | $this->code( 500 ); 784 | throw new ErrorException( $err ); 785 | } 786 | } 787 | 788 | // Routes an exception through the error callbacks 789 | 790 | public function flash( $msg, $type = 'info', $params = null ) { 791 | klein_start_session(); 792 | if ( is_array( $type ) ) { 793 | $params = $type; 794 | $type = 'info'; 795 | } 796 | if ( ! isset( $_SESSION['__flashes'] ) ) { 797 | $_SESSION['__flashes'] = array( $type => array() ); 798 | } elseif ( ! isset( $_SESSION['__flashes'][ $type ] ) ) { 799 | $_SESSION['__flashes'][ $type ] = array(); 800 | } 801 | $_SESSION['__flashes'][ $type ][] = $this->markdown( $msg, $params ); 802 | } 803 | 804 | // Returns an escaped request paramater 805 | 806 | public function markdown( $str, $args = null ) { 807 | $args = func_get_args(); 808 | $md = array( 809 | '/\[([^\]]++)\]\(([^\)]++)\)/' => '$1', 810 | '/\*\*([^\*]++)\*\*/' => '$1', 811 | '/\*([^\*]++)\*/' => '$1', 812 | ); 813 | $str = array_shift( $args ); 814 | if ( is_array( $args[0] ) ) { 815 | $args = $args[0]; 816 | } 817 | foreach ( $args as &$arg ) { 818 | $arg = htmlentities( $arg, ENT_QUOTES ); 819 | } 820 | 821 | return vsprintf( preg_replace( array_keys( $md ), $md, $str ), $args ); 822 | } 823 | 824 | // Returns and clears all flashes of optional $type 825 | 826 | public function param( $param, $default = null ) { 827 | return isset( $_REQUEST[ $param ] ) ? htmlentities( $_REQUEST[ $param ], ENT_QUOTES ) : $default; 828 | } 829 | 830 | // Escapes a string 831 | 832 | public function flashes( $type = null ) { 833 | klein_start_session(); 834 | if ( ! isset( $_SESSION['__flashes'] ) ) { 835 | return array(); 836 | } 837 | if ( null === $type ) { 838 | $flashes = $_SESSION['__flashes']; 839 | unset( $_SESSION['__flashes'] ); 840 | } elseif ( null !== $type ) { 841 | $flashes = array(); 842 | if ( isset( $_SESSION['__flashes'][ $type ] ) ) { 843 | $flashes = $_SESSION['__flashes'][ $type ]; 844 | unset( $_SESSION['__flashes'][ $type ] ); 845 | } 846 | } 847 | 848 | return $flashes; 849 | } 850 | 851 | // Discards the current output buffer and restarts it if passed a true boolean 852 | 853 | public function escape( $str ) { 854 | return htmlentities( $str, ENT_QUOTES ); 855 | } 856 | 857 | // Flushes the current output buffer 858 | 859 | public function flush() { 860 | ob_end_flush(); 861 | } 862 | 863 | // Return the current output buffer as a string 864 | public function buffer() { 865 | return ob_get_contents(); 866 | } 867 | 868 | // Dump a variable 869 | public function dump( $obj ) { 870 | if ( is_array( $obj ) || is_object( $obj ) ) { 871 | $obj = print_r( $obj, true ); 872 | } 873 | echo '
' . htmlentities( $obj, ENT_QUOTES ) . "

\n"; 874 | } 875 | 876 | // Allow callbacks to be assigned as properties and called like normal methods 877 | public function __call( $method, $args ) { 878 | if ( ! isset( $this->$method ) || ! is_callable( $this->$method ) ) { 879 | throw new ErrorException( "Unknown method $method()" ); 880 | } 881 | $callback = $this->$method; 882 | switch ( count( $args ) ) { 883 | case 1: 884 | return $callback( $args[0] ); 885 | case 2: 886 | return $callback( $args[0], $args[1] ); 887 | case 3: 888 | return $callback( $args[0], $args[1], $args[2] ); 889 | case 4: 890 | return $callback( $args[0], $args[1], $args[2], $args[3] ); 891 | default: 892 | return call_user_func_array( $callback, $args ); 893 | } 894 | } 895 | } 896 | 897 | /** 898 | * Adds a custom route validation callback. 899 | * 900 | * Validators registered with this method will be available using the `is` and `not` chained 901 | * on the `$request->validate()` method; see the README.md file. 902 | * 903 | * @since 1.0.3 904 | * 905 | * @param string $method The validation pattern slug; e.g. `hex` or `userId` 906 | * @param callable $callback The function that will be called to validate the pattern; should return a boolean value 907 | */ 908 | function klein_addValidator( $method, $callback ) { 909 | klein_Validator::$_methods[ strtolower( $method ) ] = $callback; 910 | } 911 | 912 | /** 913 | * Class klein_ValidatorException 914 | * 915 | * The base class of the exceptions that will be thrown when a validation callback fails. 916 | * 917 | * @since 1.0.3 918 | */ 919 | class klein_ValidatorException extends Exception {} 920 | 921 | /** 922 | * Class klein_Validator 923 | * 924 | * @since 1.0.3 925 | */ 926 | class klein_Validator { 927 | 928 | public static $_methods = array(); 929 | 930 | protected $_str = null; 931 | protected $_err = null; 932 | 933 | // Sets up the validator chain with the string and optional error message 934 | public function __construct( $str, $err = null ) { 935 | $this->_str = $str; 936 | $this->_err = $err; 937 | if ( empty( self::$_defaultAdded ) ) { 938 | self::addDefault(); 939 | } 940 | } 941 | 942 | // Adds default validators on first use. See README for usage details 943 | public static function addDefault() { 944 | self::$_methods['null'] = array( self, 'validateNull' ); 945 | self::$_methods['len'] = array( self, 'validateLen' ); 946 | self::$_methods['int'] = array( self, 'validateInt' ); 947 | self::$_methods['float'] = array( self, 'validateFloat' ); 948 | self::$_methods['email'] = array( self, 'validateEmail' ); 949 | self::$_methods['url'] = array( self, 'validateUrl' ); 950 | self::$_methods['ip'] = array( self, 'validateIp' ); 951 | self::$_methods['alnum'] = array( self, 'validateAlnum' ); 952 | self::$_methods['alpha'] = array( self, 'validateAlpha' ); 953 | self::$_methods['contains'] = array( self, 'validateContains' ); 954 | self::$_methods['regex'] = array( self, 'validateRegex' ); 955 | self::$_methods['chars'] = array( self, 'validateChars' ); 956 | } 957 | 958 | /** 959 | * @return Closure 960 | */ 961 | private static function validateNull( $str ) { 962 | return $str === null || $str === ''; 963 | } 964 | 965 | /** 966 | * @return Closure 967 | */ 968 | private static function validateLen( $str, $min, $max = null ) { 969 | $len = strlen( $str ); 970 | 971 | return null === $max ? $len === $min : $len >= $min && $len <= $max; 972 | } 973 | 974 | /** 975 | * @return Closure 976 | */ 977 | private static function validateInt( $str ) { 978 | return (string) $str === ( (string) (int) $str ); 979 | } 980 | 981 | /** 982 | * @return Closure 983 | */ 984 | private static function validateFloat( $str ) { 985 | return (string) $str === ( (string) (float) $str ); 986 | } 987 | 988 | /** 989 | * @return Closure 990 | */ 991 | private static function validateEmail( $str ) { 992 | return filter_var( $str, FILTER_VALIDATE_EMAIL ) !== false; 993 | } 994 | 995 | /** 996 | * @return Closure 997 | */ 998 | private static function validateUrl( $str ) { 999 | return filter_var( $str, FILTER_VALIDATE_URL ) !== false; 1000 | } 1001 | 1002 | /** 1003 | * @return Closure 1004 | */ 1005 | private static function validateIp( $str ) { 1006 | return filter_var( $str, FILTER_VALIDATE_IP ) !== false; 1007 | } 1008 | 1009 | /** 1010 | * @return Closure 1011 | */ 1012 | private static function validateAlnum( $str ) { 1013 | return ctype_alnum( $str ); 1014 | } 1015 | 1016 | /** 1017 | * @return Closure 1018 | */ 1019 | private static function validateAlpha( $str ) { 1020 | return ctype_alpha( $str ); 1021 | } 1022 | 1023 | /** 1024 | * @return Closure 1025 | */ 1026 | private static function validateContains( $str, $needle ) { 1027 | return strpos( $str, $needle ) !== false; 1028 | } 1029 | 1030 | /** 1031 | * @return Closure 1032 | */ 1033 | private static function validateRegex( $str, $pattern ) { 1034 | return preg_match( $pattern, $str ); 1035 | } 1036 | 1037 | /** 1038 | * @return Closure 1039 | */ 1040 | private static function validateChars( $str, $chars ) { 1041 | return preg_match( "`^[$chars]++$`i", $str ); 1042 | } 1043 | 1044 | public function __call( $method, $args ) { 1045 | $reverse = false; 1046 | $validator = $method; 1047 | $method_substr = substr( $method, 0, 2 ); 1048 | 1049 | if ( $method_substr === 'is' ) { // is<$validator>() 1050 | $validator = substr( $method, 2 ); 1051 | } elseif ( $method_substr === 'no' ) { // not<$validator>() 1052 | $validator = substr( $method, 3 ); 1053 | $reverse = true; 1054 | } 1055 | $validator = strtolower( $validator ); 1056 | 1057 | if ( ! $validator || ! isset( self::$_methods[ $validator ] ) ) { 1058 | throw new ErrorException( "Unknown method $method()" ); 1059 | } 1060 | $validator = self::$_methods[ $validator ]; 1061 | array_unshift( $args, $this->_str ); 1062 | 1063 | switch ( count( $args ) ) { 1064 | case 1: 1065 | $result = $validator( $args[0] ); 1066 | break; 1067 | case 2: 1068 | $result = $validator( $args[0], $args[1] ); 1069 | break; 1070 | case 3: 1071 | $result = $validator( $args[0], $args[1], $args[2] ); 1072 | break; 1073 | case 4: 1074 | $result = $validator( $args[0], $args[1], $args[2], $args[3] ); 1075 | break; 1076 | default: 1077 | $result = call_user_func_array( $validator, $args ); 1078 | break; 1079 | } 1080 | 1081 | $result = (bool) ( $result ^ $reverse ); 1082 | if ( false === $this->_err ) { 1083 | return $result; 1084 | } elseif ( false === $result ) { 1085 | throw new klein_ValidatorException( $this->_err ); 1086 | } 1087 | 1088 | return $this; 1089 | } 1090 | } 1091 | 1092 | /** 1093 | * Class klein_App 1094 | * 1095 | * @since 1.0.3 1096 | */ 1097 | class klein_App { 1098 | 1099 | protected $services = array(); 1100 | protected $serviceInstances = array(); 1101 | 1102 | // Check for a lazy service 1103 | public function __get( $name ) { 1104 | if ( ! isset( $this->services[ $name ] ) ) { 1105 | throw new InvalidArgumentException( "Unknown service $name" ); 1106 | } 1107 | $service = $this->services[ $name ]; 1108 | 1109 | return $service(); 1110 | } 1111 | 1112 | // Call a class property like a method 1113 | public function __call( $method, $args ) { 1114 | if ( ! isset( $this->$method ) || ! is_callable( $this->$method ) ) { 1115 | throw new ErrorException( "Unknown method $method()" ); 1116 | } 1117 | 1118 | return call_user_func_array( $this->$method, $args ); 1119 | } 1120 | 1121 | // Register a lazy service 1122 | public function register( $name, $callable ) { 1123 | if ( isset( $this->services[ $name ] ) ) { 1124 | throw new Exception( "A service is already registered under $name" ); 1125 | } 1126 | if ( null === $this->serviceInstances[ $name ] ) { 1127 | $this->serviceInstances[ $name ] = call_user_func( $callable ); 1128 | } 1129 | 1130 | return $this->serviceInstances[ $name ]; 1131 | } 1132 | } 1133 | 1134 | /** 1135 | * Class klein_Headers 1136 | * 1137 | * @since 1.0.3 1138 | */ 1139 | class klein_Headers { 1140 | 1141 | public function header( $key, $value = null ) { 1142 | header( $this->_header( $key, $value ) ); 1143 | } 1144 | 1145 | /** 1146 | * Output an HTTP header. If $value is null, $key is 1147 | * assume to be the HTTP response code, and the ":" 1148 | * separator will be omitted. 1149 | */ 1150 | public function _header( $key, $value = null ) { 1151 | if ( null === $value ) { 1152 | return $key; 1153 | } 1154 | 1155 | $key = str_replace( ' ', '-', ucwords( str_replace( ' - ', ' ', $key ) ) ); 1156 | 1157 | return "$key: $value"; 1158 | } 1159 | } 1160 | 1161 | 1162 | klein_Request::$_headers = klein_Response::$_headers = new klein_Headers; 1163 | -------------------------------------------------------------------------------- /wp-routes.php: -------------------------------------------------------------------------------- 1 |