PageRenderTime 56ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/Comparison_Shopping_Engine/provider/langs/php/lib/base.php

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

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