PageRenderTime 58ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/a/lib/base.php

https://github.com/symtkn/usim
PHP | 2325 lines | 1607 code | 84 blank | 634 comment | 237 complexity | 311af53c65d11e7ae427298e14e45967 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. PHP Fat-Free Framework
  4. The contents of this file are subject to the terms of the GNU General
  5. Public License Version 3.0. You may not use this file except in
  6. compliance with the license. Any of the license terms and conditions
  7. can be waived if you get permission from the copyright holder.
  8. Copyright (c) 2009-2011 F3::Factory
  9. Bong Cosca <bong.cosca@yahoo.com>
  10. @package Base
  11. @version 2.0.6
  12. **/
  13. //! Base structure
  14. class Base {
  15. //@{ Framework details
  16. const
  17. TEXT_AppName='Fat-Free Framework',
  18. TEXT_Version='2.0.6',
  19. TEXT_AppURL='http://fatfree.sourceforge.net';
  20. //@}
  21. //@{ Locale-specific error/exception messages
  22. const
  23. TEXT_Illegal='%s is not a valid framework variable name',
  24. TEXT_Config='The configuration file %s was not found',
  25. TEXT_Section='%s is not a valid section',
  26. TEXT_MSet='Invalid multi-variable assignment',
  27. TEXT_NotArray='%s is not an array',
  28. TEXT_PHPExt='PHP extension %s is not enabled',
  29. TEXT_Apache='Apache mod_rewrite module is not enabled',
  30. TEXT_Object='%s cannot be used in object context',
  31. TEXT_Class='Undefined class %s',
  32. TEXT_Method='Undefined method %s',
  33. TEXT_NotFound='The URL %s was not found',
  34. TEXT_NotAllowed='%s request is not allowed for the URL %s',
  35. TEXT_NoRoutes='No routes specified',
  36. TEXT_HTTP='HTTP status code %s is invalid',
  37. TEXT_Render='Unable to render %s - file does not exist',
  38. TEXT_Form='The input handler for %s is invalid',
  39. TEXT_Static='%s must be a static method',
  40. TEXT_Fatal='Fatal error: %s',
  41. TEXT_Write='%s must have write permission on %s',
  42. TEXT_Tags='PHP short tags are not supported by this server';
  43. //@}
  44. //@{ HTTP status codes (RFC 2616)
  45. const
  46. HTTP_100='Continue',
  47. HTTP_101='Switching Protocols',
  48. HTTP_200='OK',
  49. HTTP_201='Created',
  50. HTTP_202='Accepted',
  51. HTTP_203='Non-Authorative Information',
  52. HTTP_204='No Content',
  53. HTTP_205='Reset Content',
  54. HTTP_206='Partial Content',
  55. HTTP_300='Multiple Choices',
  56. HTTP_301='Moved Permanently',
  57. HTTP_302='Found',
  58. HTTP_303='See Other',
  59. HTTP_304='Not Modified',
  60. HTTP_305='Use Proxy',
  61. HTTP_307='Temporary Redirect',
  62. HTTP_400='Bad Request',
  63. HTTP_401='Unauthorized',
  64. HTTP_402='Payment Required',
  65. HTTP_403='Forbidden',
  66. HTTP_404='Not Found',
  67. HTTP_405='Method Not Allowed',
  68. HTTP_406='Not Acceptable',
  69. HTTP_407='Proxy Authentication Required',
  70. HTTP_408='Request Timeout',
  71. HTTP_409='Conflict',
  72. HTTP_410='Gone',
  73. HTTP_411='Length Required',
  74. HTTP_412='Precondition Failed',
  75. HTTP_413='Request Entity Too Large',
  76. HTTP_414='Request-URI Too Long',
  77. HTTP_415='Unsupported Media Type',
  78. HTTP_416='Requested Range Not Satisfiable',
  79. HTTP_417='Expectation Failed',
  80. HTTP_500='Internal Server Error',
  81. HTTP_501='Not Implemented',
  82. HTTP_502='Bad Gateway',
  83. HTTP_503='Service Unavailable',
  84. HTTP_504='Gateway Timeout',
  85. HTTP_505='HTTP Version Not Supported';
  86. //@}
  87. //@{ HTTP headers (RFC 2616)
  88. const
  89. HTTP_AcceptEnc='Accept-Encoding',
  90. HTTP_Agent='User-Agent',
  91. HTTP_Allow='Allow',
  92. HTTP_Cache='Cache-Control',
  93. HTTP_Connect='Connection',
  94. HTTP_Content='Content-Type',
  95. HTTP_Disposition='Content-Disposition',
  96. HTTP_Encoding='Content-Encoding',
  97. HTTP_Expires='Expires',
  98. HTTP_Host='Host',
  99. HTTP_IfMod='If-Modified-Since',
  100. HTTP_Keep='Keep-Alive',
  101. HTTP_LastMod='Last-Modified',
  102. HTTP_Length='Content-Length',
  103. HTTP_Location='Location',
  104. HTTP_Partial='Accept-Ranges',
  105. HTTP_Powered='X-Powered-By',
  106. HTTP_Pragma='Pragma',
  107. HTTP_Referer='Referer',
  108. HTTP_Transfer='Content-Transfer-Encoding',
  109. HTTP_WebAuth='WWW-Authenticate';
  110. //@}
  111. const
  112. //! Framework-mapped PHP globals
  113. PHP_Globals='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV',
  114. //! HTTP methods for RESTful interface
  115. HTTP_Methods='GET|HEAD|POST|PUT|DELETE|OPTIONS';
  116. //@{ Global variables and references to constants
  117. protected static
  118. $vars,
  119. $null=NULL,
  120. $true=TRUE,
  121. $false=FALSE;
  122. //@}
  123. private static
  124. //! Read-only framework variables
  125. $readonly='BASE|PROTOCOL|ROUTES|STATS|VERSION';
  126. /**
  127. Convert Windows double-backslashes to slashes; Also for
  128. referencing namespaced classes in subdirectories
  129. @return string
  130. @param $str string
  131. @public
  132. **/
  133. static function fixslashes($str) {
  134. return $str?strtr($str,'\\','/'):$str;
  135. }
  136. /**
  137. Convert PHP expression/value to string
  138. @return string
  139. @param $val mixed
  140. @public
  141. **/
  142. static function stringify($val) {
  143. return preg_replace('/\s+=>\s+/','=>',
  144. is_object($val) && !method_exists($val,'__set_state')?
  145. (method_exists($val,'__toString')?
  146. var_export((string)stripslashes($val),TRUE):
  147. ('object:'.get_class($val))):
  148. var_export($val,TRUE));
  149. }
  150. /**
  151. Flatten array values and return as CSV string
  152. @return string
  153. @param $args mixed
  154. @public
  155. **/
  156. static function csv($args) {
  157. if (!is_array($args))
  158. $args=array($args);
  159. $str='';
  160. foreach ($args as $key=>$val) {
  161. $str.=($str?',':'');
  162. if (is_string($key))
  163. $str.=var_export($key,TRUE).'=>';
  164. $str.=is_array($val)?
  165. ('array('.self::csv($val).')'):self::stringify($val);
  166. }
  167. return $str;
  168. }
  169. /**
  170. Split pipe-, semi-colon, comma-separated string
  171. @return array
  172. @param $str string
  173. @public
  174. **/
  175. static function split($str) {
  176. return array_map('trim',
  177. preg_split('/[|;,]/',$str,0,PREG_SPLIT_NO_EMPTY));
  178. }
  179. /**
  180. Generate Base36/CRC32 hash code
  181. @return string
  182. @param $str string
  183. @public
  184. **/
  185. static function hash($str) {
  186. return str_pad(base_convert(
  187. sprintf('%u',crc32($str)),10,36),7,'0',STR_PAD_LEFT);
  188. }
  189. /**
  190. Convert hexadecimal to binary-packed data
  191. @return string
  192. @param $hex string
  193. @public
  194. **/
  195. static function hexbin($hex) {
  196. return pack('H*',$hex);
  197. }
  198. /**
  199. Convert binary-packed data to hexadecimal
  200. @return string
  201. @param $bin string
  202. @public
  203. **/
  204. static function binhex($bin) {
  205. return implode('',unpack('H*',$bin));
  206. }
  207. /**
  208. Returns -1 if the specified number is negative, 0 if zero, or 1 if
  209. the number is positive
  210. @return int
  211. @param $num mixed
  212. @public
  213. **/
  214. static function sign($num) {
  215. return $num?$num/abs($num):0;
  216. }
  217. /**
  218. Convert engineering-notated string to bytes
  219. @return int
  220. @param $str string
  221. @public
  222. **/
  223. static function bytes($str) {
  224. $greek='KMGT';
  225. $exp=strpbrk($str,$greek);
  226. return pow(1024,strpos($greek,$exp)+1)*(int)$str;
  227. }
  228. /**
  229. Convert from JS dot notation to PHP array notation
  230. @return string
  231. @param $key string
  232. @public
  233. **/
  234. static function remix($key) {
  235. $out='';
  236. $obj=FALSE;
  237. foreach (preg_split('/\[\s*[\'"]?|[\'"]?\s*\]|\.|(->)/',
  238. $key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE) as $fix) {
  239. if ($out) {
  240. if ($fix=='->') {
  241. $obj=TRUE;
  242. continue;
  243. }
  244. elseif ($obj) {
  245. $obj=FALSE;
  246. $fix='->'.$fix;
  247. }
  248. else
  249. $fix='['.var_export($fix,TRUE).']';
  250. }
  251. $out.=$fix;
  252. }
  253. return $out;
  254. }
  255. /**
  256. Return TRUE if specified string is a valid framework variable name
  257. @return bool
  258. @param $key string
  259. @public
  260. **/
  261. static function valid($key) {
  262. if (preg_match('/^(\w+(?:\[[^\]]+\]|\.\w+|\s*->\s*\w+)*)$/',$key))
  263. return TRUE;
  264. // Invalid variable name
  265. trigger_error(sprintf(self::TEXT_Illegal,var_export($key,TRUE)));
  266. return FALSE;
  267. }
  268. /**
  269. Get framework variable reference/contents
  270. @return mixed
  271. @param $key string
  272. @param $set bool
  273. @public
  274. **/
  275. static function &ref($key,$set=TRUE) {
  276. // Traverse array
  277. $matches=preg_split(
  278. '/\[\s*[\'"]?|[\'"]?\s*\]|\.|(->)/',self::remix($key),
  279. NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
  280. // Referencing a SESSION variable element auto-starts a session
  281. if ($matches[0]=='SESSION' && !session_id()) {
  282. session_start();
  283. // Sync framework and PHP global
  284. self::$vars['SESSION']=&$_SESSION;
  285. }
  286. // Read-only framework variable?
  287. if ($set && !preg_match('/^('.self::$readonly.')\b/',$matches[0]))
  288. $var=&self::$vars;
  289. else
  290. $var=self::$vars;
  291. $obj=FALSE;
  292. foreach ($matches as $match)
  293. if ($match=='->')
  294. $obj=TRUE;
  295. else {
  296. if (preg_match('/@(\w+)/',$match,$token))
  297. // Token found
  298. $match=&self::ref($token[1]);
  299. if ($set) {
  300. // Create property/array element if not found
  301. if ($obj) {
  302. if (!is_object($var))
  303. $var=new stdClass;
  304. if (!isset($var->$match))
  305. $var->$match=NULL;
  306. $var=&$var->$match;
  307. $obj=FALSE;
  308. }
  309. else
  310. $var=&$var[$match];
  311. }
  312. elseif ($obj && isset($var->$match)) {
  313. // Object property found
  314. $var=$var->$match;
  315. $obj=FALSE;
  316. }
  317. elseif (is_array($var) && isset($var[$match]))
  318. // Array element found
  319. $var=$var[$match];
  320. else
  321. // Property/array element doesn't exist
  322. return self::$null;
  323. }
  324. if ($set && count($matches)>1 &&
  325. preg_match('/GET|POST|COOKIE/',$matches[0],$php)) {
  326. // Sync with REQUEST
  327. $req=&self::ref(preg_replace('/^'.$php[0].'\b/','REQUEST',$key));
  328. $req=$var;
  329. }
  330. return $var;
  331. }
  332. /**
  333. Copy contents of framework variable to another
  334. @param $src string
  335. @param $dst string
  336. @public
  337. **/
  338. static function copy($src,$dst) {
  339. $ref=&self::ref($dst);
  340. $ref=self::ref($src);
  341. }
  342. /**
  343. Concatenate string to framework string variable
  344. @param $var string
  345. @param $val string
  346. @public
  347. **/
  348. static function concat($var,$val) {
  349. $ref=&self::ref($var);
  350. $ref.=$val;
  351. }
  352. /**
  353. Format framework string variable
  354. @return string
  355. @public
  356. **/
  357. static function sprintf() {
  358. return call_user_func_array('sprintf',
  359. array_map('self::resolve',func_get_args()));
  360. }
  361. /**
  362. Add keyed element to the end of framework array variable
  363. @param $var string
  364. @param $key string
  365. @param $val mixed
  366. @public
  367. **/
  368. static function append($var,$key,$val) {
  369. $ref=&self::ref($var);
  370. $ref[self::resolve($key)]=$val;
  371. }
  372. /**
  373. Swap keys and values of framework array variable
  374. @param $var string
  375. @public
  376. **/
  377. static function flip($var) {
  378. $ref=&self::ref($var);
  379. $ref=array_combine(array_values($ref),array_keys($ref));
  380. }
  381. /**
  382. Merge one or more framework array variables
  383. @public
  384. **/
  385. static function merge() {
  386. $args=func_get_args();
  387. foreach ($args as &$arg) {
  388. if (is_string($arg))
  389. $arg=self::ref($arg);
  390. if (!is_array($arg))
  391. trigger_error(sprintf(self::TEXT_NotArray,
  392. self::stringify($arg)));
  393. }
  394. $ref=&self::ref($var);
  395. $ref=call_user_func_array('array_merge',$args);
  396. }
  397. /**
  398. Add element to the end of framework array variable
  399. @param $var string
  400. @param $val mixed
  401. @public
  402. **/
  403. static function push($var,$val) {
  404. $ref=&self::ref($var);
  405. if (!is_array($ref))
  406. $ref=array();
  407. array_push($ref,is_array($val)?
  408. array_map('self::resolve',$val):
  409. (is_string($val)?self::resolve($val):$val));
  410. }
  411. /**
  412. Remove last element of framework array variable and
  413. return the element
  414. @return mixed
  415. @param $var string
  416. @public
  417. **/
  418. static function pop($var) {
  419. $ref=&self::ref($var);
  420. if (is_array($ref))
  421. return array_pop($ref);
  422. trigger_error(sprintf(self::TEXT_NotArray,$var));
  423. return FALSE;
  424. }
  425. /**
  426. Add element to the beginning of framework array variable
  427. @param $var string
  428. @param $val mixed
  429. @public
  430. **/
  431. static function unshift($var,$val) {
  432. $ref=&self::ref($var);
  433. if (!is_array($ref))
  434. $ref=array();
  435. array_unshift($ref,is_array($val)?
  436. array_map('self::resolve',$val):
  437. (is_string($val)?self::resolve($val):$val));
  438. }
  439. /**
  440. Remove first element of framework array variable and
  441. return the element
  442. @return mixed
  443. @param $var string
  444. @public
  445. **/
  446. static function shift($var) {
  447. $ref=&self::ref($var);
  448. if (is_array($ref))
  449. return array_shift($ref);
  450. trigger_error(sprintf(self::TEXT_NotArray,$var));
  451. return FALSE;
  452. }
  453. /**
  454. Execute callback as a mutex operation
  455. @return mixed
  456. @public
  457. **/
  458. static function mutex() {
  459. $args=func_get_args();
  460. $func=array_shift($args);
  461. $handles=array();
  462. foreach ($args as $file) {
  463. $lock=$file.'.lock';
  464. while (TRUE) {
  465. usleep(mt_rand(0,100));
  466. if (is_resource($handle=@fopen($lock,'x'))) {
  467. $handles[$lock]=$handle;
  468. break;
  469. }
  470. if (is_file($lock) &&
  471. filemtime($lock)+self::$vars['MUTEX']<time())
  472. unlink($lock);
  473. }
  474. }
  475. $out=$func();
  476. foreach ($handles as $lock=>$handle) {
  477. fclose($handle);
  478. unlink($lock);
  479. }
  480. return $out;
  481. }
  482. /**
  483. Lock-aware file reader
  484. @param $file string
  485. @public
  486. **/
  487. static function getfile($file) {
  488. $out=FALSE;
  489. if (!function_exists('flock'))
  490. $out=self::mutex(
  491. function() use($file) {
  492. return file_get_contents($file);
  493. },
  494. $file
  495. );
  496. elseif ($handle=@fopen($file,'r')) {
  497. flock($handle,LOCK_EX);
  498. $size=filesize($file);
  499. $out=$size?fread($handle,$size):$out;
  500. flock($handle,LOCK_UN);
  501. fclose($handle);
  502. }
  503. return $out;
  504. }
  505. /**
  506. Lock-aware file writer
  507. @param $file string
  508. @param $data string
  509. @public
  510. **/
  511. static function putfile($file,$data) {
  512. if (!function_exists('flock'))
  513. $out=self::mutex(
  514. function() use($file,$data) {
  515. return file_put_contents($file,$data,LOCK_EX);
  516. },
  517. $file
  518. );
  519. else
  520. $out=file_put_contents($file,$data,LOCK_EX);
  521. return $out;
  522. }
  523. /**
  524. Evaluate template expressions in string
  525. @return string
  526. @param $val mixed
  527. @public
  528. **/
  529. static function resolve($val) {
  530. // Analyze string for correct framework expression syntax
  531. $self=__CLASS__;
  532. $str=preg_replace_callback(
  533. // Expression
  534. '/{{(.+?)}}/i',
  535. function($expr) use($self) {
  536. // Evaluate expression
  537. $out=preg_replace_callback(
  538. // Function
  539. '/(?<!@)\b(\w+)\s*\(([^\)]*)\)/',
  540. function($func) use($self) {
  541. return is_callable($ref=$self::ref($func[1],FALSE))?
  542. // Variable holds an anonymous function
  543. call_user_func_array($ref,str_getcsv($func[2])):
  544. // Check if empty array
  545. ($func[1].$func[2]=='array'?'NULL':$func[0]);
  546. },
  547. preg_replace_callback(
  548. // Framework variable
  549. '/(?<!\w)@(\w+(?:\[[^\]]+\]|\.\w+)*'.
  550. '(?:\s*->\s*\w+)?)\s*(\(([^\)]*)\))?(?:\\\(.+))?/',
  551. function($var) use($self) {
  552. // Retrieve variable contents
  553. $val=$self::ref($var[1],FALSE);
  554. if (isset($var[2]) && is_callable($val))
  555. // Anonymous function
  556. $val=call_user_func_array(
  557. $val,str_getcsv($var[3]));
  558. if (isset($var[4]) && class_exists('ICU',FALSE))
  559. // ICU-formatted string
  560. $val=call_user_func_array('ICU::format',
  561. array($val,str_getcsv($var[4])));
  562. return $self::stringify($val);
  563. },
  564. $expr[1]
  565. )
  566. );
  567. return !preg_match('/@|\bnew\s+/i',$out) &&
  568. ($eval=eval('return (string)'.$out.';'))!==FALSE?
  569. $eval:$out;
  570. },
  571. $val
  572. );
  573. return $str;
  574. }
  575. /**
  576. Sniff headers for real IP address
  577. @return string
  578. @public
  579. **/
  580. static function realip() {
  581. if (isset($_SERVER['HTTP_CLIENT_IP']))
  582. // Behind proxy
  583. return $_SERVER['HTTP_CLIENT_IP'];
  584. elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  585. // Use first IP address in list
  586. list($ip)=explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']);
  587. return $ip;
  588. }
  589. return $_SERVER['REMOTE_ADDR'];
  590. }
  591. /**
  592. Return TRUE if IP address is local or within a private IPv4 range
  593. @return bool
  594. @param $addr string
  595. @public
  596. **/
  597. static function privateip($addr=NULL) {
  598. if (!$addr)
  599. $addr=self::realip();
  600. return preg_match('/^127\.0\.0\.\d{1,3}$/',$addr) ||
  601. !filter_var($addr,FILTER_VALIDATE_IP,
  602. FILTER_FLAG_IPV4|FILTER_FLAG_NO_PRIV_RANGE);
  603. }
  604. /**
  605. Clean and repair HTML
  606. @return string
  607. @param $html string
  608. @public
  609. **/
  610. static function tidy($html) {
  611. if (!extension_loaded('tidy'))
  612. return $html;
  613. $tidy=new Tidy;
  614. $tidy->parseString($html,self::$vars['TIDY'],
  615. str_replace('-','',self::$vars['ENCODING']));
  616. $tidy->cleanRepair();
  617. return (string)$tidy;
  618. }
  619. /**
  620. Create folder; Trigger error and return FALSE if script has no
  621. permission to create folder in the specified path
  622. @param $name string
  623. @param $perm int
  624. @public
  625. **/
  626. static function mkdir($name,$perm=0775) {
  627. $parent=dirname($name);
  628. if (!@is_writable($parent) && !chmod($parent,775)) {
  629. trigger_error(sprintf(self::TEXT_Write,
  630. $uid['name'],realpath(dirname($name))));
  631. return FALSE;
  632. }
  633. // Create the folder
  634. mkdir($name,$perm);
  635. }
  636. /**
  637. Intercept calls to undefined methods
  638. @param $func string
  639. @param $args array
  640. @public
  641. **/
  642. function __call($func,array $args) {
  643. trigger_error(sprintf(self::TEXT_Method,get_called_class().'->'.
  644. $func.'('.self::csv($args).')'));
  645. }
  646. /**
  647. Intercept calls to undefined static methods
  648. @param $func string
  649. @param $args array
  650. @public
  651. **/
  652. static function __callStatic($func,array $args) {
  653. trigger_error(sprintf(self::TEXT_Method,get_called_class().'::'.
  654. $func.'('.self::csv($args).')'));
  655. }
  656. /**
  657. Return instance of child class
  658. @public
  659. **/
  660. static function instance() {
  661. return eval('return new '.get_called_class().
  662. '('.self::csv(func_get_args()).');');
  663. }
  664. /**
  665. Class constructor
  666. @public
  667. **/
  668. function __construct() {
  669. // Prohibit use of class as an object
  670. trigger_error(sprintf(self::TEXT_Object,get_called_class()));
  671. }
  672. }
  673. //! Main framework code
  674. class F3 extends Base {
  675. /**
  676. Bind value to framework variable
  677. @param $key string
  678. @param $val mixed
  679. @param $persist bool
  680. @param $resolve bool
  681. @public
  682. **/
  683. static function set($key,$val,$persist=FALSE,$resolve=TRUE) {
  684. if (preg_match('/{{.+}}/',$key))
  685. // Variable variable
  686. $key=self::resolve($key);
  687. if (!self::valid($key))
  688. return;
  689. if (preg_match('/COOKIE\b/',$key) && !headers_sent()) {
  690. // Create/modify cookie
  691. $matches=preg_split(
  692. '/\[\s*[\'"]?|[\'"]?\s*\]|\./',self::remix($key),
  693. NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
  694. array_shift($matches);
  695. if ($matches) {
  696. $var='';
  697. foreach ($matches as $match)
  698. if (!$var)
  699. $var=$match;
  700. else
  701. $var.='[\''.$match.'\']';
  702. $val=array($var,$val);
  703. }
  704. if (is_array($val))
  705. foreach ($val as $var=>$sub) {
  706. $func=self::$vars['JAR'];
  707. array_unshift($func,$var,$sub);
  708. call_user_func_array('setcookie',$func);
  709. }
  710. return;
  711. }
  712. $var=&self::ref($key);
  713. if (is_string($val) && $resolve)
  714. $val=self::resolve($val);
  715. elseif (is_array($val)) {
  716. $var=array();
  717. // Recursive token substitution
  718. foreach ($val as $subk=>$subv)
  719. self::set($key.'['.var_export($subk,TRUE).']',
  720. $subv,FALSE);
  721. return;
  722. }
  723. $var=$val;
  724. if (preg_match('/LANGUAGE|LOCALES/',$key) && class_exists('ICU'))
  725. // Load appropriate dictionaries
  726. ICU::load();
  727. // Initialize cache if explicitly defined
  728. elseif ($key=='CACHE' && $val)
  729. Cache::prep();
  730. if ($persist) {
  731. $hash='var.'.self::hash(self::remix($key));
  732. Cache::set($hash,$val);
  733. }
  734. }
  735. /**
  736. Retrieve value of framework variable and apply locale rules
  737. @return mixed
  738. @param $key string
  739. @param $args mixed
  740. @public
  741. **/
  742. static function get($key,$args=NULL) {
  743. if (preg_match('/{{.+}}/',$key))
  744. // Variable variable
  745. $key=self::resolve($key);
  746. if (!self::valid($key))
  747. return self::$null;
  748. $val=self::ref($key,FALSE);
  749. if (is_string($val))
  750. return class_exists('ICU',FALSE) && $args?
  751. ICU::format($val,$args):$val;
  752. elseif (is_null($val)) {
  753. // Attempt to retrieve from cache
  754. $hash='var.'.self::hash(self::remix($key));
  755. if (Cache::cached($hash))
  756. $val=Cache::get($hash);
  757. }
  758. return $val;
  759. }
  760. /**
  761. Unset framework variable
  762. @param $key string
  763. @public
  764. **/
  765. static function clear($key) {
  766. if (preg_match('/{{.+}}/',$key))
  767. // Variable variable
  768. $key=self::resolve($key);
  769. if (!self::valid($key))
  770. return;
  771. if (preg_match('/COOKIE/',$key) && !headers_sent()) {
  772. $val=$_COOKIE;
  773. $matches=preg_split(
  774. '/\[\s*[\'"]?|[\'"]?\s*\]|\./',self::remix($key),
  775. NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
  776. array_shift($matches);
  777. if ($matches) {
  778. // Expire specific cookie
  779. $var='';
  780. foreach ($matches as $match)
  781. if (!$var)
  782. $var=$match;
  783. else
  784. $var.='[\''.$match.'\']';
  785. $val=array($var,FALSE);
  786. }
  787. if (is_array($val))
  788. // Expire all cookies
  789. foreach ($val as $var=>$sub) {
  790. $func=self::$vars['JAR'];
  791. $func['expire']=strtotime('-1 year');
  792. array_unshift($func,$var,FALSE);
  793. call_user_func_array('setcookie',$func);
  794. }
  795. return;
  796. }
  797. // Clearing SESSION array ends the current session
  798. if ($key=='SESSION') {
  799. if (!session_id())
  800. session_start();
  801. // End the session
  802. session_unset();
  803. session_destroy();
  804. }
  805. preg_match('/^('.self::PHP_Globals.')(.*)$/',$key,$match);
  806. if (isset($match[1])) {
  807. $name=self::remix($key,FALSE);
  808. eval($match[2]?'unset($_'.$name.');':'$_'.$name.'=NULL;');
  809. }
  810. $name=preg_replace('/^(\w+)/','[\'\1\']',self::remix($key));
  811. // Assign NULL to framework variables; do not unset
  812. eval(ctype_upper(preg_replace('/^\w+/','\0',$key))?
  813. 'self::$vars'.$name.'=NULL;':'unset(self::$vars'.$name.');');
  814. // Remove from cache
  815. $hash='var.'.self::hash(self::remix($key));
  816. if (Cache::cached($hash))
  817. Cache::clear($hash);
  818. }
  819. /**
  820. Return TRUE if framework variable has been assigned a value
  821. @return bool
  822. @param $key string
  823. @public
  824. **/
  825. static function exists($key) {
  826. if (preg_match('/{{.+}}/',$key))
  827. // Variable variable
  828. $key=self::resolve($key);
  829. if (!self::valid($key))
  830. return FALSE;
  831. $var=&self::ref($key,FALSE);
  832. return isset($var);
  833. }
  834. /**
  835. Multi-variable assignment using associative array
  836. @param $arg array
  837. @param $pfx string
  838. @public
  839. **/
  840. static function mset($arg,$pfx='') {
  841. if (!is_array($arg))
  842. // Invalid argument
  843. trigger_error(self::TEXT_MSet);
  844. else
  845. // Bind key-value pairs
  846. foreach ($arg as $key=>$val)
  847. self::set($pfx.$key,$val);
  848. }
  849. /**
  850. Determine if framework variable has been cached
  851. @return mixed
  852. @param $key string
  853. @public
  854. **/
  855. static function cached($key) {
  856. if (preg_match('/{{.+}}/',$key))
  857. // Variable variable
  858. $key=self::resolve($key);
  859. return self::valid($key)?
  860. Cache::cached('var.'.self::hash(self::remix($key))):
  861. FALSE;
  862. }
  863. /**
  864. Configure framework according to INI-style file settings;
  865. Cache auto-generated PHP code to speed up execution
  866. @param $file string
  867. @public
  868. **/
  869. static function config($file) {
  870. // Generate hash code for config file
  871. $hash='php.'.self::hash($file);
  872. $cached=Cache::cached($hash);
  873. if ($cached && filemtime($file)<$cached)
  874. // Retrieve from cache
  875. $save=Cache::get($hash);
  876. else {
  877. if (!is_file($file)) {
  878. // Configuration file not found
  879. trigger_error(sprintf(self::TEXT_Config,$file));
  880. return;
  881. }
  882. // Map sections to framework methods
  883. $map=array('globals'=>'set','routes'=>'route','maps'=>'map');
  884. // Read the .ini file
  885. preg_match_all(
  886. '/\s*(?:\[(.+?)\]|(?:;.+?)?|(?:([^=]+)=(.+?)))(?:\v|$)/s',
  887. self::getfile($file),$matches,PREG_SET_ORDER);
  888. $cfg=array();
  889. $ptr=&$cfg;
  890. foreach ($matches as $match)
  891. if (isset($match[1]) && !empty($match[1])) {
  892. // Section header
  893. if (!isset($map[$match[1]])) {
  894. // Unknown section
  895. trigger_error(sprintf(self::TEXT_Section,$section));
  896. return;
  897. }
  898. $ptr=&$cfg[$match[1]];
  899. }
  900. elseif (isset($match[2]) && !empty($match[2])) {
  901. $csv=array_map(
  902. function($val) {
  903. // Typecast if necessary
  904. return is_numeric($val) ||
  905. preg_match('/^(TRUE|FALSE)\b/i',$val)?
  906. eval('return '.$val.';'):$val;
  907. },
  908. str_getcsv($match[3])
  909. );
  910. // Convert comma-separated values to array
  911. $match[3]=count($csv)>1?$csv:$csv[0];
  912. if (preg_match('/([^\[]+)\[([^\]]*)\]/',$match[2],$sub)) {
  913. if ($sub[2])
  914. // Associative array
  915. $ptr[$sub[1]][$sub[2]]=$match[3];
  916. else
  917. // Numeric-indexed array
  918. $ptr[$sub[1]][]=$match[3];
  919. }
  920. else
  921. // Key-value pair
  922. $ptr[trim($match[2])]=$match[3];
  923. }
  924. ob_start();
  925. foreach ($cfg as $section=>$pairs)
  926. if (isset($map[$section]) && is_array($pairs)) {
  927. $func=$map[$section];
  928. foreach ($pairs as $key=>$val)
  929. // Generate PHP snippet
  930. echo 'self::'.$func.'('.var_export($key,TRUE).','.
  931. ($func=='set' || !is_array($val)?
  932. var_export($val,TRUE):self::csv($val)).
  933. ');'."\n";
  934. }
  935. $save=ob_get_clean();
  936. // Compress and save to cache
  937. Cache::set($hash,$save);
  938. }
  939. // Execute cached PHP code
  940. eval($save);
  941. if (!is_null(self::$vars['ERROR']))
  942. // Remove from cache
  943. Cache::clear($hash);
  944. }
  945. /**
  946. Convert special characters to HTML entities using globally-
  947. defined character set
  948. @return string
  949. @param $str string
  950. @param $all bool
  951. @public
  952. **/
  953. static function htmlencode($str,$all=FALSE) {
  954. return call_user_func(
  955. $all?'htmlentities':'htmlspecialchars',
  956. $str,ENT_COMPAT,self::$vars['ENCODING'],TRUE);
  957. }
  958. /**
  959. Convert HTML entities back to their equivalent characters
  960. @return string
  961. @param $str string
  962. @param $all bool
  963. @public
  964. **/
  965. static function htmldecode($str,$all=FALSE) {
  966. return $all?
  967. html_entity_decode($str,ENT_COMPAT,self::$vars['ENCODING']):
  968. htmlspecialchars_decode($str,ENT_COMPAT);
  969. }
  970. /**
  971. Send HTTP status header; Return text equivalent of status code
  972. @return mixed
  973. @param $code int
  974. @public
  975. **/
  976. static function status($code) {
  977. if (!defined('self::HTTP_'.$code)) {
  978. // Invalid status code
  979. trigger_error(sprintf(self::TEXT_HTTP,$code));
  980. return FALSE;
  981. }
  982. // Get description
  983. $response=constant('self::HTTP_'.$code);
  984. // Send raw HTTP header
  985. if (PHP_SAPI!='cli' && !headers_sent())
  986. header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$response);
  987. return $response;
  988. }
  989. /**
  990. Retrieve HTTP headers
  991. @return array
  992. @public
  993. **/
  994. static function headers() {
  995. if (PHP_SAPI!='cli') {
  996. if (function_exists('getallheaders'))
  997. // Apache server
  998. return getallheaders();
  999. // Workaround
  1000. $req=array();
  1001. foreach ($_SERVER as $key=>$val)
  1002. if (substr($key,0,5)=='HTTP_')
  1003. $req[strtr(ucwords(strtolower(
  1004. strtr(substr($key,5),'_',' '))),' ','-')]=$val;
  1005. return $req;
  1006. }
  1007. return array();
  1008. }
  1009. /**
  1010. Send HTTP header with expiration date (seconds from current time)
  1011. @param $secs int
  1012. @public
  1013. **/
  1014. static function expire($secs=0) {
  1015. if (PHP_SAPI!='cli' && !headers_sent()) {
  1016. $time=time();
  1017. $req=self::headers();
  1018. if (isset($req[self::HTTP_IfMod]) &&
  1019. strtotime($req[self::HTTP_IfMod])+$secs>$time) {
  1020. self::status(304);
  1021. die;
  1022. }
  1023. header(self::HTTP_Powered.': '.self::TEXT_AppName.' '.
  1024. '('.self::TEXT_AppURL.')');
  1025. if ($secs) {
  1026. header_remove(self::HTTP_Pragma);
  1027. header(self::HTTP_Expires.': '.gmdate('r',$time+$secs));
  1028. header(self::HTTP_Cache.': max-age='.$secs);
  1029. header(self::HTTP_LastMod.': '.gmdate('r'));
  1030. }
  1031. else {
  1032. header(self::HTTP_Pragma.': no-cache');
  1033. header(self::HTTP_Cache.': no-cache, must-revalidate');
  1034. }
  1035. }
  1036. }
  1037. /**
  1038. Reroute to specified URI
  1039. @param $uri string
  1040. @public
  1041. **/
  1042. static function reroute($uri) {
  1043. $uri=self::resolve($uri);
  1044. if (PHP_SAPI!='cli' && !headers_sent()) {
  1045. // HTTP redirect
  1046. self::status($_SERVER['REQUEST_METHOD']=='GET'?301:303);
  1047. if (session_id())
  1048. session_commit();
  1049. header(self::HTTP_Location.': '.
  1050. (preg_match('/^https?:\/\//',$uri)?
  1051. $uri:(self::$vars['BASE'].$uri)));
  1052. die;
  1053. }
  1054. self::mock('GET '.$uri);
  1055. self::run();
  1056. }
  1057. /**
  1058. Assign handler to route pattern
  1059. @param $pattern string
  1060. @param $funcs mixed
  1061. @param $ttl int
  1062. @param $throttle int
  1063. @param $hotlink bool
  1064. @public
  1065. **/
  1066. static function route($pattern,$funcs,$ttl=0,$throttle=0,$hotlink=TRUE) {
  1067. list($methods,$uri)=
  1068. preg_split('/\s+/',$pattern,2,PREG_SPLIT_NO_EMPTY);
  1069. foreach (self::split($methods) as $method)
  1070. // Use pattern and HTTP methods as route indexes
  1071. self::$vars['ROUTES'][$uri][strtoupper($method)]=
  1072. // Save handler, cache timeout and hotlink permission
  1073. array($funcs,$ttl,$throttle,$hotlink);
  1074. }
  1075. /**
  1076. Provide REST interface by mapping URL to object/class
  1077. @param $url string
  1078. @param $class mixed
  1079. @param $ttl int
  1080. @param $throttle int
  1081. @param $hotlink bool
  1082. @public
  1083. **/
  1084. static function map($url,$class,$ttl=0,$throttle=0,$hotlink=TRUE) {
  1085. foreach (explode('|',self::HTTP_Methods) as $method)
  1086. if (method_exists($class,$method)) {
  1087. $ref=new ReflectionMethod($class,$method);
  1088. self::route($method.' '.$url,$ref->isStatic()?
  1089. array($class,$method):array(new $class,$method),$ttl,
  1090. $throttle,$hotlink);
  1091. unset($ref);
  1092. }
  1093. }
  1094. /**
  1095. Call route handler
  1096. @return mixed
  1097. @param $funcs string
  1098. @param $listen bool
  1099. @public
  1100. **/
  1101. static function call($funcs,$listen=FALSE) {
  1102. $classes=array();
  1103. $funcs=is_string($funcs)?self::split($funcs):array($funcs);
  1104. foreach ($funcs as $func) {
  1105. if (is_string($func)) {
  1106. $func=self::resolve($func);
  1107. if (preg_match('/(.+)\s*(->|::)\s*(.+)/s',$func,$match)) {
  1108. if (!class_exists($match[1]) ||
  1109. !method_exists($match[1],$match[3])) {
  1110. self::error(404);
  1111. return FALSE;
  1112. }
  1113. $func=array($match[2]=='->'?
  1114. new $match[1]:$match[1],$match[3]);
  1115. }
  1116. elseif (!function_exists($func)) {
  1117. if (preg_match('/\.php$/i',$func)) {
  1118. foreach (self::split(self::$vars['IMPORTS'])
  1119. as $path)
  1120. if (is_file($file=$path.$func)) {
  1121. $instance=new F3instance;
  1122. return $instance->sandbox($file);
  1123. }
  1124. }
  1125. self::error(404);
  1126. return FALSE;
  1127. }
  1128. }
  1129. if (!is_callable($func)) {
  1130. self::error(404);
  1131. return FALSE;
  1132. }
  1133. $oop=is_array($func) &&
  1134. (is_object($func[0]) || is_string($func[0]));
  1135. if ($listen && $oop &&
  1136. method_exists($func[0],$before='beforeRoute') &&
  1137. !in_array($func[0],$classes)) {
  1138. // Execute beforeRoute() once per class
  1139. if (call_user_func(array($func[0],$before))===FALSE)
  1140. return FALSE;
  1141. $classes[]=is_object($func[0])?get_class($func[0]):$func[0];
  1142. }
  1143. $out=call_user_func($func);
  1144. if ($listen && $oop &&
  1145. method_exists($func[0],$after='afterRoute') &&
  1146. !in_array($func[0],$classes)) {
  1147. // Execute afterRoute() once per class
  1148. call_user_func(array($func[0],$after));
  1149. $classes[]=is_object($func[0])?get_class($func[0]):$func[0];
  1150. }
  1151. }
  1152. return $out;
  1153. }
  1154. /**
  1155. Process routes based on incoming URI
  1156. @public
  1157. **/
  1158. static function run() {
  1159. // Validate user against spam blacklists
  1160. if (self::$vars['DNSBL'] && !self::privateip($addr=self::realip()) &&
  1161. (!self::$vars['EXEMPT'] ||
  1162. !in_array($addr,self::split(self::$vars['EXEMPT'])))) {
  1163. // Convert to reverse IP dotted quad
  1164. $quad=implode('.',array_reverse(explode('.',$addr)));
  1165. foreach (self::split(self::$vars['DNSBL']) as $list)
  1166. // Check against DNS blacklist
  1167. if (gethostbyname($quad.'.'.$list)!=$quad.'.'.$list) {
  1168. if (self::$vars['SPAM'])
  1169. // Spammer detected; Send to blackhole
  1170. self::reroute(self::$vars['SPAM']);
  1171. else {
  1172. // Forbidden
  1173. self::error(403);
  1174. die;
  1175. }
  1176. }
  1177. }
  1178. // Process routes
  1179. if (!isset(self::$vars['ROUTES']) || !self::$vars['ROUTES']) {
  1180. trigger_error(self::TEXT_NoRoutes);
  1181. return;
  1182. }
  1183. $found=FALSE;
  1184. // Detailed routes get matched first
  1185. krsort(self::$vars['ROUTES']);
  1186. $time=time();
  1187. $req=preg_replace('/^'.preg_quote(self::$vars['BASE'],'/').
  1188. '\b(.+)/','\1',$_SERVER['REQUEST_URI']);
  1189. foreach (self::$vars['ROUTES'] as $uri=>$route) {
  1190. if (!preg_match('/^'.
  1191. preg_replace(
  1192. '/(?:{{)?@(\w+\b)(?:}})?/i',
  1193. // Valid URL characters (RFC 1738)
  1194. '(?P<\1>[\w\-\.!~\*\'"(),\s]+)',
  1195. // Wildcard character in URI
  1196. str_replace('\*','(.*)',preg_quote($uri,'/'))
  1197. ).'\/?(?:\?.*)?$/i',$req,$args))
  1198. continue;
  1199. $found=TRUE;
  1200. // Inspect each defined route
  1201. foreach ($route as $method=>$proc) {
  1202. if (!preg_match('/'.$method.'/',$_SERVER['REQUEST_METHOD']))
  1203. continue;
  1204. list($funcs,$ttl,$throttle,$hotlink)=$proc;
  1205. if (!$hotlink && isset(self::$vars['HOTLINK']) &&
  1206. isset($_SERVER['HTTP_REFERER']) &&
  1207. parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST)!=
  1208. $_SERVER['SERVER_NAME'])
  1209. // Hot link detected; Redirect page
  1210. self::reroute(self::$vars['HOTLINK']);
  1211. // Save named uri captures
  1212. foreach ($args as $key=>$arg)
  1213. // Remove non-zero indexed elements
  1214. if (is_numeric($key) && $key)
  1215. unset($args[$key]);
  1216. self::$vars['PARAMS']=$args;
  1217. // Default: Do not cache
  1218. self::expire(0);
  1219. if ($_SERVER['REQUEST_METHOD']=='GET' && $ttl) {
  1220. $_SERVER['REQUEST_TTL']=$ttl;
  1221. // Get HTTP request headers
  1222. $req=self::headers();
  1223. // Content divider
  1224. $div=chr(0);
  1225. // Get hash code for this Web page
  1226. $hash='url.'.self::hash(
  1227. $_SERVER['REQUEST_METHOD'].' '.
  1228. $_SERVER['REQUEST_URI']
  1229. );
  1230. $cached=Cache::cached($hash);
  1231. $uri='/^'.self::HTTP_Content.':.+/';
  1232. if ($cached && $time-$cached<$ttl) {
  1233. if (!isset($req[self::HTTP_IfMod]) ||
  1234. $cached>strtotime($req[self::HTTP_IfMod])) {
  1235. // Activate cache timer
  1236. self::expire($cached+$ttl-$time);
  1237. // Retrieve from cache
  1238. $buffer=Cache::get($hash);
  1239. $type=strstr($buffer,$div,TRUE);
  1240. if (PHP_SAPI!='cli' && !headers_sent() &&
  1241. preg_match($uri,$type,$match))
  1242. // Cached MIME type
  1243. header($match[0]);
  1244. // Save response
  1245. self::$vars['RESPONSE']=substr(
  1246. strstr($buffer,$div),1);
  1247. }
  1248. else {
  1249. // Client-side cache is still fresh
  1250. self::status(304);
  1251. die;
  1252. }
  1253. }
  1254. else {
  1255. // Activate cache timer
  1256. self::expire($ttl);
  1257. $type='';
  1258. foreach (headers_list() as $hdr)
  1259. if (preg_match($uri,$hdr)) {
  1260. // Add Content-Type header to buffer
  1261. $type=$hdr;
  1262. break;
  1263. }
  1264. // Cache this page
  1265. ob_start();
  1266. self::call($funcs,TRUE);
  1267. self::$vars['RESPONSE']=ob_get_clean();
  1268. if (!self::$vars['ERROR'] &&
  1269. self::$vars['RESPONSE'])
  1270. // Compress and save to cache
  1271. Cache::set($hash,
  1272. $type.$div.self::$vars['RESPONSE']);
  1273. }
  1274. }
  1275. else {
  1276. // Capture output
  1277. ob_start();
  1278. self::$vars['REQBODY']=file_get_contents('php://input');
  1279. self::call($funcs,TRUE);
  1280. self::$vars['RESPONSE']=ob_get_clean();
  1281. }
  1282. $elapsed=time()-$time;
  1283. $throttle=$throttle?:self::$vars['THROTTLE'];
  1284. if ($throttle/1e3>$elapsed)
  1285. // Delay output
  1286. usleep(1e6*($throttle/1e3-$elapsed));
  1287. if (strlen(self::$vars['RESPONSE']) && !self::$vars['QUIET'])
  1288. // Display response
  1289. echo self::$vars['RESPONSE'];
  1290. // Hail the conquering hero
  1291. return;
  1292. }
  1293. // Method not allowed
  1294. if (PHP_SAPI!='cli' && !headers_sent())
  1295. header(self::HTTP_Allow.': '.
  1296. implode(',',array_keys($route)));
  1297. self::error(405);
  1298. return;
  1299. }
  1300. // No such Web page
  1301. self::error(404);
  1302. }
  1303. /**
  1304. Transmit a file for downloading by HTTP client; If kilobytes per
  1305. second is specified, output is throttled (bandwidth will not be
  1306. controlled by default); Return TRUE if successful, FALSE otherwise;
  1307. Support for partial downloads is indicated by third argument
  1308. @param $file string
  1309. @param $kbps int
  1310. @param $partial
  1311. @public
  1312. **/
  1313. static function send($file,$kbps=0,$partial=TRUE) {
  1314. $file=self::resolve($file);
  1315. if (!is_file($file)) {
  1316. self::error(404);
  1317. return FALSE;
  1318. }
  1319. if (PHP_SAPI!='cli' && !headers_sent()) {
  1320. header(self::HTTP_Content.': application/octet-stream');
  1321. header(self::HTTP_Partial.': '.($partial?'bytes':'none'));
  1322. header(self::HTTP_Length.': '.filesize($file));
  1323. }
  1324. $ctr=1;
  1325. $handle=fopen($file,'r');
  1326. $time=microtime(TRUE);
  1327. while (!feof($handle) && !connection_aborted()) {
  1328. if ($kbps) {
  1329. // Throttle bandwidth
  1330. $ctr++;
  1331. if (($ctr/$kbps)>$elapsed=microtime(TRUE)-$time)
  1332. usleep(1e6*($ctr/$kbps-$elapsed));
  1333. }
  1334. // Send 1KiB and reset timer
  1335. echo fread($handle,1024);
  1336. }
  1337. fclose($handle);
  1338. die;
  1339. }
  1340. /**
  1341. Remove HTML tags (except those enumerated) to protect against
  1342. XSS/code injection attacks
  1343. @return mixed
  1344. @param $input string
  1345. @param $tags string
  1346. @public
  1347. **/
  1348. static function scrub($input,$tags=NULL) {
  1349. if (is_array($input))
  1350. foreach ($input as &$val)
  1351. $val=self::scrub($val,$tags);
  1352. if (is_string($input)) {
  1353. $input=($tags=='*')?
  1354. $input:strip_tags($input,is_string($tags)?
  1355. ('<'.implode('><',self::split($tags)).'>'):$tags);
  1356. }
  1357. return $input;
  1358. }
  1359. /**
  1360. Call form field handler
  1361. @param $fields string
  1362. @param $funcs mixed
  1363. @param $tags string
  1364. @param $filter int
  1365. @param $opt array
  1366. @param $assign bool
  1367. @public
  1368. **/
  1369. static function input($fields,$funcs=NULL,
  1370. $tags=NULL,$filter=FILTER_UNSAFE_RAW,$opt=array(),$assign=TRUE) {
  1371. $funcs=is_string($funcs)?self::split($funcs):array($funcs);
  1372. foreach (self::split($fields) as $field) {
  1373. $found=FALSE;
  1374. // Sanitize relevant globals
  1375. foreach (explode('|','GET|POST|REQUEST') as $var)
  1376. if (self::exists($var.'.'.$field)) {
  1377. $key=&self::ref($var.'.'.$field);
  1378. $key=self::scrub($key,$tags);
  1379. $val=filter_var($key,$filter,$opt);
  1380. foreach ($funcs as $func)
  1381. if ($func) {
  1382. if (is_string($func) &&
  1383. preg_match('/([\w\\\]+)\s*->\s*(\w+)/',
  1384. $func,$match))
  1385. // Convert class->method syntax
  1386. $func=array(new $match[1],$match[2]);
  1387. if (!is_callable($func)) {
  1388. // Invalid handler
  1389. trigger_error(
  1390. sprintf(self::TEXT_Form,$field)
  1391. );
  1392. return;
  1393. }
  1394. if (!$found) {
  1395. $out=call_user_func($func,$val,$field);
  1396. if (!$assign)
  1397. return $out;
  1398. if ($out)
  1399. $key=$out;
  1400. $found=TRUE;
  1401. }
  1402. elseif ($assign && $out)
  1403. $key=$val;
  1404. }
  1405. }
  1406. if (!$found) {
  1407. // Invalid handler
  1408. trigger_error(sprintf(self::TEXT_Form,$field));
  1409. return;
  1410. }
  1411. }
  1412. }
  1413. /**
  1414. Render user interface
  1415. @return string
  1416. @param $file string
  1417. @public
  1418. **/
  1419. static function render($file) {
  1420. $file=self::resolve($file);
  1421. foreach (self::split(self::$vars['GUI']) as $gui)
  1422. if (is_file($view=self::fixslashes($gui.$file))) {
  1423. $instance=new F3instance;
  1424. $out=$instance->grab($view);
  1425. return self::$vars['TIDY']?self::tidy($out):$out;
  1426. }
  1427. trigger_error(sprintf(self::TEXT_Render,$view));
  1428. }
  1429. /**
  1430. Return runtime performance analytics
  1431. @return array
  1432. @public
  1433. **/
  1434. static function profile() {
  1435. $stats=&self::$vars['STATS'];
  1436. // Compute elapsed time
  1437. $stats['TIME']['elapsed']=microtime(TRUE)-$stats['TIME']['start'];
  1438. // Compute memory consumption
  1439. $stats['MEMORY']['current']=memory_get_usage();
  1440. $stats['MEMORY']['peak']=memory_get_peak_usage();
  1441. return $stats;
  1442. }
  1443. /**
  1444. Mock environment for command-line use and/or unit testing
  1445. @param $pattern string
  1446. @param $args array
  1447. @public
  1448. **/
  1449. static function mock($pattern,array $args=NULL) {
  1450. list($method,$uri)=explode(' ',$pattern,2);
  1451. $method=strtoupper($method);
  1452. $url=parse_url($uri);
  1453. $query='';
  1454. if ($args)
  1455. $query.=http_build_query($args);
  1456. $query.=isset($url['query'])?(($query?'&':'').$url['query']):'';
  1457. if ($query) {
  1458. parse_str($query,$GLOBALS['_'.$method]);
  1459. parse_str($query,$GLOBALS['_REQUEST']);
  1460. }
  1461. $_SERVER['REQUEST_METHOD']=$method;
  1462. $_SERVER['REQUEST_URI']=self::$vars['BASE'].$url['path'].
  1463. ($query?('?'.$query):'');
  1464. }
  1465. /**
  1466. Perform test and append result to TEST global variable
  1467. @return string
  1468. @param $cond bool
  1469. @param $pass string
  1470. @param $fail string
  1471. @public
  1472. **/
  1473. static function expect($cond,$pass=NULL,$fail=NULL) {
  1474. if (is_string($cond))
  1475. $cond=self::resolve($cond);
  1476. $text=$cond?$pass:$fail;
  1477. self::$vars['TEST'][]=array(
  1478. 'result'=>(int)(boolean)$cond,
  1479. 'text'=>is_string($text)?
  1480. self::resolve($text):var_export($text,TRUE)
  1481. );
  1482. return $text;
  1483. }
  1484. /**
  1485. Display default error page; Use custom page if found
  1486. @param $code int
  1487. @param $str string
  1488. @param $trace array
  1489. @param $fatal bool
  1490. @public
  1491. **/
  1492. static function error($code,$str='',array $trace=NULL,$fatal=FALSE) {
  1493. $prior=self::$vars['ERROR'];
  1494. $out='';
  1495. switch ($code) {
  1496. case 404:
  1497. $str=sprintf(self::TEXT_NotFound,$_SERVER['REQUEST_URI']);
  1498. break;
  1499. case 405:
  1500. $str=sprintf(self::TEXT_NotAllowed,
  1501. $_SERVER['REQUEST_METHOD'],$_SERVER['REQUEST_URI']);
  1502. break;
  1503. default:
  1504. // Generate internal server error if code is zero
  1505. if (!$code)
  1506. $code=500;
  1507. if (!self::$vars['DEBUG'])
  1508. // Disable stack trace
  1509. $trace=NULL;
  1510. elseif ($code==500 && !$trace)
  1511. $trace=debug_backtrace();
  1512. if (is_array($trace)) {
  1513. $line=0;
  1514. $plugins=is_array(
  1515. $plugins=glob(self::$vars['PLUGINS'].'*.php'))?
  1516. array_map('self::fixslashes',$plugins):array();
  1517. // Stringify the stack trace
  1518. ob_start();
  1519. foreach ($trace as $nexus) {
  1520. // Remove stack trace noise
  1521. if (self::$vars['DEBUG']<3 && !$fatal &&
  1522. (!isset($nexus['file']) ||
  1523. self::$vars['DEBUG']<2 &&
  1524. (strrchr(basename($nexus['file']),'.')=='.tmp' ||
  1525. in_array(self::fixslashes(
  1526. $nexus['file']),$plugins)) ||
  1527. isset($nexus['function']) &&
  1528. preg_match('/^(call_user_func(?:_array)?|'.
  1529. 'trigger_error|{.+}|'.__FUNCTION__.'|__)/',
  1530. $nexus['function'])))
  1531. continue;
  1532. echo '#'.$line.' '.
  1533. (isset($nexus['line'])?
  1534. (urldecode(self::fixslashes(
  1535. $nexus['file'])).':'.
  1536. $nexus['line'].' '):'').
  1537. (isset($nexus['function'])?
  1538. ((isset($nexus['class'])?
  1539. ($nexus['class'].$nexus['type']):'').
  1540. $nexus['function'].
  1541. '('.(!preg_match('/{{.+}}/',
  1542. $nexus['function']) &&
  1543. isset($nexus['args'])?
  1544. (self::csv($nexus['args'])):'').')'):'').
  1545. "\n";
  1546. $line++;
  1547. }
  1548. $out=ob_get_clean();
  1549. }
  1550. }
  1551. // Save error details
  1552. self::$vars['ERROR']=array(
  1553. 'code'=>$code,
  1554. 'title'=>self::status($code),
  1555. 'text'=>preg_replace('/\v/','',$str),
  1556. 'trace'=>self::$vars['DEBUG']?$out:''
  1557. );
  1558. $error=&self::$vars['ERROR'];
  1559. if (self::$vars['DEBUG']<2 && self::$vars['QUIET'])
  1560. return;
  1561. // Write to server's error log (with complete stack trace)
  1562. error_log($error['text']);
  1563. foreach (explode("\n",$out) as $str)
  1564. if ($str)
  1565. error_log($str);
  1566. if ($prior || self::$vars['QUIET'])
  1567. return;
  1568. foreach (array('title','text','trace') as $sub)
  1569. // Convert to HTML entities for safety
  1570. $error[$sub]=self::htmlencode(rawurldecode($error[$sub]));
  1571. $error['trace']=nl2br($error['trace']);
  1572. $func=self::$vars['ONERROR'];
  1573. if ($func && !$fatal)
  1574. self::call($func,TRUE);
  1575. else
  1576. echo '<html>'.
  1577. '<head>'.
  1578. '<title>'.$error['code'].' '.$error['title'].'</title>'.
  1579. '</head>'.
  1580. '<body>'.
  1581. '<h1>'.$error['title'].'</h1>'.
  1582. '<p><i>'.$error['text'].'</i></p>'.
  1583. '<p>'.$error['trace'].'</p>'.
  1584. '</body>'.
  1585. '</html>';
  1586. if (self::$vars['STRICT'])
  1587. die;
  1588. }
  1589. /**
  1590. Bootstrap code
  1591. @public
  1592. **/
  1593. static function start() {
  1594. // Prohibit multiple calls
  1595. if (self::$vars)
  1596. return;
  1597. // Handle all exceptions/non-fatal errors
  1598. error_reporting(E_ALL|E_STRICT);
  1599. ini_set('display_errors',0);
  1600. ini_set('register_globals',0);
  1601. // Get PHP settings
  1602. $ini=ini_get_all(NULL,FALSE);
  1603. // Intercept errors and send output to browser
  1604. set_error_handler(
  1605. function($errno,$errstr) {
  1606. if (error_reporting()) {
  1607. // Error suppression (@) is not enabled
  1608. $self=__CLASS__;
  1609. $self::error(500,$errstr);
  1610. }
  1611. }
  1612. );
  1613. // Do the same for PHP exceptions
  1614. set_exception_handler(
  1615. function($ex) {
  1616. if (!count($trace=$ex->getTrace())) {
  1617. // Translate exception trace
  1618. list($trace)=debug_backtrace();
  1619. $arg=$trace['args'][0];
  1620. $trace=array(
  1621. array(
  1622. 'file'=>$arg->getFile(),
  1623. 'line'=>$arg->getLine(),
  1624. 'function'=>'{main}',
  1625. 'args'=>array()
  1626. )
  1627. );
  1628. }
  1629. $self=__CLASS__;
  1630. $self::error(500,$ex->getMessage(),$trace);
  1631. // PHP aborts at this point
  1632. }
  1633. );
  1634. // Apache mod_rewrite enabled?
  1635. if (function_exists('apache_get_modules') &&
  1636. !in_array('mod_rewrite',apache_get_modules())) {
  1637. trigger_error(self::TEXT_Apache);
  1638. return;
  1639. }
  1640. // Fix Apache's VirtualDocumentRoot limitation
  1641. $_SERVER['DOCUMENT_ROOT']=str_replace($_SERVER['SCRIPT_NAME'],'',
  1642. self::fixslashes($_SERVER['SCRIPT_FILENAME']));
  1643. // Adjust HTTP request time precision
  1644. $_SERVER['REQUEST_TIME']=microtime(TRUE);
  1645. // Hydrate framework variables
  1646. $base=self::fixslashes(
  1647. preg_replace('/\/[^\/]+$/','',$_SERVER['SCRIPT_NAME']));
  1648. $scheme=PHP_SAPI=='cli'?
  1649. NULL:
  1650. isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']!='off' ||
  1651. isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
  1652. $_SERVER['HTTP_X_FORWARDED_PROTO']=='https'?'https':'http';
  1653. self::$vars=array(
  1654. // Autoload folders
  1655. 'AUTOLOAD'=>'./',
  1656. // Web root folder
  1657. 'BASE'=>$base,
  1658. // Cache backend to use (autodetect if true; disable if false)
  1659. 'CACHE'=>FALSE,
  1660. // Default cookie settings
  1661. 'JAR'=>array(
  1662. 'expire'=>0,
  1663. 'path'=>$base?:'/',
  1664. 'domain'=>'.'.$_SERVER['SERVER_NAME'],
  1665. 'secure'=>($scheme=='https'),
  1666. 'httponly'=>TRUE
  1667. ),
  1668. // Stack trace verbosity:
  1669. // 0-no stack trace, 1-noise removed, 2-normal, 3-verbose
  1670. 'DEBUG'=>1,
  1671. // DNS black lists
  1672. 'DNSBL'=>NULL,
  1673. // Document encoding
  1674. 'ENCODING'=>'utf-8',
  1675. // Last error
  1676. 'ERROR'=>NULL,
  1677. // Allow/prohibit framework class extension
  1678. 'EXTEND'=>TRUE,
  1679. // IP addresses exempt from spam detection
  1680. 'EXEMPT'=>NULL,
  1681. // User interface folders
  1682. 'GUI'=>'./',
  1683. // URL for hotlink redirection
  1684. 'HOTLINK'=>NULL,
  1685. // Include path for procedural code
  1686. 'IMPORTS'=>'./',
  1687. // Default language (auto-detect if null)
  1688. 'LANGUAGE'=>NULL,
  1689. // Autoloaded classes
  1690. 'LOADED'=>NULL,
  1691. // Dictionary folder
  1692. 'LOCALES'=>'./',
  1693. // Maximum POST size
  1694. 'MAXSIZE'=>self::bytes($ini['post_max_size']),
  1695. // Max mutex lock duration
  1696. 'MUTEX'=>60,
  1697. // Custom error handler
  1698. 'ONERROR'=>NULL,
  1699. // Plugins folder
  1700. 'PLUGINS'=>self::fixslashes(__DIR__).'/',
  1701. // Scheme/protocol
  1702. 'PROTOCOL'=>$scheme,
  1703. // Allow framework to proxy for plugins
  1704. 'PROXY'=>FALSE,
  1705. // Stream handle for HTTP PUT method
  1706. 'PUT'=>NULL,
  1707. // Output suppression switch
  1708. 'QUIET'=>FALSE,
  1709. // Absolute path to document root folder
  1710. 'ROOT'=>$_SERVER['DOCUMENT_ROOT'].'/',
  1711. // Framework routes
  1712. 'ROUTES'=>NULL,
  1713. // URL for spam redirection
  1714. 'SPAM'=>NULL,
  1715. // Stop script on error?
  1716. 'STRICT'=>TRUE,
  1717. // Profiler statistics
  1718. 'STATS'=>array(
  1719. 'MEMORY'=>array('start'=>memory_get_usage()),
  1720. 'TIME'=>array('start'=>microtime(TRUE))
  1721. ),
  1722. // Temporary folder
  1723. 'TEMP'=>'temp/',
  1724. // Minimum script execution time
  1725. 'THROTTLE'=>0,
  1726. // Tidy options
  1727. 'TIDY'=>array(),
  1728. // Framework version
  1729. 'VERSION'=>self::TEXT_AppName.' '.self::TEXT_Version,
  1730. // Default whois server
  1731. 'WHOIS'=>'whois.internic.net'
  1732. );
  1733. // Alias the GUI variable (2.0+)
  1734. self::$vars['UI']=&self::$vars['GUI'];
  1735. // Create convenience containers for PHP globals
  1736. foreach (explode('|',self::PHP_Globals) as $var) {
  1737. // Sync framework and PHP globals
  1738. self::$vars[$var]=&$GLOBALS['_'.$var];
  1739. if ($ini['magic_quotes_gpc'] && preg_match('/^[GPCR]/',$var))
  1740. // Corrective action on PHP magic quotes
  1741. array_walk_recursive(
  1742. self::$vars[$var],
  1743. function(&$val) {
  1744. $val=stripslashes($val);
  1745. }
  1746. );
  1747. }
  1748. if (PHP_SAPI=='cli') {
  1749. // Command line: Parse GET variables in URL, if any
  1750. if (isset($_SERVER['argc']) && $_SERVER['argc']<2)
  1751. array_push($_SERVER['argv'],'/');
  1752. // Detect host name from environment
  1753. $_SERVER['SERVER_NAME']=gethostname();
  1754. // Convert URI to human-readable string
  1755. self::mock('GET '.$_SERVER['argv'][1]);
  1756. }
  1757. // Initialize autoload stack and shutdown sequence
  1758. spl_autoload_register(__CLASS__.'::autoload');
  1759. register_shutdown_function(__CLASS__.'::stop');
  1760. }
  1761. /**
  1762. Execute shutdown function
  1763. @public
  1764. **/
  1765. static function stop() {
  1766. chdir($_SERVER['DOCUMENT_ROOT'].self::$vars['BASE']);
  1767. $error=error_get_last();
  1768. if ($error && !self::$vars['QUIET'] && in_array($error['type'],
  1769. array(E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR)))
  1770. // Intercept fatal error
  1771. self::error(500,sprintf(self::TEXT_Fatal,$error['message']),
  1772. array($error),TRUE);
  1773. if (isset(self::$vars['UNLOAD']) &&
  1774. is_callable(self::$vars['UNLOAD']))
  1775. self::call(self::$vars['UNLOAD']);
  1776. }
  1777. /**
  1778. onLoad event handler (static class initializer)
  1779. @public
  1780. **/
  1781. static function loadstatic($class) {
  1782. $loaded=&self::$vars['LOADED'];
  1783. $lower=strtolower($class);
  1784. if (!isset($loaded[$lower])) {
  1785. $loaded[$lower]=
  1786. array_map('strtolower',get_class_methods($class));
  1787. if (in_array('onload',$loaded[$lower])) {
  1788. // Execute onload method
  1789. $method=new ReflectionMethod($class,'onload');
  1790. if ($method->isStatic())
  1791. call_user_func(array($class,'onload'));
  1792. else
  1793. trigger_error(sprintf(self::TEXT_Static,
  1794. $class.'::onload'));
  1795. }
  1796. }
  1797. }
  1798. /**
  1799. Intercept instantiation of objects in undefined classes
  1800. @param $class string
  1801. @public
  1802. **/
  1803. static function autoload($class) {
  1804. foreach (self::split(
  1805. self::$vars['PLUGINS'].';'.self::$vars['AUTOLOAD']) as $auto) {
  1806. $ns='';
  1807. $iter=ltrim($class,'\\');
  1808. for (;;) {
  1809. if ($glob=glob($auto.self::fixslashes($ns).'*')) {
  1810. if ($grep=preg_grep('/^'.preg_quote($auto,'/').
  1811. implode('[\/_]',explode('\\',$ns.$iter)).
  1812. '(?:\.class)?\.php/i',$glob)) {
  1813. $instance=new F3instance;
  1814. $instance->sandbox(current($grep));
  1815. // Verify that the class was loaded
  1816. if (class_exists($class,FALSE)) {
  1817. // Run onLoad event handler if defined
  1818. self::loadstatic($class);
  1819. return;
  1820. }
  1821. elseif (interface_exists($class,FALSE))
  1822. return;
  1823. }
  1824. $parts=explode('\\',$iter,2);
  1825. if (count($parts)>1) {
  1826. $iter=$parts[1];
  1827. if ($grep=preg_grep('/^'.
  1828. preg_quote($auto.self::fixslashes($ns).
  1829. $parts[0],'/').'$/i',$glob)) {
  1830. $ns=str_replace('/','\\',preg_replace('/^'.
  1831. preg_quote($auto,'/').'/','',
  1832. current($grep))).'\\';
  1833. continue;
  1834. }
  1835. $ns.=$parts[0].'\\';
  1836. }
  1837. }
  1838. break;
  1839. }
  1840. }
  1841. if (count(spl_autol

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