base.php 86 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453
  1. <?php
  2. /*
  3. Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfreeframework.com).
  5. This is free software: you can redistribute it and/or modify it under the
  6. terms of the GNU General Public License as published by the Free Software
  7. Foundation, either version 3 of the License, or later.
  8. Fat-Free Framework is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. //! Factory class for single-instance objects
  16. abstract class Prefab {
  17. /**
  18. * Return class instance
  19. * @return static
  20. **/
  21. static function instance() {
  22. if (!Registry::exists($class=get_called_class())) {
  23. $ref=new Reflectionclass($class);
  24. $args=func_get_args();
  25. Registry::set($class,
  26. $args?$ref->newinstanceargs($args):new $class);
  27. }
  28. return Registry::get($class);
  29. }
  30. }
  31. //! Base structure
  32. final class Base extends Prefab implements ArrayAccess {
  33. //@{ Framework details
  34. const
  35. PACKAGE='Fat-Free Framework',
  36. VERSION='3.6.4-Release';
  37. //@}
  38. //@{ HTTP status codes (RFC 2616)
  39. const
  40. HTTP_100='Continue',
  41. HTTP_101='Switching Protocols',
  42. HTTP_103='Early Hints',
  43. HTTP_200='OK',
  44. HTTP_201='Created',
  45. HTTP_202='Accepted',
  46. HTTP_203='Non-Authorative Information',
  47. HTTP_204='No Content',
  48. HTTP_205='Reset Content',
  49. HTTP_206='Partial Content',
  50. HTTP_300='Multiple Choices',
  51. HTTP_301='Moved Permanently',
  52. HTTP_302='Found',
  53. HTTP_303='See Other',
  54. HTTP_304='Not Modified',
  55. HTTP_305='Use Proxy',
  56. HTTP_307='Temporary Redirect',
  57. HTTP_400='Bad Request',
  58. HTTP_401='Unauthorized',
  59. HTTP_402='Payment Required',
  60. HTTP_403='Forbidden',
  61. HTTP_404='Not Found',
  62. HTTP_405='Method Not Allowed',
  63. HTTP_406='Not Acceptable',
  64. HTTP_407='Proxy Authentication Required',
  65. HTTP_408='Request Timeout',
  66. HTTP_409='Conflict',
  67. HTTP_410='Gone',
  68. HTTP_411='Length Required',
  69. HTTP_412='Precondition Failed',
  70. HTTP_413='Request Entity Too Large',
  71. HTTP_414='Request-URI Too Long',
  72. HTTP_415='Unsupported Media Type',
  73. HTTP_416='Requested Range Not Satisfiable',
  74. HTTP_417='Expectation Failed',
  75. HTTP_500='Internal Server Error',
  76. HTTP_501='Not Implemented',
  77. HTTP_502='Bad Gateway',
  78. HTTP_503='Service Unavailable',
  79. HTTP_504='Gateway Timeout',
  80. HTTP_505='HTTP Version Not Supported';
  81. //@}
  82. const
  83. //! Mapped PHP globals
  84. GLOBALS='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV',
  85. //! HTTP verbs
  86. VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS',
  87. //! Default directory permissions
  88. MODE=0755,
  89. //! Syntax highlighting stylesheet
  90. CSS='code.css';
  91. //@{ Request types
  92. const
  93. REQ_SYNC=1,
  94. REQ_AJAX=2,
  95. REQ_CLI=4;
  96. //@}
  97. //@{ Error messages
  98. const
  99. E_Pattern='Invalid routing pattern: %s',
  100. E_Named='Named route does not exist: %s',
  101. E_Fatal='Fatal error: %s',
  102. E_Open='Unable to open %s',
  103. E_Routes='No routes specified',
  104. E_Class='Invalid class %s',
  105. E_Method='Invalid method %s',
  106. E_Hive='Invalid hive key %s';
  107. //@}
  108. private
  109. //! Globals
  110. $hive,
  111. //! Initial settings
  112. $init,
  113. //! Language lookup sequence
  114. $languages,
  115. //! Mutex locks
  116. $locks=[],
  117. //! Default fallback language
  118. $fallback='en';
  119. /**
  120. * Sync PHP global with corresponding hive key
  121. * @return array
  122. * @param $key string
  123. **/
  124. function sync($key) {
  125. return $this->hive[$key]=&$GLOBALS['_'.$key];
  126. }
  127. /**
  128. * Return the parts of specified hive key
  129. * @return array
  130. * @param $key string
  131. **/
  132. private function cut($key) {
  133. return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
  134. $key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
  135. }
  136. /**
  137. * Replace tokenized URL with available token values
  138. * @return string
  139. * @param $url array|string
  140. * @param $args array
  141. **/
  142. function build($url,$args=[]) {
  143. $args+=$this->hive['PARAMS'];
  144. if (is_array($url))
  145. foreach ($url as &$var) {
  146. $var=$this->build($var,$args);
  147. unset($var);
  148. }
  149. else {
  150. $i=0;
  151. $url=preg_replace_callback('/@(\w+)|(\*)/',
  152. function($match) use(&$i,$args) {
  153. if (isset($match[1]) &&
  154. array_key_exists($match[1],$args))
  155. return $args[$match[1]];
  156. if (isset($match[2]) &&
  157. array_key_exists($match[2],$args)) {
  158. if (!is_array($args[$match[2]]))
  159. return $args[$match[2]];
  160. $i++;
  161. return $args[$match[2]][$i-1];
  162. }
  163. return $match[0];
  164. },$url);
  165. }
  166. return $url;
  167. }
  168. /**
  169. * Parse string containing key-value pairs
  170. * @return array
  171. * @param $str string
  172. **/
  173. function parse($str) {
  174. preg_match_all('/(\w+|\*)\h*=\h*(?:\[(.+?)\]|(.+?))(?=,|$)/',
  175. $str,$pairs,PREG_SET_ORDER);
  176. $out=[];
  177. foreach ($pairs as $pair)
  178. if ($pair[2]) {
  179. $out[$pair[1]]=[];
  180. foreach (explode(',',$pair[2]) as $val)
  181. array_push($out[$pair[1]],$val);
  182. }
  183. else
  184. $out[$pair[1]]=trim($pair[3]);
  185. return $out;
  186. }
  187. /**
  188. * cast string variable to php type or constant
  189. * @param $val
  190. * @return mixed
  191. */
  192. function cast($val) {
  193. if (is_numeric($val))
  194. return $val+0;
  195. $val=trim($val);
  196. if (preg_match('/^\w+$/i',$val) && defined($val))
  197. return constant($val);
  198. return $val;
  199. }
  200. /**
  201. * Convert JS-style token to PHP expression
  202. * @return string
  203. * @param $str string
  204. **/
  205. function compile($str) {
  206. return preg_replace_callback(
  207. '/(?<!\w)@(\w+(?:(?:\->|::)\w+)?)'.
  208. '((?:\.\w+|\[(?:(?:[^\[\]]*|(?R))*)\]|(?:\->|::)\w+|\()*)/',
  209. function($expr) {
  210. $str='$'.$expr[1];
  211. if (isset($expr[2]))
  212. $str.=preg_replace_callback(
  213. '/\.(\w+)(\()?|\[((?:[^\[\]]*|(?R))*)\]/',
  214. function($sub) {
  215. if (empty($sub[2])) {
  216. if (ctype_digit($sub[1]))
  217. $sub[1]=(int)$sub[1];
  218. $out='['.
  219. (isset($sub[3])?
  220. $this->compile($sub[3]):
  221. var_export($sub[1],TRUE)).
  222. ']';
  223. }
  224. else
  225. $out=function_exists($sub[1])?
  226. $sub[0]:
  227. ('['.var_export($sub[1],TRUE).']'.$sub[2]);
  228. return $out;
  229. },
  230. $expr[2]
  231. );
  232. return $str;
  233. },
  234. $str
  235. );
  236. }
  237. /**
  238. * Get hive key reference/contents; Add non-existent hive keys,
  239. * array elements, and object properties by default
  240. * @return mixed
  241. * @param $key string
  242. * @param $add bool
  243. * @param $var mixed
  244. **/
  245. function &ref($key,$add=TRUE,&$var=NULL) {
  246. $null=NULL;
  247. $parts=$this->cut($key);
  248. if ($parts[0]=='SESSION') {
  249. if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  250. session_start();
  251. $this->sync('SESSION');
  252. }
  253. elseif (!preg_match('/^\w+$/',$parts[0]))
  254. user_error(sprintf(self::E_Hive,$this->stringify($key)),
  255. E_USER_ERROR);
  256. if (is_null($var)) {
  257. if ($add)
  258. $var=&$this->hive;
  259. else
  260. $var=$this->hive;
  261. }
  262. $obj=FALSE;
  263. foreach ($parts as $part)
  264. if ($part=='->')
  265. $obj=TRUE;
  266. elseif ($obj) {
  267. $obj=FALSE;
  268. if (!is_object($var))
  269. $var=new stdclass;
  270. if ($add || property_exists($var,$part))
  271. $var=&$var->$part;
  272. else {
  273. $var=&$null;
  274. break;
  275. }
  276. }
  277. else {
  278. if (!is_array($var))
  279. $var=[];
  280. if ($add || array_key_exists($part,$var))
  281. $var=&$var[$part];
  282. else {
  283. $var=&$null;
  284. break;
  285. }
  286. }
  287. return $var;
  288. }
  289. /**
  290. * Return TRUE if hive key is set
  291. * (or return timestamp and TTL if cached)
  292. * @return bool
  293. * @param $key string
  294. * @param $val mixed
  295. **/
  296. function exists($key,&$val=NULL) {
  297. $val=$this->ref($key,FALSE);
  298. return isset($val)?
  299. TRUE:
  300. (Cache::instance()->exists($this->hash($key).'.var',$val)?:FALSE);
  301. }
  302. /**
  303. * Return TRUE if hive key is empty and not cached
  304. * @param $key string
  305. * @param $val mixed
  306. * @return bool
  307. **/
  308. function devoid($key,&$val=NULL) {
  309. $val=$this->ref($key,FALSE);
  310. return empty($val) &&
  311. (!Cache::instance()->exists($this->hash($key).'.var',$val) ||
  312. !$val);
  313. }
  314. /**
  315. * Bind value to hive key
  316. * @return mixed
  317. * @param $key string
  318. * @param $val mixed
  319. * @param $ttl int
  320. **/
  321. function set($key,$val,$ttl=0) {
  322. $time=(int)$this->hive['TIME'];
  323. if (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
  324. $this->set('REQUEST'.$expr[2],$val);
  325. if ($expr[1]=='COOKIE') {
  326. $parts=$this->cut($key);
  327. $jar=$this->unserialize($this->serialize($this->hive['JAR']));
  328. unset($jar['lifetime']);
  329. if (isset($_COOKIE[$parts[1]]))
  330. call_user_func_array('setcookie',
  331. array_merge([$parts[1],NULL],['expire'=>0]+$jar));
  332. if ($ttl)
  333. $jar['expire']=$time+$ttl;
  334. call_user_func_array('setcookie',[$parts[1],$val]+$jar);
  335. $_COOKIE[$parts[1]]=$val;
  336. return $val;
  337. }
  338. }
  339. else switch ($key) {
  340. case 'CACHE':
  341. $val=Cache::instance()->load($val);
  342. break;
  343. case 'ENCODING':
  344. ini_set('default_charset',$val);
  345. if (extension_loaded('mbstring'))
  346. mb_internal_encoding($val);
  347. break;
  348. case 'FALLBACK':
  349. $this->fallback=$val;
  350. $lang=$this->language($this->hive['LANGUAGE']);
  351. case 'LANGUAGE':
  352. if (!isset($lang))
  353. $val=$this->language($val);
  354. $lex=$this->lexicon($this->hive['LOCALES'],$ttl);
  355. case 'LOCALES':
  356. if (isset($lex) || $lex=$this->lexicon($val,$ttl))
  357. foreach ($lex as $dt=>$dd) {
  358. $ref=&$this->ref($this->hive['PREFIX'].$dt);
  359. $ref=$dd;
  360. unset($ref);
  361. }
  362. break;
  363. case 'TZ':
  364. date_default_timezone_set($val);
  365. break;
  366. }
  367. $ref=&$this->ref($key);
  368. $ref=$val;
  369. if (preg_match('/^JAR\b/',$key)) {
  370. if ($key=='JAR.lifetime')
  371. $this->set('JAR.expire',$val==0?0:
  372. (is_int($val)?$time+$val:strtotime($val)));
  373. else {
  374. if ($key=='JAR.expire')
  375. $this->hive['JAR']['lifetime']=max(0,$val-$time);
  376. $jar=$this->unserialize($this->serialize($this->hive['JAR']));
  377. unset($jar['expire']);
  378. if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  379. call_user_func_array('session_set_cookie_params',$jar);
  380. }
  381. }
  382. if ($ttl)
  383. // Persist the key-value pair
  384. Cache::instance()->set($this->hash($key).'.var',$val,$ttl);
  385. return $ref;
  386. }
  387. /**
  388. * Retrieve contents of hive key
  389. * @return mixed
  390. * @param $key string
  391. * @param $args string|array
  392. **/
  393. function get($key,$args=NULL) {
  394. if (is_string($val=$this->ref($key,FALSE)) && !is_null($args))
  395. return call_user_func_array(
  396. [$this,'format'],
  397. array_merge([$val],is_array($args)?$args:[$args])
  398. );
  399. if (is_null($val)) {
  400. // Attempt to retrieve from cache
  401. if (Cache::instance()->exists($this->hash($key).'.var',$data))
  402. return $data;
  403. }
  404. return $val;
  405. }
  406. /**
  407. * Unset hive key
  408. * @return NULL
  409. * @param $key string
  410. **/
  411. function clear($key) {
  412. // Normalize array literal
  413. $cache=Cache::instance();
  414. $parts=$this->cut($key);
  415. if ($key=='CACHE')
  416. // Clear cache contents
  417. $cache->reset();
  418. elseif (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
  419. $this->clear('REQUEST'.$expr[2]);
  420. if ($expr[1]=='COOKIE') {
  421. $parts=$this->cut($key);
  422. $jar=$this->hive['JAR'];
  423. unset($jar['lifetime']);
  424. $jar['expire']=0;
  425. call_user_func_array('setcookie',
  426. array_merge([$parts[1],NULL],$jar));
  427. unset($_COOKIE[$parts[1]]);
  428. }
  429. }
  430. elseif ($parts[0]=='SESSION') {
  431. if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  432. session_start();
  433. if (empty($parts[1])) {
  434. // End session
  435. session_unset();
  436. session_destroy();
  437. $this->clear('COOKIE.'.session_name());
  438. }
  439. $this->sync('SESSION');
  440. }
  441. if (!isset($parts[1]) && array_key_exists($parts[0],$this->init))
  442. // Reset global to default value
  443. $this->hive[$parts[0]]=$this->init[$parts[0]];
  444. else {
  445. eval('unset('.$this->compile('@this->hive.'.$key).');');
  446. if ($parts[0]=='SESSION') {
  447. session_commit();
  448. session_start();
  449. }
  450. if ($cache->exists($hash=$this->hash($key).'.var'))
  451. // Remove from cache
  452. $cache->clear($hash);
  453. }
  454. }
  455. /**
  456. * Return TRUE if hive variable is 'on'
  457. * @return bool
  458. * @param $key string
  459. **/
  460. function checked($key) {
  461. $ref=&$this->ref($key);
  462. return $ref=='on';
  463. }
  464. /**
  465. * Return TRUE if property has public visibility
  466. * @return bool
  467. * @param $obj object
  468. * @param $key string
  469. **/
  470. function visible($obj,$key) {
  471. if (property_exists($obj,$key)) {
  472. $ref=new ReflectionProperty(get_class($obj),$key);
  473. $out=$ref->ispublic();
  474. unset($ref);
  475. return $out;
  476. }
  477. return FALSE;
  478. }
  479. /**
  480. * Multi-variable assignment using associative array
  481. * @return NULL
  482. * @param $vars array
  483. * @param $prefix string
  484. * @param $ttl int
  485. **/
  486. function mset(array $vars,$prefix='',$ttl=0) {
  487. foreach ($vars as $key=>$val)
  488. $this->set($prefix.$key,$val,$ttl);
  489. }
  490. /**
  491. * Publish hive contents
  492. * @return array
  493. **/
  494. function hive() {
  495. return $this->hive;
  496. }
  497. /**
  498. * Copy contents of hive variable to another
  499. * @return mixed
  500. * @param $src string
  501. * @param $dst string
  502. **/
  503. function copy($src,$dst) {
  504. $ref=&$this->ref($dst);
  505. return $ref=$this->ref($src,FALSE);
  506. }
  507. /**
  508. * Concatenate string to hive string variable
  509. * @return string
  510. * @param $key string
  511. * @param $val string
  512. **/
  513. function concat($key,$val) {
  514. $ref=&$this->ref($key);
  515. $ref.=$val;
  516. return $ref;
  517. }
  518. /**
  519. * Swap keys and values of hive array variable
  520. * @return array
  521. * @param $key string
  522. * @public
  523. **/
  524. function flip($key) {
  525. $ref=&$this->ref($key);
  526. return $ref=array_combine(array_values($ref),array_keys($ref));
  527. }
  528. /**
  529. * Add element to the end of hive array variable
  530. * @return mixed
  531. * @param $key string
  532. * @param $val mixed
  533. **/
  534. function push($key,$val) {
  535. $ref=&$this->ref($key);
  536. $ref[]=$val;
  537. return $val;
  538. }
  539. /**
  540. * Remove last element of hive array variable
  541. * @return mixed
  542. * @param $key string
  543. **/
  544. function pop($key) {
  545. $ref=&$this->ref($key);
  546. return array_pop($ref);
  547. }
  548. /**
  549. * Add element to the beginning of hive array variable
  550. * @return mixed
  551. * @param $key string
  552. * @param $val mixed
  553. **/
  554. function unshift($key,$val) {
  555. $ref=&$this->ref($key);
  556. array_unshift($ref,$val);
  557. return $val;
  558. }
  559. /**
  560. * Remove first element of hive array variable
  561. * @return mixed
  562. * @param $key string
  563. **/
  564. function shift($key) {
  565. $ref=&$this->ref($key);
  566. return array_shift($ref);
  567. }
  568. /**
  569. * Merge array with hive array variable
  570. * @return array
  571. * @param $key string
  572. * @param $src string|array
  573. * @param $keep bool
  574. **/
  575. function merge($key,$src,$keep=FALSE) {
  576. $ref=&$this->ref($key);
  577. if (!$ref)
  578. $ref=[];
  579. $out=array_merge($ref,is_string($src)?$this->hive[$src]:$src);
  580. if ($keep)
  581. $ref=$out;
  582. return $out;
  583. }
  584. /**
  585. * Extend hive array variable with default values from $src
  586. * @return array
  587. * @param $key string
  588. * @param $src string|array
  589. * @param $keep bool
  590. **/
  591. function extend($key,$src,$keep=FALSE) {
  592. $ref=&$this->ref($key);
  593. if (!$ref)
  594. $ref=[];
  595. $out=array_replace_recursive(
  596. is_string($src)?$this->hive[$src]:$src,$ref);
  597. if ($keep)
  598. $ref=$out;
  599. return $out;
  600. }
  601. /**
  602. * Convert backslashes to slashes
  603. * @return string
  604. * @param $str string
  605. **/
  606. function fixslashes($str) {
  607. return $str?strtr($str,'\\','/'):$str;
  608. }
  609. /**
  610. * Split comma-, semi-colon, or pipe-separated string
  611. * @return array
  612. * @param $str string
  613. * @param $noempty bool
  614. **/
  615. function split($str,$noempty=TRUE) {
  616. return array_map('trim',
  617. preg_split('/[,;|]/',$str,0,$noempty?PREG_SPLIT_NO_EMPTY:0));
  618. }
  619. /**
  620. * Convert PHP expression/value to compressed exportable string
  621. * @return string
  622. * @param $arg mixed
  623. * @param $stack array
  624. **/
  625. function stringify($arg,array $stack=NULL) {
  626. if ($stack) {
  627. foreach ($stack as $node)
  628. if ($arg===$node)
  629. return '*RECURSION*';
  630. }
  631. else
  632. $stack=[];
  633. switch (gettype($arg)) {
  634. case 'object':
  635. $str='';
  636. foreach (get_object_vars($arg) as $key=>$val)
  637. $str.=($str?',':'').
  638. var_export($key,TRUE).'=>'.
  639. $this->stringify($val,
  640. array_merge($stack,[$arg]));
  641. return get_class($arg).'::__set_state(['.$str.'])';
  642. case 'array':
  643. $str='';
  644. $num=isset($arg[0]) &&
  645. ctype_digit(implode('',array_keys($arg)));
  646. foreach ($arg as $key=>$val)
  647. $str.=($str?',':'').
  648. ($num?'':(var_export($key,TRUE).'=>')).
  649. $this->stringify($val,array_merge($stack,[$arg]));
  650. return '['.$str.']';
  651. default:
  652. return var_export($arg,TRUE);
  653. }
  654. }
  655. /**
  656. * Flatten array values and return as CSV string
  657. * @return string
  658. * @param $args array
  659. **/
  660. function csv(array $args) {
  661. return implode(',',array_map('stripcslashes',
  662. array_map([$this,'stringify'],$args)));
  663. }
  664. /**
  665. * Convert snakecase string to camelcase
  666. * @return string
  667. * @param $str string
  668. **/
  669. function camelcase($str) {
  670. return preg_replace_callback(
  671. '/_(\pL)/u',
  672. function($match) {
  673. return strtoupper($match[1]);
  674. },
  675. $str
  676. );
  677. }
  678. /**
  679. * Convert camelcase string to snakecase
  680. * @return string
  681. * @param $str string
  682. **/
  683. function snakecase($str) {
  684. return strtolower(preg_replace('/(?!^)\p{Lu}/u','_\0',$str));
  685. }
  686. /**
  687. * Return -1 if specified number is negative, 0 if zero,
  688. * or 1 if the number is positive
  689. * @return int
  690. * @param $num mixed
  691. **/
  692. function sign($num) {
  693. return $num?($num/abs($num)):0;
  694. }
  695. /**
  696. * Extract values of array whose keys start with the given prefix
  697. * @return array
  698. * @param $arr array
  699. * @param $prefix string
  700. **/
  701. function extract($arr,$prefix) {
  702. $out=[];
  703. foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr))
  704. as $key)
  705. $out[substr($key,strlen($prefix))]=$arr[$key];
  706. return $out;
  707. }
  708. /**
  709. * Convert class constants to array
  710. * @return array
  711. * @param $class object|string
  712. * @param $prefix string
  713. **/
  714. function constants($class,$prefix='') {
  715. $ref=new ReflectionClass($class);
  716. return $this->extract($ref->getconstants(),$prefix);
  717. }
  718. /**
  719. * Generate 64bit/base36 hash
  720. * @return string
  721. * @param $str
  722. **/
  723. function hash($str) {
  724. return str_pad(base_convert(
  725. substr(sha1($str),-16),16,36),11,'0',STR_PAD_LEFT);
  726. }
  727. /**
  728. * Return Base64-encoded equivalent
  729. * @return string
  730. * @param $data string
  731. * @param $mime string
  732. **/
  733. function base64($data,$mime) {
  734. return 'data:'.$mime.';base64,'.base64_encode($data);
  735. }
  736. /**
  737. * Convert special characters to HTML entities
  738. * @return string
  739. * @param $str string
  740. **/
  741. function encode($str) {
  742. return @htmlspecialchars($str,$this->hive['BITMASK'],
  743. $this->hive['ENCODING'])?:$this->scrub($str);
  744. }
  745. /**
  746. * Convert HTML entities back to characters
  747. * @return string
  748. * @param $str string
  749. **/
  750. function decode($str) {
  751. return htmlspecialchars_decode($str,$this->hive['BITMASK']);
  752. }
  753. /**
  754. * Invoke callback recursively for all data types
  755. * @return mixed
  756. * @param $arg mixed
  757. * @param $func callback
  758. * @param $stack array
  759. **/
  760. function recursive($arg,$func,$stack=[]) {
  761. if ($stack) {
  762. foreach ($stack as $node)
  763. if ($arg===$node)
  764. return $arg;
  765. }
  766. switch (gettype($arg)) {
  767. case 'object':
  768. $ref=new ReflectionClass($arg);
  769. if ($ref->iscloneable()) {
  770. $arg=clone($arg);
  771. $cast=is_a($arg,'IteratorAggregate')?
  772. iterator_to_array($arg):get_object_vars($arg);
  773. foreach ($cast as $key=>$val)
  774. $arg->$key=$this->recursive(
  775. $val,$func,array_merge($stack,[$arg]));
  776. }
  777. return $arg;
  778. case 'array':
  779. $copy=[];
  780. foreach ($arg as $key=>$val)
  781. $copy[$key]=$this->recursive($val,$func,
  782. array_merge($stack,[$arg]));
  783. return $copy;
  784. }
  785. return $func($arg);
  786. }
  787. /**
  788. * Remove HTML tags (except those enumerated) and non-printable
  789. * characters to mitigate XSS/code injection attacks
  790. * @return mixed
  791. * @param $arg mixed
  792. * @param $tags string
  793. **/
  794. function clean($arg,$tags=NULL) {
  795. return $this->recursive($arg,
  796. function($val) use($tags) {
  797. if ($tags!='*')
  798. $val=trim(strip_tags($val,
  799. '<'.implode('><',$this->split($tags)).'>'));
  800. return trim(preg_replace(
  801. '/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',$val));
  802. }
  803. );
  804. }
  805. /**
  806. * Similar to clean(), except that variable is passed by reference
  807. * @return mixed
  808. * @param $var mixed
  809. * @param $tags string
  810. **/
  811. function scrub(&$var,$tags=NULL) {
  812. return $var=$this->clean($var,$tags);
  813. }
  814. /**
  815. * Return locale-aware formatted string
  816. * @return string
  817. **/
  818. function format() {
  819. $args=func_get_args();
  820. $val=array_shift($args);
  821. // Get formatting rules
  822. $conv=localeconv();
  823. return preg_replace_callback(
  824. '/\{\s*(?P<pos>\d+)\s*(?:,\s*(?P<type>\w+)\s*'.
  825. '(?:,\s*(?P<mod>(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'.
  826. '(?:,\s*(?P<prop>.+?))?)?)?\s*\}/',
  827. function($expr) use($args,$conv) {
  828. extract($expr);
  829. extract($conv);
  830. if (!array_key_exists($pos,$args))
  831. return $expr[0];
  832. if (isset($type)) {
  833. if (isset($this->hive['FORMATS'][$type]))
  834. return $this->call($this->hive['FORMATS'][$type],
  835. [$args[$pos],isset($mod)?$mod:null,isset($prop)?$prop:null]);
  836. switch ($type) {
  837. case 'plural':
  838. preg_match_all('/(?<tag>\w+)'.
  839. '(?:\s*\{\s*(?<data>.+?)\s*\})/',
  840. $mod,$matches,PREG_SET_ORDER);
  841. $ord=['zero','one','two'];
  842. foreach ($matches as $match) {
  843. extract($match);
  844. if (isset($ord[$args[$pos]]) &&
  845. $tag==$ord[$args[$pos]] || $tag=='other')
  846. return str_replace('#',$args[$pos],$data);
  847. }
  848. case 'number':
  849. if (isset($mod))
  850. switch ($mod) {
  851. case 'integer':
  852. return number_format(
  853. $args[$pos],0,'',$thousands_sep);
  854. case 'currency':
  855. $int=$cstm=FALSE;
  856. if (isset($prop) &&
  857. $cstm=!$int=($prop=='int'))
  858. $currency_symbol=$prop;
  859. if (!$cstm &&
  860. function_exists('money_format'))
  861. return money_format(
  862. '%'.($int?'i':'n'),$args[$pos]);
  863. $fmt=[
  864. 0=>'(nc)',1=>'(n c)',
  865. 2=>'(nc)',10=>'+nc',
  866. 11=>'+n c',12=>'+ nc',
  867. 20=>'nc+',21=>'n c+',
  868. 22=>'nc +',30=>'n+c',
  869. 31=>'n +c',32=>'n+ c',
  870. 40=>'nc+',41=>'n c+',
  871. 42=>'nc +',100=>'(cn)',
  872. 101=>'(c n)',102=>'(cn)',
  873. 110=>'+cn',111=>'+c n',
  874. 112=>'+ cn',120=>'cn+',
  875. 121=>'c n+',122=>'cn +',
  876. 130=>'+cn',131=>'+c n',
  877. 132=>'+ cn',140=>'c+n',
  878. 141=>'c+ n',142=>'c +n'
  879. ];
  880. if ($args[$pos]<0) {
  881. $sgn=$negative_sign;
  882. $pre='n';
  883. }
  884. else {
  885. $sgn=$positive_sign;
  886. $pre='p';
  887. }
  888. return str_replace(
  889. ['+','n','c'],
  890. [$sgn,number_format(
  891. abs($args[$pos]),
  892. $frac_digits,
  893. $decimal_point,
  894. $thousands_sep),
  895. $int?$int_curr_symbol
  896. :$currency_symbol],
  897. $fmt[(int)(
  898. (${$pre.'_cs_precedes'}%2).
  899. (${$pre.'_sign_posn'}%5).
  900. (${$pre.'_sep_by_space'}%3)
  901. )]
  902. );
  903. case 'percent':
  904. return number_format(
  905. $args[$pos]*100,0,$decimal_point,
  906. $thousands_sep).'%';
  907. }
  908. return number_format(
  909. $args[$pos],isset($prop)?$prop:2,
  910. $decimal_point,$thousands_sep);
  911. case 'date':
  912. if (empty($mod) || $mod=='short')
  913. $prop='%x';
  914. elseif ($mod=='long')
  915. $prop='%A, %d %B %Y';
  916. return strftime($prop,$args[$pos]);
  917. case 'time':
  918. if (empty($mod) || $mod=='short')
  919. $prop='%X';
  920. return strftime($prop,$args[$pos]);
  921. default:
  922. return $expr[0];
  923. }
  924. }
  925. return $args[$pos];
  926. },
  927. $val
  928. );
  929. }
  930. /**
  931. * Assign/auto-detect language
  932. * @return string
  933. * @param $code string
  934. **/
  935. function language($code) {
  936. $code=preg_replace('/\h+|;q=[0-9.]+/','',$code);
  937. $code.=($code?',':'').$this->fallback;
  938. $this->languages=[];
  939. foreach (array_reverse(explode(',',$code)) as $lang)
  940. if (preg_match('/^(\w{2})(?:-(\w{2}))?\b/i',$lang,$parts)) {
  941. // Generic language
  942. array_unshift($this->languages,$parts[1]);
  943. if (isset($parts[2])) {
  944. // Specific language
  945. $parts[0]=$parts[1].'-'.($parts[2]=strtoupper($parts[2]));
  946. array_unshift($this->languages,$parts[0]);
  947. }
  948. }
  949. $this->languages=array_unique($this->languages);
  950. $locales=[];
  951. $windows=preg_match('/^win/i',PHP_OS);
  952. // Work around PHP's Turkish locale bug
  953. foreach (preg_grep('/^(?!tr)/i',$this->languages) as $locale) {
  954. if ($windows) {
  955. $parts=explode('-',$locale);
  956. $locale=@constant('ISO::LC_'.$parts[0]);
  957. if (isset($parts[1]) &&
  958. $country=@constant('ISO::CC_'.strtolower($parts[1])))
  959. $locale.='-'.$country;
  960. }
  961. $locale=str_replace('-','_',$locale);
  962. $locales[]=$locale.'.'.ini_get('default_charset');
  963. $locales[]=$locale;
  964. }
  965. setlocale(LC_ALL,$locales);
  966. return $this->hive['LANGUAGE']=implode(',',$this->languages);
  967. }
  968. /**
  969. * Return lexicon entries
  970. * @return array
  971. * @param $path string
  972. * @param $ttl int
  973. **/
  974. function lexicon($path,$ttl=0) {
  975. $languages=$this->languages?:explode(',',$this->fallback);
  976. $cache=Cache::instance();
  977. if ($cache->exists(
  978. $hash=$this->hash(implode(',',$languages)).'.dic',$lex))
  979. return $lex;
  980. $lex=[];
  981. foreach ($languages as $lang)
  982. foreach ($this->split($path) as $dir)
  983. if ((is_file($file=($base=$dir.$lang).'.php') ||
  984. is_file($file=$base.'.php')) &&
  985. is_array($dict=require($file)))
  986. $lex+=$dict;
  987. elseif (is_file($file=$base.'.ini')) {
  988. preg_match_all(
  989. '/(?<=^|\n)(?:'.
  990. '\[(?<prefix>.+?)\]|'.
  991. '(?<lval>[^\h\r\n;].*?)\h*=\h*'.
  992. '(?<rval>(?:\\\\\h*\r?\n|.+?)*)'.
  993. ')(?=\r?\n|$)/',
  994. $this->read($file),$matches,PREG_SET_ORDER);
  995. if ($matches) {
  996. $prefix='';
  997. foreach ($matches as $match)
  998. if ($match['prefix'])
  999. $prefix=$match['prefix'].'.';
  1000. elseif (!array_key_exists(
  1001. $key=$prefix.$match['lval'],$lex))
  1002. $lex[$key]=trim(preg_replace(
  1003. '/\\\\\h*\r?\n/',"\n",$match['rval']));
  1004. }
  1005. }
  1006. if ($ttl)
  1007. $cache->set($hash,$lex,$ttl);
  1008. return $lex;
  1009. }
  1010. /**
  1011. * Return string representation of PHP value
  1012. * @return string
  1013. * @param $arg mixed
  1014. **/
  1015. function serialize($arg) {
  1016. switch (strtolower($this->hive['SERIALIZER'])) {
  1017. case 'igbinary':
  1018. return igbinary_serialize($arg);
  1019. default:
  1020. return serialize($arg);
  1021. }
  1022. }
  1023. /**
  1024. * Return PHP value derived from string
  1025. * @return string
  1026. * @param $arg mixed
  1027. **/
  1028. function unserialize($arg) {
  1029. switch (strtolower($this->hive['SERIALIZER'])) {
  1030. case 'igbinary':
  1031. return igbinary_unserialize($arg);
  1032. default:
  1033. return unserialize($arg);
  1034. }
  1035. }
  1036. /**
  1037. * Send HTTP status header; Return text equivalent of status code
  1038. * @return string
  1039. * @param $code int
  1040. **/
  1041. function status($code) {
  1042. $reason=@constant('self::HTTP_'.$code);
  1043. if (!$this->hive['CLI'] && !headers_sent())
  1044. header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason);
  1045. return $reason;
  1046. }
  1047. /**
  1048. * Send cache metadata to HTTP client
  1049. * @return NULL
  1050. * @param $secs int
  1051. **/
  1052. function expire($secs=0) {
  1053. if (!$this->hive['CLI'] && !headers_sent()) {
  1054. $secs=(int)$secs;
  1055. if ($this->hive['PACKAGE'])
  1056. header('X-Powered-By: '.$this->hive['PACKAGE']);
  1057. if ($this->hive['XFRAME'])
  1058. header('X-Frame-Options: '.$this->hive['XFRAME']);
  1059. header('X-XSS-Protection: 1; mode=block');
  1060. header('X-Content-Type-Options: nosniff');
  1061. if ($this->hive['VERB']=='GET' && $secs) {
  1062. $time=microtime(TRUE);
  1063. header_remove('Pragma');
  1064. header('Cache-Control: max-age='.$secs);
  1065. header('Expires: '.gmdate('r',$time+$secs));
  1066. header('Last-Modified: '.gmdate('r'));
  1067. }
  1068. else {
  1069. header('Pragma: no-cache');
  1070. header('Cache-Control: no-cache, no-store, must-revalidate');
  1071. header('Expires: '.gmdate('r',0));
  1072. }
  1073. }
  1074. }
  1075. /**
  1076. * Return HTTP user agent
  1077. * @return string
  1078. **/
  1079. function agent() {
  1080. $headers=$this->hive['HEADERS'];
  1081. return isset($headers['X-Operamini-Phone-UA'])?
  1082. $headers['X-Operamini-Phone-UA']:
  1083. (isset($headers['X-Skyfire-Phone'])?
  1084. $headers['X-Skyfire-Phone']:
  1085. (isset($headers['User-Agent'])?
  1086. $headers['User-Agent']:''));
  1087. }
  1088. /**
  1089. * Return TRUE if XMLHttpRequest detected
  1090. * @return bool
  1091. **/
  1092. function ajax() {
  1093. $headers=$this->hive['HEADERS'];
  1094. return isset($headers['X-Requested-With']) &&
  1095. $headers['X-Requested-With']=='XMLHttpRequest';
  1096. }
  1097. /**
  1098. * Sniff IP address
  1099. * @return string
  1100. **/
  1101. function ip() {
  1102. $headers=$this->hive['HEADERS'];
  1103. return isset($headers['Client-IP'])?
  1104. $headers['Client-IP']:
  1105. (isset($headers['X-Forwarded-For'])?
  1106. explode(',',$headers['X-Forwarded-For'])[0]:
  1107. (isset($_SERVER['REMOTE_ADDR'])?
  1108. $_SERVER['REMOTE_ADDR']:''));
  1109. }
  1110. /**
  1111. * Return filtered stack trace as a formatted string (or array)
  1112. * @return string|array
  1113. * @param $trace array|NULL
  1114. * @param $format bool
  1115. **/
  1116. function trace(array $trace=NULL,$format=TRUE) {
  1117. if (!$trace) {
  1118. $trace=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  1119. $frame=$trace[0];
  1120. if (isset($frame['file']) && $frame['file']==__FILE__)
  1121. array_shift($trace);
  1122. }
  1123. $debug=$this->hive['DEBUG'];
  1124. $trace=array_filter(
  1125. $trace,
  1126. function($frame) use($debug) {
  1127. return isset($frame['file']) &&
  1128. ($debug>1 ||
  1129. ($frame['file']!=__FILE__ || $debug) &&
  1130. (empty($frame['function']) ||
  1131. !preg_match('/^(?:(?:trigger|user)_error|'.
  1132. '__call|call_user_func)/',$frame['function'])));
  1133. }
  1134. );
  1135. if (!$format)
  1136. return $trace;
  1137. $out='';
  1138. $eol="\n";
  1139. // Analyze stack trace
  1140. foreach ($trace as $frame) {
  1141. $line='';
  1142. if (isset($frame['class']))
  1143. $line.=$frame['class'].$frame['type'];
  1144. if (isset($frame['function']))
  1145. $line.=$frame['function'].'('.
  1146. ($debug>2 && isset($frame['args'])?
  1147. $this->csv($frame['args']):'').')';
  1148. $src=$this->fixslashes(str_replace($_SERVER['DOCUMENT_ROOT'].
  1149. '/','',$frame['file'])).':'.$frame['line'];
  1150. $out.='['.$src.'] '.$line.$eol;
  1151. }
  1152. return $out;
  1153. }
  1154. /**
  1155. * Log error; Execute ONERROR handler if defined, else display
  1156. * default error page (HTML for synchronous requests, JSON string
  1157. * for AJAX requests)
  1158. * @return NULL
  1159. * @param $code int
  1160. * @param $text string
  1161. * @param $trace array
  1162. * @param $level int
  1163. **/
  1164. function error($code,$text='',array $trace=NULL,$level=0) {
  1165. $prior=$this->hive['ERROR'];
  1166. $header=$this->status($code);
  1167. $req=$this->hive['VERB'].' '.$this->hive['PATH'];
  1168. if ($this->hive['QUERY'])
  1169. $req.='?'.$this->hive['QUERY'];
  1170. if (!$text)
  1171. $text='HTTP '.$code.' ('.$req.')';
  1172. $trace=$this->trace($trace);
  1173. $loggable=$this->hive['LOGGABLE'];
  1174. if (!is_array($loggable))
  1175. $loggable=$this->split($loggable);
  1176. foreach ($loggable as $status)
  1177. if ($status=='*' || preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
  1178. error_log($text);
  1179. foreach (explode("\n",$trace) as $nexus)
  1180. if ($nexus)
  1181. error_log($nexus);
  1182. break;
  1183. }
  1184. if ($highlight=!$this->hive['CLI'] && !$this->hive['AJAX'] &&
  1185. $this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS))
  1186. $trace=$this->highlight($trace);
  1187. $this->hive['ERROR']=[
  1188. 'status'=>$header,
  1189. 'code'=>$code,
  1190. 'text'=>$text,
  1191. 'trace'=>$trace,
  1192. 'level'=>$level
  1193. ];
  1194. $this->expire(-1);
  1195. $handler=$this->hive['ONERROR'];
  1196. $this->hive['ONERROR']=NULL;
  1197. $eol="\n";
  1198. if ((!$handler ||
  1199. $this->call($handler,[$this,$this->hive['PARAMS']],
  1200. 'beforeroute,afterroute')===FALSE) &&
  1201. !$prior && !$this->hive['CLI'] && !$this->hive['QUIET'])
  1202. echo $this->hive['AJAX']?
  1203. json_encode(array_diff_key($this->hive['ERROR'],$this->hive['DEBUG']?[]:['trace'=>1])):
  1204. ('<!DOCTYPE html>'.$eol.
  1205. '<html>'.$eol.
  1206. '<head>'.
  1207. '<title>'.$code.' '.$header.'</title>'.
  1208. ($highlight?
  1209. ('<style>'.$this->read($css).'</style>'):'').
  1210. '</head>'.$eol.
  1211. '<body>'.$eol.
  1212. '<h1>'.$header.'</h1>'.$eol.
  1213. '<p>'.$this->encode($text?:$req).'</p>'.$eol.
  1214. ($this->hive['DEBUG']?('<pre>'.$trace.'</pre>'.$eol):'').
  1215. '</body>'.$eol.
  1216. '</html>');
  1217. if ($this->hive['HALT'])
  1218. die(1);
  1219. }
  1220. /**
  1221. * Mock HTTP request
  1222. * @return mixed
  1223. * @param $pattern string
  1224. * @param $args array
  1225. * @param $headers array
  1226. * @param $body string
  1227. **/
  1228. function mock($pattern,
  1229. array $args=NULL,array $headers=NULL,$body=NULL) {
  1230. if (!$args)
  1231. $args=[];
  1232. $types=['sync','ajax','cli'];
  1233. preg_match('/([\|\w]+)\h+(?:@(\w+)(?:(\(.+?)\))*|([^\h]+))'.
  1234. '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
  1235. $verb=strtoupper($parts[1]);
  1236. if ($parts[2]) {
  1237. if (empty($this->hive['ALIASES'][$parts[2]]))
  1238. user_error(sprintf(self::E_Named,$parts[2]),E_USER_ERROR);
  1239. $parts[4]=$this->hive['ALIASES'][$parts[2]];
  1240. $parts[4]=$this->build($parts[4],
  1241. isset($parts[3])?$this->parse($parts[3]):[]);
  1242. }
  1243. if (empty($parts[4]))
  1244. user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
  1245. $url=parse_url($parts[4]);
  1246. parse_str(@$url['query'],$GLOBALS['_GET']);
  1247. if (preg_match('/GET|HEAD/',$verb))
  1248. $GLOBALS['_GET']=array_merge($GLOBALS['_GET'],$args);
  1249. $GLOBALS['_POST']=$verb=='POST'?$args:[];
  1250. $GLOBALS['_REQUEST']=array_merge($GLOBALS['_GET'],$GLOBALS['_POST']);
  1251. foreach ($headers?:[] as $key=>$val)
  1252. $_SERVER['HTTP_'.strtr(strtoupper($key),'-','_')]=$val;
  1253. $this->hive['VERB']=$verb;
  1254. $this->hive['PATH']=$url['path'];
  1255. $this->hive['URI']=$this->hive['BASE'].$url['path'];
  1256. if ($GLOBALS['_GET'])
  1257. $this->hive['URI'].='?'.http_build_query($GLOBALS['_GET']);
  1258. $this->hive['BODY']='';
  1259. if (!preg_match('/GET|HEAD/',$verb))
  1260. $this->hive['BODY']=$body?:http_build_query($args);
  1261. $this->hive['AJAX']=isset($parts[5]) &&
  1262. preg_match('/ajax/i',$parts[5]);
  1263. $this->hive['CLI']=isset($parts[5]) &&
  1264. preg_match('/cli/i',$parts[5]);
  1265. return $this->run();
  1266. }
  1267. /**
  1268. * Assemble url from alias name
  1269. * @return string
  1270. * @param $name string
  1271. * @param $params array|string
  1272. * @param $query string|array
  1273. **/
  1274. function alias($name,$params=[],$query=NULL) {
  1275. if (!is_array($params))
  1276. $params=$this->parse($params);
  1277. if (empty($this->hive['ALIASES'][$name]))
  1278. user_error(sprintf(self::E_Named,$name),E_USER_ERROR);
  1279. $url=$this->build($this->hive['ALIASES'][$name],$params);
  1280. if (is_array($query))
  1281. $query=http_build_query($query);
  1282. return $url.($query?('?'.$query):'');
  1283. }
  1284. /**
  1285. * Bind handler to route pattern
  1286. * @return NULL
  1287. * @param $pattern string|array
  1288. * @param $handler callback
  1289. * @param $ttl int
  1290. * @param $kbps int
  1291. **/
  1292. function route($pattern,$handler,$ttl=0,$kbps=0) {
  1293. $types=['sync','ajax','cli'];
  1294. $alias=null;
  1295. if (is_array($pattern)) {
  1296. foreach ($pattern as $item)
  1297. $this->route($item,$handler,$ttl,$kbps);
  1298. return;
  1299. }
  1300. preg_match('/([\|\w]+)\h+(?:(?:@?(.+?)\h*:\h*)?(@(\w+)|[^\h]+))'.
  1301. '(?:\h+\[('.implode('|',$types).')\])?/u',$pattern,$parts);
  1302. if (isset($parts[2]) && $parts[2])
  1303. $this->hive['ALIASES'][$alias=$parts[2]]=$parts[3];
  1304. elseif (!empty($parts[4])) {
  1305. if (empty($this->hive['ALIASES'][$parts[4]]))
  1306. user_error(sprintf(self::E_Named,$parts[4]),E_USER_ERROR);
  1307. $parts[3]=$this->hive['ALIASES'][$alias=$parts[4]];
  1308. }
  1309. if (empty($parts[3]))
  1310. user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
  1311. $type=empty($parts[5])?0:constant('self::REQ_'.strtoupper($parts[5]));
  1312. foreach ($this->split($parts[1]) as $verb) {
  1313. if (!preg_match('/'.self::VERBS.'/',$verb))
  1314. $this->error(501,$verb.' '.$this->hive['URI']);
  1315. $this->hive['ROUTES'][$parts[3]][$type][strtoupper($verb)]=
  1316. [$handler,$ttl,$kbps,$alias];
  1317. }
  1318. }
  1319. /**
  1320. * Reroute to specified URI
  1321. * @return NULL
  1322. * @param $url array|string
  1323. * @param $permanent bool
  1324. * @param $die bool
  1325. **/
  1326. function reroute($url=NULL,$permanent=FALSE,$die=TRUE) {
  1327. if (!$url)
  1328. $url=$this->hive['REALM'];
  1329. if (is_array($url))
  1330. $url=call_user_func_array([$this,'alias'],$url);
  1331. elseif (preg_match('/^(?:@?([^\/()?]+)(?:(\(.+?)\))*(\?.+)*)/',
  1332. $url,$parts) &&
  1333. isset($this->hive['ALIASES'][$parts[1]]))
  1334. $url=$this->hive['ALIASES'][$parts[1]];
  1335. $url=$this->build($url,isset($parts[2])?$this->parse($parts[2]):[]).
  1336. (isset($parts[3])?$parts[3]:'');
  1337. if (($handler=$this->hive['ONREROUTE']) &&
  1338. $this->call($handler,[$url,$permanent])!==FALSE)
  1339. return;
  1340. if ($url[0]=='/' && (empty($url[1]) || $url[1]!='/')) {
  1341. $port=$this->hive['PORT'];
  1342. $port=in_array($port,[80,443])?'':(':'.$port);
  1343. $url=$this->hive['SCHEME'].'://'.
  1344. $this->hive['HOST'].$port.$this->hive['BASE'].$url;
  1345. }
  1346. if ($this->hive['CLI'])
  1347. $this->mock('GET '.$url.' [cli]');
  1348. else {
  1349. header('Location: '.$url);
  1350. $this->status($permanent?301:302);
  1351. if ($die)
  1352. die;
  1353. }
  1354. }
  1355. /**
  1356. * Provide ReST interface by mapping HTTP verb to class method
  1357. * @return NULL
  1358. * @param $url string
  1359. * @param $class string|object
  1360. * @param $ttl int
  1361. * @param $kbps int
  1362. **/
  1363. function map($url,$class,$ttl=0,$kbps=0) {
  1364. if (is_array($url)) {
  1365. foreach ($url as $item)
  1366. $this->map($item,$class,$ttl,$kbps);
  1367. return;
  1368. }
  1369. foreach (explode('|',self::VERBS) as $method)
  1370. $this->route($method.' '.$url,is_string($class)?
  1371. $class.'->'.$this->hive['PREMAP'].strtolower($method):
  1372. [$class,$this->hive['PREMAP'].strtolower($method)],
  1373. $ttl,$kbps);
  1374. }
  1375. /**
  1376. * Redirect a route to another URL
  1377. * @return NULL
  1378. * @param $pattern string|array
  1379. * @param $url string
  1380. * @param $permanent bool
  1381. */
  1382. function redirect($pattern,$url,$permanent=TRUE) {
  1383. if (is_array($pattern)) {
  1384. foreach ($pattern as $item)
  1385. $this->redirect($item,$url,$permanent);
  1386. return;
  1387. }
  1388. $this->route($pattern,function($fw) use($url,$permanent) {
  1389. $fw->reroute($url,$permanent);
  1390. });
  1391. }
  1392. /**
  1393. * Return TRUE if IPv4 address exists in DNSBL
  1394. * @return bool
  1395. * @param $ip string
  1396. **/
  1397. function blacklisted($ip) {
  1398. if ($this->hive['DNSBL'] &&
  1399. !in_array($ip,
  1400. is_array($this->hive['EXEMPT'])?
  1401. $this->hive['EXEMPT']:
  1402. $this->split($this->hive['EXEMPT']))) {
  1403. // Reverse IPv4 dotted quad
  1404. $rev=implode('.',array_reverse(explode('.',$ip)));
  1405. foreach (is_array($this->hive['DNSBL'])?
  1406. $this->hive['DNSBL']:
  1407. $this->split($this->hive['DNSBL']) as $server)
  1408. // DNSBL lookup
  1409. if (checkdnsrr($rev.'.'.$server,'A'))
  1410. return TRUE;
  1411. }
  1412. return FALSE;
  1413. }
  1414. /**
  1415. * Applies the specified URL mask and returns parameterized matches
  1416. * @return $args array
  1417. * @param $pattern string
  1418. * @param $url string|NULL
  1419. **/
  1420. function mask($pattern,$url=NULL) {
  1421. if (!$url)
  1422. $url=$this->rel($this->hive['URI']);
  1423. $case=$this->hive['CASELESS']?'i':'';
  1424. $wild=preg_quote($pattern,'/');
  1425. $i=0;
  1426. while (is_int($pos=strpos($wild,'\*'))) {
  1427. $wild=substr_replace($wild,'(?P<_'.$i.'>[^\?]*)',$pos,2);
  1428. $i++;
  1429. }
  1430. preg_match('/^'.
  1431. preg_replace(
  1432. '/((\\\{)?@(\w+\b)(?(2)\\\}))/',
  1433. '(?P<\3>[^\/\?]+)',
  1434. $wild).'\/?$/'.$case.'um',$url,$args);
  1435. foreach (array_keys($args) as $key) {
  1436. if (preg_match('/^_\d+$/',$key)) {
  1437. if (empty($args['*']))
  1438. $args['*']=$args[$key];
  1439. else {
  1440. if (is_string($args['*']))
  1441. $args['*']=[$args['*']];
  1442. array_push($args['*'],$args[$key]);
  1443. }
  1444. unset($args[$key]);
  1445. }
  1446. elseif (is_numeric($key) && $key)
  1447. unset($args[$key]);
  1448. }
  1449. return $args;
  1450. }
  1451. /**
  1452. * Match routes against incoming URI
  1453. * @return mixed
  1454. **/
  1455. function run() {
  1456. if ($this->blacklisted($this->hive['IP']))
  1457. // Spammer detected
  1458. $this->error(403);
  1459. if (!$this->hive['ROUTES'])
  1460. // No routes defined
  1461. user_error(self::E_Routes,E_USER_ERROR);
  1462. // Match specific routes first
  1463. $paths=[];
  1464. foreach ($keys=array_keys($this->hive['ROUTES']) as $key) {
  1465. $path=preg_replace('/@\w+/','*@',$key);
  1466. if (substr($path,-1)!='*')
  1467. $path.='+';
  1468. $paths[]=$path;
  1469. }
  1470. $vals=array_values($this->hive['ROUTES']);
  1471. array_multisort($paths,SORT_DESC,$keys,$vals);
  1472. $this->hive['ROUTES']=array_combine($keys,$vals);
  1473. // Convert to BASE-relative URL
  1474. $req=urldecode($this->hive['PATH']);
  1475. $preflight=FALSE;
  1476. if ($cors=(isset($this->hive['HEADERS']['Origin']) &&
  1477. $this->hive['CORS']['origin'])) {
  1478. $cors=$this->hive['CORS'];
  1479. header('Access-Control-Allow-Origin: '.$cors['origin']);
  1480. header('Access-Control-Allow-Credentials: '.
  1481. var_export($cors['credentials'],TRUE));
  1482. $preflight=
  1483. isset($this->hive['HEADERS']['Access-Control-Request-Method']);
  1484. }
  1485. $allowed=[];
  1486. foreach ($this->hive['ROUTES'] as $pattern=>$routes) {
  1487. if (!$args=$this->mask($pattern,$req))
  1488. continue;
  1489. ksort($args);
  1490. $route=NULL;
  1491. $ptr=$this->hive['CLI']?self::REQ_CLI:$this->hive['AJAX']+1;
  1492. if (isset($routes[$ptr][$this->hive['VERB']]) ||
  1493. isset($routes[$ptr=0]))
  1494. $route=$routes[$ptr];
  1495. if (!$route)
  1496. continue;
  1497. if (isset($route[$this->hive['VERB']]) && !$preflight) {
  1498. if ($this->hive['VERB']=='GET' &&
  1499. preg_match('/.+\/$/',$this->hive['PATH']))
  1500. $this->reroute(substr($this->hive['PATH'],0,-1).
  1501. ($this->hive['QUERY']?('?'.$this->hive['QUERY']):''));
  1502. list($handler,$ttl,$kbps,$alias)=$route[$this->hive['VERB']];
  1503. // Capture values of route pattern tokens
  1504. $this->hive['PARAMS']=$args;
  1505. // Save matching route
  1506. $this->hive['ALIAS']=$alias;
  1507. $this->hive['PATTERN']=$pattern;
  1508. if ($cors && $cors['expose'])
  1509. header('Access-Control-Expose-Headers: '.
  1510. (is_array($cors['expose'])?
  1511. implode(',',$cors['expose']):$cors['expose']));
  1512. if (is_string($handler)) {
  1513. // Replace route pattern tokens in handler if any
  1514. $handler=preg_replace_callback('/({)?@(\w+\b)(?(1)})/',
  1515. function($id) use($args) {
  1516. $pid=count($id)>2?2:1;
  1517. return isset($args[$id[$pid]])?
  1518. $args[$id[$pid]]:
  1519. $id[0];
  1520. },
  1521. $handler
  1522. );
  1523. if (preg_match('/(.+)\h*(?:->|::)/',$handler,$match) &&
  1524. !class_exists($match[1]))
  1525. $this->error(404);
  1526. }
  1527. // Process request
  1528. $result=NULL;
  1529. $body='';
  1530. $now=microtime(TRUE);
  1531. if (preg_match('/GET|HEAD/',$this->hive['VERB']) && $ttl) {
  1532. // Only GET and HEAD requests are cacheable
  1533. $headers=$this->hive['HEADERS'];
  1534. $cache=Cache::instance();
  1535. $cached=$cache->exists(
  1536. $hash=$this->hash($this->hive['VERB'].' '.
  1537. $this->hive['URI']).'.url',$data);
  1538. if ($cached) {
  1539. if (isset($headers['If-Modified-Since']) &&
  1540. strtotime($headers['If-Modified-Since'])+
  1541. $ttl>$now) {
  1542. $this->status(304);
  1543. die;
  1544. }
  1545. // Retrieve from cache backend
  1546. list($headers,$body,$result)=$data;
  1547. if (!$this->hive['CLI'])
  1548. array_walk($headers,'header');
  1549. $this->expire($cached[0]+$ttl-$now);
  1550. }
  1551. else
  1552. // Expire HTTP client-cached page
  1553. $this->expire($ttl);
  1554. }
  1555. else
  1556. $this->expire(0);
  1557. if (!strlen($body)) {
  1558. if (!$this->hive['RAW'] && !$this->hive['BODY'])
  1559. $this->hive['BODY']=file_get_contents('php://input');
  1560. ob_start();
  1561. // Call route handler
  1562. $result=$this->call($handler,[$this,$args,$handler],
  1563. 'beforeroute,afterroute');
  1564. $body=ob_get_clean();
  1565. if (isset($cache) && !error_get_last()) {
  1566. // Save to cache backend
  1567. $cache->set($hash,[
  1568. // Remove cookies
  1569. preg_grep('/Set-Cookie\:/',headers_list(),
  1570. PREG_GREP_INVERT),$body,$result],$ttl);
  1571. }
  1572. }
  1573. $this->hive['RESPONSE']=$body;
  1574. if (!$this->hive['QUIET']) {
  1575. if ($kbps) {
  1576. $ctr=0;
  1577. foreach (str_split($body,1024) as $part) {
  1578. // Throttle output
  1579. $ctr++;
  1580. if ($ctr/$kbps>($elapsed=microtime(TRUE)-$now) &&
  1581. !connection_aborted())
  1582. usleep(1e6*($ctr/$kbps-$elapsed));
  1583. echo $part;
  1584. }
  1585. }
  1586. else
  1587. echo $body;
  1588. }
  1589. if ($result || $this->hive['VERB']!='OPTIONS')
  1590. return $result;
  1591. }
  1592. $allowed=array_merge($allowed,array_keys($route));
  1593. }
  1594. if (!$allowed)
  1595. // URL doesn't match any route
  1596. $this->error(404);
  1597. elseif (!$this->hive['CLI']) {
  1598. // Unhandled HTTP method
  1599. header('Allow: '.implode(',',array_unique($allowed)));
  1600. if ($cors) {
  1601. header('Access-Control-Allow-Methods: OPTIONS,'.
  1602. implode(',',$allowed));
  1603. if ($cors['headers'])
  1604. header('Access-Control-Allow-Headers: '.
  1605. (is_array($cors['headers'])?
  1606. implode(',',$cors['headers']):
  1607. $cors['headers']));
  1608. if ($cors['ttl']>0)
  1609. header('Access-Control-Max-Age: '.$cors['ttl']);
  1610. }
  1611. if ($this->hive['VERB']!='OPTIONS')
  1612. $this->error(405);
  1613. }
  1614. return FALSE;
  1615. }
  1616. /**
  1617. * Loop until callback returns TRUE (for long polling)
  1618. * @return mixed
  1619. * @param $func callback
  1620. * @param $args array
  1621. * @param $timeout int
  1622. **/
  1623. function until($func,$args=NULL,$timeout=60) {
  1624. if (!$args)
  1625. $args=[];
  1626. $time=time();
  1627. $max=ini_get('max_execution_time');
  1628. $limit=max(0,($max?min($timeout,$max):$timeout)-1);
  1629. $out='';
  1630. // Turn output buffering on
  1631. ob_start();
  1632. // Not for the weak of heart
  1633. while (
  1634. // No error occurred
  1635. !$this->hive['ERROR'] &&
  1636. // Got time left?
  1637. time()-$time+1<$limit &&
  1638. // Still alive?
  1639. !connection_aborted() &&
  1640. // Restart session
  1641. !headers_sent() &&
  1642. (session_status()==PHP_SESSION_ACTIVE || session_start()) &&
  1643. // CAUTION: Callback will kill host if it never becomes truthy!
  1644. !$out=$this->call($func,$args)) {
  1645. if (!$this->hive['CLI'])
  1646. session_commit();
  1647. // Hush down
  1648. sleep(1);
  1649. }
  1650. ob_flush();
  1651. flush();
  1652. return $out;
  1653. }
  1654. /**
  1655. * Disconnect HTTP client
  1656. **/
  1657. function abort() {
  1658. if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  1659. session_start();
  1660. $out='';
  1661. while (ob_get_level())
  1662. $out=ob_get_clean().$out;
  1663. header('Content-Encoding: none');
  1664. header('Content-Length: '.strlen($out));
  1665. header('Connection: close');
  1666. session_commit();
  1667. echo $out;
  1668. flush();
  1669. if (function_exists('fastcgi_finish_request'))
  1670. fastcgi_finish_request();
  1671. }
  1672. /**
  1673. * Grab the real route handler behind the string expression
  1674. * @return string|array
  1675. * @param $func string
  1676. * @param $args array
  1677. **/
  1678. function grab($func,$args=NULL) {
  1679. if (preg_match('/(.+)\h*(->|::)\h*(.+)/s',$func,$parts)) {
  1680. // Convert string to executable PHP callback
  1681. if (!class_exists($parts[1]))
  1682. user_error(sprintf(self::E_Class,$parts[1]),E_USER_ERROR);
  1683. if ($parts[2]=='->') {
  1684. if (is_subclass_of($parts[1],'Prefab'))
  1685. $parts[1]=call_user_func($parts[1].'::instance');
  1686. elseif ($container=$this->get('CONTAINER')) {
  1687. if (is_object($container) && is_callable([$container,'has'])
  1688. && $container->has($parts[1])) // PSR11
  1689. $parts[1]=call_user_func([$container,'get'],$parts[1]);
  1690. elseif (is_callable($container))
  1691. $parts[1]=call_user_func($container,$parts[1],$args);
  1692. elseif (is_string($container) && is_subclass_of($container,'Prefab'))
  1693. $parts[1]=call_user_func($container.'::instance')->get($parts[1]);
  1694. else
  1695. user_error(sprintf(self::E_Class,
  1696. $this->stringify($container)),
  1697. E_USER_ERROR);
  1698. }
  1699. else {
  1700. $ref=new ReflectionClass($parts[1]);
  1701. $parts[1]=method_exists($parts[1],'__construct') && $args?
  1702. $ref->newinstanceargs($args):
  1703. $ref->newinstance();
  1704. }
  1705. }
  1706. $func=[$parts[1],$parts[3]];
  1707. }
  1708. return $func;
  1709. }
  1710. /**
  1711. * Execute callback/hooks (supports 'class->method' format)
  1712. * @return mixed|FALSE
  1713. * @param $func callback
  1714. * @param $args mixed
  1715. * @param $hooks string
  1716. **/
  1717. function call($func,$args=NULL,$hooks='') {
  1718. if (!is_array($args))
  1719. $args=[$args];
  1720. // Grab the real handler behind the string representation
  1721. if (is_string($func))
  1722. $func=$this->grab($func,$args);
  1723. // Execute function; abort if callback/hook returns FALSE
  1724. if (!is_callable($func))
  1725. // No route handler
  1726. if ($hooks=='beforeroute,afterroute') {
  1727. $allowed=[];
  1728. if (is_array($func))
  1729. $allowed=array_intersect(
  1730. array_map('strtoupper',get_class_methods($func[0])),
  1731. explode('|',self::VERBS)
  1732. );
  1733. header('Allow: '.implode(',',$allowed));
  1734. $this->error(405);
  1735. }
  1736. else
  1737. user_error(sprintf(self::E_Method,
  1738. is_string($func)?$func:$this->stringify($func)),
  1739. E_USER_ERROR);
  1740. $obj=FALSE;
  1741. if (is_array($func)) {
  1742. $hooks=$this->split($hooks);
  1743. $obj=TRUE;
  1744. }
  1745. // Execute pre-route hook if any
  1746. if ($obj && $hooks && in_array($hook='beforeroute',$hooks) &&
  1747. method_exists($func[0],$hook) &&
  1748. call_user_func_array([$func[0],$hook],$args)===FALSE)
  1749. return FALSE;
  1750. // Execute callback
  1751. $out=call_user_func_array($func,$args?:[]);
  1752. if ($out===FALSE)
  1753. return FALSE;
  1754. // Execute post-route hook if any
  1755. if ($obj && $hooks && in_array($hook='afterroute',$hooks) &&
  1756. method_exists($func[0],$hook) &&
  1757. call_user_func_array([$func[0],$hook],$args)===FALSE)
  1758. return FALSE;
  1759. return $out;
  1760. }
  1761. /**
  1762. * Execute specified callbacks in succession; Apply same arguments
  1763. * to all callbacks
  1764. * @return array
  1765. * @param $funcs array|string
  1766. * @param $args mixed
  1767. **/
  1768. function chain($funcs,$args=NULL) {
  1769. $out=[];
  1770. foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
  1771. $out[]=$this->call($func,$args);
  1772. return $out;
  1773. }
  1774. /**
  1775. * Execute specified callbacks in succession; Relay result of
  1776. * previous callback as argument to the next callback
  1777. * @return array
  1778. * @param $funcs array|string
  1779. * @param $args mixed
  1780. **/
  1781. function relay($funcs,$args=NULL) {
  1782. foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
  1783. $args=[$this->call($func,$args)];
  1784. return array_shift($args);
  1785. }
  1786. /**
  1787. * Configure framework according to .ini-style file settings;
  1788. * If optional 2nd arg is provided, template strings are interpreted
  1789. * @return object
  1790. * @param $source string|array
  1791. * @param $allow bool
  1792. **/
  1793. function config($source,$allow=FALSE) {
  1794. if (is_string($source))
  1795. $source=$this->split($source);
  1796. if ($allow)
  1797. $preview=Preview::instance();
  1798. foreach ($source as $file) {
  1799. preg_match_all(
  1800. '/(?<=^|\n)(?:'.
  1801. '\[(?<section>.+?)\]|'.
  1802. '(?<lval>[^\h\r\n;].*?)\h*=\h*'.
  1803. '(?<rval>(?:\\\\\h*\r?\n|.+?)*)'.
  1804. ')(?=\r?\n|$)/',
  1805. $this->read($file),
  1806. $matches,PREG_SET_ORDER);
  1807. if ($matches) {
  1808. $sec='globals';
  1809. $cmd=[];
  1810. foreach ($matches as $match) {
  1811. if ($match['section']) {
  1812. $sec=$match['section'];
  1813. if (preg_match(
  1814. '/^(?!(?:global|config|route|map|redirect)s\b)'.
  1815. '((?:\.?\w)+)/i',$sec,$msec) &&
  1816. !$this->exists($msec[0]))
  1817. $this->set($msec[0],NULL);
  1818. preg_match('/^(config|route|map|redirect)s\b|'.
  1819. '^((?:\.?\w)+)\s*\>\s*(.*)/i',$sec,$cmd);
  1820. continue;
  1821. }
  1822. if ($allow)
  1823. foreach (['lval','rval'] as $ndx)
  1824. $match[$ndx]=$preview->
  1825. resolve($match[$ndx],NULL,0,FALSE,FALSE);
  1826. if (!empty($cmd)) {
  1827. isset($cmd[3])?
  1828. $this->call($cmd[3],
  1829. [$match['lval'],$match['rval'],$cmd[2]]):
  1830. call_user_func_array(
  1831. [$this,$cmd[1]],
  1832. array_merge([$match['lval']],
  1833. str_getcsv($cmd[1]=='config'?$this->cast($match['rval']):
  1834. $match['rval']))
  1835. );
  1836. }
  1837. else {
  1838. $rval=preg_replace(
  1839. '/\\\\\h*(\r?\n)/','\1',$match['rval']);
  1840. $ttl=NULL;
  1841. if (preg_match('/^(.+)\|\h*(\d+)$/',$rval,$tmp)) {
  1842. array_shift($tmp);
  1843. list($rval,$ttl)=$tmp;
  1844. }
  1845. $args=array_map(
  1846. function($val) {
  1847. $val=$this->cast($val);
  1848. return is_string($val)
  1849. ? preg_replace('/\\\\"/','"',$val)
  1850. : $val;
  1851. },
  1852. // Mark quoted strings with 0x00 whitespace
  1853. str_getcsv(preg_replace(
  1854. '/(?<!\\\\)(")(.*?)\1/',
  1855. "\\1\x00\\2\\1",trim($rval)))
  1856. );
  1857. preg_match('/^(?<section>[^:]+)(?:\:(?<func>.+))?/',
  1858. $sec,$parts);
  1859. $func=isset($parts['func'])?$parts['func']:NULL;
  1860. $custom=(strtolower($parts['section'])!='globals');
  1861. if ($func)
  1862. $args=[$this->call($func,$args)];
  1863. if (count($args)>1)
  1864. $args=[$args];
  1865. if (isset($ttl))
  1866. $args=array_merge($args,[$ttl]);
  1867. call_user_func_array(
  1868. [$this,'set'],
  1869. array_merge(
  1870. [
  1871. ($custom?($parts['section'].'.'):'').
  1872. $match['lval']
  1873. ],
  1874. $args
  1875. )
  1876. );
  1877. }
  1878. }
  1879. }
  1880. }
  1881. return $this;
  1882. }
  1883. /**
  1884. * Create mutex, invoke callback then drop ownership when done
  1885. * @return mixed
  1886. * @param $id string
  1887. * @param $func callback
  1888. * @param $args mixed
  1889. **/
  1890. function mutex($id,$func,$args=NULL) {
  1891. if (!is_dir($tmp=$this->hive['TEMP']))
  1892. mkdir($tmp,self::MODE,TRUE);
  1893. // Use filesystem lock
  1894. if (is_file($lock=$tmp.
  1895. $this->get('SEED').'.'.$this->hash($id).'.lock') &&
  1896. filemtime($lock)+ini_get('max_execution_time')<microtime(TRUE))
  1897. // Stale lock
  1898. @unlink($lock);
  1899. while (!($handle=@fopen($lock,'x')) && !connection_aborted())
  1900. usleep(mt_rand(0,100));
  1901. $this->locks[$id]=$lock;
  1902. $out=$this->call($func,$args);
  1903. fclose($handle);
  1904. @unlink($lock);
  1905. unset($this->locks[$id]);
  1906. return $out;
  1907. }
  1908. /**
  1909. * Read file (with option to apply Unix LF as standard line ending)
  1910. * @return string
  1911. * @param $file string
  1912. * @param $lf bool
  1913. **/
  1914. function read($file,$lf=FALSE) {
  1915. $out=@file_get_contents($file);
  1916. return $lf?preg_replace('/\r\n|\r/',"\n",$out):$out;
  1917. }
  1918. /**
  1919. * Exclusive file write
  1920. * @return int|FALSE
  1921. * @param $file string
  1922. * @param $data mixed
  1923. * @param $append bool
  1924. **/
  1925. function write($file,$data,$append=FALSE) {
  1926. return file_put_contents($file,$data,LOCK_EX|($append?FILE_APPEND:0));
  1927. }
  1928. /**
  1929. * Apply syntax highlighting
  1930. * @return string
  1931. * @param $text string
  1932. **/
  1933. function highlight($text) {
  1934. $out='';
  1935. $pre=FALSE;
  1936. $text=trim($text);
  1937. if ($text && !preg_match('/^<\?php/',$text)) {
  1938. $text='<?php '.$text;
  1939. $pre=TRUE;
  1940. }
  1941. foreach (token_get_all($text) as $token)
  1942. if ($pre)
  1943. $pre=FALSE;
  1944. else
  1945. $out.='<span'.
  1946. (is_array($token)?
  1947. (' class="'.
  1948. substr(strtolower(token_name($token[0])),2).'">'.
  1949. $this->encode($token[1]).''):
  1950. ('>'.$this->encode($token))).
  1951. '</span>';
  1952. return $out?('<code>'.$out.'</code>'):$text;
  1953. }
  1954. /**
  1955. * Dump expression with syntax highlighting
  1956. * @return NULL
  1957. * @param $expr mixed
  1958. **/
  1959. function dump($expr) {
  1960. echo $this->highlight($this->stringify($expr));
  1961. }
  1962. /**
  1963. * Return path (and query parameters) relative to the base directory
  1964. * @return string
  1965. * @param $url string
  1966. **/
  1967. function rel($url) {
  1968. return preg_replace('/^(?:https?:\/\/)?'.
  1969. preg_quote($this->hive['BASE'],'/').'(\/.*|$)/','\1',$url);
  1970. }
  1971. /**
  1972. * Namespace-aware class autoloader
  1973. * @return mixed
  1974. * @param $class string
  1975. **/
  1976. protected function autoload($class) {
  1977. $class=$this->fixslashes(ltrim($class,'\\'));
  1978. $func=NULL;
  1979. if (is_array($path=$this->hive['AUTOLOAD']) &&
  1980. isset($path[1]) && is_callable($path[1]))
  1981. list($path,$func)=$path;
  1982. foreach ($this->split($this->hive['PLUGINS'].';'.$path) as $auto)
  1983. if ($func && is_file($file=$func($auto.$class).'.php') ||
  1984. is_file($file=$auto.$class.'.php') ||
  1985. is_file($file=$auto.strtolower($class).'.php') ||
  1986. is_file($file=strtolower($auto.$class).'.php'))
  1987. return require($file);
  1988. }
  1989. /**
  1990. * Execute framework/application shutdown sequence
  1991. * @param $cwd string
  1992. **/
  1993. function unload($cwd) {
  1994. chdir($cwd);
  1995. if (!($error=error_get_last()) &&
  1996. session_status()==PHP_SESSION_ACTIVE)
  1997. session_commit();
  1998. foreach ($this->locks as $lock)
  1999. @unlink($lock);
  2000. $handler=$this->hive['UNLOAD'];
  2001. if ((!$handler || $this->call($handler,$this)===FALSE) &&
  2002. $error && in_array($error['type'],
  2003. [E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR]))
  2004. // Fatal error detected
  2005. $this->error(500,
  2006. sprintf(self::E_Fatal,$error['message']),[$error]);
  2007. }
  2008. /**
  2009. * Convenience method for checking hive key
  2010. * @return mixed
  2011. * @param $key string
  2012. **/
  2013. function offsetexists($key) {
  2014. return $this->exists($key);
  2015. }
  2016. /**
  2017. * Convenience method for assigning hive value
  2018. * @return mixed
  2019. * @param $key string
  2020. * @param $val scalar
  2021. **/
  2022. function offsetset($key,$val) {
  2023. return $this->set($key,$val);
  2024. }
  2025. /**
  2026. * Convenience method for retrieving hive value
  2027. * @return mixed
  2028. * @param $key string
  2029. **/
  2030. function &offsetget($key) {
  2031. $val=&$this->ref($key);
  2032. return $val;
  2033. }
  2034. /**
  2035. * Convenience method for removing hive key
  2036. * @return NULL
  2037. * @param $key string
  2038. **/
  2039. function offsetunset($key) {
  2040. $this->clear($key);
  2041. }
  2042. /**
  2043. * Alias for offsetexists()
  2044. * @return mixed
  2045. * @param $key string
  2046. **/
  2047. function __isset($key) {
  2048. return $this->offsetexists($key);
  2049. }
  2050. /**
  2051. * Alias for offsetset()
  2052. * @return mixed
  2053. * @param $key string
  2054. * @param $val mixed
  2055. **/
  2056. function __set($key,$val) {
  2057. return $this->offsetset($key,$val);
  2058. }
  2059. /**
  2060. * Alias for offsetget()
  2061. * @return mixed
  2062. * @param $key string
  2063. **/
  2064. function &__get($key) {
  2065. $val=&$this->offsetget($key);
  2066. return $val;
  2067. }
  2068. /**
  2069. * Alias for offsetunset()
  2070. * @return mixed
  2071. * @param $key string
  2072. **/
  2073. function __unset($key) {
  2074. $this->offsetunset($key);
  2075. }
  2076. /**
  2077. * Call function identified by hive key
  2078. * @return mixed
  2079. * @param $key string
  2080. * @param $args array
  2081. **/
  2082. function __call($key,array $args) {
  2083. if ($this->exists($key,$val))
  2084. return call_user_func_array($val,$args);
  2085. user_error(sprintf(self::E_Method,$key),E_USER_ERROR);
  2086. }
  2087. //! Prohibit cloning
  2088. private function __clone() {
  2089. }
  2090. //! Bootstrap
  2091. function __construct() {
  2092. // Managed directives
  2093. ini_set('default_charset',$charset='UTF-8');
  2094. if (extension_loaded('mbstring'))
  2095. mb_internal_encoding($charset);
  2096. ini_set('display_errors',0);
  2097. // Deprecated directives
  2098. @ini_set('magic_quotes_gpc',0);
  2099. @ini_set('register_globals',0);
  2100. // Intercept errors/exceptions; PHP5.3-compatible
  2101. $check=error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE));
  2102. set_exception_handler(
  2103. function($obj) {
  2104. $this->hive['EXCEPTION']=$obj;
  2105. $this->error(500,
  2106. $obj->getmessage().' '.
  2107. '['.$obj->getFile().':'.$obj->getLine().']',
  2108. $obj->gettrace());
  2109. }
  2110. );
  2111. set_error_handler(
  2112. function($level,$text,$file,$line) {
  2113. if ($level & error_reporting())
  2114. $this->error(500,$text,NULL,$level);
  2115. }
  2116. );
  2117. if (!isset($_SERVER['SERVER_NAME']) || $_SERVER['SERVER_NAME']==='')
  2118. $_SERVER['SERVER_NAME']=gethostname();
  2119. if ($cli=PHP_SAPI=='cli') {
  2120. // Emulate HTTP request
  2121. $_SERVER['REQUEST_METHOD']='GET';
  2122. if (!isset($_SERVER['argv'][1])) {
  2123. $_SERVER['argc']++;
  2124. $_SERVER['argv'][1]='/';
  2125. }
  2126. $req=$query='';
  2127. if (substr($_SERVER['argv'][1],0,1)=='/') {
  2128. $req=$_SERVER['argv'][1];
  2129. $query=parse_url($req,PHP_URL_QUERY);
  2130. } else {
  2131. foreach($_SERVER['argv'] as $i=>$arg) {
  2132. if (!$i) continue;
  2133. if (preg_match('/^\-(\-)?(\w+)(?:\=(.*))?$/',$arg,$m)) {
  2134. foreach($m[1]?[$m[2]]:str_split($m[2]) as $k)
  2135. $query.=($query?'&':'').urlencode($k).'=';
  2136. if (isset($m[3]))
  2137. $query.=urlencode($m[3]);
  2138. } else
  2139. $req.='/'.$arg;
  2140. }
  2141. if (!$req)
  2142. $req='/';
  2143. if ($query)
  2144. $req.='?'.$query;
  2145. }
  2146. $_SERVER['REQUEST_URI']=$req;
  2147. parse_str($query,$GLOBALS['_GET']);
  2148. }
  2149. $headers=[];
  2150. if (!$cli) {
  2151. if (function_exists('getallheaders')) {
  2152. foreach (getallheaders() as $key=>$val) {
  2153. $tmp=strtoupper(strtr($key,'-','_'));
  2154. // TODO: use ucwords delimiters for php 5.4.32+ & 5.5.16+
  2155. $key=strtr(ucwords(strtolower(strtr($key,'-',' '))),' ','-');
  2156. $headers[$key]=$val;
  2157. if (isset($_SERVER['HTTP_'.$tmp]))
  2158. $headers[$key]=&$_SERVER['HTTP_'.$tmp];
  2159. }
  2160. }
  2161. else {
  2162. if (isset($_SERVER['CONTENT_LENGTH']))
  2163. $headers['Content-Length']=&$_SERVER['CONTENT_LENGTH'];
  2164. if (isset($_SERVER['CONTENT_TYPE']))
  2165. $headers['Content-Type']=&$_SERVER['CONTENT_TYPE'];
  2166. foreach (array_keys($_SERVER) as $key)
  2167. if (substr($key,0,5)=='HTTP_')
  2168. $headers[strtr(ucwords(strtolower(strtr(
  2169. substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
  2170. }
  2171. }
  2172. if (isset($headers['X-HTTP-Method-Override']))
  2173. $_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
  2174. elseif ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['_method']))
  2175. $_SERVER['REQUEST_METHOD']=$_POST['_method'];
  2176. $scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
  2177. isset($headers['X-Forwarded-Proto']) &&
  2178. $headers['X-Forwarded-Proto']=='https'?'https':'http';
  2179. // Create hive early on to expose header methods
  2180. $this->hive=['HEADERS'=>&$headers];
  2181. if (function_exists('apache_setenv')) {
  2182. // Work around Apache pre-2.4 VirtualDocumentRoot bug
  2183. $_SERVER['DOCUMENT_ROOT']=str_replace($_SERVER['SCRIPT_NAME'],'',
  2184. $_SERVER['SCRIPT_FILENAME']);
  2185. apache_setenv("DOCUMENT_ROOT",$_SERVER['DOCUMENT_ROOT']);
  2186. }
  2187. $_SERVER['DOCUMENT_ROOT']=realpath($_SERVER['DOCUMENT_ROOT']);
  2188. $base='';
  2189. if (!$cli)
  2190. $base=rtrim($this->fixslashes(
  2191. dirname($_SERVER['SCRIPT_NAME'])),'/');
  2192. $uri=parse_url((preg_match('/^\w+:\/\//',$_SERVER['REQUEST_URI'])?'':
  2193. $scheme.'://'.$_SERVER['SERVER_NAME']).$_SERVER['REQUEST_URI']);
  2194. $_SERVER['REQUEST_URI']=$uri['path'].
  2195. (isset($uri['query'])?'?'.$uri['query']:'').
  2196. (isset($uri['fragment'])?'#'.$uri['fragment']:'');
  2197. $path=preg_replace('/^'.preg_quote($base,'/').'/','',$uri['path']);
  2198. $jar=[
  2199. 'expire'=>0,
  2200. 'lifetime'=>0,
  2201. 'path'=>$base?:'/',
  2202. 'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) &&
  2203. !filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
  2204. $_SERVER['SERVER_NAME']:'',
  2205. 'secure'=>($scheme=='https'),
  2206. 'httponly'=>TRUE
  2207. ];
  2208. $port=80;
  2209. if (isset($headers['X-Forwarded-Port']))
  2210. $port=$headers['X-Forwarded-Port'];
  2211. elseif (isset($_SERVER['SERVER_PORT']))
  2212. $port=$_SERVER['SERVER_PORT'];
  2213. // Default configuration
  2214. $this->hive+=[
  2215. 'AGENT'=>$this->agent(),
  2216. 'AJAX'=>$this->ajax(),
  2217. 'ALIAS'=>NULL,
  2218. 'ALIASES'=>[],
  2219. 'AUTOLOAD'=>'./',
  2220. 'BASE'=>$base,
  2221. 'BITMASK'=>ENT_COMPAT,
  2222. 'BODY'=>NULL,
  2223. 'CACHE'=>FALSE,
  2224. 'CASELESS'=>TRUE,
  2225. 'CLI'=>$cli,
  2226. 'CORS'=>[
  2227. 'headers'=>'',
  2228. 'origin'=>FALSE,
  2229. 'credentials'=>FALSE,
  2230. 'expose'=>FALSE,
  2231. 'ttl'=>0
  2232. ],
  2233. 'DEBUG'=>0,
  2234. 'DIACRITICS'=>[],
  2235. 'DNSBL'=>'',
  2236. 'EMOJI'=>[],
  2237. 'ENCODING'=>$charset,
  2238. 'ERROR'=>NULL,
  2239. 'ESCAPE'=>TRUE,
  2240. 'EXCEPTION'=>NULL,
  2241. 'EXEMPT'=>NULL,
  2242. 'FALLBACK'=>$this->fallback,
  2243. 'FORMATS'=>[],
  2244. 'FRAGMENT'=>isset($uri['fragment'])?$uri['fragment']:'',
  2245. 'HALT'=>TRUE,
  2246. 'HIGHLIGHT'=>FALSE,
  2247. 'HOST'=>$_SERVER['SERVER_NAME'],
  2248. 'IP'=>$this->ip(),
  2249. 'JAR'=>$jar,
  2250. 'LANGUAGE'=>isset($headers['Accept-Language'])?
  2251. $this->language($headers['Accept-Language']):
  2252. $this->fallback,
  2253. 'LOCALES'=>'./',
  2254. 'LOGGABLE'=>'*',
  2255. 'LOGS'=>'./',
  2256. 'MB'=>extension_loaded('mbstring'),
  2257. 'ONERROR'=>NULL,
  2258. 'ONREROUTE'=>NULL,
  2259. 'PACKAGE'=>self::PACKAGE,
  2260. 'PARAMS'=>[],
  2261. 'PATH'=>$path,
  2262. 'PATTERN'=>NULL,
  2263. 'PLUGINS'=>$this->fixslashes(__DIR__).'/',
  2264. 'PORT'=>$port,
  2265. 'PREFIX'=>NULL,
  2266. 'PREMAP'=>'',
  2267. 'QUERY'=>isset($uri['query'])?$uri['query']:'',
  2268. 'QUIET'=>FALSE,
  2269. 'RAW'=>FALSE,
  2270. 'REALM'=>$scheme.'://'.$_SERVER['SERVER_NAME'].
  2271. ($port && !in_array($port,[80,443])?(':'.$port):'').
  2272. $_SERVER['REQUEST_URI'],
  2273. 'RESPONSE'=>'',
  2274. 'ROOT'=>$_SERVER['DOCUMENT_ROOT'],
  2275. 'ROUTES'=>[],
  2276. 'SCHEME'=>$scheme,
  2277. 'SEED'=>$this->hash($_SERVER['SERVER_NAME'].$base),
  2278. 'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
  2279. 'TEMP'=>'tmp/',
  2280. 'TIME'=>&$_SERVER['REQUEST_TIME_FLOAT'],
  2281. 'TZ'=>@date_default_timezone_get(),
  2282. 'UI'=>'./',
  2283. 'UNLOAD'=>NULL,
  2284. 'UPLOADS'=>'./',
  2285. 'URI'=>&$_SERVER['REQUEST_URI'],
  2286. 'VERB'=>&$_SERVER['REQUEST_METHOD'],
  2287. 'VERSION'=>self::VERSION,
  2288. 'XFRAME'=>'SAMEORIGIN'
  2289. ];
  2290. if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) {
  2291. unset($jar['expire']);
  2292. session_cache_limiter('');
  2293. call_user_func_array('session_set_cookie_params',$jar);
  2294. }
  2295. if (PHP_SAPI=='cli-server' &&
  2296. preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
  2297. $this->reroute('/');
  2298. if (ini_get('auto_globals_jit'))
  2299. // Override setting
  2300. $GLOBALS+=['_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST];
  2301. // Sync PHP globals with corresponding hive keys
  2302. $this->init=$this->hive;
  2303. foreach (explode('|',self::GLOBALS) as $global) {
  2304. $sync=$this->sync($global);
  2305. $this->init+=[
  2306. $global=>preg_match('/SERVER|ENV/',$global)?$sync:[]
  2307. ];
  2308. }
  2309. if ($check && $error=error_get_last())
  2310. // Error detected
  2311. $this->error(500,
  2312. sprintf(self::E_Fatal,$error['message']),[$error]);
  2313. date_default_timezone_set($this->hive['TZ']);
  2314. // Register framework autoloader
  2315. spl_autoload_register([$this,'autoload']);
  2316. // Register shutdown handler
  2317. register_shutdown_function([$this,'unload'],getcwd());
  2318. }
  2319. }
  2320. //! Cache engine
  2321. class Cache extends Prefab {
  2322. protected
  2323. //! Cache DSN
  2324. $dsn,
  2325. //! Prefix for cache entries
  2326. $prefix,
  2327. //! MemCache or Redis object
  2328. $ref;
  2329. /**
  2330. * Return timestamp and TTL of cache entry or FALSE if not found
  2331. * @return array|FALSE
  2332. * @param $key string
  2333. * @param $val mixed
  2334. **/
  2335. function exists($key,&$val=NULL) {
  2336. $fw=Base::instance();
  2337. if (!$this->dsn)
  2338. return FALSE;
  2339. $ndx=$this->prefix.'.'.$key;
  2340. $parts=explode('=',$this->dsn,2);
  2341. switch ($parts[0]) {
  2342. case 'apc':
  2343. case 'apcu':
  2344. $raw=call_user_func($parts[0].'_fetch',$ndx);
  2345. break;
  2346. case 'redis':
  2347. $raw=$this->ref->get($ndx);
  2348. break;
  2349. case 'memcache':
  2350. $raw=memcache_get($this->ref,$ndx);
  2351. break;
  2352. case 'memcached':
  2353. $raw=$this->ref->get($ndx);
  2354. break;
  2355. case 'wincache':
  2356. $raw=wincache_ucache_get($ndx);
  2357. break;
  2358. case 'xcache':
  2359. $raw=xcache_get($ndx);
  2360. break;
  2361. case 'folder':
  2362. $raw=$fw->read($parts[1].$ndx);
  2363. break;
  2364. }
  2365. if (!empty($raw)) {
  2366. list($val,$time,$ttl)=(array)$fw->unserialize($raw);
  2367. if ($ttl===0 || $time+$ttl>microtime(TRUE))
  2368. return [$time,$ttl];
  2369. $val=null;
  2370. $this->clear($key);
  2371. }
  2372. return FALSE;
  2373. }
  2374. /**
  2375. * Store value in cache
  2376. * @return mixed|FALSE
  2377. * @param $key string
  2378. * @param $val mixed
  2379. * @param $ttl int
  2380. **/
  2381. function set($key,$val,$ttl=0) {
  2382. $fw=Base::instance();
  2383. if (!$this->dsn)
  2384. return TRUE;
  2385. $ndx=$this->prefix.'.'.$key;
  2386. $time=microtime(TRUE);
  2387. if ($cached=$this->exists($key))
  2388. list($time,$ttl)=$cached;
  2389. $data=$fw->serialize([$val,$time,$ttl]);
  2390. $parts=explode('=',$this->dsn,2);
  2391. switch ($parts[0]) {
  2392. case 'apc':
  2393. case 'apcu':
  2394. return call_user_func($parts[0].'_store',$ndx,$data,$ttl);
  2395. case 'redis':
  2396. return $this->ref->set($ndx,$data,$ttl?['ex'=>$ttl]:[]);
  2397. case 'memcache':
  2398. return memcache_set($this->ref,$ndx,$data,0,$ttl);
  2399. case 'memcached':
  2400. return $this->ref->set($ndx,$data,$ttl);
  2401. case 'wincache':
  2402. return wincache_ucache_set($ndx,$data,$ttl);
  2403. case 'xcache':
  2404. return xcache_set($ndx,$data,$ttl);
  2405. case 'folder':
  2406. return $fw->write($parts[1].str_replace(['/','\\'],'',$ndx),$data);
  2407. }
  2408. return FALSE;
  2409. }
  2410. /**
  2411. * Retrieve value of cache entry
  2412. * @return mixed|FALSE
  2413. * @param $key string
  2414. **/
  2415. function get($key) {
  2416. return $this->dsn && $this->exists($key,$data)?$data:FALSE;
  2417. }
  2418. /**
  2419. * Delete cache entry
  2420. * @return bool
  2421. * @param $key string
  2422. **/
  2423. function clear($key) {
  2424. if (!$this->dsn)
  2425. return;
  2426. $ndx=$this->prefix.'.'.$key;
  2427. $parts=explode('=',$this->dsn,2);
  2428. switch ($parts[0]) {
  2429. case 'apc':
  2430. case 'apcu':
  2431. return call_user_func($parts[0].'_delete',$ndx);
  2432. case 'redis':
  2433. return $this->ref->del($ndx);
  2434. case 'memcache':
  2435. return memcache_delete($this->ref,$ndx);
  2436. case 'memcached':
  2437. return $this->ref->delete($ndx);
  2438. case 'wincache':
  2439. return wincache_ucache_delete($ndx);
  2440. case 'xcache':
  2441. return xcache_unset($ndx);
  2442. case 'folder':
  2443. return @unlink($parts[1].$ndx);
  2444. }
  2445. return FALSE;
  2446. }
  2447. /**
  2448. * Clear contents of cache backend
  2449. * @return bool
  2450. * @param $suffix string
  2451. **/
  2452. function reset($suffix=NULL) {
  2453. if (!$this->dsn)
  2454. return TRUE;
  2455. $regex='/'.preg_quote($this->prefix.'.','/').'.*'.
  2456. preg_quote($suffix,'/').'/';
  2457. $parts=explode('=',$this->dsn,2);
  2458. switch ($parts[0]) {
  2459. case 'apc':
  2460. case 'apcu':
  2461. $info=call_user_func($parts[0].'_cache_info',
  2462. $parts[0]=='apcu'?FALSE:'user');
  2463. if (!empty($info['cache_list'])) {
  2464. $key=array_key_exists('info',
  2465. $info['cache_list'][0])?'info':'key';
  2466. foreach ($info['cache_list'] as $item)
  2467. if (preg_match($regex,$item[$key]))
  2468. call_user_func($parts[0].'_delete',$item[$key]);
  2469. }
  2470. return TRUE;
  2471. case 'redis':
  2472. $keys=$this->ref->keys($this->prefix.'.*'.$suffix);
  2473. foreach($keys as $key)
  2474. $this->ref->del($key);
  2475. return TRUE;
  2476. case 'memcache':
  2477. foreach (memcache_get_extended_stats(
  2478. $this->ref,'slabs') as $slabs)
  2479. foreach (array_filter(array_keys($slabs),'is_numeric')
  2480. as $id)
  2481. foreach (memcache_get_extended_stats(
  2482. $this->ref,'cachedump',$id) as $data)
  2483. if (is_array($data))
  2484. foreach (array_keys($data) as $key)
  2485. if (preg_match($regex,$key))
  2486. memcache_delete($this->ref,$key);
  2487. return TRUE;
  2488. case 'memcached':
  2489. foreach ($this->ref->getallkeys()?:[] as $key)
  2490. if (preg_match($regex,$key))
  2491. $this->ref->delete($key);
  2492. return TRUE;
  2493. case 'wincache':
  2494. $info=wincache_ucache_info();
  2495. foreach ($info['ucache_entries'] as $item)
  2496. if (preg_match($regex,$item['key_name']))
  2497. wincache_ucache_delete($item['key_name']);
  2498. return TRUE;
  2499. case 'xcache':
  2500. if ($suffix && !ini_get('xcache.admin.enable_auth')) {
  2501. $cnt=xcache_count(XC_TYPE_VAR);
  2502. for ($i=0;$i<$cnt;$i++) {
  2503. $list=xcache_list(XC_TYPE_VAR,$i);
  2504. foreach ($list['cache_list'] as $item)
  2505. if (preg_match($regex,$item['name']))
  2506. xcache_unset($item['name']);
  2507. }
  2508. } else
  2509. xcache_unset_by_prefix($this->prefix.'.');
  2510. return TRUE;
  2511. case 'folder':
  2512. if ($glob=@glob($parts[1].'*'))
  2513. foreach ($glob as $file)
  2514. if (preg_match($regex,basename($file)))
  2515. @unlink($file);
  2516. return TRUE;
  2517. }
  2518. return FALSE;
  2519. }
  2520. /**
  2521. * Load/auto-detect cache backend
  2522. * @return string
  2523. * @param $dsn bool|string
  2524. * @param $seed bool|string
  2525. **/
  2526. function load($dsn,$seed=NULL) {
  2527. $fw=Base::instance();
  2528. if ($dsn=trim($dsn)) {
  2529. if (preg_match('/^redis=(.+)/',$dsn,$parts) &&
  2530. extension_loaded('redis')) {
  2531. list($host,$port,$db)=explode(':',$parts[1])+[1=>6379,2=>NULL];
  2532. $this->ref=new Redis;
  2533. if(!$this->ref->connect($host,$port,2))
  2534. $this->ref=NULL;
  2535. if(isset($db))
  2536. $this->ref->select($db);
  2537. }
  2538. elseif (preg_match('/^memcache=(.+)/',$dsn,$parts) &&
  2539. extension_loaded('memcache'))
  2540. foreach ($fw->split($parts[1]) as $server) {
  2541. list($host,$port)=explode(':',$server)+[1=>11211];
  2542. if (empty($this->ref))
  2543. $this->ref=@memcache_connect($host,$port)?:NULL;
  2544. else
  2545. memcache_add_server($this->ref,$host,$port);
  2546. }
  2547. elseif (preg_match('/^memcached=(.+)/',$dsn,$parts) &&
  2548. extension_loaded('memcached'))
  2549. foreach ($fw->split($parts[1]) as $server) {
  2550. list($host,$port)=explode(':',$server)+[1=>11211];
  2551. if (empty($this->ref))
  2552. $this->ref=new Memcached();
  2553. $this->ref->addServer($host,$port);
  2554. }
  2555. if (empty($this->ref) && !preg_match('/^folder\h*=/',$dsn))
  2556. $dsn=($grep=preg_grep('/^(apc|wincache|xcache)/',
  2557. array_map('strtolower',get_loaded_extensions())))?
  2558. // Auto-detect
  2559. current($grep):
  2560. // Use filesystem as fallback
  2561. ('folder='.$fw->TEMP.'cache/');
  2562. if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) &&
  2563. !is_dir($parts[1]))
  2564. mkdir($parts[1],Base::MODE,TRUE);
  2565. }
  2566. $this->prefix=$seed?:$fw->SEED;
  2567. return $this->dsn=$dsn;
  2568. }
  2569. /**
  2570. * Class constructor
  2571. * @param $dsn bool|string
  2572. **/
  2573. function __construct($dsn=FALSE) {
  2574. if ($dsn)
  2575. $this->load($dsn);
  2576. }
  2577. }
  2578. //! View handler
  2579. class View extends Prefab {
  2580. private
  2581. //! Temporary hive
  2582. $temp;
  2583. protected
  2584. //! Template file
  2585. $file,
  2586. //! Post-rendering handler
  2587. $trigger,
  2588. //! Nesting level
  2589. $level=0;
  2590. /** @var \Base Framework instance */
  2591. protected $fw;
  2592. function __construct() {
  2593. $this->fw=\Base::instance();
  2594. }
  2595. /**
  2596. * Encode characters to equivalent HTML entities
  2597. * @return string
  2598. * @param $arg mixed
  2599. **/
  2600. function esc($arg) {
  2601. return $this->fw->recursive($arg,
  2602. function($val) {
  2603. return is_string($val)?$this->fw->encode($val):$val;
  2604. }
  2605. );
  2606. }
  2607. /**
  2608. * Decode HTML entities to equivalent characters
  2609. * @return string
  2610. * @param $arg mixed
  2611. **/
  2612. function raw($arg) {
  2613. return $this->fw->recursive($arg,
  2614. function($val) {
  2615. return is_string($val)?$this->fw->decode($val):$val;
  2616. }
  2617. );
  2618. }
  2619. /**
  2620. * Create sandbox for template execution
  2621. * @return string
  2622. * @param $hive array
  2623. * @param $mime string
  2624. **/
  2625. protected function sandbox(array $hive=NULL,$mime=NULL) {
  2626. $fw=$this->fw;
  2627. $implicit=FALSE;
  2628. if (is_null($hive)) {
  2629. $implicit=TRUE;
  2630. $hive=$fw->hive();
  2631. }
  2632. if ($this->level<1 || $implicit) {
  2633. if (!$fw->CLI && $mime && !headers_sent() &&
  2634. !preg_grep ('/^Content-Type:/',headers_list()))
  2635. header('Content-Type: '.$mime.'; '.
  2636. 'charset='.$fw->ENCODING);
  2637. if ($fw->ESCAPE)
  2638. $hive=$this->esc($hive);
  2639. if (isset($hive['ALIASES']))
  2640. $hive['ALIASES']=$fw->build($hive['ALIASES']);
  2641. }
  2642. $this->temp=$hive;
  2643. unset($fw,$hive,$implicit,$mime);
  2644. extract($this->temp);
  2645. $this->temp=NULL;
  2646. $this->level++;
  2647. ob_start();
  2648. require($this->file);
  2649. $this->level--;
  2650. return ob_get_clean();
  2651. }
  2652. /**
  2653. * Render template
  2654. * @return string
  2655. * @param $file string
  2656. * @param $mime string
  2657. * @param $hive array
  2658. * @param $ttl int
  2659. **/
  2660. function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
  2661. $fw=$this->fw;
  2662. $cache=Cache::instance();
  2663. foreach ($fw->split($fw->UI) as $dir) {
  2664. if ($cache->exists($hash=$fw->hash($dir.$file),$data))
  2665. return $data;
  2666. if (is_file($this->file=$fw->fixslashes($dir.$file))) {
  2667. if (isset($_COOKIE[session_name()]) &&
  2668. !headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  2669. session_start();
  2670. $fw->sync('SESSION');
  2671. $data=$this->sandbox($hive,$mime);
  2672. if (isset($this->trigger['afterrender']))
  2673. foreach($this->trigger['afterrender'] as $func)
  2674. $data=$fw->call($func,[$data, $dir.$file]);
  2675. if ($ttl)
  2676. $cache->set($hash,$data,$ttl);
  2677. return $data;
  2678. }
  2679. }
  2680. user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
  2681. }
  2682. /**
  2683. * post rendering handler
  2684. * @param $func callback
  2685. */
  2686. function afterrender($func) {
  2687. $this->trigger['afterrender'][]=$func;
  2688. }
  2689. }
  2690. //! Lightweight template engine
  2691. class Preview extends View {
  2692. protected
  2693. //! token filter
  2694. $filter=[
  2695. 'c'=>'$this->c',
  2696. 'esc'=>'$this->esc',
  2697. 'raw'=>'$this->raw',
  2698. 'alias'=>'Base::instance()->alias',
  2699. 'format'=>'Base::instance()->format'
  2700. ];
  2701. protected
  2702. //! newline interpolation
  2703. $interpolation=true;
  2704. /**
  2705. * enable/disable markup parsing interpolation
  2706. * mainly used for adding appropriate newlines
  2707. * @param $bool bool
  2708. */
  2709. function interpolation($bool) {
  2710. $this->interpolation=$bool;
  2711. }
  2712. /**
  2713. * Return C-locale equivalent of number
  2714. * @return string
  2715. * @param $val int|float
  2716. **/
  2717. function c($val) {
  2718. $locale=setlocale(LC_NUMERIC,0);
  2719. setlocale(LC_NUMERIC,'C');
  2720. $out=(string)(float)$val;
  2721. $locale=setlocale(LC_NUMERIC,$locale);
  2722. return $out;
  2723. }
  2724. /**
  2725. * Convert token to variable
  2726. * @return string
  2727. * @param $str string
  2728. **/
  2729. function token($str) {
  2730. $fw=$this->fw;
  2731. $str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
  2732. $fw->compile($str)));
  2733. if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
  2734. $str,$parts)) {
  2735. $str=trim($parts[1]);
  2736. foreach ($fw->split(trim($parts[2],"\xC2\xA0")) as $func)
  2737. $str=is_string($cmd=$this->filter($func))?
  2738. $cmd.'('.$str.')':
  2739. 'Base::instance()->'.
  2740. 'call($this->filter(\''.$func.'\'),['.$str.'])';
  2741. }
  2742. return $str;
  2743. }
  2744. /**
  2745. * Register or get (a specific one or all) token filters
  2746. * @param string $key
  2747. * @param string|closure $func
  2748. * @return array|closure|string
  2749. */
  2750. function filter($key=NULL,$func=NULL) {
  2751. if (!$key)
  2752. return array_keys($this->filter);
  2753. $key=strtolower($key);
  2754. if (!$func)
  2755. return $this->filter[$key];
  2756. $this->filter[$key]=$func;
  2757. }
  2758. /**
  2759. * Assemble markup
  2760. * @return string
  2761. * @param $node string
  2762. **/
  2763. protected function build($node) {
  2764. return preg_replace_callback(
  2765. '/\{~(.+?)~\}|\{\*(.+?)\*\}|\{\-(.+?)\-\}|'.
  2766. '\{\{(.+?)\}\}((\r?\n)*)/s',
  2767. function($expr) {
  2768. if ($expr[1])
  2769. $str='<?php '.$this->token($expr[1]).' ?>';
  2770. elseif ($expr[2])
  2771. return '';
  2772. elseif ($expr[3])
  2773. $str=$expr[3];
  2774. else {
  2775. $str='<?= ('.trim($this->token($expr[4])).')'.
  2776. ($this->interpolation?
  2777. (!empty($expr[6])?'."'.$expr[6].'"':''):'').' ?>';
  2778. if (isset($expr[5]))
  2779. $str.=$expr[5];
  2780. }
  2781. return $str;
  2782. },
  2783. $node
  2784. );
  2785. }
  2786. /**
  2787. * Render template string
  2788. * @return string
  2789. * @param $node string|array
  2790. * @param $hive array
  2791. * @param $ttl int
  2792. * @param $persist bool
  2793. * @param $escape bool
  2794. **/
  2795. function resolve($node,array $hive=NULL,$ttl=0,$persist=FALSE,$escape=NULL) {
  2796. $fw=$this->fw;
  2797. $cache=Cache::instance();
  2798. if ($escape!==NULL) {
  2799. $esc=$fw->ESCAPE;
  2800. $fw->ESCAPE=$escape;
  2801. }
  2802. if ($ttl || $persist)
  2803. $hash=$fw->hash($fw->serialize($node));
  2804. if ($ttl && $cache->exists($hash,$data))
  2805. return $data;
  2806. if ($persist) {
  2807. if (!is_dir($tmp=$fw->TEMP))
  2808. mkdir($tmp,Base::MODE,TRUE);
  2809. if (!is_file($this->file=($tmp.
  2810. $fw->SEED.'.'.$hash.'.php')))
  2811. $fw->write($this->file,$this->build($node));
  2812. if (isset($_COOKIE[session_name()]) &&
  2813. !headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  2814. session_start();
  2815. $fw->sync('SESSION');
  2816. $data=$this->sandbox($hive);
  2817. }
  2818. else {
  2819. if (!$hive)
  2820. $hive=$fw->hive();
  2821. if ($fw->ESCAPE)
  2822. $hive=$this->esc($hive);
  2823. extract($hive);
  2824. unset($hive);
  2825. ob_start();
  2826. eval(' ?>'.$this->build($node).'<?php ');
  2827. $data=ob_get_clean();
  2828. }
  2829. if ($ttl)
  2830. $cache->set($hash,$data,$ttl);
  2831. if ($escape!==NULL)
  2832. $fw->ESCAPE=$esc;
  2833. return $data;
  2834. }
  2835. /**
  2836. * Parse template string
  2837. * @return string
  2838. * @param $text string
  2839. **/
  2840. function parse($text) {
  2841. // Remove PHP code and comments
  2842. return preg_replace(
  2843. '/\h*<\?(?!xml)(?:php|\s*=)?.+?\?>\h*|'.
  2844. '\{\*.+?\*\}/is','', $text);
  2845. }
  2846. /**
  2847. * Render template
  2848. * @return string
  2849. * @param $file string
  2850. * @param $mime string
  2851. * @param $hive array
  2852. * @param $ttl int
  2853. **/
  2854. function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
  2855. $fw=$this->fw;
  2856. $cache=Cache::instance();
  2857. if (!is_dir($tmp=$fw->TEMP))
  2858. mkdir($tmp,Base::MODE,TRUE);
  2859. foreach ($fw->split($fw->UI) as $dir) {
  2860. if ($cache->exists($hash=$fw->hash($dir.$file),$data))
  2861. return $data;
  2862. if (is_file($view=$fw->fixslashes($dir.$file))) {
  2863. if (!is_file($this->file=($tmp.
  2864. $fw->SEED.'.'.$fw->hash($view).'.php')) ||
  2865. filemtime($this->file)<filemtime($view)) {
  2866. $contents=$fw->read($view);
  2867. if (isset($this->trigger['beforerender']))
  2868. foreach ($this->trigger['beforerender'] as $func)
  2869. $contents=$fw->call($func, [$contents, $view]);
  2870. $text=$this->parse($contents);
  2871. $fw->write($this->file,$this->build($text));
  2872. }
  2873. if (isset($_COOKIE[session_name()]) &&
  2874. !headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
  2875. session_start();
  2876. $fw->sync('SESSION');
  2877. $data=$this->sandbox($hive,$mime);
  2878. if(isset($this->trigger['afterrender']))
  2879. foreach ($this->trigger['afterrender'] as $func)
  2880. $data=$fw->call($func, [$data, $view]);
  2881. if ($ttl)
  2882. $cache->set($hash,$data,$ttl);
  2883. return $data;
  2884. }
  2885. }
  2886. user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
  2887. }
  2888. /**
  2889. * post rendering handler
  2890. * @param $func callback
  2891. */
  2892. function beforerender($func) {
  2893. $this->trigger['beforerender'][]=$func;
  2894. }
  2895. }
  2896. //! ISO language/country codes
  2897. class ISO extends Prefab {
  2898. //@{ ISO 3166-1 country codes
  2899. const
  2900. CC_af='Afghanistan',
  2901. CC_ax='Åland Islands',
  2902. CC_al='Albania',
  2903. CC_dz='Algeria',
  2904. CC_as='American Samoa',
  2905. CC_ad='Andorra',
  2906. CC_ao='Angola',
  2907. CC_ai='Anguilla',
  2908. CC_aq='Antarctica',
  2909. CC_ag='Antigua and Barbuda',
  2910. CC_ar='Argentina',
  2911. CC_am='Armenia',
  2912. CC_aw='Aruba',
  2913. CC_au='Australia',
  2914. CC_at='Austria',
  2915. CC_az='Azerbaijan',
  2916. CC_bs='Bahamas',
  2917. CC_bh='Bahrain',
  2918. CC_bd='Bangladesh',
  2919. CC_bb='Barbados',
  2920. CC_by='Belarus',
  2921. CC_be='Belgium',
  2922. CC_bz='Belize',
  2923. CC_bj='Benin',
  2924. CC_bm='Bermuda',
  2925. CC_bt='Bhutan',
  2926. CC_bo='Bolivia',
  2927. CC_bq='Bonaire, Sint Eustatius and Saba',
  2928. CC_ba='Bosnia and Herzegovina',
  2929. CC_bw='Botswana',
  2930. CC_bv='Bouvet Island',
  2931. CC_br='Brazil',
  2932. CC_io='British Indian Ocean Territory',
  2933. CC_bn='Brunei Darussalam',
  2934. CC_bg='Bulgaria',
  2935. CC_bf='Burkina Faso',
  2936. CC_bi='Burundi',
  2937. CC_kh='Cambodia',
  2938. CC_cm='Cameroon',
  2939. CC_ca='Canada',
  2940. CC_cv='Cape Verde',
  2941. CC_ky='Cayman Islands',
  2942. CC_cf='Central African Republic',
  2943. CC_td='Chad',
  2944. CC_cl='Chile',
  2945. CC_cn='China',
  2946. CC_cx='Christmas Island',
  2947. CC_cc='Cocos (Keeling) Islands',
  2948. CC_co='Colombia',
  2949. CC_km='Comoros',
  2950. CC_cg='Congo',
  2951. CC_cd='Congo, The Democratic Republic of',
  2952. CC_ck='Cook Islands',
  2953. CC_cr='Costa Rica',
  2954. CC_ci='Côte d\'ivoire',
  2955. CC_hr='Croatia',
  2956. CC_cu='Cuba',
  2957. CC_cw='Curaçao',
  2958. CC_cy='Cyprus',
  2959. CC_cz='Czech Republic',
  2960. CC_dk='Denmark',
  2961. CC_dj='Djibouti',
  2962. CC_dm='Dominica',
  2963. CC_do='Dominican Republic',
  2964. CC_ec='Ecuador',
  2965. CC_eg='Egypt',
  2966. CC_sv='El Salvador',
  2967. CC_gq='Equatorial Guinea',
  2968. CC_er='Eritrea',
  2969. CC_ee='Estonia',
  2970. CC_et='Ethiopia',
  2971. CC_fk='Falkland Islands (Malvinas)',
  2972. CC_fo='Faroe Islands',
  2973. CC_fj='Fiji',
  2974. CC_fi='Finland',
  2975. CC_fr='France',
  2976. CC_gf='French Guiana',
  2977. CC_pf='French Polynesia',
  2978. CC_tf='French Southern Territories',
  2979. CC_ga='Gabon',
  2980. CC_gm='Gambia',
  2981. CC_ge='Georgia',
  2982. CC_de='Germany',
  2983. CC_gh='Ghana',
  2984. CC_gi='Gibraltar',
  2985. CC_gr='Greece',
  2986. CC_gl='Greenland',
  2987. CC_gd='Grenada',
  2988. CC_gp='Guadeloupe',
  2989. CC_gu='Guam',
  2990. CC_gt='Guatemala',
  2991. CC_gg='Guernsey',
  2992. CC_gn='Guinea',
  2993. CC_gw='Guinea-Bissau',
  2994. CC_gy='Guyana',
  2995. CC_ht='Haiti',
  2996. CC_hm='Heard Island and McDonald Islands',
  2997. CC_va='Holy See (Vatican City State)',
  2998. CC_hn='Honduras',
  2999. CC_hk='Hong Kong',
  3000. CC_hu='Hungary',
  3001. CC_is='Iceland',
  3002. CC_in='India',
  3003. CC_id='Indonesia',
  3004. CC_ir='Iran, Islamic Republic of',
  3005. CC_iq='Iraq',
  3006. CC_ie='Ireland',
  3007. CC_im='Isle of Man',
  3008. CC_il='Israel',
  3009. CC_it='Italy',
  3010. CC_jm='Jamaica',
  3011. CC_jp='Japan',
  3012. CC_je='Jersey',
  3013. CC_jo='Jordan',
  3014. CC_kz='Kazakhstan',
  3015. CC_ke='Kenya',
  3016. CC_ki='Kiribati',
  3017. CC_kp='Korea, Democratic People\'s Republic of',
  3018. CC_kr='Korea, Republic of',
  3019. CC_kw='Kuwait',
  3020. CC_kg='Kyrgyzstan',
  3021. CC_la='Lao People\'s Democratic Republic',
  3022. CC_lv='Latvia',
  3023. CC_lb='Lebanon',
  3024. CC_ls='Lesotho',
  3025. CC_lr='Liberia',
  3026. CC_ly='Libya',
  3027. CC_li='Liechtenstein',
  3028. CC_lt='Lithuania',
  3029. CC_lu='Luxembourg',
  3030. CC_mo='Macao',
  3031. CC_mk='Macedonia, The Former Yugoslav Republic of',
  3032. CC_mg='Madagascar',
  3033. CC_mw='Malawi',
  3034. CC_my='Malaysia',
  3035. CC_mv='Maldives',
  3036. CC_ml='Mali',
  3037. CC_mt='Malta',
  3038. CC_mh='Marshall Islands',
  3039. CC_mq='Martinique',
  3040. CC_mr='Mauritania',
  3041. CC_mu='Mauritius',
  3042. CC_yt='Mayotte',
  3043. CC_mx='Mexico',
  3044. CC_fm='Micronesia, Federated States of',
  3045. CC_md='Moldova, Republic of',
  3046. CC_mc='Monaco',
  3047. CC_mn='Mongolia',
  3048. CC_me='Montenegro',
  3049. CC_ms='Montserrat',
  3050. CC_ma='Morocco',
  3051. CC_mz='Mozambique',
  3052. CC_mm='Myanmar',
  3053. CC_na='Namibia',
  3054. CC_nr='Nauru',
  3055. CC_np='Nepal',
  3056. CC_nl='Netherlands',
  3057. CC_nc='New Caledonia',
  3058. CC_nz='New Zealand',
  3059. CC_ni='Nicaragua',
  3060. CC_ne='Niger',
  3061. CC_ng='Nigeria',
  3062. CC_nu='Niue',
  3063. CC_nf='Norfolk Island',
  3064. CC_mp='Northern Mariana Islands',
  3065. CC_no='Norway',
  3066. CC_om='Oman',
  3067. CC_pk='Pakistan',
  3068. CC_pw='Palau',
  3069. CC_ps='Palestinian Territory, Occupied',
  3070. CC_pa='Panama',
  3071. CC_pg='Papua New Guinea',
  3072. CC_py='Paraguay',
  3073. CC_pe='Peru',
  3074. CC_ph='Philippines',
  3075. CC_pn='Pitcairn',
  3076. CC_pl='Poland',
  3077. CC_pt='Portugal',
  3078. CC_pr='Puerto Rico',
  3079. CC_qa='Qatar',
  3080. CC_re='Réunion',
  3081. CC_ro='Romania',
  3082. CC_ru='Russian Federation',
  3083. CC_rw='Rwanda',
  3084. CC_bl='Saint Barthélemy',
  3085. CC_sh='Saint Helena, Ascension and Tristan da Cunha',
  3086. CC_kn='Saint Kitts and Nevis',
  3087. CC_lc='Saint Lucia',
  3088. CC_mf='Saint Martin (French Part)',
  3089. CC_pm='Saint Pierre and Miquelon',
  3090. CC_vc='Saint Vincent and The Grenadines',
  3091. CC_ws='Samoa',
  3092. CC_sm='San Marino',
  3093. CC_st='Sao Tome and Principe',
  3094. CC_sa='Saudi Arabia',
  3095. CC_sn='Senegal',
  3096. CC_rs='Serbia',
  3097. CC_sc='Seychelles',
  3098. CC_sl='Sierra Leone',
  3099. CC_sg='Singapore',
  3100. CC_sk='Slovakia',
  3101. CC_sx='Sint Maarten (Dutch Part)',
  3102. CC_si='Slovenia',
  3103. CC_sb='Solomon Islands',
  3104. CC_so='Somalia',
  3105. CC_za='South Africa',
  3106. CC_gs='South Georgia and The South Sandwich Islands',
  3107. CC_ss='South Sudan',
  3108. CC_es='Spain',
  3109. CC_lk='Sri Lanka',
  3110. CC_sd='Sudan',
  3111. CC_sr='Suriname',
  3112. CC_sj='Svalbard and Jan Mayen',
  3113. CC_sz='Swaziland',
  3114. CC_se='Sweden',
  3115. CC_ch='Switzerland',
  3116. CC_sy='Syrian Arab Republic',
  3117. CC_tw='Taiwan, Province of China',
  3118. CC_tj='Tajikistan',
  3119. CC_tz='Tanzania, United Republic of',
  3120. CC_th='Thailand',
  3121. CC_tl='Timor-Leste',
  3122. CC_tg='Togo',
  3123. CC_tk='Tokelau',
  3124. CC_to='Tonga',
  3125. CC_tt='Trinidad and Tobago',
  3126. CC_tn='Tunisia',
  3127. CC_tr='Turkey',
  3128. CC_tm='Turkmenistan',
  3129. CC_tc='Turks and Caicos Islands',
  3130. CC_tv='Tuvalu',
  3131. CC_ug='Uganda',
  3132. CC_ua='Ukraine',
  3133. CC_ae='United Arab Emirates',
  3134. CC_gb='United Kingdom',
  3135. CC_us='United States',
  3136. CC_um='United States Minor Outlying Islands',
  3137. CC_uy='Uruguay',
  3138. CC_uz='Uzbekistan',
  3139. CC_vu='Vanuatu',
  3140. CC_ve='Venezuela',
  3141. CC_vn='Viet Nam',
  3142. CC_vg='Virgin Islands, British',
  3143. CC_vi='Virgin Islands, U.S.',
  3144. CC_wf='Wallis and Futuna',
  3145. CC_eh='Western Sahara',
  3146. CC_ye='Yemen',
  3147. CC_zm='Zambia',
  3148. CC_zw='Zimbabwe';
  3149. //@}
  3150. //@{ ISO 639-1 language codes (Windows-compatibility subset)
  3151. const
  3152. LC_af='Afrikaans',
  3153. LC_am='Amharic',
  3154. LC_ar='Arabic',
  3155. LC_as='Assamese',
  3156. LC_ba='Bashkir',
  3157. LC_be='Belarusian',
  3158. LC_bg='Bulgarian',
  3159. LC_bn='Bengali',
  3160. LC_bo='Tibetan',
  3161. LC_br='Breton',
  3162. LC_ca='Catalan',
  3163. LC_co='Corsican',
  3164. LC_cs='Czech',
  3165. LC_cy='Welsh',
  3166. LC_da='Danish',
  3167. LC_de='German',
  3168. LC_dv='Divehi',
  3169. LC_el='Greek',
  3170. LC_en='English',
  3171. LC_es='Spanish',
  3172. LC_et='Estonian',
  3173. LC_eu='Basque',
  3174. LC_fa='Persian',
  3175. LC_fi='Finnish',
  3176. LC_fo='Faroese',
  3177. LC_fr='French',
  3178. LC_gd='Scottish Gaelic',
  3179. LC_gl='Galician',
  3180. LC_gu='Gujarati',
  3181. LC_he='Hebrew',
  3182. LC_hi='Hindi',
  3183. LC_hr='Croatian',
  3184. LC_hu='Hungarian',
  3185. LC_hy='Armenian',
  3186. LC_id='Indonesian',
  3187. LC_ig='Igbo',
  3188. LC_is='Icelandic',
  3189. LC_it='Italian',
  3190. LC_ja='Japanese',
  3191. LC_ka='Georgian',
  3192. LC_kk='Kazakh',
  3193. LC_km='Khmer',
  3194. LC_kn='Kannada',
  3195. LC_ko='Korean',
  3196. LC_lb='Luxembourgish',
  3197. LC_lo='Lao',
  3198. LC_lt='Lithuanian',
  3199. LC_lv='Latvian',
  3200. LC_mi='Maori',
  3201. LC_ml='Malayalam',
  3202. LC_mr='Marathi',
  3203. LC_ms='Malay',
  3204. LC_mt='Maltese',
  3205. LC_ne='Nepali',
  3206. LC_nl='Dutch',
  3207. LC_no='Norwegian',
  3208. LC_oc='Occitan',
  3209. LC_or='Oriya',
  3210. LC_pl='Polish',
  3211. LC_ps='Pashto',
  3212. LC_pt='Portuguese',
  3213. LC_qu='Quechua',
  3214. LC_ro='Romanian',
  3215. LC_ru='Russian',
  3216. LC_rw='Kinyarwanda',
  3217. LC_sa='Sanskrit',
  3218. LC_si='Sinhala',
  3219. LC_sk='Slovak',
  3220. LC_sl='Slovenian',
  3221. LC_sq='Albanian',
  3222. LC_sv='Swedish',
  3223. LC_ta='Tamil',
  3224. LC_te='Telugu',
  3225. LC_th='Thai',
  3226. LC_tk='Turkmen',
  3227. LC_tr='Turkish',
  3228. LC_tt='Tatar',
  3229. LC_uk='Ukrainian',
  3230. LC_ur='Urdu',
  3231. LC_vi='Vietnamese',
  3232. LC_wo='Wolof',
  3233. LC_yo='Yoruba',
  3234. LC_zh='Chinese';
  3235. //@}
  3236. /**
  3237. * Return list of languages indexed by ISO 639-1 language code
  3238. * @return array
  3239. **/
  3240. function languages() {
  3241. return \Base::instance()->constants($this,'LC_');
  3242. }
  3243. /**
  3244. * Return list of countries indexed by ISO 3166-1 country code
  3245. * @return array
  3246. **/
  3247. function countries() {
  3248. return \Base::instance()->constants($this,'CC_');
  3249. }
  3250. }
  3251. //! Container for singular object instances
  3252. final class Registry {
  3253. private static
  3254. //! Object catalog
  3255. $table;
  3256. /**
  3257. * Return TRUE if object exists in catalog
  3258. * @return bool
  3259. * @param $key string
  3260. **/
  3261. static function exists($key) {
  3262. return isset(self::$table[$key]);
  3263. }
  3264. /**
  3265. * Add object to catalog
  3266. * @return object
  3267. * @param $key string
  3268. * @param $obj object
  3269. **/
  3270. static function set($key,$obj) {
  3271. return self::$table[$key]=$obj;
  3272. }
  3273. /**
  3274. * Retrieve object from catalog
  3275. * @return object
  3276. * @param $key string
  3277. **/
  3278. static function get($key) {
  3279. return self::$table[$key];
  3280. }
  3281. /**
  3282. * Delete object from catalog
  3283. * @return NULL
  3284. * @param $key string
  3285. **/
  3286. static function clear($key) {
  3287. self::$table[$key]=NULL;
  3288. unset(self::$table[$key]);
  3289. }
  3290. //! Prohibit cloning
  3291. private function __clone() {
  3292. }
  3293. //! Prohibit instantiation
  3294. private function __construct() {
  3295. }
  3296. }
  3297. return Base::instance();