PageRenderTime 64ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/core/Druid.php

https://github.com/Wixel/Druid
PHP | 545 lines | 342 code | 70 blank | 133 comment | 28 complexity | 025b95a790645f2fed526d147ab80bba MD5 | raw file
  1. <?php
  2. class Druid
  3. {
  4. const
  5. VERSION = '0.5';
  6. const
  7. HTTP_100='Continue',
  8. HTTP_101='Switching Protocols',
  9. HTTP_200='OK',
  10. HTTP_201='Created',
  11. HTTP_202='Accepted',
  12. HTTP_203='Non-Authorative Information',
  13. HTTP_204='No Content',
  14. HTTP_205='Reset Content',
  15. HTTP_206='Partial Content',
  16. HTTP_300='Multiple Choices',
  17. HTTP_301='Moved Permanently',
  18. HTTP_302='Found',
  19. HTTP_303='See Other',
  20. HTTP_304='Not Modified',
  21. HTTP_305='Use Proxy',
  22. HTTP_306='Temporary Redirect',
  23. HTTP_400='Bad Request',
  24. HTTP_401='Unauthorized',
  25. HTTP_402='Payment Required',
  26. HTTP_403='Forbidden',
  27. HTTP_404='Not Found',
  28. HTTP_405='Method Not Allowed',
  29. HTTP_406='Not Acceptable',
  30. HTTP_407='Proxy Authentication Required',
  31. HTTP_408='Request Timeout',
  32. HTTP_409='Conflict',
  33. HTTP_410='Gone',
  34. HTTP_411='Length Required',
  35. HTTP_412='Precondition Failed',
  36. HTTP_413='Request Entity Too Large',
  37. HTTP_414='Request-URI Too Long',
  38. HTTP_415='Unsupported Media Type',
  39. HTTP_416='Requested Range Not Satisfiable',
  40. HTTP_417='Expectation Failed',
  41. HTTP_500='Internal Server Error',
  42. HTTP_501='Not Implemented',
  43. HTTP_502='Bad Gateway',
  44. HTTP_503='Service Unavailable',
  45. HTTP_504='Gateway Timeout',
  46. HTTP_505='HTTP Version Not Supported';
  47. private static $instance = NULL;
  48. private static $routing = array();
  49. private static $registry = array();
  50. private static $request = array();
  51. public static $headers = array();
  52. private static $fatal_errors = array(
  53. E_PARSE,
  54. E_ERROR,
  55. E_USER_ERROR
  56. );
  57. public static $php_errors = array(
  58. E_ERROR => 'Fatal Error',
  59. E_USER_ERROR => 'User Error',
  60. E_PARSE => 'Parse Error',
  61. E_WARNING => 'Warning',
  62. E_USER_WARNING => 'User Warning',
  63. E_STRICT => 'Strict',
  64. E_NOTICE => 'Notice',
  65. E_RECOVERABLE_ERROR => 'Recoverable Error',
  66. );
  67. /**
  68. * Create and return the singleton instance of Druid
  69. *
  70. * @access public
  71. * @return Druid
  72. */
  73. public static function instance()
  74. {
  75. if(is_null(self::$instance))
  76. {
  77. $class = __CLASS__;
  78. self::$instance = new $class;
  79. }
  80. return self::$instance;
  81. }
  82. /**
  83. * Set up the Druid instance
  84. *
  85. * @access private
  86. * @return void
  87. */
  88. private function __construct()
  89. {
  90. // Set the autoloader
  91. $this->set_auto_loader();
  92. // Handle all generic errors
  93. set_error_handler(array('Druid', 'druid_error_handler'));
  94. // Handle all generic exceptions
  95. set_exception_handler(array('Druid', 'druid_exception_handler'));
  96. // Handle all critical errors
  97. register_shutdown_function(array('Druid', 'druid_shutdown_handler'));
  98. self::$headers[] = 'Content-Type: text/html; charset=UTF-8';
  99. self::$headers[] = 'X-Powered-By: Druid v'. Druid::VERSION;
  100. if(ini_get('register_globals'))
  101. {
  102. $this->revert_register_globals();
  103. }
  104. self::set('magic_quotes', (bool)get_magic_quotes_gpc());
  105. $_GET = Druid::sanitize($_GET);
  106. $_POST = Druid::sanitize($_POST);
  107. $_COOKIE = Druid::sanitize($_COOKIE);
  108. $this->parse_request();
  109. }
  110. /**
  111. * Sanitize the input for better security and consistency
  112. *
  113. * @static
  114. * @access private
  115. * @param mixed $value
  116. * @return mixed
  117. */
  118. private static function sanitize($value)
  119. {
  120. if(is_array($value) OR is_object($value))
  121. {
  122. foreach ($value as $key => $val)
  123. {
  124. $value[$key] = self::sanitize($val);
  125. }
  126. }
  127. elseif(is_string($value))
  128. {
  129. if(self::get('magic_quotes') === TRUE)
  130. {
  131. $value = stripslashes($value);
  132. }
  133. if(strpos($value, "\r") !== FALSE)
  134. {
  135. $value = str_replace(array("\r\n", "\r"), "\n", $value);
  136. }
  137. }
  138. return $value;
  139. }
  140. /**
  141. * Revert the effects of 'register globals'
  142. *
  143. * @access private
  144. * @return void
  145. */
  146. private function revert_register_globals()
  147. {
  148. if(isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS']))
  149. {
  150. exit(1); // Malicious attack detected, exit
  151. }
  152. $global_variables = array_diff(array_keys($GLOBALS), array(
  153. '_COOKIE',
  154. '_ENV',
  155. '_GET',
  156. '_FILES',
  157. '_POST',
  158. '_REQUEST',
  159. '_SERVER',
  160. '_SESSION',
  161. 'GLOBALS',
  162. ));
  163. foreach ($global_variables as $name)
  164. {
  165. unset($GLOBALS[$name]);
  166. }
  167. }
  168. /**
  169. * Retrieve an item from the global instance registry
  170. *
  171. * @access public
  172. * @param string $key
  173. * @param mixed $default
  174. * @return mixed
  175. */
  176. public static function get($key, $default = FALSE)
  177. {
  178. if(isset(self::$registry[$key]))
  179. {
  180. return self::$registry[$key];
  181. }
  182. else
  183. {
  184. return $default;
  185. }
  186. }
  187. /**
  188. * Set an item to the global object registry
  189. *
  190. * @static
  191. * @access public
  192. * @param string $key
  193. * @param mixed $value
  194. * @return void
  195. */
  196. public static function set($key, $value)
  197. {
  198. self::$registry[$key] = $value;
  199. return;
  200. }
  201. /**
  202. * Map a route to a specified controller
  203. *
  204. * @static
  205. * @access public
  206. * @param string $route
  207. * @param mixed $conroller
  208. * @return void
  209. */
  210. public static function map($route, $controller)
  211. {
  212. $method = 'GET';
  213. $route = trim($route);
  214. if(strpos($route, chr(32)) !== FALSE)
  215. {
  216. $route = explode(chr(32), $route);
  217. $method = $route[0];
  218. $route = $route[1];
  219. }
  220. self::$routing[$method][$route] = $controller;
  221. return;
  222. }
  223. /**
  224. * Execute and process the current request
  225. *
  226. * @static
  227. * @access public
  228. * @return void
  229. */
  230. public function run()
  231. {
  232. self::set('start_time', time());
  233. $this->output_headers();
  234. ob_start();
  235. foreach(self::$routing[self::request_method()] as $route => $handler)
  236. {
  237. if(strpos($route, '%') !== FALSE)
  238. {
  239. preg_match_all($route, self::request_path(), $matches);
  240. if(count($matches[0]) != 0)
  241. {
  242. foreach($matches as $name => $match)
  243. {
  244. if(is_string($name))
  245. {
  246. self::set('request.'.$name, $match[0]);
  247. }
  248. }
  249. if(is_callable($handler))
  250. {
  251. call_user_func_array($handler, array());
  252. self::$request['mapped'] = TRUE;
  253. break;
  254. }
  255. }
  256. }
  257. else
  258. {
  259. if($route == self::request_path())
  260. {
  261. if(is_callable($handler))
  262. {
  263. call_user_func_array($handler, array());
  264. self::$request['mapped'] = TRUE;
  265. break;
  266. }
  267. }
  268. }
  269. }
  270. $output = ob_get_contents();
  271. ob_end_clean();
  272. self::set('end_time', time());
  273. if(!isset(self::$request['mapped']))
  274. {
  275. if(!headers_sent())
  276. {
  277. header("HTTP/1.0 404 Not Found");
  278. }
  279. }
  280. else
  281. {
  282. echo $output;
  283. }
  284. }
  285. /**
  286. * Parse the current request object
  287. *
  288. * @access private
  289. * @return void
  290. */
  291. private function parse_request()
  292. {
  293. $domain = strtolower(
  294. trim(
  295. mb_substr(
  296. $_SERVER['SERVER_NAME'], 0, strpos($_SERVER['SERVER_NAME'], '.')
  297. )
  298. )
  299. );
  300. self::$request['is_subdomain'] = FALSE;
  301. self::$request['path'] = strtolower($_SERVER['REQUEST_URI']);
  302. self::$request['method'] = strtoupper($_SERVER['REQUEST_METHOD']);
  303. self::$request['remote_addr'] = $_SERVER['REMOTE_ADDR'];
  304. self::$request['time'] = $_SERVER['REQUEST_TIME'];
  305. return;
  306. }
  307. /**
  308. * Utility method to return the current request path
  309. *
  310. * @static
  311. * @access public
  312. * @return string
  313. */
  314. public static function request_path()
  315. {
  316. return self::$request['path'];
  317. }
  318. /**
  319. * Utility method to return the remote address
  320. *
  321. * @static
  322. * @access public
  323. * @return string
  324. */
  325. public static function request_remote_addr()
  326. {
  327. return self::$request['remote_addr'];
  328. }
  329. /**
  330. * Utility method to return the current request method
  331. *
  332. * @static
  333. * @access public
  334. * @return string
  335. */
  336. public static function request_method()
  337. {
  338. return self::$request['method'];
  339. }
  340. /**
  341. * Utility method to return the subdomain indicator
  342. *
  343. * @static
  344. * @access public
  345. * @return bool
  346. */
  347. public static function request_is_subdomain()
  348. {
  349. return self::$request['is_subdomain'];
  350. }
  351. /**
  352. * Set the output headers (default to UTF-8)
  353. *
  354. * @access private
  355. * @return boolean
  356. */
  357. private function output_headers()
  358. {
  359. if(!headers_sent())
  360. {
  361. foreach(self::$headers as $header)
  362. {
  363. header($header);
  364. }
  365. }
  366. }
  367. /**
  368. * Set the system autoloader (single autoloader)
  369. *
  370. * @access private
  371. * @return boolean
  372. */
  373. private function set_auto_loader()
  374. {
  375. spl_autoload_register(
  376. function($class)
  377. {
  378. $path = str_replace('_', '/', strtolower($class)).'.php';
  379. $locations = array(
  380. realpath('../').'/www/',
  381. realpath('../').'/modules/',
  382. );
  383. foreach($locations as $location)
  384. {
  385. if(file_exists($location.$path))
  386. {
  387. require $location.$path;
  388. return TRUE;
  389. }
  390. }
  391. return FALSE;
  392. }
  393. );
  394. }
  395. /**
  396. * Set the druid error handler
  397. *
  398. * @access private
  399. * @return boolean
  400. */
  401. public static function druid_error_handler($code, $error, $file = NULL, $line = NULL)
  402. {
  403. if (error_reporting() & $code)
  404. {
  405. throw new ErrorException($error, $code, 0, $file, $line);
  406. }
  407. return TRUE;
  408. }
  409. /**
  410. * Set the druid exception handler
  411. *
  412. * @access private
  413. * @return void
  414. */
  415. public static function druid_exception_handler($e)
  416. {
  417. $type = get_class($e);
  418. $code = $e->getCode();
  419. $message = $e->getMessage();
  420. $file = $e->getFile();
  421. $line = $e->getLine();
  422. $trace = $e->getTrace();
  423. if ($e instanceof ErrorException)
  424. {
  425. if (isset(self::$php_errors[$code]))
  426. {
  427. $code = self::$php_errors[$code];
  428. }
  429. if (version_compare(PHP_VERSION, '5.3', '<')) // @see http://bugs.php.net/bug.php?id=45895
  430. {
  431. for ($i = count($trace) - 1; $i > 0; --$i)
  432. {
  433. if (isset($trace[$i - 1]['args']))
  434. {
  435. $trace[$i]['args'] = $trace[$i - 1]['args'];
  436. unset($trace[$i - 1]['args']);
  437. }
  438. }
  439. }
  440. }
  441. $error = sprintf(
  442. '%s [ %s ]: %s ~ %s [ %d ]', get_class($e), $e->getCode(), strip_tags($e->getMessage()), $e->getFile(), $e->getLine()
  443. );
  444. error_log($error, 0); // Log to PHP error log
  445. echo $error;
  446. return TRUE;
  447. }
  448. /**
  449. * Set the shutdown handler, handle critical errors
  450. *
  451. * @access private
  452. * @return void
  453. */
  454. public static function druid_shutdown_handler()
  455. {
  456. $last_error = error_get_last();
  457. if($last_error['type'] === E_ERROR || in_array($last_error['type'], self::$fatal_errors))
  458. {
  459. self::druid_exception_handler(
  460. new ErrorException($last_error['message'], $last_error['type'], 0, $last_error['file'], $last_error['line'])
  461. );
  462. exit(1);
  463. }
  464. }
  465. /**
  466. * Prevent Druid instance from being cloned
  467. *
  468. * @access private
  469. * @return void
  470. */
  471. public function __clone()
  472. {
  473. trigger_error('Cloning of Druid not permitted.', E_USER_ERROR);
  474. }
  475. } // EOC