base.php 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651
  1. <?php
  2. /*
  3. Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfree.sf.net).
  5. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
  6. ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  7. IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  8. PURPOSE.
  9. Please see the license.txt file for more information.
  10. */
  11. //! Factory class for single-instance objects
  12. abstract class Prefab {
  13. /**
  14. * Return class instance
  15. * @return static
  16. **/
  17. static function instance() {
  18. if (!Registry::exists($class=get_called_class())) {
  19. $ref=new Reflectionclass($class);
  20. $args=func_get_args();
  21. Registry::set($class,
  22. $args?$ref->newinstanceargs($args):new $class);
  23. }
  24. return Registry::get($class);
  25. }
  26. }
  27. //! Base structure
  28. class Base extends Prefab {
  29. //@{ Framework details
  30. const
  31. PACKAGE='Fat-Free Framework',
  32. VERSION='3.3.0-Dev';
  33. //@}
  34. //@{ HTTP status codes (RFC 2616)
  35. const
  36. HTTP_100='Continue',
  37. HTTP_101='Switching Protocols',
  38. HTTP_200='OK',
  39. HTTP_201='Created',
  40. HTTP_202='Accepted',
  41. HTTP_203='Non-Authorative Information',
  42. HTTP_204='No Content',
  43. HTTP_205='Reset Content',
  44. HTTP_206='Partial Content',
  45. HTTP_300='Multiple Choices',
  46. HTTP_301='Moved Permanently',
  47. HTTP_302='Found',
  48. HTTP_303='See Other',
  49. HTTP_304='Not Modified',
  50. HTTP_305='Use Proxy',
  51. HTTP_307='Temporary Redirect',
  52. HTTP_400='Bad Request',
  53. HTTP_401='Unauthorized',
  54. HTTP_402='Payment Required',
  55. HTTP_403='Forbidden',
  56. HTTP_404='Not Found',
  57. HTTP_405='Method Not Allowed',
  58. HTTP_406='Not Acceptable',
  59. HTTP_407='Proxy Authentication Required',
  60. HTTP_408='Request Timeout',
  61. HTTP_409='Conflict',
  62. HTTP_410='Gone',
  63. HTTP_411='Length Required',
  64. HTTP_412='Precondition Failed',
  65. HTTP_413='Request Entity Too Large',
  66. HTTP_414='Request-URI Too Long',
  67. HTTP_415='Unsupported Media Type',
  68. HTTP_416='Requested Range Not Satisfiable',
  69. HTTP_417='Expectation Failed',
  70. HTTP_500='Internal Server Error',
  71. HTTP_501='Not Implemented',
  72. HTTP_502='Bad Gateway',
  73. HTTP_503='Service Unavailable',
  74. HTTP_504='Gateway Timeout',
  75. HTTP_505='HTTP Version Not Supported';
  76. //@}
  77. const
  78. //! Mapped PHP globals
  79. GLOBALS='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV',
  80. //! HTTP verbs
  81. VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT',
  82. //! Default directory permissions
  83. MODE=0755,
  84. //! Syntax highlighting stylesheet
  85. CSS='code.css';
  86. //@{ HTTP request types
  87. const
  88. REQ_SYNC=1,
  89. REQ_AJAX=2;
  90. //@}
  91. //@{ Error messages
  92. const
  93. E_Pattern='Invalid routing pattern: %s',
  94. E_Named='Named route does not exist: %s',
  95. E_Fatal='Fatal error: %s',
  96. E_Open='Unable to open %s',
  97. E_Routes='No routes specified',
  98. E_Class='Invalid class %s',
  99. E_Method='Invalid method %s',
  100. E_Hive='Invalid hive key %s';
  101. //@}
  102. private
  103. //! Globals
  104. $hive,
  105. //! Initial settings
  106. $init,
  107. //! Language lookup sequence
  108. $languages,
  109. //! Default fallback language
  110. $fallback='en';
  111. /**
  112. * Sync PHP global with corresponding hive key
  113. * @return array
  114. * @param $key string
  115. **/
  116. function sync($key) {
  117. return $this->hive[$key]=&$GLOBALS['_'.$key];
  118. }
  119. /**
  120. * Return the parts of specified hive key
  121. * @return array
  122. * @param $key string
  123. **/
  124. private function cut($key) {
  125. return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
  126. $key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
  127. }
  128. /**
  129. * Replace tokenized URL with current route's token values
  130. * @return string
  131. * @param $url array|string
  132. **/
  133. function build($url) {
  134. if (is_array($url))
  135. foreach ($url as &$var) {
  136. $var=$this->build($var);
  137. unset($var);
  138. }
  139. elseif (preg_match_all('/@(\w+)/',$url,$matches,PREG_SET_ORDER))
  140. foreach ($matches as $match)
  141. if (array_key_exists($match[1],$this->hive['PARAMS']))
  142. $url=str_replace($match[0],
  143. $this->hive['PARAMS'][$match[1]],$url);
  144. return $url;
  145. }
  146. /**
  147. * Parse string containing key-value pairs and use as routing tokens
  148. * @return NULL
  149. * @param $str string
  150. **/
  151. function parse($str) {
  152. preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/',
  153. $str,$pairs,PREG_SET_ORDER);
  154. foreach ($pairs as $pair)
  155. $this->hive['PARAMS'][$pair[1]]=trim($pair[2]);
  156. }
  157. /**
  158. * Convert JS-style token to PHP expression
  159. * @return string
  160. * @param $str string
  161. **/
  162. function compile($str) {
  163. $fw=$this;
  164. return preg_replace_callback(
  165. '/(?<!\w)@(\w(?:[\w\.\[\]]|\->|::)*)/',
  166. function($var) use($fw) {
  167. return '$'.preg_replace_callback(
  168. '/\.(\w+)|\[((?:[^\[\]]*|(?R))*)\]/',
  169. function($expr) use($fw) {
  170. return '['.var_export(
  171. isset($expr[2])?
  172. $fw->compile($expr[2]):
  173. (ctype_digit($expr[1])?
  174. (int)$expr[1]:
  175. $expr[1]),TRUE).']';
  176. },
  177. $var[1]
  178. );
  179. },
  180. $str
  181. );
  182. }
  183. /**
  184. * Get hive key reference/contents; Add non-existent hive keys,
  185. * array elements, and object properties by default
  186. * @return mixed
  187. * @param $key string
  188. * @param $add bool
  189. **/
  190. function &ref($key,$add=TRUE) {
  191. $parts=$this->cut($key);
  192. if ($parts[0]=='SESSION') {
  193. @session_start();
  194. $this->sync('SESSION');
  195. }
  196. elseif (!preg_match('/^\w+$/',$parts[0]))
  197. user_error(sprintf(self::E_Hive,$this->stringify($key)));
  198. if ($add)
  199. $var=&$this->hive;
  200. else
  201. $var=$this->hive;
  202. $obj=FALSE;
  203. foreach ($parts as $part)
  204. if ($part=='->')
  205. $obj=TRUE;
  206. elseif ($obj) {
  207. $obj=FALSE;
  208. if (!is_object($var))
  209. $var=new stdclass;
  210. if ($add || property_exists($var,$part))
  211. $var=&$var->$part;
  212. else {
  213. $var=&$this->null;
  214. break;
  215. }
  216. }
  217. else {
  218. if (!is_array($var))
  219. $var=array();
  220. if ($add || array_key_exists($part,$var))
  221. $var=&$var[$part];
  222. else {
  223. $var=&$this->null;
  224. break;
  225. }
  226. }
  227. if ($parts[0]=='ALIASES')
  228. $var=$this->build($var);
  229. return $var;
  230. }
  231. /**
  232. * Return TRUE if hive key is not set
  233. * (or return timestamp and TTL if cached)
  234. * @return bool
  235. * @param $key string
  236. * @param $val mixed
  237. **/
  238. function exists($key,&$val=NULL) {
  239. $val=$this->ref($key,FALSE);
  240. return isset($val)?
  241. TRUE:
  242. (Cache::instance()->exists($this->hash($key).'.var',$val)?
  243. $val:FALSE);
  244. }
  245. /**
  246. * Return TRUE if hive key is empty and not cached
  247. * @return bool
  248. * @param $key string
  249. **/
  250. function devoid($key) {
  251. $val=$this->ref($key,FALSE);
  252. return empty($val) &&
  253. (!Cache::instance()->exists($this->hash($key).'.var',$val) ||
  254. !$val);
  255. }
  256. /**
  257. * Bind value to hive key
  258. * @return mixed
  259. * @param $key string
  260. * @param $val mixed
  261. * @param $ttl int
  262. **/
  263. function set($key,$val,$ttl=0) {
  264. if (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
  265. $this->set('REQUEST'.$expr[2],$val);
  266. if ($expr[1]=='COOKIE') {
  267. $parts=$this->cut($key);
  268. $jar=$this->hive['JAR'];
  269. if ($ttl)
  270. $jar['expire']=time()+$ttl;
  271. call_user_func_array('setcookie',array($parts[1],$val)+$jar);
  272. }
  273. }
  274. else switch ($key) {
  275. case 'CACHE':
  276. $val=Cache::instance()->load($val,TRUE);
  277. break;
  278. case 'ENCODING':
  279. $val=ini_set('default_charset',$val);
  280. if (extension_loaded('mbstring'))
  281. mb_internal_encoding($val);
  282. break;
  283. case 'FALLBACK':
  284. $this->fallback=$val;
  285. $lang=$this->language($this->hive['LANGUAGE']);
  286. case 'LANGUAGE':
  287. if (isset($lang) || $lang=$this->language($val))
  288. $val=$this->language($val);
  289. $lex=$this->lexicon($this->hive['LOCALES']);
  290. case 'LOCALES':
  291. if (isset($lex) || $lex=$this->lexicon($val))
  292. $this->mset($lex,$this->hive['PREFIX'],$ttl);
  293. break;
  294. case 'TZ':
  295. date_default_timezone_set($val);
  296. break;
  297. }
  298. $ref=&$this->ref($key);
  299. $ref=$val;
  300. if (preg_match('/^JAR\b/',$key))
  301. call_user_func_array(
  302. 'session_set_cookie_params',$this->hive['JAR']);
  303. $cache=Cache::instance();
  304. if ($cache->exists($hash=$this->hash($key).'.var') || $ttl)
  305. // Persist the key-value pair
  306. $cache->set($hash,$val,$ttl);
  307. return $ref;
  308. }
  309. /**
  310. * Retrieve contents of hive key
  311. * @return mixed
  312. * @param $key string
  313. * @param $args string|array
  314. **/
  315. function get($key,$args=NULL) {
  316. if (is_string($val=$this->ref($key,FALSE)) && !is_null($args))
  317. return call_user_func_array(
  318. array($this,'format'),
  319. array_merge(array($val),is_array($args)?$args:array($args))
  320. );
  321. if (is_null($val)) {
  322. // Attempt to retrieve from cache
  323. if (Cache::instance()->exists($this->hash($key).'.var',$data))
  324. return $data;
  325. }
  326. return $val;
  327. }
  328. /**
  329. * Unset hive key
  330. * @return NULL
  331. * @param $key string
  332. **/
  333. function clear($key) {
  334. // Normalize array literal
  335. $cache=Cache::instance();
  336. $parts=$this->cut($key);
  337. if ($key=='CACHE')
  338. // Clear cache contents
  339. $cache->reset();
  340. elseif (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
  341. $this->clear('REQUEST'.$expr[2]);
  342. if ($expr[1]=='COOKIE') {
  343. $parts=$this->cut($key);
  344. $jar=$this->hive['JAR'];
  345. $jar['expire']=strtotime('-1 year');
  346. call_user_func_array('setcookie',
  347. array_merge(array($parts[1],''),$jar));
  348. unset($_COOKIE[$parts[1]]);
  349. }
  350. }
  351. elseif ($parts[0]=='SESSION') {
  352. @session_start();
  353. if (empty($parts[1])) {
  354. // End session
  355. session_unset();
  356. session_destroy();
  357. unset($_COOKIE[session_name()]);
  358. header_remove('Set-Cookie');
  359. }
  360. $this->sync('SESSION');
  361. }
  362. if (!isset($parts[1]) && array_key_exists($parts[0],$this->init))
  363. // Reset global to default value
  364. $this->hive[$parts[0]]=$this->init[$parts[0]];
  365. else {
  366. eval('unset('.$this->compile('@this->hive.'.$key).');');
  367. if ($parts[0]=='SESSION') {
  368. session_commit();
  369. session_start();
  370. }
  371. if ($cache->exists($hash=$this->hash($key).'.var'))
  372. // Remove from cache
  373. $cache->clear($hash);
  374. }
  375. }
  376. /**
  377. * Multi-variable assignment using associative array
  378. * @return NULL
  379. * @param $vars array
  380. * @param $prefix string
  381. * @param $ttl int
  382. **/
  383. function mset(array $vars,$prefix='',$ttl=0) {
  384. foreach ($vars as $key=>$val)
  385. $this->set($prefix.$key,$val,$ttl);
  386. }
  387. /**
  388. * Publish hive contents
  389. * @return array
  390. **/
  391. function hive() {
  392. return $this->hive;
  393. }
  394. /**
  395. * Copy contents of hive variable to another
  396. * @return mixed
  397. * @param $src string
  398. * @param $dst string
  399. **/
  400. function copy($src,$dst) {
  401. $ref=&$this->ref($dst);
  402. return $ref=$this->ref($src,FALSE);
  403. }
  404. /**
  405. * Concatenate string to hive string variable
  406. * @return string
  407. * @param $key string
  408. * @param $val string
  409. **/
  410. function concat($key,$val) {
  411. $ref=&$this->ref($key);
  412. $ref.=$val;
  413. return $ref;
  414. }
  415. /**
  416. * Swap keys and values of hive array variable
  417. * @return array
  418. * @param $key string
  419. * @public
  420. **/
  421. function flip($key) {
  422. $ref=&$this->ref($key);
  423. return $ref=array_combine(array_values($ref),array_keys($ref));
  424. }
  425. /**
  426. * Add element to the end of hive array variable
  427. * @return mixed
  428. * @param $key string
  429. * @param $val mixed
  430. **/
  431. function push($key,$val) {
  432. $ref=&$this->ref($key);
  433. array_push($ref,$val);
  434. return $val;
  435. }
  436. /**
  437. * Remove last element of hive array variable
  438. * @return mixed
  439. * @param $key string
  440. **/
  441. function pop($key) {
  442. $ref=&$this->ref($key);
  443. return array_pop($ref);
  444. }
  445. /**
  446. * Add element to the beginning of hive array variable
  447. * @return mixed
  448. * @param $key string
  449. * @param $val mixed
  450. **/
  451. function unshift($key,$val) {
  452. $ref=&$this->ref($key);
  453. array_unshift($ref,$val);
  454. return $val;
  455. }
  456. /**
  457. * Remove first element of hive array variable
  458. * @return mixed
  459. * @param $key string
  460. **/
  461. function shift($key) {
  462. $ref=&$this->ref($key);
  463. return array_shift($ref);
  464. }
  465. /**
  466. * Merge array with hive array variable
  467. * @return array
  468. * @param $key string
  469. * @param $src string|array
  470. **/
  471. function merge($key,$src) {
  472. $ref=&$this->ref($key);
  473. return array_merge($ref,is_string($src)?$this->hive[$src]:$src);
  474. }
  475. /**
  476. * Convert backslashes to slashes
  477. * @return string
  478. * @param $str string
  479. **/
  480. function fixslashes($str) {
  481. return $str?strtr($str,'\\','/'):$str;
  482. }
  483. /**
  484. * Split comma-, semi-colon, or pipe-separated string
  485. * @return array
  486. * @param $str string
  487. **/
  488. function split($str) {
  489. return array_map('trim',
  490. preg_split('/[,;|]/',$str,0,PREG_SPLIT_NO_EMPTY));
  491. }
  492. /**
  493. * Convert PHP expression/value to compressed exportable string
  494. * @return string
  495. * @param $arg mixed
  496. * @param $stack array
  497. **/
  498. function stringify($arg,array $stack=NULL) {
  499. if ($stack) {
  500. foreach ($stack as $node)
  501. if ($arg===$node)
  502. return '*RECURSION*';
  503. }
  504. else
  505. $stack=array();
  506. switch (gettype($arg)) {
  507. case 'object':
  508. $str='';
  509. foreach (get_object_vars($arg) as $key=>$val)
  510. $str.=($str?',':'').
  511. var_export($key,TRUE).'=>'.
  512. $this->stringify($val,
  513. array_merge($stack,array($arg)));
  514. return get_class($arg).'::__set_state(array('.$str.'))';
  515. case 'array':
  516. $str='';
  517. $num=isset($arg[0]) &&
  518. ctype_digit(implode('',array_keys($arg)));
  519. foreach ($arg as $key=>$val)
  520. $str.=($str?',':'').
  521. ($num?'':(var_export($key,TRUE).'=>')).
  522. $this->stringify($val,
  523. array_merge($stack,array($arg)));
  524. return 'array('.$str.')';
  525. default:
  526. return var_export($arg,TRUE);
  527. }
  528. }
  529. /**
  530. * Flatten array values and return as CSV string
  531. * @return string
  532. * @param $args array
  533. **/
  534. function csv(array $args) {
  535. return implode(',',array_map('stripcslashes',
  536. array_map(array($this,'stringify'),$args)));
  537. }
  538. /**
  539. * Convert snakecase string to camelcase
  540. * @return string
  541. * @param $str string
  542. **/
  543. function camelcase($str) {
  544. return preg_replace_callback(
  545. '/_(\w)/',
  546. function($match) {
  547. return strtoupper($match[1]);
  548. },
  549. $str
  550. );
  551. }
  552. /**
  553. * Convert camelcase string to snakecase
  554. * @return string
  555. * @param $str string
  556. **/
  557. function snakecase($str) {
  558. return strtolower(preg_replace('/[[:upper:]]/','_\0',$str));
  559. }
  560. /**
  561. * Return -1 if specified number is negative, 0 if zero,
  562. * or 1 if the number is positive
  563. * @return int
  564. * @param $num mixed
  565. **/
  566. function sign($num) {
  567. return $num?($num/abs($num)):0;
  568. }
  569. /**
  570. * Generate 64bit/base36 hash
  571. * @return string
  572. * @param $str
  573. **/
  574. function hash($str) {
  575. return str_pad(base_convert(
  576. hexdec(substr(sha1($str),-16)),10,36),11,'0',STR_PAD_LEFT);
  577. }
  578. /**
  579. * Return Base64-encoded equivalent
  580. * @return string
  581. * @param $data string
  582. * @param $mime string
  583. **/
  584. function base64($data,$mime) {
  585. return 'data:'.$mime.';base64,'.base64_encode($data);
  586. }
  587. /**
  588. * Convert special characters to HTML entities
  589. * @return string
  590. * @param $str string
  591. **/
  592. function encode($str) {
  593. return @htmlentities($str,$this->hive['BITMASK'],
  594. $this->hive['ENCODING'],FALSE)?:$this->scrub($str);
  595. }
  596. /**
  597. * Convert HTML entities back to characters
  598. * @return string
  599. * @param $str string
  600. **/
  601. function decode($str) {
  602. return html_entity_decode($str,$this->hive['BITMASK'],
  603. $this->hive['ENCODING']);
  604. }
  605. /**
  606. * Invoke callback recursively for all data types
  607. * @return mixed
  608. * @param $arg mixed
  609. * @param $func callback
  610. * @param $stack array
  611. **/
  612. function recursive($arg,$func,$stack=NULL) {
  613. if ($stack) {
  614. foreach ($stack as $node)
  615. if ($arg===$node)
  616. return $arg;
  617. }
  618. else
  619. $stack=array();
  620. switch (gettype($arg)) {
  621. case 'object':
  622. if (method_exists('ReflectionClass','iscloneable')) {
  623. $ref=new ReflectionClass($arg);
  624. if ($ref->iscloneable()) {
  625. $arg=clone($arg);
  626. foreach (get_object_vars($arg) as $key=>$val)
  627. $arg->$key=$this->recursive($val,$func,
  628. array_merge($stack,array($arg)));
  629. }
  630. }
  631. return $arg;
  632. case 'array':
  633. $tmp=array();
  634. foreach ($arg as $key=>$val)
  635. $tmp[$key]=$this->recursive($val,$func,
  636. array_merge($stack,array($arg)));
  637. return $tmp;
  638. }
  639. return $func($arg);
  640. }
  641. /**
  642. * Remove HTML tags (except those enumerated) and non-printable
  643. * characters to mitigate XSS/code injection attacks
  644. * @return mixed
  645. * @param $arg mixed
  646. * @param $tags string
  647. **/
  648. function clean($arg,$tags=NULL) {
  649. $fw=$this;
  650. return $this->recursive($arg,
  651. function($val) use($fw,$tags) {
  652. if ($tags!='*')
  653. $val=trim(strip_tags($val,
  654. '<'.implode('><',$fw->split($tags)).'>'));
  655. return trim(preg_replace(
  656. '/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',$val));
  657. }
  658. );
  659. }
  660. /**
  661. * Similar to clean(), except that variable is passed by reference
  662. * @return mixed
  663. * @param $var mixed
  664. * @param $tags string
  665. **/
  666. function scrub(&$var,$tags=NULL) {
  667. return $var=$this->clean($var,$tags);
  668. }
  669. /**
  670. * Return locale-aware formatted string
  671. * @return string
  672. **/
  673. function format() {
  674. $args=func_get_args();
  675. $val=array_shift($args);
  676. // Get formatting rules
  677. $conv=localeconv();
  678. return preg_replace_callback(
  679. '/\{(?P<pos>\d+)\s*(?:,\s*(?P<type>\w+)\s*'.
  680. '(?:,\s*(?P<mod>(?:\w+(?:\s*\{.+?\}\s*,?)?)*)'.
  681. '(?:,\s*(?P<prop>.+?))?)?)?\}/',
  682. function($expr) use($args,$conv) {
  683. extract($expr);
  684. extract($conv);
  685. if (!array_key_exists($pos,$args))
  686. return $expr[0];
  687. if (isset($type))
  688. switch ($type) {
  689. case 'plural':
  690. preg_match_all('/(?<tag>\w+)'.
  691. '(?:\s+\{\s*(?<data>.+?)\s*\})/',
  692. $mod,$matches,PREG_SET_ORDER);
  693. $ord=array('zero','one','two');
  694. foreach ($matches as $match) {
  695. extract($match);
  696. if (isset($ord[$args[$pos]]) &&
  697. $tag==$ord[$args[$pos]] || $tag=='other')
  698. return str_replace('#',$args[$pos],$data);
  699. }
  700. case 'number':
  701. if (isset($mod))
  702. switch ($mod) {
  703. case 'integer':
  704. return number_format(
  705. $args[$pos],0,'',$thousands_sep);
  706. case 'currency':
  707. if (function_exists('money_format'))
  708. return money_format(
  709. '%n',$args[$pos]);
  710. $fmt=array(
  711. 0=>'(nc)',1=>'(n c)',
  712. 2=>'(nc)',10=>'+nc',
  713. 11=>'+n c',12=>'+ nc',
  714. 20=>'nc+',21=>'n c+',
  715. 22=>'nc +',30=>'n+c',
  716. 31=>'n +c',32=>'n+ c',
  717. 40=>'nc+',41=>'n c+',
  718. 42=>'nc +',100=>'(cn)',
  719. 101=>'(c n)',102=>'(cn)',
  720. 110=>'+cn',111=>'+c n',
  721. 112=>'+ cn',120=>'cn+',
  722. 121=>'c n+',122=>'cn +',
  723. 130=>'+cn',131=>'+c n',
  724. 132=>'+ cn',140=>'c+n',
  725. 141=>'c+ n',142=>'c +n'
  726. );
  727. if ($args[$pos]<0) {
  728. $sgn=$negative_sign;
  729. $pre='n';
  730. }
  731. else {
  732. $sgn=$positive_sign;
  733. $pre='p';
  734. }
  735. return str_replace(
  736. array('+','n','c'),
  737. array($sgn,number_format(
  738. abs($args[$pos]),
  739. $frac_digits,
  740. $decimal_point,
  741. $thousands_sep),
  742. $currency_symbol),
  743. $fmt[(int)(
  744. (${$pre.'_cs_precedes'}%2).
  745. (${$pre.'_sign_posn'}%5).
  746. (${$pre.'_sep_by_space'}%3)
  747. )]
  748. );
  749. case 'percent':
  750. return number_format(
  751. $args[$pos]*100,0,$decimal_point,
  752. $thousands_sep).'%';
  753. case 'decimal':
  754. return number_format(
  755. $args[$pos],$prop,$decimal_point,
  756. $thousands_sep);
  757. }
  758. break;
  759. case 'date':
  760. if (empty($mod) || $mod=='short')
  761. $prop='%x';
  762. elseif ($mod=='long')
  763. $prop='%A, %d %B %Y';
  764. return strftime($prop,$args[$pos]);
  765. case 'time':
  766. if (empty($mod) || $mod=='short')
  767. $prop='%X';
  768. return strftime($prop,$args[$pos]);
  769. default:
  770. return $expr[0];
  771. }
  772. return $args[$pos];
  773. },
  774. $val
  775. );
  776. }
  777. /**
  778. * Assign/auto-detect language
  779. * @return string
  780. * @param $code string
  781. **/
  782. function language($code) {
  783. $code=preg_replace('/;q=.+?(?=,|$)/','',$code);
  784. $code.=($code?',':'').$this->fallback;
  785. $this->languages=array();
  786. foreach (array_reverse(explode(',',$code)) as $lang) {
  787. if (preg_match('/^(\w{2})(?:-(\w{2}))?\b/i',$lang,$parts)) {
  788. // Generic language
  789. array_unshift($this->languages,$parts[1]);
  790. if (isset($parts[2])) {
  791. // Specific language
  792. $parts[0]=$parts[1].'-'.($parts[2]=strtoupper($parts[2]));
  793. array_unshift($this->languages,$parts[0]);
  794. }
  795. }
  796. }
  797. $this->languages=array_unique($this->languages);
  798. $locales=array();
  799. $windows=preg_match('/^win/i',PHP_OS);
  800. foreach ($this->languages as $locale) {
  801. if ($windows) {
  802. $parts=explode('-',$locale);
  803. $locale=@constant('ISO::LC_'.$parts[0]);
  804. if (isset($parts[1]) &&
  805. $country=@constant('ISO::CC_'.strtolower($parts[1])))
  806. $locale.='-'.$country;
  807. }
  808. $locales[]=$locale;
  809. $locales[]=$locale.'.'.ini_get('default_charset');
  810. }
  811. setlocale(LC_ALL,str_replace('-','_',$locales));
  812. return implode(',',$this->languages);
  813. }
  814. /**
  815. * Transfer lexicon entries to hive
  816. * @return array
  817. * @param $path string
  818. **/
  819. function lexicon($path) {
  820. $lex=array();
  821. foreach ($this->languages?:array($this->fallback) as $lang) {
  822. if ((is_file($file=($base=$path.$lang).'.php') ||
  823. is_file($file=$base.'.php')) &&
  824. is_array($dict=require($file)))
  825. $lex+=$dict;
  826. elseif (is_file($file=$base.'.ini')) {
  827. preg_match_all(
  828. '/(?<=^|\n)(?:'.
  829. '(.+?)\h*=\h*'.
  830. '((?:\\\\\h*\r?\n|.+?)*)'.
  831. ')(?=\r?\n|$)/',
  832. $this->read($file),$matches,PREG_SET_ORDER);
  833. if ($matches)
  834. foreach ($matches as $match)
  835. if (isset($match[1]) &&
  836. !array_key_exists($match[1],$lex))
  837. $lex[$match[1]]=trim(preg_replace(
  838. '/(?<!\\\\)"|\\\\\h*\r?\n/','',$match[2]));
  839. }
  840. }
  841. return $lex;
  842. }
  843. /**
  844. * Return string representation of PHP value
  845. * @return string
  846. * @param $arg mixed
  847. **/
  848. function serialize($arg) {
  849. switch (strtolower($this->hive['SERIALIZER'])) {
  850. case 'igbinary':
  851. return igbinary_serialize($arg);
  852. default:
  853. return serialize($arg);
  854. }
  855. }
  856. /**
  857. * Return PHP value derived from string
  858. * @return string
  859. * @param $arg mixed
  860. **/
  861. function unserialize($arg) {
  862. switch (strtolower($this->hive['SERIALIZER'])) {
  863. case 'igbinary':
  864. return igbinary_unserialize($arg);
  865. default:
  866. return unserialize($arg);
  867. }
  868. }
  869. /**
  870. * Send HTTP/1.1 status header; Return text equivalent of status code
  871. * @return string
  872. * @param $code int
  873. **/
  874. function status($code) {
  875. $reason=@constant('self::HTTP_'.$code);
  876. if (PHP_SAPI!='cli')
  877. header('HTTP/1.1 '.$code.' '.$reason);
  878. return $reason;
  879. }
  880. /**
  881. * Send cache metadata to HTTP client
  882. * @return NULL
  883. * @param $secs int
  884. **/
  885. function expire($secs=0) {
  886. if (PHP_SAPI!='cli') {
  887. header('X-Content-Type-Options: nosniff');
  888. header('X-Frame-Options: '.$this->hive['XFRAME']);
  889. header('X-Powered-By: '.$this->hive['PACKAGE']);
  890. header('X-XSS-Protection: 1; mode=block');
  891. if ($secs) {
  892. $time=microtime(TRUE);
  893. header_remove('Pragma');
  894. header('Expires: '.gmdate('r',$time+$secs));
  895. header('Cache-Control: max-age='.$secs);
  896. header('Last-Modified: '.gmdate('r'));
  897. $headers=$this->hive['HEADERS'];
  898. if (isset($headers['If-Modified-Since']) &&
  899. strtotime($headers['If-Modified-Since'])+$secs>$time) {
  900. $this->status(304);
  901. die;
  902. }
  903. }
  904. else
  905. header('Cache-Control: no-cache, no-store, must-revalidate');
  906. }
  907. }
  908. /**
  909. * Log error; Execute ONERROR handler if defined, else display
  910. * default error page (HTML for synchronous requests, JSON string
  911. * for AJAX requests)
  912. * @return NULL
  913. * @param $code int
  914. * @param $text string
  915. * @param $trace array
  916. **/
  917. function error($code,$text='',array $trace=NULL) {
  918. $prior=$this->hive['ERROR'];
  919. $header=$this->status($code);
  920. $req=$this->hive['VERB'].' '.$this->hive['PATH'];
  921. if (!$text)
  922. $text='HTTP '.$code.' ('.$req.')';
  923. error_log($text);
  924. if (!$trace)
  925. $trace=array_slice(debug_backtrace(FALSE),1);
  926. $debug=$this->hive['DEBUG'];
  927. $trace=array_filter(
  928. $trace,
  929. function($frame) use($debug) {
  930. return $debug && isset($frame['file']) &&
  931. ($frame['file']!=__FILE__ || $debug>1) &&
  932. (empty($frame['function']) ||
  933. !preg_match('/^(?:(?:trigger|user)_error|'.
  934. '__call|call_user_func)/',$frame['function']));
  935. }
  936. );
  937. $highlight=PHP_SAPI!='cli' &&
  938. $this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS);
  939. $out='';
  940. $eol="\n";
  941. // Analyze stack trace
  942. foreach ($trace as $frame) {
  943. $line='';
  944. if (isset($frame['class']))
  945. $line.=$frame['class'].$frame['type'];
  946. if (isset($frame['function']))
  947. $line.=$frame['function'].'('.
  948. ($debug>2 && isset($frame['args'])?
  949. $this->csv($frame['args']):'').')';
  950. $src=$this->fixslashes(str_replace($_SERVER['DOCUMENT_ROOT'].
  951. '/','',$frame['file'])).':'.$frame['line'].' ';
  952. error_log('- '.$src.$line);
  953. $out.='• '.($highlight?
  954. ($this->highlight($src).' '.$this->highlight($line)):
  955. ($src.$line)).$eol;
  956. }
  957. $this->hive['ERROR']=array(
  958. 'status'=>$header,
  959. 'code'=>$code,
  960. 'text'=>$text,
  961. 'trace'=>$trace
  962. );
  963. $handler=$this->hive['ONERROR'];
  964. $this->hive['ONERROR']=NULL;
  965. if ((!$handler ||
  966. $this->call($handler,$this,'beforeroute,afterroute')===FALSE) &&
  967. !$prior && PHP_SAPI!='cli' && !$this->hive['QUIET'])
  968. echo $this->hive['AJAX']?
  969. json_encode($this->hive['ERROR']):
  970. ('<!DOCTYPE html>'.$eol.
  971. '<html>'.$eol.
  972. '<head>'.
  973. '<title>'.$code.' '.$header.'</title>'.
  974. ($highlight?
  975. ('<style>'.$this->read($css).'</style>'):'').
  976. '</head>'.$eol.
  977. '<body>'.$eol.
  978. '<h1>'.$header.'</h1>'.$eol.
  979. '<p>'.$this->encode($text?:$req).'</p>'.$eol.
  980. ($debug?('<pre>'.$out.'</pre>'.$eol):'').
  981. '</body>'.$eol.
  982. '</html>');
  983. if ($this->hive['HALT'])
  984. die;
  985. }
  986. /**
  987. * Mock HTTP request
  988. * @return NULL
  989. * @param $pattern string
  990. * @param $args array
  991. * @param $headers array
  992. * @param $body string
  993. **/
  994. function mock($pattern,array $args=NULL,array $headers=NULL,$body=NULL) {
  995. $types=array('sync','ajax');
  996. preg_match('/([\|\w]+)\h+(?:@(\w+)(?:(\(.+?)\))*|([^\h]+))'.
  997. '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
  998. $verb=strtoupper($parts[1]);
  999. if ($parts[2]) {
  1000. if (empty($this->hive['ALIASES'][$parts[2]]))
  1001. user_error(sprintf(self::E_Named,$parts[2]));
  1002. $parts[4]=$this->hive['ALIASES'][$parts[2]];
  1003. if (isset($parts[3]))
  1004. $this->parse($parts[3]);
  1005. $parts[4]=$this->build($parts[4]);
  1006. }
  1007. if (empty($parts[4]))
  1008. user_error(sprintf(self::E_Pattern,$pattern));
  1009. $url=parse_url($parts[4]);
  1010. $query='';
  1011. if ($args)
  1012. $query.=http_build_query($args);
  1013. $query.=isset($url['query'])?(($query?'&':'').$url['query']):'';
  1014. if ($query && preg_match('/GET|POST/',$verb)) {
  1015. parse_str($query,$GLOBALS['_'.$verb]);
  1016. parse_str($query,$GLOBALS['_REQUEST']);
  1017. }
  1018. foreach ($headers?:array() as $key=>$val)
  1019. $_SERVER['HTTP_'.strtr(strtoupper($key),'-','_')]=$val;
  1020. $this->hive['VERB']=$verb;
  1021. $this->hive['URI']=$this->hive['BASE'].$url['path'];
  1022. $this->hive['AJAX']=isset($parts[5]) &&
  1023. preg_match('/ajax/i',$parts[5]);
  1024. if (preg_match('/GET|HEAD/',$verb) && $query)
  1025. $this->hive['URI'].='?'.$query;
  1026. else
  1027. $this->hive['BODY']=$body?:$query;
  1028. $this->run();
  1029. }
  1030. /**
  1031. * Bind handler to route pattern
  1032. * @return NULL
  1033. * @param $pattern string|array
  1034. * @param $handler callback
  1035. * @param $ttl int
  1036. * @param $kbps int
  1037. **/
  1038. function route($pattern,$handler,$ttl=0,$kbps=0) {
  1039. $types=array('sync','ajax');
  1040. if (is_array($pattern)) {
  1041. foreach ($pattern as $item)
  1042. $this->route($item,$handler,$ttl,$kbps);
  1043. return;
  1044. }
  1045. preg_match('/([\|\w]+)\h+(?:(?:@(\w+)\h*:\h*)?([^\h]+)|@(\w+))'.
  1046. '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
  1047. if ($parts[2])
  1048. $this->hive['ALIASES'][$parts[2]]=$parts[3];
  1049. elseif (!empty($parts[4])) {
  1050. if (empty($this->hive['ALIASES'][$parts[4]]))
  1051. user_error(sprintf(self::E_Named,$parts[4]));
  1052. $parts[3]=$this->hive['ALIASES'][$parts[4]];
  1053. }
  1054. if (empty($parts[3]))
  1055. user_error(sprintf(self::E_Pattern,$pattern));
  1056. $type=empty($parts[5])?
  1057. self::REQ_SYNC|self::REQ_AJAX:
  1058. constant('self::REQ_'.strtoupper($parts[5]));
  1059. foreach ($this->split($parts[1]) as $verb) {
  1060. if (!preg_match('/'.self::VERBS.'/',$verb))
  1061. $this->error(501,$verb.' '.$this->hive['URI']);
  1062. $this->hive['ROUTES'][str_replace('@',"\x00".'@',$parts[3])]
  1063. [$type][strtoupper($verb)]=array($handler,$ttl,$kbps);
  1064. }
  1065. }
  1066. /**
  1067. * Reroute to specified URI
  1068. * @return NULL
  1069. * @param $url string
  1070. * @param $permanent bool
  1071. **/
  1072. function reroute($url,$permanent=FALSE) {
  1073. if (PHP_SAPI!='cli') {
  1074. if (preg_match('/^(?:@(\w+)(?:(\(.+?)\))*|https?:\/\/)/',
  1075. $url,$parts)) {
  1076. if (isset($parts[1])) {
  1077. if (empty($this->hive['ALIASES'][$parts[1]]))
  1078. user_error(sprintf(self::E_Named,$parts[1]));
  1079. $url=$this->hive['BASE'].
  1080. $this->hive['ALIASES'][$parts[1]];
  1081. if (isset($parts[2]))
  1082. $this->parse($parts[2]);
  1083. $url=$this->build($url);
  1084. }
  1085. }
  1086. else
  1087. $url=$this->hive['BASE'].$url;
  1088. header('Location: '.$url);
  1089. $this->status($permanent?301:302);
  1090. die;
  1091. }
  1092. $this->mock('GET '.$url);
  1093. }
  1094. /**
  1095. * Provide ReST interface by mapping HTTP verb to class method
  1096. * @return NULL
  1097. * @param $url string
  1098. * @param $class string
  1099. * @param $ttl int
  1100. * @param $kbps int
  1101. **/
  1102. function map($url,$class,$ttl=0,$kbps=0) {
  1103. if (is_array($url)) {
  1104. foreach ($url as $item)
  1105. $this->map($item,$class,$ttl,$kbps);
  1106. return;
  1107. }
  1108. foreach (explode('|',self::VERBS) as $method)
  1109. $this->route($method.' '.$url,
  1110. $class.'->'.strtolower($method),$ttl,$kbps);
  1111. }
  1112. /**
  1113. * Function to redirect a route to another url or route
  1114. * @return NULL
  1115. * @param $pattern string|array
  1116. * @param $url string
  1117. */
  1118. function redirect($pattern,$url) {
  1119. if (is_array($pattern)) {
  1120. foreach ($pattern as $item)
  1121. $this->redirect($item,$url);
  1122. return;
  1123. }
  1124. $this->route($pattern, function($this) use ($url) { $this->reroute($url); });
  1125. }
  1126. /**
  1127. * Return TRUE if IPv4 address exists in DNSBL
  1128. * @return bool
  1129. * @param $ip string
  1130. **/
  1131. function blacklisted($ip) {
  1132. if ($this->hive['DNSBL'] &&
  1133. !in_array($ip,
  1134. is_array($this->hive['EXEMPT'])?
  1135. $this->hive['EXEMPT']:
  1136. $this->split($this->hive['EXEMPT']))) {
  1137. // Reverse IPv4 dotted quad
  1138. $rev=implode('.',array_reverse(explode('.',$ip)));
  1139. foreach (is_array($this->hive['DNSBL'])?
  1140. $this->hive['DNSBL']:
  1141. $this->split($this->hive['DNSBL']) as $server)
  1142. // DNSBL lookup
  1143. if (checkdnsrr($rev.'.'.$server,'A'))
  1144. return TRUE;
  1145. }
  1146. return FALSE;
  1147. }
  1148. /**
  1149. * Match routes against incoming URI
  1150. * @return NULL
  1151. **/
  1152. function run() {
  1153. if ($this->blacklisted($this->hive['IP']))
  1154. // Spammer detected
  1155. $this->error(403);
  1156. if (!$this->hive['ROUTES'])
  1157. // No routes defined
  1158. user_error(self::E_Routes);
  1159. // Match specific routes first
  1160. krsort($this->hive['ROUTES']);
  1161. // Convert to BASE-relative URL
  1162. $req=preg_replace(
  1163. '/^'.preg_quote($this->hive['BASE'],'/').'(\/.*|$)/','\1',
  1164. $this->hive['URI']
  1165. );
  1166. $allowed=array();
  1167. $case=$this->hive['CASELESS']?'i':'';
  1168. foreach ($this->hive['ROUTES'] as $url=>$routes) {
  1169. $url=str_replace("\x00".'@','@',$url);
  1170. if (!preg_match('/^'.
  1171. preg_replace('/@(\w+\b)/','(?P<\1>[^\/\?]+)',
  1172. str_replace('\*','([^\?]*)',preg_quote($url,'/'))).
  1173. '(?:[\?\/].*)?$/'.$case.'um',$req,$args))
  1174. continue;
  1175. $route=NULL;
  1176. if (isset($routes[$this->hive['AJAX']+1]))
  1177. $route=$routes[$this->hive['AJAX']+1];
  1178. elseif (isset($routes[self::REQ_SYNC|self::REQ_AJAX]))
  1179. $route=$routes[self::REQ_SYNC|self::REQ_AJAX];
  1180. if (!$route)
  1181. continue;
  1182. if ($this->hive['VERB']!='OPTIONS' &&
  1183. isset($route[$this->hive['VERB']])) {
  1184. $parts=parse_url($req);
  1185. if ($this->hive['VERB']=='GET' &&
  1186. preg_match('/.+\/$/',$parts['path']))
  1187. $this->reroute(substr($parts['path'],0,-1).
  1188. (isset($parts['query'])?('?'.$parts['query']):''));
  1189. list($handler,$ttl,$kbps)=$route[$this->hive['VERB']];
  1190. if (is_bool(strpos($url,'/*')))
  1191. foreach (array_keys($args) as $key)
  1192. if (is_numeric($key) && $key)
  1193. unset($args[$key]);
  1194. if (is_string($handler)) {
  1195. // Replace route pattern tokens in handler if any
  1196. $handler=preg_replace_callback('/@(\w+\b)/',
  1197. function($id) use($args) {
  1198. return isset($args[$id[1]])?$args[$id[1]]:$id[0];
  1199. },
  1200. $handler
  1201. );
  1202. if (preg_match('/(.+)\h*(?:->|::)/',$handler,$match) &&
  1203. !class_exists($match[1]))
  1204. $this->error(404);
  1205. }
  1206. // Capture values of route pattern tokens
  1207. $this->hive['PARAMS']=$args=array_map('urldecode',$args);
  1208. // Save matching route
  1209. $this->hive['PATTERN']=$url;
  1210. // Process request
  1211. $body='';
  1212. $now=microtime(TRUE);
  1213. if (preg_match('/GET|HEAD/',$this->hive['VERB']) &&
  1214. isset($ttl)) {
  1215. // Only GET and HEAD requests are cacheable
  1216. $headers=$this->hive['HEADERS'];
  1217. $cache=Cache::instance();
  1218. $cached=$cache->exists(
  1219. $hash=$this->hash($this->hive['VERB'].' '.
  1220. $this->hive['URI']).'.url',$data);
  1221. if ($cached && $cached[0]+$ttl>$now) {
  1222. // Retrieve from cache backend
  1223. list($headers,$body)=$data;
  1224. if (PHP_SAPI!='cli')
  1225. array_walk($headers,'header');
  1226. $this->expire($cached[0]+$ttl-$now);
  1227. }
  1228. else
  1229. // Expire HTTP client-cached page
  1230. $this->expire($ttl);
  1231. }
  1232. else
  1233. $this->expire(0);
  1234. if (!strlen($body)) {
  1235. if (!$this->hive['RAW'] && !$this->hive['BODY'])
  1236. $this->hive['BODY']=file_get_contents('php://input');
  1237. ob_start();
  1238. // Call route handler
  1239. $this->call($handler,array($this,$args),
  1240. 'beforeroute,afterroute');
  1241. $body=ob_get_clean();
  1242. if ($ttl && !error_get_last())
  1243. // Save to cache backend
  1244. $cache->set($hash,array(headers_list(),$body),$ttl);
  1245. }
  1246. $this->hive['RESPONSE']=$body;
  1247. if (!$this->hive['QUIET']) {
  1248. if ($kbps) {
  1249. $ctr=0;
  1250. foreach (str_split($body,1024) as $part) {
  1251. // Throttle output
  1252. $ctr++;
  1253. if ($ctr/$kbps>($elapsed=microtime(TRUE)-$now) &&
  1254. !connection_aborted())
  1255. usleep(1e6*($ctr/$kbps-$elapsed));
  1256. echo $part;
  1257. }
  1258. }
  1259. else
  1260. echo $body;
  1261. }
  1262. return;
  1263. }
  1264. $allowed=array_keys($route);
  1265. break;
  1266. }
  1267. if (!$allowed)
  1268. // URL doesn't match any route
  1269. $this->error(404);
  1270. elseif (PHP_SAPI!='cli') {
  1271. // Unhandled HTTP method
  1272. header('Allow: '.implode(',',$allowed));
  1273. if ($this->hive['VERB']!='OPTIONS')
  1274. $this->error(405);
  1275. }
  1276. }
  1277. /**
  1278. * Execute callback/hooks (supports 'class->method' format)
  1279. * @return mixed|FALSE
  1280. * @param $func callback
  1281. * @param $args mixed
  1282. * @param $hooks string
  1283. **/
  1284. function call($func,$args=NULL,$hooks='') {
  1285. if (!is_array($args))
  1286. $args=array($args);
  1287. // Execute function; abort if callback/hook returns FALSE
  1288. if (is_string($func) &&
  1289. preg_match('/(.+)\h*(->|::)\h*(.+)/s',$func,$parts)) {
  1290. // Convert string to executable PHP callback
  1291. if (!class_exists($parts[1]))
  1292. user_error(sprintf(self::E_Class,
  1293. is_string($func)?$parts[1]:$this->stringify()));
  1294. if ($parts[2]=='->')
  1295. $parts[1]=is_subclass_of($parts[1],'Prefab')?
  1296. call_user_func($parts[1].'::instance'):
  1297. new $parts[1]($this);
  1298. $func=array($parts[1],$parts[3]);
  1299. }
  1300. if (!is_callable($func))
  1301. // No route handler
  1302. if ($hooks=='beforeroute,afterroute')
  1303. $this->error(405);
  1304. else
  1305. user_error(sprintf(self::E_Method,
  1306. is_string($func)?$func:$this->stringify($func)));
  1307. $obj=FALSE;
  1308. if (is_array($func)) {
  1309. $hooks=$this->split($hooks);
  1310. $obj=TRUE;
  1311. }
  1312. // Execute pre-route hook if any
  1313. if ($obj && $hooks && in_array($hook='beforeroute',$hooks) &&
  1314. method_exists($func[0],$hook) &&
  1315. call_user_func_array(array($func[0],$hook),$args)===FALSE)
  1316. return FALSE;
  1317. // Execute callback
  1318. $out=call_user_func_array($func,$args?:array());
  1319. if ($out===FALSE)
  1320. return FALSE;
  1321. // Execute post-route hook if any
  1322. if ($obj && $hooks && in_array($hook='afterroute',$hooks) &&
  1323. method_exists($func[0],$hook) &&
  1324. call_user_func_array(array($func[0],$hook),$args)===FALSE)
  1325. return FALSE;
  1326. return $out;
  1327. }
  1328. /**
  1329. * Execute specified callbacks in succession; Apply same arguments
  1330. * to all callbacks
  1331. * @return array
  1332. * @param $funcs array|string
  1333. * @param $args mixed
  1334. **/
  1335. function chain($funcs,$args=NULL) {
  1336. $out=array();
  1337. foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
  1338. $out[]=$this->call($func,$args);
  1339. return $out;
  1340. }
  1341. /**
  1342. * Execute specified callbacks in succession; Relay result of
  1343. * previous callback as argument to the next callback
  1344. * @return array
  1345. * @param $funcs array|string
  1346. * @param $args mixed
  1347. **/
  1348. function relay($funcs,$args=NULL) {
  1349. foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
  1350. $args=array($this->call($func,$args));
  1351. return array_shift($args);
  1352. }
  1353. /**
  1354. * Configure framework according to .ini-style file settings
  1355. * @return NULL
  1356. * @param $file string
  1357. **/
  1358. function config($file) {
  1359. preg_match_all(
  1360. '/(?<=^|\n)(?:'.
  1361. '\[(?<section>.+?)\]|'.
  1362. '(?<lval>[^\h\r\n;].+?)\h*=\h*'.
  1363. '(?<rval>(?:\\\\\h*\r?\n|.+?)*)'.
  1364. ')(?=\r?\n|$)/',
  1365. $this->read($file),$matches,PREG_SET_ORDER);
  1366. if ($matches) {
  1367. $sec='globals';
  1368. foreach ($matches as $match) {
  1369. if ($match['section'])
  1370. $sec=$match['section'];
  1371. elseif (in_array($sec,array('routes','maps','redirects'))) {
  1372. call_user_func_array(
  1373. array($this,rtrim($sec,'s')),
  1374. array_merge(array($match['lval']),
  1375. str_getcsv($match['rval'])));
  1376. }
  1377. else {
  1378. $args=array_map(
  1379. function($val) {
  1380. if (is_numeric($val))
  1381. return $val+0;
  1382. $val=ltrim($val);
  1383. if (preg_match('/^\w+$/i',$val) && defined($val))
  1384. return constant($val);
  1385. return preg_replace('/\\\\\h*(\r?\n)/','\1',$val);
  1386. },
  1387. // Mark quoted strings with 0x00 whitespace
  1388. str_getcsv(preg_replace('/(?<!\\\\)(")(.*?)\1/',
  1389. "\\1\x00\\2\\1",$match['rval']))
  1390. );
  1391. call_user_func_array(array($this,'set'),
  1392. array_merge(
  1393. array($match['lval']),
  1394. count($args)>1?array($args):$args));
  1395. }
  1396. }
  1397. }
  1398. }
  1399. /**
  1400. * Create mutex, invoke callback then drop ownership when done
  1401. * @return mixed
  1402. * @param $id string
  1403. * @param $func callback
  1404. * @param $args mixed
  1405. **/
  1406. function mutex($id,$func,$args=NULL) {
  1407. if (!is_dir($tmp=$this->hive['TEMP']))
  1408. mkdir($tmp,self::MODE,TRUE);
  1409. // Use filesystem lock
  1410. if (is_file($lock=$tmp.
  1411. $this->hash($this->hive['ROOT'].$this->hive['BASE']).'.'.
  1412. $this->hash($id).'.lock') &&
  1413. filemtime($lock)+ini_get('max_execution_time')<microtime(TRUE))
  1414. // Stale lock
  1415. @unlink($lock);
  1416. while (!($handle=@fopen($lock,'x')) && !connection_aborted())
  1417. usleep(mt_rand(0,100));
  1418. $out=$this->call($func,$args);
  1419. fclose($handle);
  1420. @unlink($lock);
  1421. return $out;
  1422. }
  1423. /**
  1424. * Read file (with option to apply Unix LF as standard line ending)
  1425. * @return string
  1426. * @param $file string
  1427. * @param $lf bool
  1428. **/
  1429. function read($file,$lf=FALSE) {
  1430. $out=@file_get_contents($file);
  1431. return $lf?preg_replace('/\r\n|\r/',"\n",$out):$out;
  1432. }
  1433. /**
  1434. * Exclusive file write
  1435. * @return int|FALSE
  1436. * @param $file string
  1437. * @param $data mixed
  1438. * @param $append bool
  1439. **/
  1440. function write($file,$data,$append=FALSE) {
  1441. return file_put_contents($file,$data,LOCK_EX|($append?FILE_APPEND:0));
  1442. }
  1443. /**
  1444. * Apply syntax highlighting
  1445. * @return string
  1446. * @param $text string
  1447. **/
  1448. function highlight($text) {
  1449. $out='';
  1450. $pre=FALSE;
  1451. $text=trim($text);
  1452. if (!preg_match('/^<\?php/',$text)) {
  1453. $text='<?php '.$text;
  1454. $pre=TRUE;
  1455. }
  1456. foreach (token_get_all($text) as $token)
  1457. if ($pre)
  1458. $pre=FALSE;
  1459. else
  1460. $out.='<span'.
  1461. (is_array($token)?
  1462. (' class="'.
  1463. substr(strtolower(token_name($token[0])),2).'">'.
  1464. $this->encode($token[1]).''):
  1465. ('>'.$this->encode($token))).
  1466. '</span>';
  1467. return $out?('<code>'.$out.'</code>'):$text;
  1468. }
  1469. /**
  1470. * Dump expression with syntax highlighting
  1471. * @return NULL
  1472. * @param $expr mixed
  1473. **/
  1474. function dump($expr) {
  1475. echo $this->highlight($this->stringify($expr));
  1476. }
  1477. /**
  1478. * Return path relative to the base directory
  1479. * @return string
  1480. * @param $url string
  1481. **/
  1482. function rel($url) {
  1483. return preg_replace('/(?:https?:\/\/)?'.
  1484. preg_quote($this->hive['BASE'],'/').'/','',rtrim($url,'/'));
  1485. }
  1486. /**
  1487. * Namespace-aware class autoloader
  1488. * @return mixed
  1489. * @param $class string
  1490. **/
  1491. protected function autoload($class) {
  1492. $class=$this->fixslashes(ltrim($class,'\\'));
  1493. foreach ($this->split($this->hive['PLUGINS'].';'.
  1494. $this->hive['AUTOLOAD']) as $auto)
  1495. if (is_file($file=$auto.$class.'.php') ||
  1496. is_file($file=$auto.strtolower($class).'.php') ||
  1497. is_file($file=strtolower($auto.$class).'.php'))
  1498. return require($file);
  1499. }
  1500. /**
  1501. * Execute framework/application shutdown sequence
  1502. * @return NULL
  1503. * @param $cwd string
  1504. **/
  1505. function unload($cwd) {
  1506. chdir($cwd);
  1507. if (!$error=error_get_last())
  1508. @session_commit();
  1509. $handler=$this->hive['UNLOAD'];
  1510. if ((!$handler || $this->call($handler,$this)===FALSE) &&
  1511. $error && in_array($error['type'],
  1512. array(E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR)))
  1513. // Fatal error detected
  1514. $this->error(sprintf(self::E_Fatal,$error['message']));
  1515. }
  1516. //! Prohibit cloning
  1517. private function __clone() {
  1518. }
  1519. //! Bootstrap
  1520. function __construct() {
  1521. // Managed directives
  1522. ini_set('default_charset',$charset='UTF-8');
  1523. if (extension_loaded('mbstring'))
  1524. mb_internal_encoding($charset);
  1525. ini_set('display_errors',0);
  1526. // Deprecated directives
  1527. @ini_set('magic_quotes_gpc',0);
  1528. @ini_set('register_globals',0);
  1529. // Abort on startup error
  1530. // Intercept errors/exceptions; PHP5.3-compatible
  1531. error_reporting(E_ALL|E_STRICT);
  1532. $fw=$this;
  1533. set_exception_handler(
  1534. function($obj) use($fw) {
  1535. $fw->error(500,$obj->getmessage(),$obj->gettrace());
  1536. }
  1537. );
  1538. set_error_handler(
  1539. function($code,$text) use($fw) {
  1540. if (error_reporting())
  1541. $fw->error(500,$text);
  1542. }
  1543. );
  1544. if (!isset($_SERVER['SERVER_NAME']))
  1545. $_SERVER['SERVER_NAME']=gethostname();
  1546. if (PHP_SAPI=='cli') {
  1547. // Emulate HTTP request
  1548. if (isset($_SERVER['argc']) && $_SERVER['argc']<2) {
  1549. $_SERVER['argc']++;
  1550. $_SERVER['argv'][1]='/';
  1551. }
  1552. $_SERVER['REQUEST_METHOD']='GET';
  1553. $_SERVER['REQUEST_URI']=$_SERVER['argv'][1];
  1554. }
  1555. $headers=array();
  1556. if (PHP_SAPI!='cli')
  1557. foreach (array_keys($_SERVER) as $key)
  1558. if (substr($key,0,5)=='HTTP_')
  1559. $headers[strtr(ucwords(strtolower(strtr(
  1560. substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
  1561. if (isset($headers['X-HTTP-Method-Override']))
  1562. $_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
  1563. elseif ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['_method']))
  1564. $_SERVER['REQUEST_METHOD']=$_POST['_method'];
  1565. $scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
  1566. isset($headers['X-Forwarded-Proto']) &&
  1567. $headers['X-Forwarded-Proto']=='https'?'https':'http';
  1568. if (function_exists('apache_setenv')) {
  1569. // Work around Apache pre-2.4 VirtualDocumentRoot bug
  1570. $_SERVER['DOCUMENT_ROOT']=str_replace($_SERVER['SCRIPT_NAME'],'',
  1571. $_SERVER['SCRIPT_FILENAME']);
  1572. apache_setenv("DOCUMENT_ROOT",$_SERVER['DOCUMENT_ROOT']);
  1573. }
  1574. $_SERVER['DOCUMENT_ROOT']=realpath($_SERVER['DOCUMENT_ROOT']);
  1575. $base='';
  1576. if (PHP_SAPI!='cli')
  1577. $base=implode('/',array_map('urlencode',
  1578. explode('/',rtrim($this->fixslashes(
  1579. dirname($_SERVER['SCRIPT_NAME'])),'/'))));
  1580. $path=preg_replace('/^'.preg_quote($base,'/').'/','',
  1581. parse_url($_SERVER['REQUEST_URI'],PHP_URL_PATH));
  1582. call_user_func_array('session_set_cookie_params',
  1583. $jar=array(
  1584. 'expire'=>0,
  1585. 'path'=>$base?:'/',
  1586. 'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) &&
  1587. !filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
  1588. $_SERVER['SERVER_NAME']:'',
  1589. 'secure'=>($scheme=='https'),
  1590. 'httponly'=>TRUE
  1591. )
  1592. );
  1593. // Default configuration
  1594. $this->hive=array(
  1595. 'AGENT'=>isset($headers['X-Operamini-Phone-UA'])?
  1596. $headers['X-Operamini-Phone-UA']:
  1597. (isset($headers['X-Skyfire-Phone'])?
  1598. $headers['X-Skyfire-Phone']:
  1599. (isset($headers['User-Agent'])?
  1600. $headers['User-Agent']:'')),
  1601. 'AJAX'=>isset($headers['X-Requested-With']) &&
  1602. $headers['X-Requested-With']=='XMLHttpRequest',
  1603. 'ALIASES'=>array(),
  1604. 'AUTOLOAD'=>'./',
  1605. 'BASE'=>$base,
  1606. 'BITMASK'=>ENT_COMPAT,
  1607. 'BODY'=>NULL,
  1608. 'CACHE'=>FALSE,
  1609. 'CASELESS'=>TRUE,
  1610. 'DEBUG'=>0,
  1611. 'DIACRITICS'=>array(),
  1612. 'DNSBL'=>'',
  1613. 'EMOJI'=>array(),
  1614. 'ENCODING'=>$charset,
  1615. 'ERROR'=>NULL,
  1616. 'ESCAPE'=>TRUE,
  1617. 'EXEMPT'=>NULL,
  1618. 'FALLBACK'=>$this->fallback,
  1619. 'HEADERS'=>$headers,
  1620. 'HALT'=>TRUE,
  1621. 'HIGHLIGHT'=>TRUE,
  1622. 'HOST'=>$_SERVER['SERVER_NAME'],
  1623. 'IP'=>isset($headers['Client-IP'])?
  1624. $headers['Client-IP']:
  1625. (isset($headers['X-Forwarded-For'])?
  1626. $headers['X-Forwarded-For']:
  1627. (isset($_SERVER['REMOTE_ADDR'])?
  1628. $_SERVER['REMOTE_ADDR']:'')),
  1629. 'JAR'=>$jar,
  1630. 'LANGUAGE'=>isset($headers['Accept-Language'])?
  1631. $this->language($headers['Accept-Language']):
  1632. $this->fallback,
  1633. 'LOCALES'=>'./',
  1634. 'LOGS'=>'./',
  1635. 'ONERROR'=>NULL,
  1636. 'PACKAGE'=>self::PACKAGE,
  1637. 'PARAMS'=>array(),
  1638. 'PATH'=>$path,
  1639. 'PATTERN'=>NULL,
  1640. 'PLUGINS'=>$this->fixslashes(__DIR__).'/',
  1641. 'PORT'=>isset($_SERVER['SERVER_PORT'])?
  1642. $_SERVER['SERVER_PORT']:NULL,
  1643. 'PREFIX'=>NULL,
  1644. 'QUIET'=>FALSE,
  1645. 'RAW'=>FALSE,
  1646. 'REALM'=>$scheme.'://'.
  1647. $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'],
  1648. 'RESPONSE'=>'',
  1649. 'ROOT'=>$_SERVER['DOCUMENT_ROOT'],
  1650. 'ROUTES'=>array(),
  1651. 'SCHEME'=>$scheme,
  1652. 'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
  1653. 'TEMP'=>'tmp/',
  1654. 'TIME'=>microtime(TRUE),
  1655. 'TZ'=>(@ini_get('date.timezone'))?:'UTC',
  1656. 'UI'=>'./',
  1657. 'UNLOAD'=>NULL,
  1658. 'UPLOADS'=>'./',
  1659. 'URI'=>&$_SERVER['REQUEST_URI'],
  1660. 'VERB'=>&$_SERVER['REQUEST_METHOD'],
  1661. 'VERSION'=>self::VERSION,
  1662. 'XFRAME'=>'SAMEORIGIN'
  1663. );
  1664. if (PHP_SAPI=='cli-server' &&
  1665. preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
  1666. $this->reroute('/');
  1667. if (ini_get('auto_globals_jit'))
  1668. // Override setting
  1669. $GLOBALS+=array('_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST);
  1670. // Sync PHP globals with corresponding hive keys
  1671. $this->init=$this->hive;
  1672. foreach (explode('|',self::GLOBALS) as $global) {
  1673. $sync=$this->sync($global);
  1674. $this->init+=array(
  1675. $global=>preg_match('/SERVER|ENV/',$global)?$sync:array()
  1676. );
  1677. }
  1678. if ($error=error_get_last())
  1679. // Error detected
  1680. $this->error(500,sprintf(self::E_Fatal,$error['message']),
  1681. array($error));
  1682. date_default_timezone_set($this->hive['TZ']);
  1683. // Register framework autoloader
  1684. spl_autoload_register(array($this,'autoload'));
  1685. // Register shutdown handler
  1686. register_shutdown_function(array($this,'unload'),getcwd());
  1687. }
  1688. }
  1689. //! Cache engine
  1690. class Cache extends Prefab {
  1691. protected
  1692. //! Cache DSN
  1693. $dsn,
  1694. //! Prefix for cache entries
  1695. $prefix,
  1696. //! MemCache or Redis object
  1697. $ref;
  1698. /**
  1699. * Return timestamp and TTL of cache entry or FALSE if not found
  1700. * @return array|FALSE
  1701. * @param $key string
  1702. * @param $val mixed
  1703. **/
  1704. function exists($key,&$val=NULL) {
  1705. $fw=Base::instance();
  1706. if (!$this->dsn)
  1707. return FALSE;
  1708. $ndx=$this->prefix.'.'.$key;
  1709. $parts=explode('=',$this->dsn,2);
  1710. switch ($parts[0]) {
  1711. case 'apc':
  1712. case 'apcu':
  1713. $raw=apc_fetch($ndx);
  1714. break;
  1715. case 'redis':
  1716. $raw=$this->ref->get($ndx);
  1717. break;
  1718. case 'memcache':
  1719. $raw=memcache_get($this->ref,$ndx);
  1720. break;
  1721. case 'wincache':
  1722. $raw=wincache_ucache_get($ndx);
  1723. break;
  1724. case 'xcache':
  1725. $raw=xcache_get($ndx);
  1726. break;
  1727. case 'folder':
  1728. $raw=$fw->read($parts[1].$ndx);
  1729. break;
  1730. }
  1731. if (!empty($raw)) {
  1732. list($val,$time,$ttl)=(array)$fw->unserialize($raw);
  1733. if ($ttl===0 || $time+$ttl>microtime(TRUE))
  1734. return array($time,$ttl);
  1735. $this->clear($key);
  1736. }
  1737. return FALSE;
  1738. }
  1739. /**
  1740. * Store value in cache
  1741. * @return mixed|FALSE
  1742. * @param $key string
  1743. * @param $val mixed
  1744. * @param $ttl int
  1745. **/
  1746. function set($key,$val,$ttl=0) {
  1747. $fw=Base::instance();
  1748. if (!$this->dsn)
  1749. return TRUE;
  1750. $ndx=$this->prefix.'.'.$key;
  1751. $time=microtime(TRUE);
  1752. if ($cached=$this->exists($key))
  1753. list($time,$ttl)=$cached;
  1754. $data=$fw->serialize(array($val,$time,$ttl));
  1755. $parts=explode('=',$this->dsn,2);
  1756. switch ($parts[0]) {
  1757. case 'apc':
  1758. case 'apcu':
  1759. return apc_store($ndx,$data,$ttl);
  1760. case 'redis':
  1761. return $this->ref->set($ndx,$data,array('ex'=>$ttl));
  1762. case 'memcache':
  1763. return memcache_set($this->ref,$ndx,$data,0,$ttl);
  1764. case 'wincache':
  1765. return wincache_ucache_set($ndx,$data,$ttl);
  1766. case 'xcache':
  1767. return xcache_set($ndx,$data,$ttl);
  1768. case 'folder':
  1769. return $fw->write($parts[1].$ndx,$data);
  1770. }
  1771. return FALSE;
  1772. }
  1773. /**
  1774. * Retrieve value of cache entry
  1775. * @return mixed|FALSE
  1776. * @param $key string
  1777. **/
  1778. function get($key) {
  1779. return $this->dsn && $this->exists($key,$data)?$data:FALSE;
  1780. }
  1781. /**
  1782. * Delete cache entry
  1783. * @return bool
  1784. * @param $key string
  1785. **/
  1786. function clear($key) {
  1787. if (!$this->dsn)
  1788. return;
  1789. $ndx=$this->prefix.'.'.$key;
  1790. $parts=explode('=',$this->dsn,2);
  1791. switch ($parts[0]) {
  1792. case 'apc':
  1793. case 'apcu':
  1794. return apc_delete($ndx);
  1795. case 'redis':
  1796. return $this->ref->del($ndx);
  1797. case 'memcache':
  1798. return memcache_delete($this->ref,$ndx);
  1799. case 'wincache':
  1800. return wincache_ucache_delete($ndx);
  1801. case 'xcache':
  1802. return xcache_unset($ndx);
  1803. case 'folder':
  1804. return @unlink($parts[1].$ndx);
  1805. }
  1806. return FALSE;
  1807. }
  1808. /**
  1809. * Clear contents of cache backend
  1810. * @return bool
  1811. * @param $suffix string
  1812. * @param $lifetime int
  1813. **/
  1814. function reset($suffix=NULL,$lifetime=0) {
  1815. if (!$this->dsn)
  1816. return TRUE;
  1817. $regex='/'.preg_quote($this->prefix.'.','/').'.+?'.
  1818. preg_quote($suffix,'/').'/';
  1819. $parts=explode('=',$this->dsn,2);
  1820. switch ($parts[0]) {
  1821. case 'apc':
  1822. $key='info';
  1823. case 'apcu':
  1824. if (empty($key))
  1825. $key='key';
  1826. $info=apc_cache_info('user');
  1827. foreach ($info['cache_list'] as $item)
  1828. if (preg_match($regex,$item[$key]) &&
  1829. $item['mtime']+$lifetime<time())
  1830. apc_delete($item[$key]);
  1831. return TRUE;
  1832. case 'redis':
  1833. $fw=Base::instance();
  1834. $keys=$this->ref->keys($this->prefix.'.*'.$suffix);
  1835. foreach($keys as $key) {
  1836. $val=$fw->unserialize($this->ref->get($key));
  1837. if ($val[1]+$lifetime<time())
  1838. $this->ref->del($key);
  1839. }
  1840. return TRUE;
  1841. case 'memcache':
  1842. foreach (memcache_get_extended_stats(
  1843. $this->ref,'slabs') as $slabs)
  1844. foreach (array_filter(array_keys($slabs),'is_numeric')
  1845. as $id)
  1846. foreach (memcache_get_extended_stats(
  1847. $this->ref,'cachedump',$id) as $data)
  1848. if (is_array($data))
  1849. foreach ($data as $key=>$val)
  1850. if (preg_match($regex,$key) &&
  1851. $val[1]+$lifetime<time())
  1852. memcache_delete($this->ref,$key);
  1853. return TRUE;
  1854. case 'wincache':
  1855. $info=wincache_ucache_info();
  1856. foreach ($info['ucache_entries'] as $item)
  1857. if (preg_match($regex,$item['key_name']) &&
  1858. $item['use_time']+$lifetime<time())
  1859. wincache_ucache_delete($item['key_name']);
  1860. return TRUE;
  1861. case 'xcache':
  1862. return TRUE; /* Not supported */
  1863. case 'folder':
  1864. if ($glob=@glob($parts[1].'*'))
  1865. foreach ($glob as $file)
  1866. if (preg_match($regex,basename($file)) &&
  1867. filemtime($file)+$lifetime<time())
  1868. @unlink($file);
  1869. return TRUE;
  1870. }
  1871. return FALSE;
  1872. }
  1873. /**
  1874. * Load/auto-detect cache backend
  1875. * @return string
  1876. * @param $dsn bool|string
  1877. **/
  1878. function load($dsn) {
  1879. $fw=Base::instance();
  1880. if ($dsn=trim($dsn)) {
  1881. if (preg_match('/^redis=(.+)/',$dsn,$parts) &&
  1882. extension_loaded('redis')) {
  1883. $port=6379;
  1884. $parts=explode(':',$parts[1],2);
  1885. if (count($parts)>1)
  1886. list($host,$port)=$parts;
  1887. else
  1888. $host=$parts[0];
  1889. $this->ref=new Redis;
  1890. if(!$this->ref->connect($host,$port,2))
  1891. $this->ref=NULL;
  1892. }
  1893. elseif (preg_match('/^memcache=(.+)/',$dsn,$parts) &&
  1894. extension_loaded('memcache'))
  1895. foreach ($fw->split($parts[1]) as $server) {
  1896. $port=11211;
  1897. $parts=explode(':',$server,2);
  1898. if (count($parts)>1)
  1899. list($host,$port)=$parts;
  1900. else
  1901. $host=$parts[0];
  1902. if (empty($this->ref))
  1903. $this->ref=@memcache_connect($host,$port)?:NULL;
  1904. else
  1905. memcache_add_server($this->ref,$host,$port);
  1906. }
  1907. if (empty($this->ref) && !preg_match('/^folder\h*=/',$dsn))
  1908. $dsn=($grep=preg_grep('/^(apc|wincache|xcache)/',
  1909. array_map('strtolower',get_loaded_extensions())))?
  1910. // Auto-detect
  1911. current($grep):
  1912. // Use filesystem as fallback
  1913. ('folder='.$fw->get('TEMP').'cache/');
  1914. if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) &&
  1915. !is_dir($parts[1]))
  1916. mkdir($parts[1],Base::MODE,TRUE);
  1917. }
  1918. $this->prefix=$fw->hash($_SERVER['SERVER_NAME'].$fw->get('BASE'));
  1919. return $this->dsn=$dsn;
  1920. }
  1921. /**
  1922. * Class constructor
  1923. * @return object
  1924. * @param $dsn bool|string
  1925. **/
  1926. function __construct($dsn=FALSE) {
  1927. if ($dsn)
  1928. $this->load($dsn);
  1929. }
  1930. }
  1931. //! View handler
  1932. class View extends Prefab {
  1933. protected
  1934. //! Template file
  1935. $view;
  1936. /**
  1937. * Encode characters to equivalent HTML entities
  1938. * @return string
  1939. * @param $arg mixed
  1940. **/
  1941. function esc($arg) {
  1942. $fw=Base::instance();
  1943. return $fw->recursive($arg,
  1944. function($val) use($fw) {
  1945. return is_string($val)?$fw->encode($val):$val;
  1946. }
  1947. );
  1948. }
  1949. /**
  1950. * Decode HTML entities to equivalent characters
  1951. * @return string
  1952. * @param $arg mixed
  1953. **/
  1954. function raw($arg) {
  1955. $fw=Base::instance();
  1956. return $fw->recursive($arg,
  1957. function($val) use($fw) {
  1958. return is_string($val)?$fw->decode($val):$val;
  1959. }
  1960. );
  1961. }
  1962. /**
  1963. * Create sandbox for template execution
  1964. * @return string
  1965. * @param $hive array
  1966. **/
  1967. protected function sandbox(array $hive=NULL) {
  1968. $fw=Base::instance();
  1969. if (!$hive)
  1970. $hive=$fw->hive();
  1971. if ($fw->get('ESCAPE'))
  1972. $hive=$this->esc($hive);
  1973. if (isset($hive['ALIASES']))
  1974. $hive['ALIASES']=$fw->build($hive['ALIASES']);
  1975. extract($hive);
  1976. unset($fw);
  1977. unset($hive);
  1978. ob_start();
  1979. require($this->view);
  1980. return ob_get_clean();
  1981. }
  1982. /**
  1983. * Render template
  1984. * @return string
  1985. * @param $file string
  1986. * @param $mime string
  1987. * @param $hive array
  1988. * @param $ttl int
  1989. **/
  1990. function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
  1991. $fw=Base::instance();
  1992. $cache=Cache::instance();
  1993. $cached=$cache->exists($hash=$fw->hash($file),$data);
  1994. if ($cached && $cached[0]+$ttl>microtime(TRUE))
  1995. return $data;
  1996. foreach ($fw->split($fw->get('UI').';./') as $dir)
  1997. if (is_file($this->view=$fw->fixslashes($dir.$file))) {
  1998. if (isset($_COOKIE[session_name()]))
  1999. @session_start();
  2000. $fw->sync('SESSION');
  2001. if ($mime && PHP_SAPI!='cli')
  2002. header('Content-Type: '.$mime.'; '.
  2003. 'charset='.$fw->get('ENCODING'));
  2004. $data=$this->sandbox($hive);
  2005. if ($ttl)
  2006. $cache->set($hash,$data);
  2007. return $data;
  2008. }
  2009. user_error(sprintf(Base::E_Open,$file));
  2010. }
  2011. }
  2012. //! Lightweight template engine
  2013. class Preview extends View {
  2014. protected
  2015. //! MIME type
  2016. $mime;
  2017. /**
  2018. * Convert token to variable
  2019. * @return string
  2020. * @param $str string
  2021. **/
  2022. function token($str) {
  2023. return trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
  2024. Base::instance()->compile($str)));
  2025. }
  2026. /**
  2027. * Assemble markup
  2028. * @return string
  2029. * @param $node string
  2030. **/
  2031. protected function build($node) {
  2032. $self=$this;
  2033. return preg_replace_callback(
  2034. '/\{\{(.+?)\}\}/s',
  2035. function($expr) use($self) {
  2036. $str=trim($self->token($expr[1]));
  2037. if (preg_match('/^(.+?)\h*\|(\h*\w+(?:\h*[,;]\h*\w+)*)/',
  2038. $str,$parts)) {
  2039. $str=$parts[1];
  2040. foreach (Base::instance()->split($parts[2]) as $func)
  2041. $str=(($func=='format')?'\Base::instance()':'$this').
  2042. '->'.$func.'('.$str.')';
  2043. }
  2044. return '<?php echo '.$str.'; ?>';
  2045. },
  2046. preg_replace_callback(
  2047. '/\{~(.+?)~\}/s',
  2048. function($expr) use($self) {
  2049. return '<?php '.$self->token($expr[1]).' ?>';
  2050. },
  2051. $node
  2052. )
  2053. );
  2054. }
  2055. /**
  2056. * Render template string
  2057. * @return string
  2058. * @param $str string
  2059. * @param $hive array
  2060. **/
  2061. function resolve($str,array $hive=NULL) {
  2062. if (!$hive)
  2063. $hive=\Base::instance()->hive();
  2064. extract($hive);
  2065. ob_start();
  2066. eval(' ?>'.$this->build($str).'<?php ');
  2067. return ob_get_clean();
  2068. }
  2069. /**
  2070. * Render template
  2071. * @return string
  2072. * @param $file string
  2073. * @param $mime string
  2074. * @param $hive array
  2075. * @param $ttl int
  2076. **/
  2077. function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
  2078. $fw=Base::instance();
  2079. $cache=Cache::instance();
  2080. $cached=$cache->exists($hash=$fw->hash($file),$data);
  2081. if ($cached && $cached[0]+$ttl>microtime(TRUE))
  2082. return $data;
  2083. if (!is_dir($tmp=$fw->get('TEMP')))
  2084. mkdir($tmp,Base::MODE,TRUE);
  2085. foreach ($fw->split($fw->get('UI')) as $dir)
  2086. if (is_file($view=$fw->fixslashes($dir.$file))) {
  2087. if (!is_file($this->view=($tmp.
  2088. $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
  2089. $fw->hash($view).'.php')) ||
  2090. filemtime($this->view)<filemtime($view)) {
  2091. // Remove PHP code and comments
  2092. $text=preg_replace(
  2093. '/(?<!["\'])\h*<\?(?:php|\s*=).+?\?>\h*(?!["\'])|'.
  2094. '\{\*.+?\*\}/is','',
  2095. $fw->read($view));
  2096. if (method_exists($this,'parse'))
  2097. $text=$this->parse($text);
  2098. $fw->write($this->view,$this->build($text));
  2099. }
  2100. if (isset($_COOKIE[session_name()]))
  2101. @session_start();
  2102. $fw->sync('SESSION');
  2103. if ($mime && PHP_SAPI!='cli')
  2104. header('Content-Type: '.($this->mime=$mime).'; '.
  2105. 'charset='.$fw->get('ENCODING'));
  2106. $data=$this->sandbox($hive);
  2107. if ($ttl)
  2108. $cache->set($hash,$data);
  2109. return $data;
  2110. }
  2111. user_error(sprintf(Base::E_Open,$file));
  2112. }
  2113. }
  2114. //! ISO language/country codes
  2115. class ISO extends Prefab {
  2116. //@{ ISO 3166-1 country codes
  2117. const
  2118. CC_af='Afghanistan',
  2119. CC_ax='Åland Islands',
  2120. CC_al='Albania',
  2121. CC_dz='Algeria',
  2122. CC_as='American Samoa',
  2123. CC_ad='Andorra',
  2124. CC_ao='Angola',
  2125. CC_ai='Anguilla',
  2126. CC_aq='Antarctica',
  2127. CC_ag='Antigua and Barbuda',
  2128. CC_ar='Argentina',
  2129. CC_am='Armenia',
  2130. CC_aw='Aruba',
  2131. CC_au='Australia',
  2132. CC_at='Austria',
  2133. CC_az='Azerbaijan',
  2134. CC_bs='Bahamas',
  2135. CC_bh='Bahrain',
  2136. CC_bd='Bangladesh',
  2137. CC_bb='Barbados',
  2138. CC_by='Belarus',
  2139. CC_be='Belgium',
  2140. CC_bz='Belize',
  2141. CC_bj='Benin',
  2142. CC_bm='Bermuda',
  2143. CC_bt='Bhutan',
  2144. CC_bo='Bolivia',
  2145. CC_bq='Bonaire, Sint Eustatius and Saba',
  2146. CC_ba='Bosnia and Herzegovina',
  2147. CC_bw='Botswana',
  2148. CC_bv='Bouvet Island',
  2149. CC_br='Brazil',
  2150. CC_io='British Indian Ocean Territory',
  2151. CC_bn='Brunei Darussalam',
  2152. CC_bg='Bulgaria',
  2153. CC_bf='Burkina Faso',
  2154. CC_bi='Burundi',
  2155. CC_kh='Cambodia',
  2156. CC_cm='Cameroon',
  2157. CC_ca='Canada',
  2158. CC_cv='Cape Verde',
  2159. CC_ky='Cayman Islands',
  2160. CC_cf='Central African Republic',
  2161. CC_td='Chad',
  2162. CC_cl='Chile',
  2163. CC_cn='China',
  2164. CC_cx='Christmas Island',
  2165. CC_cc='Cocos (Keeling) Islands',
  2166. CC_co='Colombia',
  2167. CC_km='Comoros',
  2168. CC_cg='Congo',
  2169. CC_cd='Congo, The Democratic Republic of',
  2170. CC_ck='Cook Islands',
  2171. CC_cr='Costa Rica',
  2172. CC_ci='Côte d\'ivoire',
  2173. CC_hr='Croatia',
  2174. CC_cu='Cuba',
  2175. CC_cw='Curaçao',
  2176. CC_cy='Cyprus',
  2177. CC_cz='Czech Republic',
  2178. CC_dk='Denmark',
  2179. CC_dj='Djibouti',
  2180. CC_dm='Dominica',
  2181. CC_do='Dominican Republic',
  2182. CC_ec='Ecuador',
  2183. CC_eg='Egypt',
  2184. CC_sv='El Salvador',
  2185. CC_gq='Equatorial Guinea',
  2186. CC_er='Eritrea',
  2187. CC_ee='Estonia',
  2188. CC_et='Ethiopia',
  2189. CC_fk='Falkland Islands (Malvinas)',
  2190. CC_fo='Faroe Islands',
  2191. CC_fj='Fiji',
  2192. CC_fi='Finland',
  2193. CC_fr='France',
  2194. CC_gf='French Guiana',
  2195. CC_pf='French Polynesia',
  2196. CC_tf='French Southern Territories',
  2197. CC_ga='Gabon',
  2198. CC_gm='Gambia',
  2199. CC_ge='Georgia',
  2200. CC_de='Germany',
  2201. CC_gh='Ghana',
  2202. CC_gi='Gibraltar',
  2203. CC_gr='Greece',
  2204. CC_gl='Greenland',
  2205. CC_gd='Grenada',
  2206. CC_gp='Guadeloupe',
  2207. CC_gu='Guam',
  2208. CC_gt='Guatemala',
  2209. CC_gg='Guernsey',
  2210. CC_gn='Guinea',
  2211. CC_gw='Guinea-Bissau',
  2212. CC_gy='Guyana',
  2213. CC_ht='Haiti',
  2214. CC_hm='Heard Island and McDonald Islands',
  2215. CC_va='Holy See (Vatican City State)',
  2216. CC_hn='Honduras',
  2217. CC_hk='Hong Kong',
  2218. CC_hu='Hungary',
  2219. CC_is='Iceland',
  2220. CC_in='India',
  2221. CC_id='Indonesia',
  2222. CC_ir='Iran, Islamic Republic of',
  2223. CC_iq='Iraq',
  2224. CC_ie='Ireland',
  2225. CC_im='Isle of Man',
  2226. CC_il='Israel',
  2227. CC_it='Italy',
  2228. CC_jm='Jamaica',
  2229. CC_jp='Japan',
  2230. CC_je='Jersey',
  2231. CC_jo='Jordan',
  2232. CC_kz='Kazakhstan',
  2233. CC_ke='Kenya',
  2234. CC_ki='Kiribati',
  2235. CC_kp='Korea, Democratic People\'s Republic of',
  2236. CC_kr='Korea, Republic of',
  2237. CC_kw='Kuwait',
  2238. CC_kg='Kyrgyzstan',
  2239. CC_la='Lao People\'s Democratic Republic',
  2240. CC_lv='Latvia',
  2241. CC_lb='Lebanon',
  2242. CC_ls='Lesotho',
  2243. CC_lr='Liberia',
  2244. CC_ly='Libya',
  2245. CC_li='Liechtenstein',
  2246. CC_lt='Lithuania',
  2247. CC_lu='Luxembourg',
  2248. CC_mo='Macao',
  2249. CC_mk='Macedonia, The Former Yugoslav Republic of',
  2250. CC_mg='Madagascar',
  2251. CC_mw='Malawi',
  2252. CC_my='Malaysia',
  2253. CC_mv='Maldives',
  2254. CC_ml='Mali',
  2255. CC_mt='Malta',
  2256. CC_mh='Marshall Islands',
  2257. CC_mq='Martinique',
  2258. CC_mr='Mauritania',
  2259. CC_mu='Mauritius',
  2260. CC_yt='Mayotte',
  2261. CC_mx='Mexico',
  2262. CC_fm='Micronesia, Federated States of',
  2263. CC_md='Moldova, Republic of',
  2264. CC_mc='Monaco',
  2265. CC_mn='Mongolia',
  2266. CC_me='Montenegro',
  2267. CC_ms='Montserrat',
  2268. CC_ma='Morocco',
  2269. CC_mz='Mozambique',
  2270. CC_mm='Myanmar',
  2271. CC_na='Namibia',
  2272. CC_nr='Nauru',
  2273. CC_np='Nepal',
  2274. CC_nl='Netherlands',
  2275. CC_nc='New Caledonia',
  2276. CC_nz='New Zealand',
  2277. CC_ni='Nicaragua',
  2278. CC_ne='Niger',
  2279. CC_ng='Nigeria',
  2280. CC_nu='Niue',
  2281. CC_nf='Norfolk Island',
  2282. CC_mp='Northern Mariana Islands',
  2283. CC_no='Norway',
  2284. CC_om='Oman',
  2285. CC_pk='Pakistan',
  2286. CC_pw='Palau',
  2287. CC_ps='Palestinian Territory, Occupied',
  2288. CC_pa='Panama',
  2289. CC_pg='Papua New Guinea',
  2290. CC_py='Paraguay',
  2291. CC_pe='Peru',
  2292. CC_ph='Philippines',
  2293. CC_pn='Pitcairn',
  2294. CC_pl='Poland',
  2295. CC_pt='Portugal',
  2296. CC_pr='Puerto Rico',
  2297. CC_qa='Qatar',
  2298. CC_re='Réunion',
  2299. CC_ro='Romania',
  2300. CC_ru='Russian Federation',
  2301. CC_rw='Rwanda',
  2302. CC_bl='Saint Barthélemy',
  2303. CC_sh='Saint Helena, Ascension and Tristan da Cunha',
  2304. CC_kn='Saint Kitts and Nevis',
  2305. CC_lc='Saint Lucia',
  2306. CC_mf='Saint Martin (French Part)',
  2307. CC_pm='Saint Pierre and Miquelon',
  2308. CC_vc='Saint Vincent and The Grenadines',
  2309. CC_ws='Samoa',
  2310. CC_sm='San Marino',
  2311. CC_st='Sao Tome and Principe',
  2312. CC_sa='Saudi Arabia',
  2313. CC_sn='Senegal',
  2314. CC_rs='Serbia',
  2315. CC_sc='Seychelles',
  2316. CC_sl='Sierra Leone',
  2317. CC_sg='Singapore',
  2318. CC_sk='Slovakia',
  2319. CC_sx='Sint Maarten (Dutch Part)',
  2320. CC_si='Slovenia',
  2321. CC_sb='Solomon Islands',
  2322. CC_so='Somalia',
  2323. CC_za='South Africa',
  2324. CC_gs='South Georgia and The South Sandwich Islands',
  2325. CC_ss='South Sudan',
  2326. CC_es='Spain',
  2327. CC_lk='Sri Lanka',
  2328. CC_sd='Sudan',
  2329. CC_sr='Suriname',
  2330. CC_sj='Svalbard and Jan Mayen',
  2331. CC_sz='Swaziland',
  2332. CC_se='Sweden',
  2333. CC_ch='Switzerland',
  2334. CC_sy='Syrian Arab Republic',
  2335. CC_tw='Taiwan, Province of China',
  2336. CC_tj='Tajikistan',
  2337. CC_tz='Tanzania, United Republic of',
  2338. CC_th='Thailand',
  2339. CC_tl='Timor-Leste',
  2340. CC_tg='Togo',
  2341. CC_tk='Tokelau',
  2342. CC_to='Tonga',
  2343. CC_tt='Trinidad and Tobago',
  2344. CC_tn='Tunisia',
  2345. CC_tr='Turkey',
  2346. CC_tm='Turkmenistan',
  2347. CC_tc='Turks and Caicos Islands',
  2348. CC_tv='Tuvalu',
  2349. CC_ug='Uganda',
  2350. CC_ua='Ukraine',
  2351. CC_ae='United Arab Emirates',
  2352. CC_gb='United Kingdom',
  2353. CC_us='United States',
  2354. CC_um='United States Minor Outlying Islands',
  2355. CC_uy='Uruguay',
  2356. CC_uz='Uzbekistan',
  2357. CC_vu='Vanuatu',
  2358. CC_ve='Venezuela',
  2359. CC_vn='Viet Nam',
  2360. CC_vg='Virgin Islands, British',
  2361. CC_vi='Virgin Islands, U.S.',
  2362. CC_wf='Wallis and Futuna',
  2363. CC_eh='Western Sahara',
  2364. CC_ye='Yemen',
  2365. CC_zm='Zambia',
  2366. CC_zw='Zimbabwe';
  2367. //@}
  2368. //@{ ISO 639-1 language codes (Windows-compatibility subset)
  2369. const
  2370. LC_af='Afrikaans',
  2371. LC_am='Amharic',
  2372. LC_ar='Arabic',
  2373. LC_as='Assamese',
  2374. LC_ba='Bashkir',
  2375. LC_be='Belarusian',
  2376. LC_bg='Bulgarian',
  2377. LC_bn='Bengali',
  2378. LC_bo='Tibetan',
  2379. LC_br='Breton',
  2380. LC_ca='Catalan',
  2381. LC_co='Corsican',
  2382. LC_cs='Czech',
  2383. LC_cy='Welsh',
  2384. LC_da='Danish',
  2385. LC_de='German',
  2386. LC_dv='Divehi',
  2387. LC_el='Greek',
  2388. LC_en='English',
  2389. LC_es='Spanish',
  2390. LC_et='Estonian',
  2391. LC_eu='Basque',
  2392. LC_fa='Persian',
  2393. LC_fi='Finnish',
  2394. LC_fo='Faroese',
  2395. LC_fr='French',
  2396. LC_gd='Scottish Gaelic',
  2397. LC_gl='Galician',
  2398. LC_gu='Gujarati',
  2399. LC_he='Hebrew',
  2400. LC_hi='Hindi',
  2401. LC_hr='Croatian',
  2402. LC_hu='Hungarian',
  2403. LC_hy='Armenian',
  2404. LC_id='Indonesian',
  2405. LC_ig='Igbo',
  2406. LC_is='Icelandic',
  2407. LC_it='Italian',
  2408. LC_ja='Japanese',
  2409. LC_ka='Georgian',
  2410. LC_kk='Kazakh',
  2411. LC_km='Khmer',
  2412. LC_kn='Kannada',
  2413. LC_ko='Korean',
  2414. LC_lb='Luxembourgish',
  2415. LC_lo='Lao',
  2416. LC_lt='Lithuanian',
  2417. LC_lv='Latvian',
  2418. LC_mi='Maori',
  2419. LC_ml='Malayalam',
  2420. LC_mr='Marathi',
  2421. LC_ms='Malay',
  2422. LC_mt='Maltese',
  2423. LC_ne='Nepali',
  2424. LC_nl='Dutch',
  2425. LC_no='Norwegian',
  2426. LC_oc='Occitan',
  2427. LC_or='Oriya',
  2428. LC_pl='Polish',
  2429. LC_ps='Pashto',
  2430. LC_pt='Portuguese',
  2431. LC_qu='Quechua',
  2432. LC_ro='Romanian',
  2433. LC_ru='Russian',
  2434. LC_rw='Kinyarwanda',
  2435. LC_sa='Sanskrit',
  2436. LC_si='Sinhala',
  2437. LC_sk='Slovak',
  2438. LC_sl='Slovenian',
  2439. LC_sq='Albanian',
  2440. LC_sv='Swedish',
  2441. LC_ta='Tamil',
  2442. LC_te='Telugu',
  2443. LC_th='Thai',
  2444. LC_tk='Turkmen',
  2445. LC_tr='Turkish',
  2446. LC_tt='Tatar',
  2447. LC_uk='Ukrainian',
  2448. LC_ur='Urdu',
  2449. LC_vi='Vietnamese',
  2450. LC_wo='Wolof',
  2451. LC_yo='Yoruba',
  2452. LC_zh='Chinese';
  2453. //@}
  2454. /**
  2455. * Convert class constants to array
  2456. * @return array
  2457. * @param $prefix string
  2458. **/
  2459. protected function constants($prefix) {
  2460. $ref=new ReflectionClass($this);
  2461. $out=array();
  2462. foreach (preg_grep('/^'.$prefix.'/',array_keys($ref->getconstants()))
  2463. as $val) {
  2464. $out[$key=substr($val,strlen($prefix))]=
  2465. constant('self::'.$prefix.$key);
  2466. }
  2467. unset($ref);
  2468. return $out;
  2469. }
  2470. /**
  2471. * Return list of languages indexed by ISO 639-1 language code
  2472. * @return array
  2473. **/
  2474. function languages() {
  2475. return $this->constants('LC_');
  2476. }
  2477. /**
  2478. * Return list of countries indexed by ISO 3166-1 country code
  2479. * @return array
  2480. **/
  2481. function countries() {
  2482. return $this->constants('CC_');
  2483. }
  2484. }
  2485. //! Container for singular object instances
  2486. final class Registry {
  2487. private static
  2488. //! Object catalog
  2489. $table;
  2490. /**
  2491. * Return TRUE if object exists in catalog
  2492. * @return bool
  2493. * @param $key string
  2494. **/
  2495. static function exists($key) {
  2496. return isset(self::$table[$key]);
  2497. }
  2498. /**
  2499. * Add object to catalog
  2500. * @return object
  2501. * @param $key string
  2502. * @param $obj object
  2503. **/
  2504. static function set($key,$obj) {
  2505. return self::$table[$key]=$obj;
  2506. }
  2507. /**
  2508. * Retrieve object from catalog
  2509. * @return object
  2510. * @param $key string
  2511. **/
  2512. static function get($key) {
  2513. return self::$table[$key];
  2514. }
  2515. /**
  2516. * Delete object from catalog
  2517. * @return NULL
  2518. * @param $key string
  2519. **/
  2520. static function clear($key) {
  2521. self::$table[$key]=NULL;
  2522. unset(self::$table[$key]);
  2523. }
  2524. //! Prohibit cloning
  2525. private function __clone() {
  2526. }
  2527. //! Prohibit instantiation
  2528. private function __construct() {
  2529. }
  2530. }
  2531. return Base::instance();