PageRenderTime 74ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/website/lib/base.php

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

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