PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/base.php

https://github.com/franz-josef-kaiser/fatfree
PHP | 2267 lines | 1743 code | 92 blank | 432 comment | 153 complexity | 871f742bc888f4cad03f9b9041982bad MD5 | raw file
Possible License(s): GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. Copyright (c) 2009-2012 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfree.sf.net).
  5. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
  6. ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  7. IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  8. PURPOSE.
  9. Please see the license.txt file for more information.
  10. */
  11. //! Base structure
  12. final class Base {
  13. //@{ Framework details
  14. const
  15. PACKAGE='Fat-Free Framework',
  16. VERSION='3.0.5-Dev';
  17. //@}
  18. //@{ HTTP status codes (RFC 2616)
  19. const
  20. HTTP_100='Continue',
  21. HTTP_101='Switching Protocols',
  22. HTTP_200='OK',
  23. HTTP_201='Created',
  24. HTTP_202='Accepted',
  25. HTTP_203='Non-Authorative Information',
  26. HTTP_204='No Content',
  27. HTTP_205='Reset Content',
  28. HTTP_206='Partial Content',
  29. HTTP_300='Multiple Choices',
  30. HTTP_301='Moved Permanently',
  31. HTTP_302='Found',
  32. HTTP_303='See Other',
  33. HTTP_304='Not Modified',
  34. HTTP_305='Use Proxy',
  35. HTTP_307='Temporary Redirect',
  36. HTTP_400='Bad Request',
  37. HTTP_401='Unauthorized',
  38. HTTP_402='Payment Required',
  39. HTTP_403='Forbidden',
  40. HTTP_404='Not Found',
  41. HTTP_405='Method Not Allowed',
  42. HTTP_406='Not Acceptable',
  43. HTTP_407='Proxy Authentication Required',
  44. HTTP_408='Request Timeout',
  45. HTTP_409='Conflict',
  46. HTTP_410='Gone',
  47. HTTP_411='Length Required',
  48. HTTP_412='Precondition Failed',
  49. HTTP_413='Request Entity Too Large',
  50. HTTP_414='Request-URI Too Long',
  51. HTTP_415='Unsupported Media Type',
  52. HTTP_416='Requested Range Not Satisfiable',
  53. HTTP_417='Expectation Failed',
  54. HTTP_500='Internal Server Error',
  55. HTTP_501='Not Implemented',
  56. HTTP_502='Bad Gateway',
  57. HTTP_503='Service Unavailable',
  58. HTTP_504='Gateway Timeout',
  59. HTTP_505='HTTP Version Not Supported';
  60. //@}
  61. const
  62. //! Mapped PHP globals
  63. GLOBALS='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV',
  64. //! HTTP verbs
  65. VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT',
  66. //! Default directory permissions
  67. MODE=0755,
  68. //! Syntax highlighting stylesheet
  69. CSS='code.css';
  70. //@{ HTTP request types
  71. const
  72. REQ_SYNC=1,
  73. REQ_AJAX=2;
  74. //@}
  75. //@{ Error messages
  76. const
  77. E_Pattern='Invalid routing pattern: %s',
  78. E_Fatal='Fatal error: %s',
  79. E_Open='Unable to open %s',
  80. E_Routes='No routes specified',
  81. E_Method='Invalid method %s';
  82. //@}
  83. private
  84. //! Globals
  85. $hive,
  86. //! Initial settings
  87. $init,
  88. //! Language lookup sequence
  89. $languages,
  90. //! Equivalent Locales
  91. $locales,
  92. //! Default fallback language
  93. $fallback='en',
  94. //! NULL reference
  95. $null=NULL;
  96. /**
  97. Sync PHP global with corresponding hive key
  98. @return array
  99. @param $key string
  100. **/
  101. function sync($key) {
  102. return $this->hive[$key]=&$GLOBALS['_'.$key];
  103. }
  104. /**
  105. Return the parts of specified hive key
  106. @return array
  107. @param $key string
  108. **/
  109. private function cut($key) {
  110. return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
  111. $key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
  112. }
  113. /**
  114. Get hive key reference/contents; Add non-existent hive keys,
  115. array elements, and object properties by default
  116. @return mixed
  117. @param $key string
  118. @param $add bool
  119. **/
  120. function &ref($key,$add=TRUE) {
  121. $parts=$this->cut($key);
  122. if ($parts[0]=='SESSION') {
  123. @session_start();
  124. $this->sync('SESSION');
  125. }
  126. if ($add)
  127. $var=&$this->hive;
  128. else
  129. $var=$this->hive;
  130. $obj=FALSE;
  131. foreach ($parts as $part)
  132. if ($part=='->')
  133. $obj=TRUE;
  134. elseif ($obj) {
  135. $obj=FALSE;
  136. if ($add) {
  137. if (!is_object($var))
  138. $var=new stdclass;
  139. $var=&$var->$part;
  140. }
  141. elseif (isset($var->$part))
  142. $var=$var->$part;
  143. else
  144. return $this->null;
  145. }
  146. elseif ($add) {
  147. if (!is_array($var))
  148. $var=array();
  149. $var=&$var[$part];
  150. }
  151. elseif (isset($var[$part]))
  152. $var=$var[$part];
  153. else
  154. return $this->null;
  155. return $var;
  156. }
  157. /**
  158. Return TRUE if hive key is not empty
  159. @return bool
  160. @param $key string
  161. **/
  162. function exists($key) {
  163. $ref=&$this->ref($key,FALSE);
  164. return isset($ref)?
  165. TRUE:
  166. Cache::instance()->exists($this->hash($key).'.var');
  167. }
  168. /**
  169. Bind value to hive key
  170. @return mixed
  171. @param $key string
  172. @param $val mixed
  173. @param $ttl int
  174. **/
  175. function set($key,$val,$ttl=0) {
  176. if (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
  177. $this->set('REQUEST'.$expr[2],$val);
  178. if ($expr[1]=='COOKIE') {
  179. $parts=$this->cut($key);
  180. call_user_func_array('setcookie',
  181. array_merge(array($parts[1],$val),$this->hive['JAR']));
  182. }
  183. }
  184. else switch ($key) {
  185. case 'CACHE':
  186. $val=Cache::instance()->load($val);
  187. break;
  188. case 'ENCODING':
  189. $val=ini_set('default_charset',$val);
  190. break;
  191. case 'JAR':
  192. call_user_func_array('session_set_cookie_params',$val);
  193. break;
  194. case 'FALLBACK':
  195. $this->fallback=$val;
  196. $lang=$this->language($this->hive['LANGUAGE']);
  197. case 'LANGUAGE':
  198. if (isset($lang) || $lang=$this->language($val))
  199. $val=$this->language($val);
  200. $lex=$this->lexicon($this->hive['LOCALES']);
  201. case 'LOCALES':
  202. if (isset($lex) || $lex=$this->lexicon($val))
  203. $this->mset($lex,NULL,$ttl);
  204. break;
  205. case 'TZ':
  206. date_default_timezone_set($val);
  207. break;
  208. }
  209. $ref=&$this->ref($key);
  210. $ref=$val;
  211. if ($ttl)
  212. // Persist the key-value pair
  213. Cache::instance()->set($this->hash($key).'.var',$val);
  214. return $ref;
  215. }
  216. /**
  217. Retrieve contents of hive key
  218. @return mixed
  219. @param $key string
  220. @param $args string|array
  221. **/
  222. function get($key,$args=NULL) {
  223. if (is_string($val=$this->ref($key,FALSE)) && !is_null($args))
  224. return call_user_func_array(
  225. array($this,'format'),
  226. array_merge(array($val),is_array($args)?$args:array($args))
  227. );
  228. if (is_null($val)) {
  229. // Attempt to retrieve from cache
  230. if (Cache::instance()->exists($this->hash($key).'.var',$data))
  231. return $data;
  232. }
  233. return $val;
  234. }
  235. /**
  236. Unset hive key
  237. @return NULL
  238. @param $key string
  239. **/
  240. function clear($key) {
  241. // Normalize array literal
  242. $cache=Cache::instance();
  243. $parts=$this->cut($key);
  244. if ($key=='CACHE')
  245. // Clear cache contents
  246. $cache->reset();
  247. elseif (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
  248. $this->clear('REQUEST'.$expr[2]);
  249. if ($expr[1]=='COOKIE') {
  250. $parts=$this->cut($key);
  251. $jar=$this->hive['JAR'];
  252. $jar['expire']=strtotime('-1 year');
  253. call_user_func_array('setcookie',
  254. array_merge(array($parts[1],''),$jar));
  255. }
  256. }
  257. elseif ($parts[0]=='SESSION') {
  258. @session_start();
  259. if (empty($parts[1])) {
  260. // End session
  261. session_unset();
  262. session_destroy();
  263. unset($_COOKIE[session_name()]);
  264. header_remove('Set-Cookie');
  265. }
  266. $this->sync('SESSION');
  267. }
  268. if (!isset($parts[1]) && array_key_exists($parts[0],$this->init))
  269. // Reset global to default value
  270. $this->hive[$parts[0]]=$this->init[$parts[0]];
  271. else {
  272. $out='';
  273. $obj=FALSE;
  274. foreach ($parts as $part)
  275. if ($part=='->')
  276. $obj=TRUE;
  277. elseif ($obj) {
  278. $obj=FALSE;
  279. $out.='->'.$out;
  280. }
  281. else
  282. $out.='['.$this->stringify($part).']';
  283. // PHP can't unset a referenced variable
  284. eval('unset($this->hive'.$out.');');
  285. if ($cache->exists($hash=$this->hash($key).'.var'))
  286. // Remove from cache
  287. $cache->clear($hash);
  288. }
  289. }
  290. /**
  291. Multi-variable assignment using associative array
  292. @return NULL
  293. @param $vars array
  294. @param $prefix string
  295. @param $ttl int
  296. **/
  297. function mset(array $vars,$prefix='',$ttl=0) {
  298. foreach ($vars as $key=>$val)
  299. $this->set($prefix.$key,$val,$ttl);
  300. }
  301. /**
  302. Publish hive contents
  303. @return array
  304. **/
  305. function hive() {
  306. return $this->hive;
  307. }
  308. /**
  309. Copy contents of hive variable to another
  310. @return mixed
  311. @param $src string
  312. @param $dst string
  313. **/
  314. function copy($src,$dst) {
  315. $ref=&$this->ref($dst);
  316. return $ref=$this->ref($src);
  317. }
  318. /**
  319. Concatenate string to hive string variable
  320. @return string
  321. @param $key string
  322. @param $val string
  323. **/
  324. function concat($key,$val) {
  325. $ref=&$this->ref($key);
  326. $ref.=$val;
  327. return $ref;
  328. }
  329. /**
  330. Swap keys and values of hive array variable
  331. @return array
  332. @param $key string
  333. @public
  334. **/
  335. function flip($key) {
  336. $ref=&$this->ref($key);
  337. return $ref=array_combine(array_values($ref),array_keys($ref));
  338. }
  339. /**
  340. Add element to the end of hive array variable
  341. @return mixed
  342. @param $key string
  343. @param $val mixed
  344. **/
  345. function push($key,$val) {
  346. $ref=&$this->ref($key);
  347. array_push($ref,$val);
  348. return $val;
  349. }
  350. /**
  351. Remove last element of hive array variable
  352. @return mixed
  353. @param $key string
  354. **/
  355. function pop($key) {
  356. $ref=&$this->ref($key);
  357. return array_pop($ref);
  358. }
  359. /**
  360. Add element to the beginning of hive array variable
  361. @return mixed
  362. @param $key string
  363. @param $val mixed
  364. **/
  365. function unshift($key,$val) {
  366. $ref=&$this->ref($key);
  367. array_unshift($ref,$val);
  368. return $val;
  369. }
  370. /**
  371. Remove first element of hive array variable
  372. @return mixed
  373. @param $key string
  374. **/
  375. function shift($key) {
  376. $ref=&$this->ref($key);
  377. return array_shift($ref);
  378. }
  379. /**
  380. Convert backslashes to slashes
  381. @return string
  382. @param $str string
  383. **/
  384. function fixslashes($str) {
  385. return $str?strtr($str,'\\','/'):$str;
  386. }
  387. /**
  388. Split comma-, semi-colon, or pipe-separated string
  389. @return array
  390. @param $str string
  391. **/
  392. function split($str) {
  393. return array_map('trim',
  394. preg_split('/[,;|]/',$str,0,PREG_SPLIT_NO_EMPTY));
  395. }
  396. /**
  397. Convert PHP expression/value to compressed exportable string
  398. @return string
  399. @param $arg mixed
  400. **/
  401. function stringify($arg) {
  402. switch (gettype($arg)) {
  403. case 'object':
  404. $str='';
  405. if ($this->hive['DEBUG']>2)
  406. foreach ((array)$arg as $key=>$val)
  407. $str.=($str?',':'').$this->stringify(
  408. preg_replace('/[\x00].+?[\x00]/','',$key)).'=>'.
  409. $this->stringify($val);
  410. return addslashes(get_class($arg)).'::__set_state('.$str.')';
  411. case 'array':
  412. $str='';
  413. $num=isset($arg[0]) &&
  414. ctype_digit(implode('',array_keys($arg)));
  415. foreach ($arg as $key=>$val) {
  416. $str.=($str?',':'').
  417. ($num?'':($this->stringify($key).'=>')).
  418. ($arg==$val?'*RECURSION*':$this->stringify($val));
  419. }
  420. return 'array('.$str.')';
  421. default:
  422. return var_export(
  423. is_string($arg)?addcslashes($arg,'\''):$arg,TRUE);
  424. }
  425. }
  426. /**
  427. Flatten array values and return as CSV string
  428. @return string
  429. @param $args array
  430. **/
  431. function csv(array $args) {
  432. return implode(',',array_map('stripcslashes',
  433. array_map(array($this,'stringify'),$args)));
  434. }
  435. /**
  436. Convert snakecase string to camelcase
  437. @return string
  438. @param $str string
  439. **/
  440. function camelcase($str) {
  441. return preg_replace_callback(
  442. '/_(\w)/',
  443. function($match) {
  444. return strtoupper($match[1]);
  445. },
  446. $str
  447. );
  448. }
  449. /**
  450. Convert camelcase string to snakecase
  451. @return string
  452. @param $str string
  453. **/
  454. function snakecase($str) {
  455. return strtolower(preg_replace('/[[:upper:]]/','_\0',$str));
  456. }
  457. /**
  458. Return -1 if specified number is negative, 0 if zero,
  459. or 1 if the number is positive
  460. @return int
  461. @param $num mixed
  462. **/
  463. function sign($num) {
  464. return $num?($num/abs($num)):0;
  465. }
  466. /**
  467. Generate 64bit/base36 hash
  468. @return string
  469. @param $str
  470. **/
  471. function hash($str) {
  472. return str_pad(base_convert(
  473. hexdec(substr(sha1($str),-16)),10,36),11,'0',STR_PAD_LEFT);
  474. }
  475. /**
  476. Return Base64-encoded equivalent
  477. @return string
  478. @param $data string
  479. @param $mime string
  480. **/
  481. function base64($data,$mime) {
  482. return 'data:'.$mime.';base64,'.base64_encode($data);
  483. }
  484. /**
  485. Convert special characters to HTML entities
  486. @return string
  487. @param $str string
  488. **/
  489. function encode($str) {
  490. return @htmlentities($str,ENT_COMPAT,$this->hive['ENCODING'],FALSE)?:
  491. $this->scrub($str);
  492. }
  493. /**
  494. Convert HTML entities back to characters
  495. @return string
  496. @param $str string
  497. **/
  498. function decode($str) {
  499. return html_entity_decode($str,ENT_COMPAT,$this->hive['ENCODING']);
  500. }
  501. /**
  502. Remove HTML tags (except those enumerated) and non-printable
  503. characters to mitigate XSS/code injection attacks
  504. @return mixed
  505. @param $var mixed
  506. @param $tags string
  507. **/
  508. function scrub(&$var,$tags=NULL) {
  509. if (is_string($var)) {
  510. if ($tags)
  511. $tags='<'.implode('><',$this->split($tags)).'>';
  512. $var=trim(preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',
  513. ($tags=='*')?$var:strip_tags($var,$tags)));
  514. }
  515. elseif (is_array($var))
  516. foreach ($var as &$val) {
  517. $this->scrub($val,$tags);
  518. unset($val);
  519. }
  520. return $var;
  521. }
  522. /**
  523. Encode characters to equivalent HTML entities
  524. @return string
  525. @param $arg mixed
  526. **/
  527. function esc($arg) {
  528. if (is_string($arg))
  529. return $this->encode($arg);
  530. if (is_array($arg))
  531. foreach ($arg as &$val) {
  532. $val=$this->esc($val);
  533. unset($val);
  534. }
  535. return $arg;
  536. }
  537. /**
  538. Decode HTML entities to equivalent characters
  539. @return string
  540. @param $arg mixed
  541. **/
  542. function raw($arg) {
  543. if (is_string($arg))
  544. return $this->decode($arg);
  545. if (is_array($arg))
  546. foreach ($arg as &$val) {
  547. $val=$this->raw($val);
  548. unset($val);
  549. }
  550. return $arg;
  551. }
  552. /**
  553. Return locale-aware formatted string
  554. @return string
  555. **/
  556. function format() {
  557. $args=func_get_args();
  558. $val=array_shift($args);
  559. setlocale(LC_ALL,$this->locales);
  560. // Get formatting rules
  561. $conv=localeconv();
  562. $out=preg_replace_callback(
  563. '/\{(?P<pos>\d+)\s*(?:,\s*(?P<type>\w+)\s*'.
  564. '(?:,(?P<mod>(?:\s*\w+(?:\s+\{.+?\}\s*,?)?)*))?)?\}/',
  565. function($expr) use($args,$conv) {
  566. extract($expr);
  567. if (!array_key_exists($pos,$args))
  568. return $expr[0];
  569. if (isset($type))
  570. switch ($type) {
  571. case 'plural':
  572. preg_match_all('/(?<tag>\w+)'.
  573. '(?:\s+\{(?<data>.+?)\})/',
  574. $mod,$matches,PREG_SET_ORDER);
  575. $ord=array('zero','one','two');
  576. foreach ($matches as $match) {
  577. extract($match);
  578. if (isset($ord[$args[$pos]]) &&
  579. $tag==$ord[$args[$pos]] || $tag=='other')
  580. return str_replace('#',$args[$pos],$data);
  581. }
  582. case 'number':
  583. if (isset($mod))
  584. switch ($mod) {
  585. case 'integer':
  586. return
  587. number_format(
  588. $args[$pos],0,'',
  589. $conv['thousands_sep']);
  590. case 'currency':
  591. return
  592. $conv['currency_symbol'].
  593. number_format(
  594. $args[$pos],
  595. $conv['frac_digits'],
  596. $conv['decimal_point'],
  597. $conv['thousands_sep']);
  598. case 'percent':
  599. return
  600. number_format(
  601. $args[$pos]*100,0,
  602. $conv['decimal_point'],
  603. $conv['thousands_sep']).'%';
  604. }
  605. break;
  606. case 'date':
  607. return strftime(empty($mod) ||
  608. $mod=='short'?'%x':'%A, %d %B %Y',
  609. $args[$pos]);
  610. case 'time':
  611. return strftime('%X',$args[$pos]);
  612. default:
  613. return $expr[0];
  614. }
  615. return $args[$pos];
  616. },
  617. $val
  618. );
  619. return preg_match('/^win/i',PHP_OS)?
  620. iconv('Windows-1252',$this->hive['ENCODING'],$out):$out;
  621. }
  622. /**
  623. Assign/auto-detect language
  624. @return string
  625. @param $code string
  626. **/
  627. function language($code=NULL) {
  628. if (!$code) {
  629. $headers=$this->hive['HEADERS'];
  630. if (isset($headers['Accept-Language']))
  631. $code=$headers['Accept-Language'];
  632. }
  633. $code=str_replace('-','_',preg_replace('/;q=.+?(?=,|$)/','',$code));
  634. $code.=($code?',':'').$this->fallback;
  635. $this->languages=array();
  636. foreach (array_reverse(explode(',',$code)) as $lang) {
  637. if (preg_match('/^(\w{2})(?:_(\w{2}))?\b/i',$lang,$parts)) {
  638. // Generic language
  639. array_unshift($this->languages,$parts[1]);
  640. if (isset($parts[2])) {
  641. // Specific language
  642. $parts[0]=$parts[1].'_'.($parts[2]=strtoupper($parts[2]));
  643. array_unshift($this->languages,$parts[0]);
  644. }
  645. }
  646. }
  647. $this->languages=array_unique($this->languages);
  648. $this->locales=array();
  649. $windows=preg_match('/^win/i',PHP_OS);
  650. foreach ($this->languages as $locale) {
  651. if ($windows) {
  652. $parts=explode('_',$locale);
  653. $locale=@constant('ISO::LC_'.$parts[0]);
  654. if (isset($parts[1]) &&
  655. $country=@constant('ISO::CC_'.$parts[1]))
  656. $locale.='_'.$country;
  657. }
  658. $this->locales[]=$locale;
  659. $this->locales[]=$locale.'.'.$this->hive['ENCODING'];
  660. }
  661. return implode(',',$this->languages);
  662. }
  663. /**
  664. Transfer lexicon entries to hive
  665. @return NULL
  666. @param $path string
  667. **/
  668. function lexicon($path) {
  669. $lex=array();
  670. foreach ($this->languages as $lang) {
  671. if ((is_file($file=($base=$path.$lang).'.php') ||
  672. is_file($file=$base.'.php')) &&
  673. is_array($dict=require($file)))
  674. $lex+=$dict;
  675. elseif (is_file($file=$base.'.ini')) {
  676. preg_match_all(
  677. '/(?<=^|\n)(?:'.
  678. '(?:;[^\n]*)|(?:<\?php.+?\?>?)|'.
  679. '(.+?)\h*=\h*'.
  680. '((?:\\\\\h*\r?\n|.+?)*)'.
  681. ')(?=\r?\n|$)/',
  682. file_get_contents($file),$matches,PREG_SET_ORDER);
  683. if ($matches)
  684. foreach ($matches as $match)
  685. if (isset($match[1]) &&
  686. !array_key_exists($match[1],$lex))
  687. $lex[$match[1]]=preg_replace(
  688. '/\\\\\h*\r?\n/','',$match[2]);
  689. }
  690. }
  691. return $lex;
  692. }
  693. /**
  694. Return string representation of PHP value
  695. @return string
  696. @param $arg mixed
  697. **/
  698. function serialize($arg) {
  699. switch (strtolower($this->hive['SERIALIZER'])) {
  700. case 'igbinary':
  701. return igbinary_serialize($arg);
  702. case 'json':
  703. return json_encode($arg);
  704. default:
  705. return serialize($arg);
  706. }
  707. }
  708. /**
  709. Return PHP value derived from string
  710. @return string
  711. @param $arg mixed
  712. **/
  713. function unserialize($arg) {
  714. switch (strtolower($this->hive['SERIALIZER'])) {
  715. case 'igbinary':
  716. return igbinary_unserialize($arg);
  717. case 'json':
  718. return json_decode($arg);
  719. default:
  720. return unserialize($arg);
  721. }
  722. }
  723. /**
  724. Send HTTP/1.1 status header; Return text equivalent of status code
  725. @return string
  726. @param $code int
  727. **/
  728. function status($code) {
  729. if (PHP_SAPI!='cli')
  730. header('HTTP/1.1 '.$code);
  731. return @constant('self::HTTP_'.$code);
  732. }
  733. /**
  734. Send cache metadata to HTTP client
  735. @return NULL
  736. @param $secs int
  737. **/
  738. function expire($secs=0) {
  739. if (PHP_SAPI!='cli') {
  740. header('X-Powered-By: '.$this->hive['PACKAGE']);
  741. if ($secs) {
  742. $time=microtime(TRUE);
  743. header_remove('Pragma');
  744. header('Expires: '.gmdate('r',$time+$secs));
  745. header('Cache-Control: max-age='.$secs);
  746. header('Last-Modified: '.gmdate('r'));
  747. $headers=$this->hive['HEADERS'];
  748. if (isset($headers['If-Modified-Since']) &&
  749. strtotime($headers['If-Modified-Since'])+$secs>$time) {
  750. $this->status(304);
  751. die;
  752. }
  753. }
  754. else
  755. header('Cache-Control: no-cache, no-store, must-revalidate');
  756. }
  757. }
  758. /**
  759. Log error; Execute ONERROR handler if defined, else display
  760. default error page (HTML for synchronous requests, JSON string
  761. for AJAX requests)
  762. @return NULL
  763. @param $code int
  764. @param $text string
  765. @param $trace array
  766. **/
  767. function error($code,$text='',array $trace=NULL) {
  768. $prior=$this->hive['ERROR'];
  769. $header=$this->status($code);
  770. $req=$this->hive['VERB'].' '.$this->hive['URI'];
  771. if (!$text)
  772. $text='HTTP '.$code.' ('.$req.')';
  773. error_log($text);
  774. if (!$trace)
  775. $trace=array_slice(debug_backtrace(0),1);
  776. $debug=$this->hive['DEBUG'];
  777. $trace=array_filter(
  778. $trace,
  779. function($frame) use($debug) {
  780. return isset($frame['file']) &&
  781. ($frame['file']!=__FILE__ || $debug>1) &&
  782. (empty($frame['function']) ||
  783. !preg_match('/^(?:(?:trigger|user)_error|'.
  784. '__call|call_user_func)/',$frame['function']));
  785. }
  786. );
  787. $highlight=$this->hive['HIGHLIGHT'] &&
  788. is_file($css=__DIR__.'/'.self::CSS);
  789. $out='';
  790. $eol="\n";
  791. // Analyze stack trace
  792. foreach ($trace as $frame) {
  793. $line='';
  794. if (isset($frame['class']))
  795. $line.=$frame['class'].$frame['type'];
  796. if (isset($frame['function']))
  797. $line.=$frame['function'].'('.(isset($frame['args'])?
  798. $this->csv($frame['args']):'').')';
  799. $src=$this->fixslashes($frame['file']).':'.$frame['line'].' ';
  800. error_log('- '.$src.$line);
  801. $out.='• '.($highlight?
  802. ($this->highlight($src).' '.$this->highlight($line)):
  803. ($src.$line)).$eol;
  804. }
  805. $this->hive['ERROR']=array(
  806. 'code'=>$code,
  807. 'text'=>$text,
  808. 'trace'=>$trace
  809. );
  810. ob_clean();
  811. if ($this->hive['ONERROR'])
  812. // Execute custom error handler
  813. $this->call($this->hive['ONERROR'],$this);
  814. elseif (!$prior && PHP_SAPI!='cli' && !$this->hive['QUIET'])
  815. echo $this->hive['AJAX']?
  816. json_encode($this->hive['ERROR']):
  817. ('<!DOCTYPE html>'.
  818. '<html>'.$eol.
  819. '<head>'.
  820. '<title>'.$code.' '.$header.'</title>'.
  821. ($highlight?
  822. ('<style>'.file_get_contents($css).'</style>'):'').
  823. '</head>'.$eol.
  824. '<body>'.$eol.
  825. '<h1>'.$header.'</h1>'.$eol.
  826. '<p>'.$this->encode($text?:$req).'</p>'.$eol.
  827. ($debug?('<pre>'.$out.'</pre>'.$eol):'').
  828. '</body>'.$eol.
  829. '</html>');
  830. die;
  831. }
  832. /**
  833. Mock HTTP request
  834. @return NULL
  835. @param $pattern string
  836. @param $args array
  837. @param $headers array
  838. @param $body string
  839. **/
  840. function mock($pattern,array $args=NULL,array $headers=NULL,$body=NULL) {
  841. $types=array('sync','ajax');
  842. preg_match('/([\|\w]+)\h+([^\h]+)'.
  843. '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
  844. if (empty($parts[2]))
  845. user_error(sprintf(self::E_Pattern,$pattern));
  846. $verb=strtoupper($parts[1]);
  847. $url=parse_url($parts[2]);
  848. $query='';
  849. if ($args)
  850. $query.=http_build_query($args);
  851. $query.=isset($url['query'])?(($query?'&':'').$url['query']):'';
  852. if ($query && preg_match('/GET|POST/',$verb)) {
  853. parse_str($query,$GLOBALS['_'.$verb]);
  854. parse_str($query,$GLOBALS['_REQUEST']);
  855. }
  856. foreach ($headers?:array() as $key=>$val)
  857. $_SERVER['HTTP_'.str_replace('-','_',strtoupper($key))]=$val;
  858. $this->hive['VERB']=$verb;
  859. $this->hive['URI']=$this->hive['BASE'].$url['path'];
  860. $this->hive['AJAX']=isset($parts[3]) &&
  861. preg_match('/ajax/i',$parts[3]);
  862. if (preg_match('/GET|HEAD/',$verb) && $query)
  863. $this->hive['URI'].='?'.$query;
  864. else
  865. $this->hive['BODY']=$body?:$query;
  866. $this->run();
  867. }
  868. /**
  869. Bind handler to route pattern
  870. @return NULL
  871. @param $pattern string
  872. @param $handler callback
  873. @param $ttl int
  874. @param $kbps int
  875. **/
  876. function route($pattern,$handler,$ttl=0,$kbps=0) {
  877. $types=array('sync','ajax');
  878. preg_match('/([\|\w]+)\h+([^\h]+)'.
  879. '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
  880. if (empty($parts[2]))
  881. user_error(sprintf(self::E_Pattern,$pattern));
  882. $type=empty($parts[3])?
  883. self::REQ_SYNC|self::REQ_AJAX:
  884. constant('self::REQ_'.strtoupper($parts[3]));
  885. foreach ($this->split($parts[1]) as $verb) {
  886. if (!preg_match('/'.self::VERBS.'/',$verb))
  887. $this->error(501,$verb.' '.$this->hive['URI']);
  888. $this->hive['ROUTES'][$parts[2]][$type]
  889. [strtoupper($verb)]=array($handler,$ttl,$kbps);
  890. }
  891. }
  892. /**
  893. Reroute to specified URI
  894. @return NULL
  895. @param $uri string
  896. **/
  897. function reroute($uri) {
  898. if (PHP_SAPI!='cli') {
  899. @session_commit();
  900. header('Location: '.(preg_match('/^https?:\/\//',$uri)?
  901. $uri:($this->hive['BASE'].$uri)));
  902. $this->status($this->hive['VERB']=='GET'?301:303);
  903. die;
  904. }
  905. $this->mock('GET '.$uri);
  906. }
  907. /**
  908. Provide ReST interface by mapping HTTP verb to class method
  909. @param $url string
  910. @param $class string
  911. @param $ttl int
  912. @param $kbps int
  913. **/
  914. function map($url,$class,$ttl=0,$kbps=0) {
  915. foreach (explode('|',self::VERBS) as $method)
  916. $this->route($method.' '.
  917. $url,$class.'->'.strtolower($method),$ttl,$kbps);
  918. }
  919. /**
  920. Return TRUE if IPv4 address exists in DNSBL
  921. @return bool
  922. @param $ip string
  923. **/
  924. function blacklisted($ip) {
  925. if ($this->hive['DNSBL'] &&
  926. !in_array($ip,
  927. is_array($this->hive['EXEMPT'])?
  928. $this->hive['EXEMPT']:
  929. $this->split($this->hive['EXEMPT']))) {
  930. // Reverse IPv4 dotted quad
  931. $rev=implode('.',array_reverse(explode('.',$ip)));
  932. foreach (is_array($this->hive['DNSBL'])?
  933. $this->hive['DNSBL']:
  934. $this->split($this->hive['DNSBL']) as $server)
  935. // DNSBL lookup
  936. if (checkdnsrr($rev.'.'.$server,'A'))
  937. return TRUE;
  938. }
  939. return FALSE;
  940. }
  941. /**
  942. Match routes against incoming URI
  943. @return NULL
  944. **/
  945. function run() {
  946. if ($this->blacklisted($this->hive['IP']))
  947. // Spammer detected
  948. $this->error(403);
  949. if (!$this->hive['ROUTES'])
  950. // No routes defined
  951. user_error(self::E_Routes);
  952. // Match specific routes first
  953. krsort($this->hive['ROUTES']);
  954. // Convert to BASE-relative URL
  955. $req=preg_replace(
  956. '/^'.preg_quote($this->hive['BASE'],'/').'\b(.*)/','\1',
  957. $this->hive['URI']
  958. );
  959. $allowed=array();
  960. $case=$this->hive['CASELESS']?'i':'';
  961. foreach ($this->hive['ROUTES'] as $url=>$types) {
  962. if (!preg_match('/^'.
  963. preg_replace('/@(\w+\b)/','(?P<\1>[^\/\?]+)',
  964. str_replace('\*','(.*)',preg_quote($url,'/'))).
  965. '\/?(?:\?.*)?$/'.$case.'um',$req,$args))
  966. continue;
  967. $route=NULL;
  968. if (isset($types[$this->hive['AJAX']+1]))
  969. $route=$types[$this->hive['AJAX']+1];
  970. elseif (isset($types[self::REQ_SYNC|self::REQ_AJAX]))
  971. $route=$types[self::REQ_SYNC|self::REQ_AJAX];
  972. if (!$route)
  973. continue;
  974. if (isset($route[$this->hive['VERB']])) {
  975. $parts=parse_url($req);
  976. if ($this->hive['VERB']=='GET' &&
  977. preg_match('/.+\/$/',$parts['path']))
  978. $this->reroute(substr($parts['path'],0,-1).
  979. (isset($parts['query'])?('?'.$parts['query']):''));
  980. list($handler,$ttl,$kbps)=$route[$this->hive['VERB']];
  981. if (is_bool(strpos($url,'/*')))
  982. foreach (array_keys($args) as $key)
  983. if (is_numeric($key) && $key)
  984. unset($args[$key]);
  985. if (is_string($handler))
  986. // Replace route pattern tokens in handler if any
  987. $handler=preg_replace_callback('/@(\w+\b)/',
  988. function($id) use($args) {
  989. return isset($args[$id[1]])?$args[$id[1]]:$id[0];
  990. },
  991. $handler
  992. );
  993. // Capture values of route pattern tokens
  994. $this->hive['PARAMS']=$args=array_map('urldecode',$args);
  995. // Save matching route
  996. $this->hive['PATTERN']=$url;
  997. // Process request
  998. $now=microtime(TRUE);
  999. if (preg_match('/GET|HEAD/',$this->hive['VERB']) &&
  1000. isset($ttl)) {
  1001. // Only GET and HEAD requests are cacheable
  1002. $headers=$this->hive['HEADERS'];
  1003. $cache=Cache::instance();
  1004. $cached=$cache->exists(
  1005. $hash=$this->hash($this->hive['VERB'].' '.
  1006. $this->hive['URI']).'.url',$data);
  1007. if ($cached && $cached+$ttl>$now) {
  1008. if (isset($headers['If-Modified-Since']) &&
  1009. strtotime($headers['If-Modified-Since'])>
  1010. floor($cached)) {
  1011. // HTTP client-cached page is fresh
  1012. $this->status(304);
  1013. die;
  1014. }
  1015. // Retrieve from cache backend
  1016. list($headers,$body)=$data;
  1017. if (PHP_SAPI!='cli')
  1018. array_walk($headers,'header');
  1019. $this->expire($cached+$ttl-$now);
  1020. }
  1021. else
  1022. // Expire HTTP client-cached page
  1023. $this->expire($ttl);
  1024. }
  1025. else
  1026. $this->expire(0);
  1027. ob_start();
  1028. // Call route handler
  1029. $this->call($handler,array($this,$args),
  1030. 'beforeroute,afterroute');
  1031. $body=ob_get_clean();
  1032. if ($ttl && !error_get_last())
  1033. // Save to cache backend
  1034. $cache->set($hash,array(headers_list(),$body),$ttl);
  1035. $this->hive['RESPONSE']=$body;
  1036. if (!$this->hive['QUIET']) {
  1037. if ($kbps) {
  1038. $ctr=0;
  1039. foreach (str_split($body,1024) as $part) {
  1040. // Throttle output
  1041. $ctr++;
  1042. if ($ctr/$kbps>$elapsed=microtime(TRUE)-$now &&
  1043. !connection_aborted())
  1044. usleep(1e6*($ctr/$kbps-$elapsed));
  1045. echo $part;
  1046. }
  1047. }
  1048. else
  1049. echo $body;
  1050. }
  1051. return;
  1052. }
  1053. $allowed=array_keys($route);
  1054. break;
  1055. }
  1056. if (!$allowed)
  1057. // URL doesn't match any route
  1058. $this->error(404);
  1059. elseif (PHP_SAPI!='cli') {
  1060. // Unhandled HTTP method
  1061. header('Allow: '.implode(',',$allowed));
  1062. if ($this->hive['VERB']!='OPTIONS')
  1063. $this->error(405);
  1064. }
  1065. }
  1066. /**
  1067. Execute callback/hooks (supports 'class->method' format)
  1068. @return mixed|FALSE
  1069. @param $func callback
  1070. @param $args mixed
  1071. @param $hooks string
  1072. **/
  1073. function call($func,$args=NULL,$hooks='') {
  1074. if (!is_array($args))
  1075. $args=array($args);
  1076. // Execute function; abort if callback/hook returns FALSE
  1077. if (is_string($func) &&
  1078. preg_match('/(.+)\h*(->|::)\h*(.+)/s',$func,$parts)) {
  1079. // Convert string to executable PHP callback
  1080. if (!class_exists($parts[1]))
  1081. $this->error(404);
  1082. if ($parts[2]=='->')
  1083. $parts[1]=is_subclass_of($parts[1],'Prefab')?
  1084. call_user_func($parts[1].'::instance'):
  1085. new $parts[1];
  1086. $func=array($parts[1],$parts[3]);
  1087. }
  1088. if (!is_callable($func) && $hooks=='beforeroute,afterroute')
  1089. // No route handler
  1090. $this->error(404);
  1091. $obj=FALSE;
  1092. if (is_array($func)) {
  1093. $hooks=$this->split($hooks);
  1094. $obj=TRUE;
  1095. }
  1096. // Execute pre-route hook if any
  1097. if ($obj && $hooks && in_array($hook='beforeroute',$hooks) &&
  1098. method_exists($func[0],$hook) &&
  1099. call_user_func_array(array($func[0],$hook),$args)===FALSE)
  1100. return FALSE;
  1101. // Execute callback
  1102. $out=call_user_func_array($func,$args?:array());
  1103. if ($out===FALSE)
  1104. return FALSE;
  1105. // Execute post-route hook if any
  1106. if ($obj && $hooks && in_array($hook='afterroute',$hooks) &&
  1107. method_exists($func[0],$hook) &&
  1108. call_user_func_array(array($func[0],$hook),$args)===FALSE)
  1109. return FALSE;
  1110. return $out;
  1111. }
  1112. /**
  1113. Execute specified callbacks in succession; Apply same arguments
  1114. to all callbacks
  1115. @return array
  1116. @param $funcs array|string
  1117. @param $args mixed
  1118. **/
  1119. function chain($funcs,$args=NULL) {
  1120. $out=array();
  1121. foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
  1122. $out[]=$this->call($func,$args);
  1123. return $out;
  1124. }
  1125. /**
  1126. Execute specified callbacks in succession; Relay result of
  1127. previous callback as argument to the next callback
  1128. @return array
  1129. @param $funcs array|string
  1130. @param $args mixed
  1131. **/
  1132. function relay($funcs,$args=NULL) {
  1133. foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
  1134. $args=array($this->call($func,$args));
  1135. return array_shift($args);
  1136. }
  1137. /**
  1138. Configure framework according to .ini-style file settings
  1139. @return NULL
  1140. @param $file string
  1141. **/
  1142. function config($file) {
  1143. preg_match_all(
  1144. '/(?<=^|\n)(?:'.
  1145. '(?:;[^\n]*)|(?:<\?php.+?\?>?)|'.
  1146. '(?:\[(.+?)\])|'.
  1147. '(.+?)\h*=\h*'.
  1148. '((?:\\\\\h*\r?\n|.+?)*)'.
  1149. ')(?=\r?\n|$)/',
  1150. file_get_contents($file),$matches,PREG_SET_ORDER);
  1151. if ($matches) {
  1152. $sec='globals';
  1153. foreach ($matches as $match) {
  1154. if (count($match)<2)
  1155. continue;
  1156. if ($match[1])
  1157. $sec=$match[1];
  1158. elseif (in_array($sec,array('routes','maps'))) {
  1159. call_user_func_array(
  1160. array($this,rtrim($sec,'s')),
  1161. array_merge(array($match[2]),str_getcsv($match[3])));
  1162. }
  1163. else {
  1164. $args=array_map(
  1165. function($val) {
  1166. $quote=(isset($val[0]) && $val[0]=="\x00");
  1167. $val=trim($val);
  1168. if (!$quote && is_numeric($val))
  1169. return $val+0;
  1170. if (preg_match('/^\w+$/i',$val) && defined($val))
  1171. return constant($val);
  1172. return preg_replace(
  1173. '/\\\\\h*\r?\n/','',$val);
  1174. },
  1175. str_getcsv(
  1176. // Mark quoted strings with 0x00 whitespace
  1177. preg_replace('/"(.+?)"/',"\x00\\1",$match[3]))
  1178. );
  1179. call_user_func_array(array($this,'set'),
  1180. array_merge(
  1181. array($match[2]),
  1182. count($args)>1?array($args):$args));
  1183. }
  1184. }
  1185. }
  1186. }
  1187. /**
  1188. Create mutex, invoke callback then drop ownership when done
  1189. @return mixed
  1190. @param $id string
  1191. @param $func callback
  1192. @param $args mixed
  1193. **/
  1194. function mutex($id,$func,$args=NULL) {
  1195. if (!is_dir($tmp=$this->hive['TEMP']))
  1196. mkdir($tmp,self::MODE,TRUE);
  1197. // Use filesystem lock
  1198. if (is_file($lock=$tmp.
  1199. $this->hash($this->hive['ROOT'].$this->hive['BASE']).'.'.
  1200. $this->hash($id).'.lock') &&
  1201. filemtime($lock)+ini_get('max_execution_time')<microtime(TRUE))
  1202. // Stale lock
  1203. @unlink($lock);
  1204. while (!$handle=@fopen($lock,'x') && !connection_aborted())
  1205. usleep(mt_rand(0,100));
  1206. $out=$this->call($func,$args);
  1207. fclose($handle);
  1208. @unlink($lock);
  1209. return $out;
  1210. }
  1211. /**
  1212. Read file
  1213. @return string
  1214. @param $file string
  1215. **/
  1216. function read($file) {
  1217. return file_get_contents($file);
  1218. }
  1219. /**
  1220. Exclusive file write
  1221. @return int|FALSE
  1222. @param $file string
  1223. @param $data mixed
  1224. @param $append bool
  1225. **/
  1226. function write($file,$data,$append=FALSE) {
  1227. return file_put_contents($file,$data,LOCK_EX|($append?FILE_APPEND:0));
  1228. }
  1229. /**
  1230. Apply syntax highlighting
  1231. @return string
  1232. @param $text string
  1233. **/
  1234. function highlight($text) {
  1235. $out='';
  1236. $pre=FALSE;
  1237. $text=trim($text);
  1238. if (!preg_match('/^<\?php/',$text)) {
  1239. $text='<?php '.$text;
  1240. $pre=TRUE;
  1241. }
  1242. foreach (token_get_all($text) as $token)
  1243. if ($pre)
  1244. $pre=FALSE;
  1245. else
  1246. $out.='<span'.
  1247. (is_array($token)?
  1248. (' class="'.
  1249. substr(strtolower(token_name($token[0])),2).'">'.
  1250. $this->encode($token[1]).''):
  1251. ('>'.$this->encode($token))).
  1252. '</span>';
  1253. return $out?('<code class="php">'.$out.'</code>'):$text;
  1254. }
  1255. /**
  1256. Dump expression with syntax highlighting
  1257. @return NULL
  1258. @param $expr mixed
  1259. **/
  1260. function dump($expr) {
  1261. echo $this->highlight($this->stringify($expr));
  1262. }
  1263. /**
  1264. Namespace-aware class autoloader
  1265. @return mixed
  1266. @param $class string
  1267. **/
  1268. protected function autoload($class) {
  1269. $class=$this->fixslashes(ltrim($class,'\\'));
  1270. foreach ($this->split($this->hive['PLUGINS'].';'.
  1271. $this->hive['AUTOLOAD']) as $auto)
  1272. if (is_file($file=$auto.$class.'.php') ||
  1273. is_file($file=$auto.strtolower($class).'.php'))
  1274. return require($file);
  1275. }
  1276. /**
  1277. Execute framework/application shutdown sequence
  1278. @return NULL
  1279. **/
  1280. function unload() {
  1281. if (($error=error_get_last()) &&
  1282. in_array($error['type'],
  1283. array(E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR)))
  1284. // Fatal error detected
  1285. $this->error(500,sprintf(self::E_Fatal,$error['message']),
  1286. array($error));
  1287. if (isset($this->hive['UNLOAD']))
  1288. $this->hive['UNLOAD']($this);
  1289. }
  1290. /**
  1291. Return class instance
  1292. @return object
  1293. **/
  1294. static function instance() {
  1295. if (!Registry::exists($class=__CLASS__))
  1296. Registry::set($class,new $class);
  1297. return Registry::get($class);
  1298. }
  1299. //! Prohibit cloning
  1300. private function __clone() {
  1301. }
  1302. //! Bootstrap
  1303. private function __construct() {
  1304. // Managed directives
  1305. ini_set('default_charset',$charset='UTF-8');
  1306. ini_set('display_errors',0);
  1307. // Deprecated directives
  1308. ini_set('magic_quotes_gpc',0);
  1309. ini_set('register_globals',0);
  1310. // Abort on startup error
  1311. // Intercept errors/exceptions; PHP5.3-compatible
  1312. error_reporting(E_ALL|E_STRICT);
  1313. $fw=$this;
  1314. set_exception_handler(
  1315. function($obj) use($fw) {
  1316. $fw->error(500,$obj->getmessage(),$obj->gettrace());
  1317. }
  1318. );
  1319. set_error_handler(
  1320. function($code,$text) use($fw) {
  1321. if (error_reporting())
  1322. throw new ErrorException($text,$code);
  1323. }
  1324. );
  1325. if (!isset($_SERVER['SERVER_NAME']))
  1326. $_SERVER['SERVER_NAME']=gethostname();
  1327. if (PHP_SAPI=='cli') {
  1328. // Emulate HTTP request
  1329. if (isset($_SERVER['argc']) && $_SERVER['argc']<2) {
  1330. $_SERVER['argc']++;
  1331. $_SERVER['argv'][1]='/';
  1332. }
  1333. $_SERVER['REQUEST_METHOD']='GET';
  1334. $_SERVER['REQUEST_URI']=$_SERVER['argv'][1];
  1335. }
  1336. $headers=getallheaders();
  1337. if (isset($headers['X-HTTP-Method-Override']))
  1338. $_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
  1339. $scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
  1340. isset($headers['X-Forwarded-Proto']) &&
  1341. $headers['X-Forwarded-Proto']=='https'?'https':'http';
  1342. $base=implode('/',array_map('urlencode',
  1343. explode('/',$this->fixslashes(
  1344. preg_replace('/\/[^\/]+$/','',$_SERVER['SCRIPT_NAME'])))));
  1345. call_user_func_array('session_set_cookie_params',
  1346. $jar=array(
  1347. 'expire'=>0,
  1348. 'path'=>$base?:'/',
  1349. 'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) &&
  1350. !filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
  1351. $_SERVER['SERVER_NAME']:'',
  1352. 'secure'=>($scheme=='https'),
  1353. 'httponly'=>TRUE
  1354. )
  1355. );
  1356. // Default configuration
  1357. $this->hive=array(
  1358. 'AJAX'=>isset($headers['X-Requested-With']) &&
  1359. $headers['X-Requested-With']=='XMLHttpRequest',
  1360. 'AUTOLOAD'=>'./',
  1361. 'BASE'=>$base,
  1362. 'BODY'=>file_get_contents('php://input'),
  1363. 'CACHE'=>FALSE,
  1364. 'CASELESS'=>TRUE,
  1365. 'DEBUG'=>0,
  1366. 'DIACRITICS'=>array(),
  1367. 'DNSBL'=>'',
  1368. 'ENCODING'=>$charset,
  1369. 'ERROR'=>NULL,
  1370. 'ESCAPE'=>TRUE,
  1371. 'EXEMPT'=>NULL,
  1372. 'FALLBACK'=>$this->fallback,
  1373. 'HEADERS'=>$headers,
  1374. 'HIGHLIGHT'=>TRUE,
  1375. 'HOST'=>$_SERVER['SERVER_NAME'],
  1376. 'IP'=>isset($headers['Client-IP'])?
  1377. $headers['Client-IP']:
  1378. (isset($headers['X-Forwarded-For']) &&
  1379. ($ip=strstr($headers['X-Forwarded-For'],',',TRUE))?
  1380. $ip:
  1381. (isset($_SERVER['REMOTE_ADDR'])?
  1382. $_SERVER['REMOTE_ADDR']:'')),
  1383. 'JAR'=>$jar,
  1384. 'LANGUAGE'=>isset($headers['Accept-Language'])?
  1385. $this->language($headers['Accept-Language']):$this->fallback,
  1386. 'LOCALES'=>'./',
  1387. 'LOGS'=>'./',
  1388. 'ONERROR'=>NULL,
  1389. 'PACKAGE'=>self::PACKAGE,
  1390. 'PARAMS'=>array(),
  1391. 'PATTERN'=>NULL,
  1392. 'PLUGINS'=>$this->fixslashes(__DIR__).'/',
  1393. 'PORT'=>isset($_SERVER['SERVER_PORT'])?
  1394. $_SERVER['SERVER_PORT']:NULL,
  1395. 'QUIET'=>FALSE,
  1396. 'REALM'=>$scheme.'://'.
  1397. $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'],
  1398. 'RESPONSE'=>'',
  1399. 'ROOT'=>$_SERVER['DOCUMENT_ROOT'],
  1400. 'ROUTES'=>array(),
  1401. 'SCHEME'=>$scheme,
  1402. 'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
  1403. 'TEMP'=>'tmp/',
  1404. 'TIME'=>microtime(TRUE),
  1405. 'TZ'=>date_default_timezone_get(),
  1406. 'UI'=>'./',
  1407. 'UNLOAD'=>NULL,
  1408. 'UPLOADS'=>'./',
  1409. 'URI'=>&$_SERVER['REQUEST_URI'],
  1410. 'VERB'=>&$_SERVER['REQUEST_METHOD'],
  1411. 'VERSION'=>self::VERSION
  1412. );
  1413. if (PHP_SAPI=='cli-server' &&
  1414. preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
  1415. $this->reroute('/');
  1416. if (ini_get('auto_globals_jit'))
  1417. // Override setting
  1418. $GLOBALS+=array('_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST);
  1419. // Sync PHP globals with corresponding hive keys
  1420. $this->init=$this->hive;
  1421. foreach (explode('|',self::GLOBALS) as $global) {
  1422. $sync=$this->sync($global);
  1423. $this->init+=array(
  1424. $global=>preg_match('/SERVER|ENV/',$global)?$sync:array()
  1425. );
  1426. }
  1427. if ($error=error_get_last())
  1428. // Error detected
  1429. $this->error(500,sprintf(self::E_Fatal,$error['message']),
  1430. array($error));
  1431. // Register framework autoloader
  1432. spl_autoload_register(array($this,'autoload'));
  1433. // Register shutdown handler
  1434. register_shutdown_function(array($this,'unload'));
  1435. }
  1436. /**
  1437. Wrap-up
  1438. @return NULL
  1439. **/
  1440. function __destruct() {
  1441. Registry::clear(__CLASS__);
  1442. }
  1443. }
  1444. //! Cache engine
  1445. final class Cache {
  1446. private
  1447. //! Cache DSN
  1448. $dsn,
  1449. //! Prefix for cache entries
  1450. $prefix,
  1451. //! MemCache object
  1452. $ref;
  1453. /**
  1454. Return timestamp of cache entry or FALSE if not found
  1455. @return float|FALSE
  1456. @param $key string
  1457. @param $val mixed
  1458. **/
  1459. function exists($key,&$val=NULL) {
  1460. $fw=Base::instance();
  1461. if (!$this->dsn)
  1462. return FALSE;
  1463. $ndx=$this->prefix.'.'.$key;
  1464. $parts=explode('=',$this->dsn,2);
  1465. switch ($parts[0]) {
  1466. case 'apc':
  1467. $raw=apc_fetch($ndx);
  1468. break;
  1469. case 'memcache':
  1470. $raw=memcache_get($this->ref,$ndx);
  1471. break;
  1472. case 'wincache':
  1473. $raw=wincache_ucache_get($ndx);
  1474. break;
  1475. case 'xcache':
  1476. $raw=xcache_get($ndx);
  1477. break;
  1478. case 'folder':
  1479. if (is_file($file=$parts[1].$ndx))
  1480. $raw=$fw->read($file);
  1481. break;
  1482. }
  1483. if (isset($raw)) {
  1484. list($val,$time,$ttl)=$fw->unserialize($raw);
  1485. if (!$ttl || $time+$ttl>microtime(TRUE))
  1486. return $time;
  1487. $this->clear($key);
  1488. }
  1489. return FALSE;
  1490. }
  1491. /**
  1492. Store value in cache
  1493. @return mixed|FALSE
  1494. @param $key string
  1495. @param $val mixed
  1496. @param $ttl int
  1497. **/
  1498. function set($key,$val,$ttl=0) {
  1499. $fw=Base::instance();
  1500. if (!$this->dsn)
  1501. return TRUE;
  1502. $ndx=$this->prefix.'.'.$key;
  1503. $data=$fw->serialize(array($val,microtime(TRUE),$ttl));
  1504. $parts=explode('=',$this->dsn,2);
  1505. switch ($parts[0]) {
  1506. case 'apc':
  1507. return apc_store($ndx,$data,$ttl);
  1508. case 'memcache':
  1509. return memcache_set($this->ref,$ndx,$data,0,$ttl);
  1510. case 'wincache':
  1511. return wincache_ucache_set($ndx,$data,$ttl);
  1512. case 'xcache':
  1513. return xcache_set($ndx,$data,$ttl);
  1514. case 'folder':
  1515. return $fw->write($parts[1].$ndx,$data);
  1516. }
  1517. return FALSE;
  1518. }
  1519. /**
  1520. Retrieve value of cache entry
  1521. @return mixed|FALSE
  1522. @param $key string
  1523. **/
  1524. function get($key) {
  1525. return $this->dsn && $this->exists($key,$data)?$data:FALSE;
  1526. }
  1527. /**
  1528. Delete cache entry
  1529. @return bool
  1530. @param $key string
  1531. **/
  1532. function clear($key) {
  1533. if (!$this->dsn)
  1534. return;
  1535. $ndx=$this->prefix.'.'.$key;
  1536. $parts=explode('=',$this->dsn,2);
  1537. switch ($parts[0]) {
  1538. case 'apc':
  1539. return apc_delete($ndx);
  1540. case 'memcache':
  1541. return memcache_delete($this->ref,$ndx);
  1542. case 'wincache':
  1543. return wincache_ucache_delete($ndx);
  1544. case 'xcache':
  1545. return xcache_unset($ndx);
  1546. case 'folder':
  1547. return is_file($file=$parts[1].$ndx) && @unlink($file);
  1548. }
  1549. return FALSE;
  1550. }
  1551. /**
  1552. Clear contents of cache backend
  1553. @return bool
  1554. @param $suffix string
  1555. @param $lifetime int
  1556. **/
  1557. function reset($suffix=NULL,$lifetime=0) {
  1558. if (!$this->dsn)
  1559. return TRUE;
  1560. $regex='/'.preg_quote($this->prefix.'.','/').'.+?'.
  1561. preg_quote($suffix,'/').'/';
  1562. $parts=explode('=',$this->dsn,2);
  1563. switch ($parts[0]) {
  1564. case 'apc':
  1565. $info=apc_cache_info('user');
  1566. foreach ($info['cache_list'] as $item)
  1567. if (preg_match($regex,$item['info']) &&
  1568. $item['mtime']+$lifetime<time())
  1569. apc_delete($item['info']);
  1570. return TRUE;
  1571. case 'memcache':
  1572. foreach (memcache_get_extended_stats(
  1573. $this->ref,'slabs') as $slabs)
  1574. foreach (array_keys($slabs) as $id)
  1575. foreach (memcache_get_extended_stats(
  1576. $this->ref,'cachedump',floor($id)) as $data)
  1577. if (is_array($data))
  1578. foreach ($data as $key=>$val)
  1579. if (preg_match($regex,$key) &&
  1580. $val[1]+$lifetime<time())
  1581. memcache_delete($this->ref,$key);
  1582. return TRUE;
  1583. case 'wincache':
  1584. $info=wincache_ucache_info();
  1585. foreach ($info['ucache_entries'] as $item)
  1586. if (preg_match($regex,$item['key_name']) &&
  1587. $item['use_time']+$lifetime<time())
  1588. apc_delete($item['key_name']);
  1589. return TRUE;
  1590. case 'xcache':
  1591. return TRUE; /* Not supported */
  1592. case 'folder':
  1593. foreach (glob($parts[1].'*') as $file)
  1594. if (preg_match($regex,basename($file)) &&
  1595. filemtime($file)+$lifetime<time())
  1596. @unlink($file);
  1597. return TRUE;
  1598. }
  1599. return FALSE;
  1600. }
  1601. /**
  1602. Load/auto-detect cache backend
  1603. @return string
  1604. @param $dsn bool|string
  1605. **/
  1606. function load($dsn) {
  1607. if ($dsn=trim($dsn)) {
  1608. $fw=Base::instance();
  1609. if (preg_match('/^memcache=(.+)/',$dsn,$parts) &&
  1610. extension_loaded('memcache'))
  1611. foreach ($fw->split($parts[1]) as $server) {
  1612. $port=11211;
  1613. $parts=explode(':',$server,2);
  1614. if (count($parts)>1)
  1615. list($host,$port)=$parts;
  1616. else
  1617. $host=$parts[0];
  1618. if (empty($this->ref))
  1619. $this->ref=@memcache_connect($host,$port)?:NULL;
  1620. else
  1621. memcache_add_server($this->ref,$host,$port);
  1622. }
  1623. if (empty($this->ref) && !preg_match('/^folder\h*=/',$dsn))
  1624. $dsn=($grep=preg_grep('/^(apc|wincache|xcache)/',
  1625. array_map('strtolower',get_loaded_extensions())))?
  1626. // Auto-detect
  1627. current($grep):
  1628. // Use filesystem as fallback
  1629. ('folder='.$fw->get('TEMP').'cache/');
  1630. if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) &&
  1631. !is_dir($parts[1]))
  1632. mkdir($parts[1],Base::MODE,TRUE);
  1633. }
  1634. return $this->dsn=$dsn;
  1635. }
  1636. /**
  1637. Return class instance
  1638. @return object
  1639. **/
  1640. static function instance() {
  1641. if (!Registry::exists($class=__CLASS__))
  1642. Registry::set($class,new $class);
  1643. return Registry::get($class);
  1644. }
  1645. //! Prohibit cloning
  1646. private function __clone() {
  1647. }
  1648. //! Prohibit instantiation
  1649. private function __construct() {
  1650. $fw=Base::instance();
  1651. $this->prefix=$fw->hash($fw->get('ROOT').$fw->get('BASE'));
  1652. }
  1653. /**
  1654. Wrap-up
  1655. @return NULL
  1656. **/
  1657. function __destruct() {
  1658. Registry::clear(__CLASS__);
  1659. }
  1660. }
  1661. //! Prefab for classes with constructors and static factory methods
  1662. abstract class Prefab {
  1663. /**
  1664. Return class instance
  1665. @return object
  1666. **/
  1667. static function instance() {
  1668. if (!Registry::exists($class=get_called_class()))
  1669. Registry::set($class,new $class);
  1670. return Registry::get($class);
  1671. }
  1672. /**
  1673. Wrap-up
  1674. @return NULL
  1675. **/
  1676. function __destruct() {
  1677. Registry::clear(get_called_class());
  1678. }
  1679. }
  1680. //! View handler
  1681. class View extends Prefab {
  1682. protected
  1683. //! Template file
  1684. $view,
  1685. //! Local hive
  1686. $hive;
  1687. /**
  1688. Create sandbox for template execution
  1689. @return string
  1690. **/
  1691. protected function sandbox() {
  1692. extract($this->hive);
  1693. ob_start();
  1694. require($this->view);
  1695. return ob_get_clean();
  1696. }
  1697. /**
  1698. Render template
  1699. @return string
  1700. @param $file string
  1701. @param $mime string
  1702. @param $hive array
  1703. **/
  1704. function render($file,$mime='text/html',array $hive=NULL) {
  1705. $fw=Base::instance();
  1706. foreach ($fw->split($fw->get('UI')) as $dir)
  1707. if (is_file($this->view=$fw->fixslashes($dir.$file))) {
  1708. if (isset($_COOKIE[session_name()]))
  1709. @session_start();
  1710. $fw->sync('SESSION');
  1711. if (!$hive)
  1712. $hive=$fw->hive();
  1713. $this->hive=$fw->get('ESCAPE')?$hive=$fw->esc($hive):$hive;
  1714. if (PHP_SAPI!='cli')
  1715. header('Content-Type: '.$mime.'; '.
  1716. 'charset='.$fw->get('ENCODING'));
  1717. return $this->sandbox();
  1718. }
  1719. user_error(sprintf(Base::E_Open,$file));
  1720. }
  1721. }
  1722. //! ISO language/country codes
  1723. class ISO extends Prefab {
  1724. //@{ ISO 3166-1 country codes
  1725. const
  1726. CC_af='Afghanistan',
  1727. CC_ax='Åland Islands',
  1728. CC_al='Albania',
  1729. CC_dz='Algeria',
  1730. CC_as='American Samoa',
  1731. CC_ad='Andorra',
  1732. CC_ao='Angola',
  1733. CC_ai='Anguilla',
  1734. CC_aq='Antarctica',
  1735. CC_ag='Antigua and Barbuda',
  1736. CC_ar='Argentina',
  1737. CC_am='Armenia',
  1738. CC_aw='Aruba',
  1739. CC_au='Australia',
  1740. CC_at='Austria',
  1741. CC_az='Azerbaijan',
  1742. CC_bs='Bahamas',
  1743. CC_bh='Bahrain',
  1744. CC_bd='Bangladesh',
  1745. CC_bb='Barbados',
  1746. CC_by='Belarus',
  1747. CC_be='Belgium',
  1748. CC_bz='Belize',
  1749. CC_bj='Benin',
  1750. CC_bm='Bermuda',
  1751. CC_bt='Bhutan',
  1752. CC_bo='Bolivia',
  1753. CC_bq='Bonaire, Sint Eustatius and Saba',
  1754. CC_ba='Bosnia and Herzegovina',
  1755. CC_bw='Botswana',
  1756. CC_bv='Bouvet Island',
  1757. CC_br='Brazil',
  1758. CC_io='British Indian Ocean Territory',
  1759. CC_bn='Brunei Darussalam',
  1760. CC_bg='Bulgaria',
  1761. CC_bf='Burkina Faso',
  1762. CC_bi='Burundi',
  1763. CC_kh='Cambodia',
  1764. CC_cm='Cameroon',
  1765. CC_ca='Canada',
  1766. CC_cv='Cape Verde',
  1767. CC_ky='Cayman Islands',
  1768. CC_cf='Central African Republic',
  1769. CC_td='Chad',
  1770. CC_cl='Chile',
  1771. CC_cn='China',
  1772. CC_cx='Christmas Island',
  1773. CC_cc='Cocos (Keeling) Islands',
  1774. CC_co='Colombia',
  1775. CC_km='Comoros',
  1776. CC_cg='Congo',
  1777. CC_cd='Congo, The Democratic Republic of',
  1778. CC_ck='Cook Islands',
  1779. CC_cr='Costa Rica',
  1780. CC_ci='Côte d\'ivoire',
  1781. CC_hr='Croatia',
  1782. CC_cu='Cuba',
  1783. CC_cw='Curaçao',
  1784. CC_cy='Cyprus',
  1785. CC_cz='Czech Republic',
  1786. CC_dk='Denmark',
  1787. CC_dj='Djibouti',
  1788. CC_dm='Dominica',
  1789. CC_do='Dominican Republic',
  1790. CC_ec='Ecuador',
  1791. CC_eg='Egypt',
  1792. CC_sv='El Salvador',
  1793. CC_gq='Equatorial Guinea',
  1794. CC_er='Eritrea',
  1795. CC_ee='Estonia',
  1796. CC_et='Ethiopia',
  1797. CC_fk='Falkland Islands (Malvinas)',
  1798. CC_fo='Faroe Islands',
  1799. CC_fj='Fiji',
  1800. CC_fi='Finland',
  1801. CC_fr='France',
  1802. CC_gf='French Guiana',
  1803. CC_pf='French Polynesia',
  1804. CC_tf='French Southern Territories',
  1805. CC_ga='Gabon',
  1806. CC_gm='Gambia',
  1807. CC_ge='Georgia',
  1808. CC_de='Germany',
  1809. CC_gh='Ghana',
  1810. CC_gi='Gibraltar',
  1811. CC_gr='Greece',
  1812. CC_gl='Greenland',
  1813. CC_gd='Grenada',
  1814. CC_gp='Guadeloupe',
  1815. CC_gu='Guam',
  1816. CC_gt='Guatemala',
  1817. CC_gg='Guernsey',
  1818. CC_gn='Guinea',
  1819. CC_gw='Guinea-Bissau',
  1820. CC_gy='Guyana',
  1821. CC_ht='Haiti',
  1822. CC_hm='Heard Island and McDonald Islands',
  1823. CC_va='Holy See (Vatican City State)',
  1824. CC_hn='Honduras',
  1825. CC_hk='Hong Kong',
  1826. CC_hu='Hungary',
  1827. CC_is='Iceland',
  1828. CC_in='India',
  1829. CC_id='Indonesia',
  1830. CC_ir='Iran, Islamic Republic of',
  1831. CC_iq='Iraq',
  1832. CC_ie='Ireland',
  1833. CC_im='Isle of Man',
  1834. CC_il='Israel',
  1835. CC_it='Italy',
  1836. CC_jm='Jamaica',
  1837. CC_jp='Japan',
  1838. CC_je='Jersey',
  1839. CC_jo='Jordan',
  1840. CC_kz='Kazakhstan',
  1841. CC_ke='Kenya',
  1842. CC_ki='Kiribati',
  1843. CC_kp='Korea, Democratic People\'s Republic of',
  1844. CC_kr='Korea, Republic of',
  1845. CC_kw='Kuwait',
  1846. CC_kg='Kyrgyzstan',
  1847. CC_la='Lao People\'s Democratic Republic',
  1848. CC_lv='Latvia',
  1849. CC_lb='Lebanon',
  1850. CC_ls='Lesotho',
  1851. CC_lr='Liberia',
  1852. CC_ly='Libya',
  1853. CC_li='Liechtenstein',
  1854. CC_lt='Lithuania',
  1855. CC_lu='Luxembourg',
  1856. CC_mo='Macao',
  1857. CC_mk='Macedonia, The Former Yugoslav Republic of',
  1858. CC_mg='Madagascar',
  1859. CC_mw='Malawi',
  1860. CC_my='Malaysia',
  1861. CC_mv='Maldives',
  1862. CC_ml='Mali',
  1863. CC_mt='Malta',
  1864. CC_mh='Marshall Islands',
  1865. CC_mq='Martinique',
  1866. CC_mr='Mauritania',
  1867. CC_mu='Mauritius',
  1868. CC_yt='Mayotte',
  1869. CC_mx='Mexico',
  1870. CC_fm='Micronesia, Federated States of',
  1871. CC_md='Moldova, Republic of',
  1872. CC_mc='Monaco',
  1873. CC_mn='Mongolia',
  1874. CC_me='Montenegro',
  1875. CC_ms='Montserrat',
  1876. CC_ma='Morocco',
  1877. CC_mz='Mozambique',
  1878. CC_mm='Myanmar',
  1879. CC_na='Namibia',
  1880. CC_nr='Nauru',
  1881. CC_np='Nepal',
  1882. CC_nl='Netherlands',
  1883. CC_nc='New Caledonia',
  1884. CC_nz='New Zealand',
  1885. CC_ni='Nicaragua',
  1886. CC_ne='Niger',
  1887. CC_ng='Nigeria',
  1888. CC_nu='Niue',
  1889. CC_nf='Norfolk Island',
  1890. CC_mp='Northern Mariana Islands',
  1891. CC_no='Norway',
  1892. CC_om='Oman',
  1893. CC_pk='Pakistan',
  1894. CC_pw='Palau',
  1895. CC_ps='Palestinian Territory, Occupied',
  1896. CC_pa='Panama',
  1897. CC_pg='Papua New Guinea',
  1898. CC_py='Paraguay',
  1899. CC_pe='Peru',
  1900. CC_ph='Philippines',
  1901. CC_pn='Pitcairn',
  1902. CC_pl='Poland',
  1903. CC_pt='Portugal',
  1904. CC_pr='Puerto Rico',
  1905. CC_qa='Qatar',
  1906. CC_re='Réunion',
  1907. CC_ro='Romania',
  1908. CC_ru='Russian Federation',
  1909. CC_rw='Rwanda',
  1910. CC_bl='Saint Barthélemy',
  1911. CC_sh='Saint Helena, Ascension and Tristan da Cunha',
  1912. CC_kn='Saint Kitts and Nevis',
  1913. CC_lc='Saint Lucia',
  1914. CC_mf='Saint Martin (French Part)',
  1915. CC_pm='Saint Pierre and Miquelon',
  1916. CC_vc='Saint Vincent and The Grenadines',
  1917. CC_ws='Samoa',
  1918. CC_sm='San Marino',
  1919. CC_st='Sao Tome and Principe',
  1920. CC_sa='Saudi Arabia',
  1921. CC_sn='Senegal',
  1922. CC_rs='Serbia',
  1923. CC_sc='Seychelles',
  1924. CC_sl='Sierra Leone',
  1925. CC_sg='Singapore',
  1926. CC_sk='Slovakia',
  1927. CC_sx='Sint Maarten (Dutch Part)',
  1928. CC_si='Slovenia',
  1929. CC_sb='Solomon Islands',
  1930. CC_so='Somalia',
  1931. CC_za='South Africa',
  1932. CC_gs='South Georgia and The Sout…

Large files files are truncated, but you can click here to view the full file