PageRenderTime 29ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/Code/Chapter9/Recipe2/system/jolt.php

https://gitlab.com/thugside/TwilioCookbook
PHP | 630 lines | 615 code | 6 blank | 9 comment | 121 complexity | 179112b66e9c128352df213a695deefd MD5 | raw file
  1. <?php
  2. session_start();
  3. class Jolt{
  4. protected static $apps = array();
  5. public $name;
  6. public $debug = false;
  7. public $notFound;
  8. private $pattern;
  9. private $params = array();
  10. protected $conditions = array();
  11. protected static $defaultConditions = array();
  12. protected static $scheme;
  13. protected static $baseUri;
  14. protected static $uri;
  15. protected static $queryString;
  16. private $request;
  17. private $datastore;
  18. private $route_map = array(
  19. 'GET' => array(),
  20. 'POST' => array()
  21. );
  22. public function __construct($name='default',$debug = false){
  23. $this->debug = $debug;
  24. $this->request = new Jolt_Http_Request();
  25. if (is_null(static::getInstance($name))) {
  26. $this->setName($name);
  27. }
  28. }
  29. public static function getInstance($name = 'default'){
  30. return isset(static::$apps[$name]) ? static::$apps[$name] : null;
  31. }
  32. public function setName($name){
  33. $this->name = $name;
  34. static::$apps[$name] = $this;
  35. }
  36. public function getName(){
  37. return $this->name;
  38. }
  39. public function request(){
  40. return $this->request;
  41. }
  42. public function listen(){
  43. if( $this->debug ){
  44. echo '<pre>'.print_r( $this->route_map,true ).'</pre>';
  45. }
  46. $this->router();
  47. }
  48. private function add_route($method = 'GET',$pattern,$cb = null,$conditions = null){
  49. $method = strtoupper($method);
  50. if (!in_array($method, array('GET', 'POST')))
  51. error(500, 'Only GET and POST are supported');
  52. $pattern = str_replace(")",")?",$pattern); // add ? to the end of each closing bracket..
  53. $this->route_map[$method][$pattern] = array(
  54. 'xp' => $this->route_to_regex($pattern),
  55. 'cb' => $cb,
  56. 'conditions'=>$conditions
  57. );
  58. }
  59. private function router(){
  60. $route_map = $this->route_map;
  61. foreach ($route_map['GET'] as $pat => $obj) {
  62. $this->pattern = $pat;
  63. if( isset($obj['conditions']) && !is_null($obj['conditions']) ){
  64. $this->conditions = $obj['conditions'];
  65. }
  66. foreach($_GET as $k=>$v){
  67. if( $this->matches( $this->getUri() ) ){
  68. $vals = $this->params;
  69. $this->middleware($vals);
  70. $keys = array_keys( $vals );
  71. $argv = array();
  72. foreach ($keys as $index => $id) {
  73. if (isset($vals[$id])) {
  74. array_push($argv, trim(urldecode($vals[$id])));
  75. }
  76. }
  77. if (count($keys)) {
  78. $this->filter( array_values($keys), $vals );
  79. }
  80. if (is_callable($obj['cb'])) {
  81. call_user_func_array($obj['cb'], $argv);
  82. }
  83. return;
  84. }
  85. }
  86. }
  87. $this->notFound();
  88. }
  89. private function route_to_regex($route) {
  90. $route = preg_replace_callback('@:[\w]+@i', function ($matches) {
  91. $token = str_replace(':', '', $matches[0]);
  92. return '(?P<'.$token.'>[a-z0-9_\0-\.]+)';
  93. }, $route);
  94. return '@^'.rtrim($route, '/').'$@i';
  95. }
  96. public function matches( $resourceUri ) {
  97. # echo $this->pattern."<br />";
  98. preg_match_all('@:([\w]+)@', $this->pattern, $paramNames, PREG_PATTERN_ORDER);
  99. $paramNames = $paramNames[0];
  100. # print_r($paramNames);
  101. # echo "<br />";
  102. $patternAsRegex = preg_replace_callback('@:[\w]+@', array($this, 'convertPatternToRegex'), $this->pattern);
  103. if ( substr($this->pattern, -1) === '/' ) {
  104. $patternAsRegex = $patternAsRegex . '?';
  105. }
  106. $patternAsRegex = '@^' . $patternAsRegex . '$@';
  107. # echo $patternAsRegex;
  108. # echo "<hr />";
  109. if ( preg_match($patternAsRegex, $resourceUri, $paramValues) ) {
  110. array_shift($paramValues);
  111. foreach ( $paramNames as $index => $value ) {
  112. $val = substr($value, 1);
  113. if ( isset($paramValues[$val]) ) {
  114. $this->params[$val] = urldecode($paramValues[$val]);
  115. }
  116. }
  117. return true;
  118. } else {
  119. return false;
  120. }
  121. }
  122. protected function convertPatternToRegex( $matches ) {
  123. # $key = str_replace(':', '', $matches[0]);
  124. # return '(?P<' . $key . '>[a-zA-Z0-9_\-\.\!\~\*\\\'\(\)\:\@\&\=\$\+,%]+)';
  125. $key = str_replace(':', '', $matches[0]);
  126. if ( array_key_exists($key, $this->conditions) ) {
  127. return '(?P<' . $key . '>' . $this->conditions[$key] . ')';
  128. } else {
  129. return '(?P<' . $key . '>[a-zA-Z0-9_\-\.\!\~\*\\\'\(\)\:\@\&\=\$\+,%]+)';
  130. }
  131. }
  132. private function got404( $callable = null ) {
  133. if ( is_callable($callable) ) {
  134. $this->notFound = $callable;
  135. }
  136. return $this->notFound;
  137. }
  138. public function notFound( $callable = null ) {
  139. if ( !is_null($callable) ) {
  140. $this->got404($callable);
  141. } else {
  142. ob_start();
  143. $customNotFoundHandler = $this->got404();
  144. if ( is_callable($customNotFoundHandler) ) {
  145. call_user_func($customNotFoundHandler);
  146. } else {
  147. call_user_func(array($this, 'defaultNotFound'));
  148. }
  149. $this->error(404, ob_get_clean());
  150. }
  151. }
  152. public function route($pattern,$cb = null,$conditions=null){ // doesn't care about GET or POST...
  153. return $this->add_route('GET',$pattern,$cb,$conditions);
  154. }
  155. public function get($pattern,$cb = null,$conditions=null){
  156. if( $this->method('GET') ){ // only process during GET
  157. return $this->add_route('GET',$pattern,$cb,$conditions);
  158. }
  159. }
  160. public function post($pattern,$cb = null,$conditions=null){
  161. if( $this->method('POST') ){ // only process during POST
  162. return $this->add_route('GET',$pattern,$cb,$conditions);
  163. }
  164. }
  165. public function put($pattern,$cb = null,$conditions=null){
  166. if( $this->method('PUT') ){ // only process during PUT
  167. return $this->add_route('GET',$pattern,$cb,$conditions);
  168. }
  169. }
  170. public function delete($pattern,$cb = null,$conditions=null){
  171. if( $this->method('DELETE') ){ // only process during DELETE
  172. return $this->add_route('GET',$pattern,$cb,$conditions);
  173. }
  174. }
  175. public function req($key){
  176. return $_REQUEST[$key];
  177. }
  178. public function header($str){
  179. header( $str );
  180. }
  181. public function send($str){
  182. echo $str;
  183. }
  184. public function raw(){
  185. $rawData = file_get_contents("php://input");
  186. return json_decode($rawData);
  187. }
  188. public function redirect(/* $code_or_path, $path_or_cond, $cond */) {
  189. $argv = func_get_args();
  190. $argc = count($argv);
  191. $path = null;
  192. $code = 302;
  193. $cond = true;
  194. switch ($argc) {
  195. case 3:
  196. list($code, $path, $cond) = $argv;
  197. break;
  198. case 2:
  199. if (is_string($argv[0]) ? $argv[0] : $argv[1]) {
  200. $code = 302;
  201. $path = $argv[0];
  202. $cond = $argv[1];
  203. } else {
  204. $code = $argv[0];
  205. $path = $argv[1];
  206. }
  207. break;
  208. case 1:
  209. if (!is_string($argv[0]))
  210. $this->error(500, 'bad call to redirect()');
  211. $path = $argv[0];
  212. break;
  213. default:
  214. $this->error(500, 'bad call to redirect()');
  215. }
  216. $cond = (is_callable($cond) ? !!call_user_func($cond) : !!$cond);
  217. if (!$cond)return;
  218. header('Location: '.$path, true, $code);
  219. exit;
  220. }
  221. public function option($key, $value = null) {
  222. static $_option = array();
  223. if ($key === 'source' && file_exists($value))
  224. $_option = parse_ini_file($value, true);
  225. else if ($value == null)
  226. return (isset($_option[$key]) ? $_option[$key] : null);
  227. else
  228. $_option[$key] = $value;
  229. }
  230. public function error($code, $message) {
  231. @header("HTTP/1.0 {$code} {$message}", true, $code);
  232. die($message);
  233. }
  234. public function warn($name = null, $message = null) {
  235. static $warnings = array();
  236. if ($name == '*')
  237. return $warnings;
  238. if (!$name)
  239. return count(array_keys($warnings));
  240. if (!$message)
  241. return isset($warnings[$name]) ? $warnings[$name] : null ;
  242. $warnings[$name] = $message;
  243. }
  244. public function from($source, $name) {
  245. if (is_array($name)) {
  246. $data = array();
  247. foreach ($name as $k)
  248. $data[$k] = isset($source[$k]) ? $source[$k] : null ;
  249. return $data;
  250. }
  251. return isset($source[$name]) ? $source[$name] : null ;
  252. }
  253. public function store($name, $value = null) {
  254. static $_store = array();
  255. if( empty($_store) && isset($_SESSION['_store']) ){
  256. $_store = $_SESSION['_store'];
  257. }
  258. if ($value === null)
  259. return isset($_store[$name]) ? $_store[$name] : null;
  260. $_store[$name] = $value;
  261. $_SESSION['_store'] = $_store;
  262. return $value;
  263. }
  264. public function method($verb = null) {
  265. if ($verb == null || (strtoupper($verb) == strtoupper($_SERVER['REQUEST_METHOD'])))
  266. return strtoupper($_SERVER['REQUEST_METHOD']);
  267. return false;
  268. }
  269. public function client_ip() {
  270. if (isset($_SERVER['HTTP_CLIENT_IP']))
  271. return $_SERVER['HTTP_CLIENT_IP'];
  272. else if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
  273. return $_SERVER['HTTP_X_FORWARDED_FOR'];
  274. return $_SERVER['REMOTE_ADDR'];
  275. }
  276. public function partial($view, $locals = null) {
  277. if (is_array($locals) && count($locals)) {
  278. extract($locals, EXTR_SKIP);
  279. }
  280. if (($view_root = $this->option('views.root')) == null)
  281. $this->error(500, "[views.root] is not set");
  282. $path = basename($view);
  283. $view = preg_replace('/'.$path.'$/', "_{$path}", $view);
  284. $view = "{$view_root}/{$view}.php";
  285. if (file_exists($view)) {
  286. ob_start();
  287. require $view;
  288. return ob_get_clean();
  289. } else {
  290. $this->error(500, "partial [{$view}] not found");
  291. }
  292. return '';
  293. }
  294. public function content($value = null) {
  295. return $this->store('$content$', $value);
  296. }
  297. public function render($view, $locals = null, $layout = null) {
  298. $locals['uri'] = $this->getBaseUri();
  299. $locals['cpage'] = $this->getUri();
  300. $locals['sitename'] = $this->option('site.name');
  301. if (is_array($locals) && count($locals)) {
  302. extract($locals, EXTR_SKIP);
  303. }
  304. if (($view_root = $this->option('views.root')) == null)
  305. $this->error(500, "[views.root] is not set");
  306. ob_start();
  307. include "{$view_root}/{$view}.php";
  308. $this->content(trim(ob_get_clean()));
  309. if ($layout !== false) {
  310. if ($layout == null) {
  311. $layout = $this->option('views.layout');
  312. $layout = ($layout == null) ? 'layout' : $layout;
  313. }
  314. $layout = "{$view_root}/{$layout}.php";
  315. header('Content-type: text/html; charset=utf-8');
  316. $pageContent = $this->content();
  317. ob_start();
  318. require $layout;
  319. echo trim(ob_get_clean());
  320. } else {
  321. // no layout
  322. echo $this->content();
  323. }
  324. }
  325. public function json($obj, $code = 200) {
  326. // output a json stream
  327. header('Content-type: application/json', true, $code);
  328. echo json_encode($obj);
  329. exit;
  330. }
  331. public function condition() {
  332. static $cb_map = array();
  333. $argv = func_get_args();
  334. $argc = count($argv);
  335. if (!$argc)
  336. $this->error(500, 'bad call to condition()');
  337. $name = array_shift($argv);
  338. $argc = $argc - 1;
  339. if (!$argc && is_callable($cb_map[$name]))
  340. return call_user_func($cb_map[$name]);
  341. if (is_callable($argv[0]))
  342. return ($cb_map[$name] = $argv[0]);
  343. if (is_callable($cb_map[$name]))
  344. return call_user_func_array($cb_map[$name], $argv);
  345. $this->error(500, 'condition ['.$name.'] is undefined');
  346. }
  347. public function middleware($cb_or_path = null) {
  348. static $cb_map = array();
  349. if ($cb_or_path == null || is_string($cb_or_path)) {
  350. foreach ($cb_map as $cb) {
  351. call_user_func($cb, $cb_or_path);
  352. }
  353. } else {
  354. array_push($cb_map, $cb_or_path);
  355. }
  356. }
  357. public function filter($sym, $cb_or_val = null) {
  358. static $cb_map = array();
  359. if (is_callable($cb_or_val)) {
  360. $cb_map[$sym] = $cb_or_val;
  361. return;
  362. }
  363. if (is_array($sym) && count($sym) > 0) {
  364. foreach ($sym as $s) {
  365. if( $s[0] == ":" ){
  366. $s = substr($s, 1);
  367. }
  368. if (isset($cb_map[$s]) && isset($cb_or_val[$s])){
  369. call_user_func($cb_map[$s], $cb_or_val[$s]);
  370. }
  371. }
  372. return;
  373. }
  374. $this->error(500, 'bad call to filter()');
  375. }
  376. public function set_cookie($name, $value, $expire = 31536000, $path = '/') {
  377. setcookie($name, $value, time() + $expire, $path);
  378. }
  379. public function get_cookie($name) {
  380. $value = $this->from($_COOKIE, $name);
  381. if ($value)
  382. return $value;
  383. }
  384. public function delete_cookie() {
  385. $cookies = func_get_args();
  386. foreach ($cookies as $ck)
  387. setcookie($ck, '', -10, '/');
  388. }
  389. public function flash($key, $msg = null, $now = false) {
  390. static $x = array(),
  391. $f = null;
  392. $f = ( $this->option('cookies.flash') ? $this->option('cookies.flash') : '_F');
  393. if ($c = $this->get_cookie($f))
  394. $c = json_decode($c, true);
  395. else
  396. $c = array();
  397. if ($msg == null) {
  398. if (isset($c[$key])) {
  399. $x[$key] = $c[$key];
  400. unset($c[$key]);
  401. $this->set_cookie($f, json_encode($c));
  402. }
  403. return (isset($x[$key]) ? $x[$key] : null);
  404. }
  405. if (!$now) {
  406. $c[$key] = $msg;
  407. $this->set_cookie($f, json_encode($c));
  408. }
  409. $x[$key] = $msg;
  410. }
  411. public static function getBaseUri( $reload = false ) {
  412. if ( $reload || is_null(self::$baseUri) ) {
  413. $requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF']; //Full Request URI
  414. $scriptName = $_SERVER['SCRIPT_NAME']; //Script path from docroot
  415. $baseUri = strpos($requestUri, $scriptName) === 0 ? $scriptName : str_replace('\\', '/', dirname($scriptName));
  416. self::$baseUri = rtrim($baseUri, '/');
  417. }
  418. return self::$baseUri;
  419. }
  420. public static function getUri( $reload = false ) {
  421. if ( $reload || is_null(self::$uri) ) {
  422. $uri = '';
  423. if ( !empty($_SERVER['PATH_INFO']) ) {
  424. $uri = $_SERVER['PATH_INFO'];
  425. } else {
  426. if ( isset($_SERVER['REQUEST_URI']) ) {
  427. $uri = parse_url(self::getScheme() . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], PHP_URL_PATH);
  428. } else if ( isset($_SERVER['PHP_SELF']) ) {
  429. $uri = $_SERVER['PHP_SELF'];
  430. } else {
  431. $this->error(500, 'Unable to detect request URI');
  432. }
  433. }
  434. if ( self::getBaseUri() !== '' && strpos($uri, self::getBaseUri()) === 0 ) {
  435. $uri = substr($uri, strlen(self::getBaseUri()));
  436. }
  437. self::$uri = '/' . ltrim($uri, '/');
  438. }
  439. return self::$uri;
  440. }
  441. public static function getScheme( $reload = false ) {
  442. if ( $reload || is_null(self::$scheme) ) {
  443. self::$scheme = ( empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ) ? 'http' : 'https';
  444. }
  445. return self::$scheme;
  446. }
  447. public static function getQueryString( $reload = false ) {
  448. if ( $reload || is_null(self::$queryString) ) {
  449. self::$queryString = $_SERVER['QUERY_STRING'];
  450. }
  451. return self::$queryString;
  452. }
  453. protected static function generateErrorMarkup( $message, $file = '', $line = '', $trace = '' ) {
  454. $body = '<p>The application could not run because of the following error:</p>';
  455. $body .= "<h2>Details:</h2><strong>Message:</strong> $message<br/>";
  456. if ( $file !== '' ) $body .= "<strong>File:</strong> $file<br/>";
  457. if ( $line !== '' ) $body .= "<strong>Line:</strong> $line<br/>";
  458. if ( $trace !== '' ) $body .= '<h2>Stack Trace:</h2>' . nl2br($trace);
  459. return self::generateTemplateMarkup('Jolt Application Error', $body);
  460. }
  461. protected static function generateTemplateMarkup( $title, $body ) {
  462. $html = "<html><head><title>$title</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:65px;}</style></head><body>";
  463. $html .= "<h1>$title</h1>";
  464. $html .= $body;
  465. $html .= '</body></html>';
  466. return $html;
  467. }
  468. protected function defaultNotFound() {
  469. echo self::generateTemplateMarkup('404 Page Not Found', '<p>The page you are looking for could not be found. Check the address bar to ensure your URL is spelled correctly. If all else fails, you can visit our home page at the link below.</p><a href="' . $this->getBaseUri() . '">Visit the Home Page</a>');
  470. }
  471. protected function defaultError() {
  472. echo self::generateTemplateMarkup('Error', '<p>A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>');
  473. }
  474. public function cache($key, $func, $ttl = 0) {
  475. if (extension_loaded('apc')) {
  476. if (($data = apc_fetch($key)) === false) {
  477. $data = call_user_func($func);
  478. if ($data !== null) {
  479. apc_store($key, $data, $ttl);
  480. }
  481. }
  482. }else{
  483. $this->datastore = new DataStore('jolt');
  484. if( ($data = $this->datastore->Get($key) ) === false) {
  485. $data = call_user_func($func);
  486. if ($data !== null) {
  487. $this->datastore->Set($key,$data,$ttl);
  488. }
  489. }
  490. }
  491. return $data;
  492. }
  493. public function cache_invalidate() {
  494. if (extension_loaded('apc')) {
  495. foreach (func_get_args() as $key) {
  496. apc_delete($key);
  497. }
  498. }else{
  499. foreach (func_get_args() as $key) {
  500. $this->datastore->nuke($key);
  501. }
  502. }
  503. }
  504. }
  505. class Jolt_Http_Request{
  506. const METHOD_HEAD = 'HEAD';
  507. const METHOD_GET = 'GET';
  508. const METHOD_POST = 'POST';
  509. const METHOD_PUT = 'PUT';
  510. const METHOD_DELETE = 'DELETE';
  511. const METHOD_OVERRIDE = '_METHOD';
  512. protected $method;
  513. private $get;
  514. private $post;
  515. public function __construct(){
  516. $this->method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : false;
  517. $this->get = $_GET;
  518. $this->post = $_POST;
  519. }
  520. public function isGet() {
  521. return $this->method === self::METHOD_GET;
  522. }
  523. public function isPost() {
  524. return $this->method === self::METHOD_POST;
  525. }
  526. public function isPut() {
  527. return $this->method === self::METHOD_PUT;
  528. }
  529. public function isDelete() {
  530. return $this->method === self::METHOD_DELETE;
  531. }
  532. public function isHead() {
  533. return $this->method === self::METHOD_HEAD;
  534. }
  535. public function isAjax() {
  536. return ( $this->params('isajax') || $this->headers('X_REQUESTED_WITH') === 'XMLHttpRequest' );
  537. }
  538. public function params( $key ) {
  539. foreach( array('post', 'get') as $dataSource ){
  540. $source = $this->$dataSource;
  541. if ( isset($source[(string)$key]) ){
  542. return $source[(string)$key];
  543. }
  544. }
  545. return null;
  546. }
  547. public function post($key){
  548. if( isset($this->post[$key]) ){
  549. return $this->post[$key];
  550. }
  551. return null;
  552. }
  553. public function get($key){
  554. if( isset($this->get[$key]) ){
  555. return $this->get[$key];
  556. }
  557. return null;
  558. }
  559. }
  560. class DataStore {
  561. public $token;
  562. public function __construct($token){
  563. $this->token = $token;
  564. $path = '_cache/';
  565. if( !is_dir($path) ){
  566. mkdir($path,0777);
  567. }
  568. if( !is_writable($path) ){
  569. chmod($path,0777);
  570. }
  571. return true;
  572. }
  573. public function Get($key){
  574. return $this->_fetch($this->token.'-'.$key);
  575. }
  576. public function Set($key,$val,$ttl=6000){
  577. return $this->_store($this->token.'-'.$key,$val,$ttl);
  578. }
  579. public function Delete($key){
  580. return $this->_nuke($this->token.'-'.$key);
  581. }
  582. private function _getFileName($key) {
  583. return '_cache/' . ($key).'.store';
  584. }
  585. private function _store($key,$data,$ttl) {
  586. $h = fopen($this->_getFileName($key),'a+');
  587. if (!$h) throw new Exception('Could not write to cache');
  588. flock($h,LOCK_EX);
  589. fseek($h,0);
  590. ftruncate($h,0);
  591. $data = serialize(array(time()+$ttl,$data));
  592. if (fwrite($h,$data)===false) {
  593. throw new Exception('Could not write to cache');
  594. }
  595. fclose($h);
  596. }
  597. private function _fetch($key) {
  598. $filename = $this->_getFileName($key);
  599. if (!file_exists($filename)) return false;
  600. $h = fopen($filename,'r');
  601. if (!$h) return false;
  602. flock($h,LOCK_SH);
  603. $data = file_get_contents($filename);
  604. fclose($h);
  605. $data = @unserialize($data);
  606. if (!$data) {
  607. unlink($filename);
  608. return false;
  609. }
  610. if (time() > $data[0]) {
  611. unlink($filename);
  612. return false;
  613. }
  614. return $data[1];
  615. }
  616. private function _nuke( $key ) {
  617. $filename = $this->_getFileName($key);
  618. if (file_exists($filename)) {
  619. return unlink($filename);
  620. } else {
  621. return false;
  622. }
  623. }
  624. }