| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584 |
- <?php
- /*
- Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
- This file is part of the Fat-Free Framework (http://fatfreeframework.com).
- This is free software: you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation, either version 3 of the License, or later.
- Fat-Free Framework is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
- */
- //! Factory class for single-instance objects
- abstract class Prefab {
- /**
- * Return class instance
- * @return static
- **/
- static function instance() {
- if (!Registry::exists($class=get_called_class())) {
- $ref=new ReflectionClass($class);
- $args=func_get_args();
- Registry::set($class,
- $args?$ref->newinstanceargs($args):new $class);
- }
- return Registry::get($class);
- }
- }
- //! Base structure
- final class Base extends Prefab implements ArrayAccess {
- //@{ Framework details
- const
- PACKAGE='Fat-Free Framework',
- VERSION='3.7.2-Release';
- //@}
- //@{ HTTP status codes (RFC 2616)
- const
- HTTP_100='Continue',
- HTTP_101='Switching Protocols',
- HTTP_103='Early Hints',
- HTTP_200='OK',
- HTTP_201='Created',
- HTTP_202='Accepted',
- HTTP_203='Non-Authorative Information',
- HTTP_204='No Content',
- HTTP_205='Reset Content',
- HTTP_206='Partial Content',
- HTTP_300='Multiple Choices',
- HTTP_301='Moved Permanently',
- HTTP_302='Found',
- HTTP_303='See Other',
- HTTP_304='Not Modified',
- HTTP_305='Use Proxy',
- HTTP_307='Temporary Redirect',
- HTTP_308='Permanent Redirect',
- HTTP_400='Bad Request',
- HTTP_401='Unauthorized',
- HTTP_402='Payment Required',
- HTTP_403='Forbidden',
- HTTP_404='Not Found',
- HTTP_405='Method Not Allowed',
- HTTP_406='Not Acceptable',
- HTTP_407='Proxy Authentication Required',
- HTTP_408='Request Timeout',
- HTTP_409='Conflict',
- HTTP_410='Gone',
- HTTP_411='Length Required',
- HTTP_412='Precondition Failed',
- HTTP_413='Request Entity Too Large',
- HTTP_414='Request-URI Too Long',
- HTTP_415='Unsupported Media Type',
- HTTP_416='Requested Range Not Satisfiable',
- HTTP_417='Expectation Failed',
- HTTP_421='Misdirected Request',
- HTTP_422='Unprocessable Entity',
- HTTP_423='Locked',
- HTTP_429='Too Many Requests',
- HTTP_451='Unavailable For Legal Reasons',
- HTTP_500='Internal Server Error',
- HTTP_501='Not Implemented',
- HTTP_502='Bad Gateway',
- HTTP_503='Service Unavailable',
- HTTP_504='Gateway Timeout',
- HTTP_505='HTTP Version Not Supported',
- HTTP_507='Insufficient Storage',
- HTTP_511='Network Authentication Required';
- //@}
- const
- //! Mapped PHP globals
- GLOBALS='GET|POST|COOKIE|REQUEST|SESSION|FILES|SERVER|ENV',
- //! HTTP verbs
- VERBS='GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS',
- //! Default directory permissions
- MODE=0755,
- //! Syntax highlighting stylesheet
- CSS='code.css';
- //@{ Request types
- const
- REQ_SYNC=1,
- REQ_AJAX=2,
- REQ_CLI=4;
- //@}
- //@{ Error messages
- const
- E_Pattern='Invalid routing pattern: %s',
- E_Named='Named route does not exist: %s',
- E_Alias='Invalid named route alias: %s',
- E_Fatal='Fatal error: %s',
- E_Open='Unable to open %s',
- E_Routes='No routes specified',
- E_Class='Invalid class %s',
- E_Method='Invalid method %s',
- E_Hive='Invalid hive key %s';
- //@}
- private
- //! Globals
- $hive,
- //! Initial settings
- $init,
- //! Language lookup sequence
- $languages,
- //! Mutex locks
- $locks=[],
- //! Default fallback language
- $fallback='en';
- /**
- * Sync PHP global with corresponding hive key
- * @return array
- * @param $key string
- **/
- function sync($key) {
- return $this->hive[$key]=&$GLOBALS['_'.$key];
- }
- /**
- * Return the parts of specified hive key
- * @return array
- * @param $key string
- **/
- private function cut($key) {
- return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
- $key,NULL,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
- }
- /**
- * Replace tokenized URL with available token values
- * @return string
- * @param $url array|string
- * @param $args array
- **/
- function build($url,$args=[]) {
- $args+=$this->hive['PARAMS'];
- if (is_array($url))
- foreach ($url as &$var) {
- $var=$this->build($var,$args);
- unset($var);
- }
- else {
- $i=0;
- $url=preg_replace_callback('/(\{)?@(\w+)(?(1)\})|(\*)/',
- function($match) use(&$i,$args) {
- if (isset($match[2]) &&
- array_key_exists($match[2],$args))
- return $args[$match[2]];
- if (isset($match[3]) &&
- array_key_exists($match[3],$args)) {
- if (!is_array($args[$match[3]]))
- return $args[$match[3]];
- $i++;
- return $args[$match[3]][$i-1];
- }
- return $match[0];
- },$url);
- }
- return $url;
- }
- /**
- * Parse string containing key-value pairs
- * @return array
- * @param $str string
- **/
- function parse($str) {
- preg_match_all('/(\w+|\*)\h*=\h*(?:\[(.+?)\]|(.+?))(?=,|$)/',
- $str,$pairs,PREG_SET_ORDER);
- $out=[];
- foreach ($pairs as $pair)
- if ($pair[2]) {
- $out[$pair[1]]=[];
- foreach (explode(',',$pair[2]) as $val)
- array_push($out[$pair[1]],$val);
- }
- else
- $out[$pair[1]]=trim($pair[3]);
- return $out;
- }
- /**
- * Cast string variable to PHP type or constant
- * @param $val
- * @return mixed
- */
- function cast($val) {
- if (preg_match('/^(?:0x[0-9a-f]+|0[0-7]+|0b[01]+)$/i',$val))
- return intval($val,0);
- if (is_numeric($val))
- return $val+0;
- $val=trim($val);
- if (preg_match('/^\w+$/i',$val) && defined($val))
- return constant($val);
- return $val;
- }
- /**
- * Convert JS-style token to PHP expression
- * @return string
- * @param $str string
- * @param $evaluate bool compile expressions as well or only convert variable access
- **/
- function compile($str, $evaluate=TRUE) {
- return (!$evaluate)
- ? preg_replace_callback(
- '/^@(\w+)((?:\..+|\[(?:(?:[^\[\]]*|(?R))*)\])*)/',
- function($expr) {
- $str='$'.$expr[1];
- if (isset($expr[2]))
- $str.=preg_replace_callback(
- '/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/',
- function($sub) {
- $val=isset($sub[2]) ? $sub[2] : $sub[1];
- if (ctype_digit($val))
- $val=(int)$val;
- $out='['.$this->export($val).']';
- return $out;
- },
- $expr[2]
- );
- return $str;
- },
- $str
- )
- : preg_replace_callback(
- '/(?<!\w)@(\w+(?:(?:\->|::)\w+)?)'.
- '((?:\.\w+|\[(?:(?:[^\[\]]*|(?R))*)\]|(?:\->|::)\w+|\()*)/',
- function($expr) {
- $str='$'.$expr[1];
- if (isset($expr[2]))
- $str.=preg_replace_callback(
- '/\.(\w+)(\()?|\[((?:[^\[\]]*|(?R))*)\]/',
- function($sub) {
- if (empty($sub[2])) {
- if (ctype_digit($sub[1]))
- $sub[1]=(int)$sub[1];
- $out='['.
- (isset($sub[3])?
- $this->compile($sub[3]):
- $this->export($sub[1])).
- ']';
- }
- else
- $out=function_exists($sub[1])?
- $sub[0]:
- ('['.$this->export($sub[1]).']'.$sub[2]);
- return $out;
- },
- $expr[2]
- );
- return $str;
- },
- $str
- );
- }
- /**
- * Get hive key reference/contents; Add non-existent hive keys,
- * array elements, and object properties by default
- * @return mixed
- * @param $key string
- * @param $add bool
- * @param $var mixed
- **/
- function &ref($key,$add=TRUE,&$var=NULL) {
- $null=NULL;
- $parts=$this->cut($key);
- if ($parts[0]=='SESSION') {
- if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- session_start();
- $this->sync('SESSION');
- }
- elseif (!preg_match('/^\w+$/',$parts[0]))
- user_error(sprintf(self::E_Hive,$this->stringify($key)),
- E_USER_ERROR);
- if (is_null($var)) {
- if ($add)
- $var=&$this->hive;
- else
- $var=$this->hive;
- }
- $obj=FALSE;
- foreach ($parts as $part)
- if ($part=='->')
- $obj=TRUE;
- elseif ($obj) {
- $obj=FALSE;
- if (!is_object($var))
- $var=new stdClass;
- if ($add || property_exists($var,$part))
- $var=&$var->$part;
- else {
- $var=&$null;
- break;
- }
- }
- else {
- if (!is_array($var))
- $var=[];
- if ($add || array_key_exists($part,$var))
- $var=&$var[$part];
- else {
- $var=&$null;
- break;
- }
- }
- return $var;
- }
- /**
- * Return TRUE if hive key is set
- * (or return timestamp and TTL if cached)
- * @return bool
- * @param $key string
- * @param $val mixed
- **/
- function exists($key,&$val=NULL) {
- $val=$this->ref($key,FALSE);
- return isset($val)?
- TRUE:
- (Cache::instance()->exists($this->hash($key).'.var',$val)?:FALSE);
- }
- /**
- * Return TRUE if hive key is empty and not cached
- * @param $key string
- * @param $val mixed
- * @return bool
- **/
- function devoid($key,&$val=NULL) {
- $val=$this->ref($key,FALSE);
- return empty($val) &&
- (!Cache::instance()->exists($this->hash($key).'.var',$val) ||
- !$val);
- }
- /**
- * Bind value to hive key
- * @return mixed
- * @param $key string
- * @param $val mixed
- * @param $ttl int
- **/
- function set($key,$val,$ttl=0) {
- $time=(int)$this->hive['TIME'];
- if (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
- $this->set('REQUEST'.$expr[2],$val);
- if ($expr[1]=='COOKIE') {
- $parts=$this->cut($key);
- $jar=$this->unserialize($this->serialize($this->hive['JAR']));
- unset($jar['lifetime']);
- if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
- unset($jar['expire']);
- if (isset($_COOKIE[$parts[1]]))
- setcookie($parts[1],NULL,['expires'=>0]+$jar);
- if ($ttl)
- $jar['expires']=$time+$ttl;
- setcookie($parts[1],$val,$jar);
- } else {
- unset($jar['samesite']);
- if (isset($_COOKIE[$parts[1]]))
- call_user_func_array('setcookie',
- array_merge([$parts[1],NULL],['expire'=>0]+$jar));
- if ($ttl)
- $jar['expire']=$time+$ttl;
- call_user_func_array('setcookie',[$parts[1],$val]+$jar);
- }
- $_COOKIE[$parts[1]]=$val;
- return $val;
- }
- }
- else switch ($key) {
- case 'CACHE':
- $val=Cache::instance()->load($val);
- break;
- case 'ENCODING':
- ini_set('default_charset',$val);
- if (extension_loaded('mbstring'))
- mb_internal_encoding($val);
- break;
- case 'FALLBACK':
- $this->fallback=$val;
- $lang=$this->language($this->hive['LANGUAGE']);
- case 'LANGUAGE':
- if (!isset($lang))
- $val=$this->language($val);
- $lex=$this->lexicon($this->hive['LOCALES'],$ttl);
- case 'LOCALES':
- if (isset($lex) || $lex=$this->lexicon($val,$ttl))
- foreach ($lex as $dt=>$dd) {
- $ref=&$this->ref($this->hive['PREFIX'].$dt);
- $ref=$dd;
- unset($ref);
- }
- break;
- case 'TZ':
- date_default_timezone_set($val);
- break;
- }
- $ref=&$this->ref($key);
- $ref=$val;
- if (preg_match('/^JAR\b/',$key)) {
- if ($key=='JAR.lifetime')
- $this->set('JAR.expire',$val==0?0:
- (is_int($val)?$time+$val:strtotime($val)));
- else {
- if ($key=='JAR.expire')
- $this->hive['JAR']['lifetime']=max(0,$val-$time);
- $jar=$this->unserialize($this->serialize($this->hive['JAR']));
- unset($jar['expire']);
- if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- if (version_compare(PHP_VERSION, '7.3.0') >= 0)
- session_set_cookie_params($jar);
- else {
- unset($jar['samesite']);
- call_user_func_array('session_set_cookie_params',$jar);
- }
- }
- }
- if ($ttl)
- // Persist the key-value pair
- Cache::instance()->set($this->hash($key).'.var',$val,$ttl);
- return $ref;
- }
- /**
- * Retrieve contents of hive key
- * @return mixed
- * @param $key string
- * @param $args string|array
- **/
- function get($key,$args=NULL) {
- if (is_string($val=$this->ref($key,FALSE)) && !is_null($args))
- return call_user_func_array(
- [$this,'format'],
- array_merge([$val],is_array($args)?$args:[$args])
- );
- if (is_null($val)) {
- // Attempt to retrieve from cache
- if (Cache::instance()->exists($this->hash($key).'.var',$data))
- return $data;
- }
- return $val;
- }
- /**
- * Unset hive key
- * @param $key string
- **/
- function clear($key) {
- // Normalize array literal
- $cache=Cache::instance();
- $parts=$this->cut($key);
- if ($key=='CACHE')
- // Clear cache contents
- $cache->reset();
- elseif (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
- $this->clear('REQUEST'.$expr[2]);
- if ($expr[1]=='COOKIE') {
- $parts=$this->cut($key);
- $jar=$this->hive['JAR'];
- unset($jar['lifetime']);
- $jar['expire']=0;
- if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
- $jar['expires']=$jar['expire'];
- unset($jar['expire']);
- setcookie($parts[1],NULL,$jar);
- } else {
- unset($jar['samesite']);
- call_user_func_array('setcookie',
- array_merge([$parts[1],NULL],$jar));
- }
- unset($_COOKIE[$parts[1]]);
- }
- }
- elseif ($parts[0]=='SESSION') {
- if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- session_start();
- if (empty($parts[1])) {
- // End session
- session_unset();
- session_destroy();
- $this->clear('COOKIE.'.session_name());
- }
- $this->sync('SESSION');
- }
- if (!isset($parts[1]) && array_key_exists($parts[0],$this->init))
- // Reset global to default value
- $this->hive[$parts[0]]=$this->init[$parts[0]];
- else {
- $val=preg_replace('/^(\$hive)/','$this->hive',
- $this->compile('@hive.'.$key, FALSE));
- eval('unset('.$val.');');
- if ($parts[0]=='SESSION') {
- session_commit();
- session_start();
- }
- if ($cache->exists($hash=$this->hash($key).'.var'))
- // Remove from cache
- $cache->clear($hash);
- }
- }
- /**
- * Return TRUE if hive variable is 'on'
- * @return bool
- * @param $key string
- **/
- function checked($key) {
- $ref=&$this->ref($key);
- return $ref=='on';
- }
- /**
- * Return TRUE if property has public visibility
- * @return bool
- * @param $obj object
- * @param $key string
- **/
- function visible($obj,$key) {
- if (property_exists($obj,$key)) {
- $ref=new ReflectionProperty(get_class($obj),$key);
- $out=$ref->ispublic();
- unset($ref);
- return $out;
- }
- return FALSE;
- }
- /**
- * Multi-variable assignment using associative array
- * @param $vars array
- * @param $prefix string
- * @param $ttl int
- **/
- function mset(array $vars,$prefix='',$ttl=0) {
- foreach ($vars as $key=>$val)
- $this->set($prefix.$key,$val,$ttl);
- }
- /**
- * Publish hive contents
- * @return array
- **/
- function hive() {
- return $this->hive;
- }
- /**
- * Copy contents of hive variable to another
- * @return mixed
- * @param $src string
- * @param $dst string
- **/
- function copy($src,$dst) {
- $ref=&$this->ref($dst);
- return $ref=$this->ref($src,FALSE);
- }
- /**
- * Concatenate string to hive string variable
- * @return string
- * @param $key string
- * @param $val string
- **/
- function concat($key,$val) {
- $ref=&$this->ref($key);
- $ref.=$val;
- return $ref;
- }
- /**
- * Swap keys and values of hive array variable
- * @return array
- * @param $key string
- * @public
- **/
- function flip($key) {
- $ref=&$this->ref($key);
- return $ref=array_combine(array_values($ref),array_keys($ref));
- }
- /**
- * Add element to the end of hive array variable
- * @return mixed
- * @param $key string
- * @param $val mixed
- **/
- function push($key,$val) {
- $ref=&$this->ref($key);
- $ref[]=$val;
- return $val;
- }
- /**
- * Remove last element of hive array variable
- * @return mixed
- * @param $key string
- **/
- function pop($key) {
- $ref=&$this->ref($key);
- return array_pop($ref);
- }
- /**
- * Add element to the beginning of hive array variable
- * @return mixed
- * @param $key string
- * @param $val mixed
- **/
- function unshift($key,$val) {
- $ref=&$this->ref($key);
- array_unshift($ref,$val);
- return $val;
- }
- /**
- * Remove first element of hive array variable
- * @return mixed
- * @param $key string
- **/
- function shift($key) {
- $ref=&$this->ref($key);
- return array_shift($ref);
- }
- /**
- * Merge array with hive array variable
- * @return array
- * @param $key string
- * @param $src string|array
- * @param $keep bool
- **/
- function merge($key,$src,$keep=FALSE) {
- $ref=&$this->ref($key);
- if (!$ref)
- $ref=[];
- $out=array_merge($ref,is_string($src)?$this->hive[$src]:$src);
- if ($keep)
- $ref=$out;
- return $out;
- }
- /**
- * Extend hive array variable with default values from $src
- * @return array
- * @param $key string
- * @param $src string|array
- * @param $keep bool
- **/
- function extend($key,$src,$keep=FALSE) {
- $ref=&$this->ref($key);
- if (!$ref)
- $ref=[];
- $out=array_replace_recursive(
- is_string($src)?$this->hive[$src]:$src,$ref);
- if ($keep)
- $ref=$out;
- return $out;
- }
- /**
- * Convert backslashes to slashes
- * @return string
- * @param $str string
- **/
- function fixslashes($str) {
- return $str?strtr($str,'\\','/'):$str;
- }
- /**
- * Split comma-, semi-colon, or pipe-separated string
- * @return array
- * @param $str string
- * @param $noempty bool
- **/
- function split($str,$noempty=TRUE) {
- return array_map('trim',
- preg_split('/[,;|]/',$str,0,$noempty?PREG_SPLIT_NO_EMPTY:0));
- }
- /**
- * Convert PHP expression/value to compressed exportable string
- * @return string
- * @param $arg mixed
- * @param $stack array
- **/
- function stringify($arg,array $stack=NULL) {
- if ($stack) {
- foreach ($stack as $node)
- if ($arg===$node)
- return '*RECURSION*';
- }
- else
- $stack=[];
- switch (gettype($arg)) {
- case 'object':
- $str='';
- foreach (get_object_vars($arg) as $key=>$val)
- $str.=($str?',':'').
- $this->export($key).'=>'.
- $this->stringify($val,
- array_merge($stack,[$arg]));
- return get_class($arg).'::__set_state(['.$str.'])';
- case 'array':
- $str='';
- $num=isset($arg[0]) &&
- ctype_digit(implode('',array_keys($arg)));
- foreach ($arg as $key=>$val)
- $str.=($str?',':'').
- ($num?'':($this->export($key).'=>')).
- $this->stringify($val,array_merge($stack,[$arg]));
- return '['.$str.']';
- default:
- return $this->export($arg);
- }
- }
- /**
- * Flatten array values and return as CSV string
- * @return string
- * @param $args array
- **/
- function csv(array $args) {
- return implode(',',array_map('stripcslashes',
- array_map([$this,'stringify'],$args)));
- }
- /**
- * Convert snakecase string to camelcase
- * @return string
- * @param $str string
- **/
- function camelcase($str) {
- return preg_replace_callback(
- '/_(\pL)/u',
- function($match) {
- return strtoupper($match[1]);
- },
- $str
- );
- }
- /**
- * Convert camelcase string to snakecase
- * @return string
- * @param $str string
- **/
- function snakecase($str) {
- return strtolower(preg_replace('/(?!^)\p{Lu}/u','_\0',$str));
- }
- /**
- * Return -1 if specified number is negative, 0 if zero,
- * or 1 if the number is positive
- * @return int
- * @param $num mixed
- **/
- function sign($num) {
- return $num?($num/abs($num)):0;
- }
- /**
- * Extract values of array whose keys start with the given prefix
- * @return array
- * @param $arr array
- * @param $prefix string
- **/
- function extract($arr,$prefix) {
- $out=[];
- foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr))
- as $key)
- $out[substr($key,strlen($prefix))]=$arr[$key];
- return $out;
- }
- /**
- * Convert class constants to array
- * @return array
- * @param $class object|string
- * @param $prefix string
- **/
- function constants($class,$prefix='') {
- $ref=new ReflectionClass($class);
- return $this->extract($ref->getconstants(),$prefix);
- }
- /**
- * Generate 64bit/base36 hash
- * @return string
- * @param $str
- **/
- function hash($str) {
- return str_pad(base_convert(
- substr(sha1($str),-16),16,36),11,'0',STR_PAD_LEFT);
- }
- /**
- * Return Base64-encoded equivalent
- * @return string
- * @param $data string
- * @param $mime string
- **/
- function base64($data,$mime) {
- return 'data:'.$mime.';base64,'.base64_encode($data);
- }
- /**
- * Convert special characters to HTML entities
- * @return string
- * @param $str string
- **/
- function encode($str) {
- return @htmlspecialchars($str,$this->hive['BITMASK'],
- $this->hive['ENCODING'])?:$this->scrub($str);
- }
- /**
- * Convert HTML entities back to characters
- * @return string
- * @param $str string
- **/
- function decode($str) {
- return htmlspecialchars_decode($str,$this->hive['BITMASK']);
- }
- /**
- * Invoke callback recursively for all data types
- * @return mixed
- * @param $arg mixed
- * @param $func callback
- * @param $stack array
- **/
- function recursive($arg,$func,$stack=[]) {
- if ($stack) {
- foreach ($stack as $node)
- if ($arg===$node)
- return $arg;
- }
- switch (gettype($arg)) {
- case 'object':
- $ref=new ReflectionClass($arg);
- if ($ref->iscloneable()) {
- $arg=clone($arg);
- $cast=is_a($arg,'IteratorAggregate')?
- iterator_to_array($arg):get_object_vars($arg);
- foreach ($cast as $key=>$val)
- $arg->$key=$this->recursive(
- $val,$func,array_merge($stack,[$arg]));
- }
- return $arg;
- case 'array':
- $copy=[];
- foreach ($arg as $key=>$val)
- $copy[$key]=$this->recursive($val,$func,
- array_merge($stack,[$arg]));
- return $copy;
- }
- return $func($arg);
- }
- /**
- * Remove HTML tags (except those enumerated) and non-printable
- * characters to mitigate XSS/code injection attacks
- * @return mixed
- * @param $arg mixed
- * @param $tags string
- **/
- function clean($arg,$tags=NULL) {
- return $this->recursive($arg,
- function($val) use($tags) {
- if ($tags!='*')
- $val=trim(strip_tags($val,
- '<'.implode('><',$this->split($tags)).'>'));
- return trim(preg_replace(
- '/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',$val));
- }
- );
- }
- /**
- * Similar to clean(), except that variable is passed by reference
- * @return mixed
- * @param $var mixed
- * @param $tags string
- **/
- function scrub(&$var,$tags=NULL) {
- return $var=$this->clean($var,$tags);
- }
- /**
- * Return locale-aware formatted string
- * @return string
- **/
- function format() {
- $args=func_get_args();
- $val=array_shift($args);
- // Get formatting rules
- $conv=localeconv();
- return preg_replace_callback(
- '/\{\s*(?P<pos>\d+)\s*(?:,\s*(?P<type>\w+)\s*'.
- '(?:,\s*(?P<mod>(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'.
- '(?:,\s*(?P<prop>.+?))?)?)?\s*\}/',
- function($expr) use($args,$conv) {
- /**
- * @var string $pos
- * @var string $mod
- * @var string $type
- * @var string $prop
- */
- extract($expr);
- /**
- * @var string $thousands_sep
- * @var string $negative_sign
- * @var string $positive_sign
- * @var string $frac_digits
- * @var string $decimal_point
- * @var string $int_curr_symbol
- * @var string $currency_symbol
- */
- extract($conv);
- if (!array_key_exists($pos,$args))
- return $expr[0];
- if (isset($type)) {
- if (isset($this->hive['FORMATS'][$type]))
- return $this->call(
- $this->hive['FORMATS'][$type],
- [
- $args[$pos],
- isset($mod)?$mod:null,
- isset($prop)?$prop:null
- ]
- );
- switch ($type) {
- case 'plural':
- preg_match_all('/(?<tag>\w+)'.
- '(?:\s*\{\s*(?<data>.+?)\s*\})/',
- $mod,$matches,PREG_SET_ORDER);
- $ord=['zero','one','two'];
- foreach ($matches as $match) {
- /** @var string $tag */
- /** @var string $data */
- extract($match);
- if (isset($ord[$args[$pos]]) &&
- $tag==$ord[$args[$pos]] || $tag=='other')
- return str_replace('#',$args[$pos],$data);
- }
- case 'number':
- if (isset($mod))
- switch ($mod) {
- case 'integer':
- return number_format(
- $args[$pos],0,'',$thousands_sep);
- case 'currency':
- $int=$cstm=FALSE;
- if (isset($prop) &&
- $cstm=!$int=($prop=='int'))
- $currency_symbol=$prop;
- if (!$cstm &&
- function_exists('money_format') &&
- version_compare(PHP_VERSION,'7.4.0')<0)
- return money_format(
- '%'.($int?'i':'n'),$args[$pos]);
- $fmt=[
- 0=>'(nc)',1=>'(n c)',
- 2=>'(nc)',10=>'+nc',
- 11=>'+n c',12=>'+ nc',
- 20=>'nc+',21=>'n c+',
- 22=>'nc +',30=>'n+c',
- 31=>'n +c',32=>'n+ c',
- 40=>'nc+',41=>'n c+',
- 42=>'nc +',100=>'(cn)',
- 101=>'(c n)',102=>'(cn)',
- 110=>'+cn',111=>'+c n',
- 112=>'+ cn',120=>'cn+',
- 121=>'c n+',122=>'cn +',
- 130=>'+cn',131=>'+c n',
- 132=>'+ cn',140=>'c+n',
- 141=>'c+ n',142=>'c +n'
- ];
- if ($args[$pos]<0) {
- $sgn=$negative_sign;
- $pre='n';
- }
- else {
- $sgn=$positive_sign;
- $pre='p';
- }
- return str_replace(
- ['+','n','c'],
- [$sgn,number_format(
- abs($args[$pos]),
- $frac_digits,
- $decimal_point,
- $thousands_sep),
- $int?$int_curr_symbol
- :$currency_symbol],
- $fmt[(int)(
- (${$pre.'_cs_precedes'}%2).
- (${$pre.'_sign_posn'}%5).
- (${$pre.'_sep_by_space'}%3)
- )]
- );
- case 'percent':
- return number_format(
- $args[$pos]*100,0,$decimal_point,
- $thousands_sep).'%';
- }
- $frac=$args[$pos]-(int)$args[$pos];
- return number_format(
- $args[$pos],
- isset($prop)?
- $prop:
- ($frac?strlen($frac)-2:0),
- $decimal_point,$thousands_sep);
- case 'date':
- if (empty($mod) || $mod=='short')
- $prop='%x';
- elseif ($mod=='full')
- $prop='%A, %d %B %Y';
- elseif ($mod!='custom')
- $prop='%d %B %Y';
- return strftime($prop,$args[$pos]);
- case 'time':
- if (empty($mod) || $mod=='short')
- $prop='%X';
- elseif ($mod!='custom')
- $prop='%r';
- return strftime($prop,$args[$pos]);
- default:
- return $expr[0];
- }
- }
- return $args[$pos];
- },
- $val
- );
- }
- /**
- * Return string representation of expression
- * @return string
- * @param $expr mixed
- **/
- function export($expr) {
- return var_export($expr,TRUE);
- }
- /**
- * Assign/auto-detect language
- * @return string
- * @param $code string
- **/
- function language($code) {
- $code=preg_replace('/\h+|;q=[0-9.]+/','',$code);
- $code.=($code?',':'').$this->fallback;
- $this->languages=[];
- foreach (array_reverse(explode(',',$code)) as $lang)
- if (preg_match('/^(\w{2})(?:-(\w{2}))?\b/i',$lang,$parts)) {
- // Generic language
- array_unshift($this->languages,$parts[1]);
- if (isset($parts[2])) {
- // Specific language
- $parts[0]=$parts[1].'-'.($parts[2]=strtoupper($parts[2]));
- array_unshift($this->languages,$parts[0]);
- }
- }
- $this->languages=array_unique($this->languages);
- $locales=[];
- $windows=preg_match('/^win/i',PHP_OS);
- // Work around PHP's Turkish locale bug
- foreach (preg_grep('/^(?!tr)/i',$this->languages) as $locale) {
- if ($windows) {
- $parts=explode('-',$locale);
- $locale=@constant('ISO::LC_'.$parts[0]);
- if (isset($parts[1]) &&
- $country=@constant('ISO::CC_'.strtolower($parts[1])))
- $locale.='-'.$country;
- }
- $locale=str_replace('-','_',$locale);
- $locales[]=$locale.'.'.ini_get('default_charset');
- $locales[]=$locale;
- }
- setlocale(LC_ALL,$locales);
- return $this->hive['LANGUAGE']=implode(',',$this->languages);
- }
- /**
- * Return lexicon entries
- * @return array
- * @param $path string
- * @param $ttl int
- **/
- function lexicon($path,$ttl=0) {
- $languages=$this->languages?:explode(',',$this->fallback);
- $cache=Cache::instance();
- if ($ttl && $cache->exists(
- $hash=$this->hash(implode(',',$languages).$path).'.dic',$lex))
- return $lex;
- $lex=[];
- foreach ($languages as $lang)
- foreach ($this->split($path) as $dir)
- if ((is_file($file=($base=$dir.$lang).'.php') ||
- is_file($file=$base.'.php')) &&
- is_array($dict=require($file)))
- $lex+=$dict;
- elseif (is_file($file=$base.'.json') &&
- is_array($dict=json_decode(file_get_contents($file), true)))
- $lex+=$dict;
- elseif (is_file($file=$base.'.ini')) {
- preg_match_all(
- '/(?<=^|\n)(?:'.
- '\[(?<prefix>.+?)\]|'.
- '(?<lval>[^\h\r\n;].*?)\h*=\h*'.
- '(?<rval>(?:\\\\\h*\r?\n|.+?)*)'.
- ')(?=\r?\n|$)/',
- $this->read($file),$matches,PREG_SET_ORDER);
- if ($matches) {
- $prefix='';
- foreach ($matches as $match)
- if ($match['prefix'])
- $prefix=$match['prefix'].'.';
- elseif (!array_key_exists(
- $key=$prefix.$match['lval'],$lex))
- $lex[$key]=trim(preg_replace(
- '/\\\\\h*\r?\n/',"\n",$match['rval']));
- }
- }
- if ($ttl)
- $cache->set($hash,$lex,$ttl);
- return $lex;
- }
- /**
- * Return string representation of PHP value
- * @return string
- * @param $arg mixed
- **/
- function serialize($arg) {
- switch (strtolower($this->hive['SERIALIZER'])) {
- case 'igbinary':
- return igbinary_serialize($arg);
- default:
- return serialize($arg);
- }
- }
- /**
- * Return PHP value derived from string
- * @return string
- * @param $arg mixed
- **/
- function unserialize($arg) {
- switch (strtolower($this->hive['SERIALIZER'])) {
- case 'igbinary':
- return igbinary_unserialize($arg);
- default:
- return unserialize($arg);
- }
- }
- /**
- * Send HTTP status header; Return text equivalent of status code
- * @return string
- * @param $code int
- **/
- function status($code) {
- $reason=@constant('self::HTTP_'.$code);
- if (!$this->hive['CLI'] && !headers_sent())
- header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason);
- return $reason;
- }
- /**
- * Send cache metadata to HTTP client
- * @param $secs int
- **/
- function expire($secs=0) {
- if (!$this->hive['CLI'] && !headers_sent()) {
- $secs=(int)$secs;
- if ($this->hive['PACKAGE'])
- header('X-Powered-By: '.$this->hive['PACKAGE']);
- if ($this->hive['XFRAME'])
- header('X-Frame-Options: '.$this->hive['XFRAME']);
- header('X-XSS-Protection: 1; mode=block');
- header('X-Content-Type-Options: nosniff');
- if ($this->hive['VERB']=='GET' && $secs) {
- $time=microtime(TRUE);
- header_remove('Pragma');
- header('Cache-Control: max-age='.$secs);
- header('Expires: '.gmdate('r',$time+$secs));
- header('Last-Modified: '.gmdate('r'));
- }
- else {
- header('Pragma: no-cache');
- header('Cache-Control: no-cache, no-store, must-revalidate');
- header('Expires: '.gmdate('r',0));
- }
- }
- }
- /**
- * Return HTTP user agent
- * @return string
- **/
- function agent() {
- $headers=$this->hive['HEADERS'];
- return isset($headers['X-Operamini-Phone-UA'])?
- $headers['X-Operamini-Phone-UA']:
- (isset($headers['X-Skyfire-Phone'])?
- $headers['X-Skyfire-Phone']:
- (isset($headers['User-Agent'])?
- $headers['User-Agent']:''));
- }
- /**
- * Return TRUE if XMLHttpRequest detected
- * @return bool
- **/
- function ajax() {
- $headers=$this->hive['HEADERS'];
- return isset($headers['X-Requested-With']) &&
- $headers['X-Requested-With']=='XMLHttpRequest';
- }
- /**
- * Sniff IP address
- * @return string
- **/
- function ip() {
- $headers=$this->hive['HEADERS'];
- return isset($headers['Client-IP'])?
- $headers['Client-IP']:
- (isset($headers['X-Forwarded-For'])?
- explode(',',$headers['X-Forwarded-For'])[0]:
- (isset($_SERVER['REMOTE_ADDR'])?
- $_SERVER['REMOTE_ADDR']:''));
- }
- /**
- * Return filtered stack trace as a formatted string (or array)
- * @return string|array
- * @param $trace array|NULL
- * @param $format bool
- **/
- function trace(array $trace=NULL,$format=TRUE) {
- if (!$trace) {
- $trace=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
- $frame=$trace[0];
- if (isset($frame['file']) && $frame['file']==__FILE__)
- array_shift($trace);
- }
- $debug=$this->hive['DEBUG'];
- $trace=array_filter(
- $trace,
- function($frame) use($debug) {
- return isset($frame['file']) &&
- ($debug>1 ||
- ($frame['file']!=__FILE__ || $debug) &&
- (empty($frame['function']) ||
- !preg_match('/^(?:(?:trigger|user)_error|'.
- '__call|call_user_func)/',$frame['function'])));
- }
- );
- if (!$format)
- return $trace;
- $out='';
- $eol="\n";
- // Analyze stack trace
- foreach ($trace as $frame) {
- $line='';
- if (isset($frame['class']))
- $line.=$frame['class'].$frame['type'];
- if (isset($frame['function']))
- $line.=$frame['function'].'('.
- ($debug>2 && isset($frame['args'])?
- $this->csv($frame['args']):'').')';
- $src=$this->fixslashes(str_replace($_SERVER['DOCUMENT_ROOT'].
- '/','',$frame['file'])).':'.$frame['line'];
- $out.='['.$src.'] '.$line.$eol;
- }
- return $out;
- }
- /**
- * Log error; Execute ONERROR handler if defined, else display
- * default error page (HTML for synchronous requests, JSON string
- * for AJAX requests)
- * @param $code int
- * @param $text string
- * @param $trace array
- * @param $level int
- **/
- function error($code,$text='',array $trace=NULL,$level=0) {
- $prior=$this->hive['ERROR'];
- $header=$this->status($code);
- $req=$this->hive['VERB'].' '.$this->hive['PATH'];
- if ($this->hive['QUERY'])
- $req.='?'.$this->hive['QUERY'];
- if (!$text)
- $text='HTTP '.$code.' ('.$req.')';
- $trace=$this->trace($trace);
- $loggable=$this->hive['LOGGABLE'];
- if (!is_array($loggable))
- $loggable=$this->split($loggable);
- foreach ($loggable as $status)
- if ($status=='*' ||
- preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
- error_log($text);
- foreach (explode("\n",$trace) as $nexus)
- if ($nexus)
- error_log($nexus);
- break;
- }
- if ($highlight=!$this->hive['CLI'] && !$this->hive['AJAX'] &&
- $this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS))
- $trace=$this->highlight($trace);
- $this->hive['ERROR']=[
- 'status'=>$header,
- 'code'=>$code,
- 'text'=>$text,
- 'trace'=>$trace,
- 'level'=>$level
- ];
- $this->expire(-1);
- $handler=$this->hive['ONERROR'];
- $this->hive['ONERROR']=NULL;
- $eol="\n";
- if ((!$handler ||
- $this->call($handler,[$this,$this->hive['PARAMS']],
- 'beforeroute,afterroute')===FALSE) &&
- !$prior && !$this->hive['CLI'] && !$this->hive['QUIET'])
- echo $this->hive['AJAX']?
- json_encode(
- array_diff_key(
- $this->hive['ERROR'],
- $this->hive['DEBUG']?
- []:
- ['trace'=>1]
- )
- ):
- ('<!DOCTYPE html>'.$eol.
- '<html>'.$eol.
- '<head>'.
- '<title>'.$code.' '.$header.'</title>'.
- ($highlight?
- ('<style>'.$this->read($css).'</style>'):'').
- '</head>'.$eol.
- '<body>'.$eol.
- '<h1>'.$header.'</h1>'.$eol.
- '<p>'.$this->encode($text?:$req).'</p>'.$eol.
- ($this->hive['DEBUG']?('<pre>'.$trace.'</pre>'.$eol):'').
- '</body>'.$eol.
- '</html>');
- if ($this->hive['HALT'])
- die(1);
- }
- /**
- * Mock HTTP request
- * @return mixed
- * @param $pattern string
- * @param $args array
- * @param $headers array
- * @param $body string
- **/
- function mock($pattern,
- array $args=NULL,array $headers=NULL,$body=NULL) {
- if (!$args)
- $args=[];
- $types=['sync','ajax','cli'];
- preg_match('/([\|\w]+)\h+(?:@(\w+)(?:(\(.+?)\))*|([^\h]+))'.
- '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts);
- $verb=strtoupper($parts[1]);
- if ($parts[2]) {
- if (empty($this->hive['ALIASES'][$parts[2]]))
- user_error(sprintf(self::E_Named,$parts[2]),E_USER_ERROR);
- $parts[4]=$this->hive['ALIASES'][$parts[2]];
- $parts[4]=$this->build($parts[4],
- isset($parts[3])?$this->parse($parts[3]):[]);
- }
- if (empty($parts[4]))
- user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
- $url=parse_url($parts[4]);
- parse_str(@$url['query'],$GLOBALS['_GET']);
- if (preg_match('/GET|HEAD/',$verb))
- $GLOBALS['_GET']=array_merge($GLOBALS['_GET'],$args);
- $GLOBALS['_POST']=$verb=='POST'?$args:[];
- $GLOBALS['_REQUEST']=array_merge($GLOBALS['_GET'],$GLOBALS['_POST']);
- foreach ($headers?:[] as $key=>$val)
- $_SERVER['HTTP_'.strtr(strtoupper($key),'-','_')]=$val;
- $this->hive['VERB']=$verb;
- $this->hive['PATH']=$url['path'];
- $this->hive['URI']=$this->hive['BASE'].$url['path'];
- if ($GLOBALS['_GET'])
- $this->hive['URI'].='?'.http_build_query($GLOBALS['_GET']);
- $this->hive['BODY']='';
- if (!preg_match('/GET|HEAD/',$verb))
- $this->hive['BODY']=$body?:http_build_query($args);
- $this->hive['AJAX']=isset($parts[5]) &&
- preg_match('/ajax/i',$parts[5]);
- $this->hive['CLI']=isset($parts[5]) &&
- preg_match('/cli/i',$parts[5]);
- return $this->run();
- }
- /**
- * Assemble url from alias name
- * @return string
- * @param $name string
- * @param $params array|string
- * @param $query string|array
- * @param $fragment string
- **/
- function alias($name,$params=[],$query=NULL,$fragment=NULL) {
- if (!is_array($params))
- $params=$this->parse($params);
- if (empty($this->hive['ALIASES'][$name]))
- user_error(sprintf(self::E_Named,$name),E_USER_ERROR);
- $url=$this->build($this->hive['ALIASES'][$name],$params);
- if (is_array($query))
- $query=http_build_query($query);
- return $url.($query?('?'.$query):'').($fragment?'#'.$fragment:'');
- }
- /**
- * Bind handler to route pattern
- * @return NULL
- * @param $pattern string|array
- * @param $handler callback
- * @param $ttl int
- * @param $kbps int
- **/
- function route($pattern,$handler,$ttl=0,$kbps=0) {
- $types=['sync','ajax','cli'];
- $alias=null;
- if (is_array($pattern)) {
- foreach ($pattern as $item)
- $this->route($item,$handler,$ttl,$kbps);
- return;
- }
- preg_match('/([\|\w]+)\h+(?:(?:@?(.+?)\h*:\h*)?(@(\w+)|[^\h]+))'.
- '(?:\h+\[('.implode('|',$types).')\])?/u',$pattern,$parts);
- if (isset($parts[2]) && $parts[2]) {
- if (!preg_match('/^\w+$/',$parts[2]))
- user_error(sprintf(self::E_Alias,$parts[2]),E_USER_ERROR);
- $this->hive['ALIASES'][$alias=$parts[2]]=$parts[3];
- }
- elseif (!empty($parts[4])) {
- if (empty($this->hive['ALIASES'][$parts[4]]))
- user_error(sprintf(self::E_Named,$parts[4]),E_USER_ERROR);
- $parts[3]=$this->hive['ALIASES'][$alias=$parts[4]];
- }
- if (empty($parts[3]))
- user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR);
- $type=empty($parts[5])?0:constant('self::REQ_'.strtoupper($parts[5]));
- foreach ($this->split($parts[1]) as $verb) {
- if (!preg_match('/'.self::VERBS.'/',$verb))
- $this->error(501,$verb.' '.$this->hive['URI']);
- $this->hive['ROUTES'][$parts[3]][$type][strtoupper($verb)]=
- [$handler,$ttl,$kbps,$alias];
- }
- }
- /**
- * Reroute to specified URI
- * @return NULL
- * @param $url array|string
- * @param $permanent bool
- * @param $die bool
- **/
- function reroute($url=NULL,$permanent=FALSE,$die=TRUE) {
- if (!$url)
- $url=$this->hive['REALM'];
- if (is_array($url))
- $url=call_user_func_array([$this,'alias'],$url);
- elseif (preg_match('/^(?:@([^\/()?#]+)(?:\((.+?)\))*(\?[^#]+)*(#.+)*)/',
- $url,$parts) && isset($this->hive['ALIASES'][$parts[1]]))
- $url=$this->build($this->hive['ALIASES'][$parts[1]],
- isset($parts[2])?$this->parse($parts[2]):[]).
- (isset($parts[3])?$parts[3]:'').(isset($parts[4])?$parts[4]:'');
- else
- $url=$this->build($url);
- if (($handler=$this->hive['ONREROUTE']) &&
- $this->call($handler,[$url,$permanent,$die])!==FALSE)
- return;
- if ($url[0]!='/' && !preg_match('/^\w+:\/\//i',$url))
- $url='/'.$url;
- if ($url[0]=='/' && (empty($url[1]) || $url[1]!='/')) {
- $port=$this->hive['PORT'];
- $port=in_array($port,[80,443])?'':(':'.$port);
- $url=$this->hive['SCHEME'].'://'.
- $this->hive['HOST'].$port.$this->hive['BASE'].$url;
- }
- if ($this->hive['CLI'])
- $this->mock('GET '.$url.' [cli]');
- else {
- header('Location: '.$url);
- $this->status($permanent?301:302);
- if ($die)
- die;
- }
- }
- /**
- * Provide ReST interface by mapping HTTP verb to class method
- * @return NULL
- * @param $url string
- * @param $class string|object
- * @param $ttl int
- * @param $kbps int
- **/
- function map($url,$class,$ttl=0,$kbps=0) {
- if (is_array($url)) {
- foreach ($url as $item)
- $this->map($item,$class,$ttl,$kbps);
- return;
- }
- foreach (explode('|',self::VERBS) as $method)
- $this->route($method.' '.$url,is_string($class)?
- $class.'->'.$this->hive['PREMAP'].strtolower($method):
- [$class,$this->hive['PREMAP'].strtolower($method)],
- $ttl,$kbps);
- }
- /**
- * Redirect a route to another URL
- * @return NULL
- * @param $pattern string|array
- * @param $url string
- * @param $permanent bool
- */
- function redirect($pattern,$url,$permanent=TRUE) {
- if (is_array($pattern)) {
- foreach ($pattern as $item)
- $this->redirect($item,$url,$permanent);
- return;
- }
- $this->route($pattern,function($fw) use($url,$permanent) {
- $fw->reroute($url,$permanent);
- });
- }
- /**
- * Return TRUE if IPv4 address exists in DNSBL
- * @return bool
- * @param $ip string
- **/
- function blacklisted($ip) {
- if ($this->hive['DNSBL'] &&
- !in_array($ip,
- is_array($this->hive['EXEMPT'])?
- $this->hive['EXEMPT']:
- $this->split($this->hive['EXEMPT']))) {
- // Reverse IPv4 dotted quad
- $rev=implode('.',array_reverse(explode('.',$ip)));
- foreach (is_array($this->hive['DNSBL'])?
- $this->hive['DNSBL']:
- $this->split($this->hive['DNSBL']) as $server)
- // DNSBL lookup
- if (checkdnsrr($rev.'.'.$server,'A'))
- return TRUE;
- }
- return FALSE;
- }
- /**
- * Applies the specified URL mask and returns parameterized matches
- * @return $args array
- * @param $pattern string
- * @param $url string|NULL
- **/
- function mask($pattern,$url=NULL) {
- if (!$url)
- $url=$this->rel($this->hive['URI']);
- $case=$this->hive['CASELESS']?'i':'';
- $wild=preg_quote($pattern,'/');
- $i=0;
- while (is_int($pos=strpos($wild,'\*'))) {
- $wild=substr_replace($wild,'(?P<_'.$i.'>[^\?]*)',$pos,2);
- $i++;
- }
- preg_match('/^'.
- preg_replace(
- '/((\\\{)?@(\w+\b)(?(2)\\\}))/',
- '(?P<\3>[^\/\?]+)',
- $wild).'\/?$/'.$case.'um',$url,$args);
- foreach (array_keys($args) as $key) {
- if (preg_match('/^_\d+$/',$key)) {
- if (empty($args['*']))
- $args['*']=$args[$key];
- else {
- if (is_string($args['*']))
- $args['*']=[$args['*']];
- array_push($args['*'],$args[$key]);
- }
- unset($args[$key]);
- }
- elseif (is_numeric($key) && $key)
- unset($args[$key]);
- }
- return $args;
- }
- /**
- * Match routes against incoming URI
- * @return mixed
- **/
- function run() {
- if ($this->blacklisted($this->hive['IP']))
- // Spammer detected
- $this->error(403);
- if (!$this->hive['ROUTES'])
- // No routes defined
- user_error(self::E_Routes,E_USER_ERROR);
- // Match specific routes first
- $paths=[];
- foreach ($keys=array_keys($this->hive['ROUTES']) as $key) {
- $path=preg_replace('/@\w+/','*@',$key);
- if (substr($path,-1)!='*')
- $path.='+';
- $paths[]=$path;
- }
- $vals=array_values($this->hive['ROUTES']);
- array_multisort($paths,SORT_DESC,$keys,$vals);
- $this->hive['ROUTES']=array_combine($keys,$vals);
- // Convert to BASE-relative URL
- $req=urldecode($this->hive['PATH']);
- $preflight=FALSE;
- if ($cors=(isset($this->hive['HEADERS']['Origin']) &&
- $this->hive['CORS']['origin'])) {
- $cors=$this->hive['CORS'];
- header('Access-Control-Allow-Origin: '.$cors['origin']);
- header('Access-Control-Allow-Credentials: '.
- $this->export($cors['credentials']));
- $preflight=
- isset($this->hive['HEADERS']['Access-Control-Request-Method']);
- }
- $allowed=[];
- foreach ($this->hive['ROUTES'] as $pattern=>$routes) {
- if (!$args=$this->mask($pattern,$req))
- continue;
- ksort($args);
- $route=NULL;
- $ptr=$this->hive['CLI']?self::REQ_CLI:$this->hive['AJAX']+1;
- if (isset($routes[$ptr][$this->hive['VERB']]) ||
- isset($routes[$ptr=0]))
- $route=$routes[$ptr];
- if (!$route)
- continue;
- if (isset($route[$this->hive['VERB']]) && !$preflight) {
- if ($this->hive['VERB']=='GET' &&
- preg_match('/.+\/$/',$this->hive['PATH']))
- $this->reroute(substr($this->hive['PATH'],0,-1).
- ($this->hive['QUERY']?('?'.$this->hive['QUERY']):''));
- list($handler,$ttl,$kbps,$alias)=$route[$this->hive['VERB']];
- // Capture values of route pattern tokens
- $this->hive['PARAMS']=$args;
- // Save matching route
- $this->hive['ALIAS']=$alias;
- $this->hive['PATTERN']=$pattern;
- if ($cors && $cors['expose'])
- header('Access-Control-Expose-Headers: '.
- (is_array($cors['expose'])?
- implode(',',$cors['expose']):$cors['expose']));
- if (is_string($handler)) {
- // Replace route pattern tokens in handler if any
- $handler=preg_replace_callback('/({)?@(\w+\b)(?(1)})/',
- function($id) use($args) {
- $pid=count($id)>2?2:1;
- return isset($args[$id[$pid]])?
- $args[$id[$pid]]:
- $id[0];
- },
- $handler
- );
- if (preg_match('/(.+)\h*(?:->|::)/',$handler,$match) &&
- !class_exists($match[1]))
- $this->error(404);
- }
- // Process request
- $result=NULL;
- $body='';
- $now=microtime(TRUE);
- if (preg_match('/GET|HEAD/',$this->hive['VERB']) && $ttl) {
- // Only GET and HEAD requests are cacheable
- $headers=$this->hive['HEADERS'];
- $cache=Cache::instance();
- $cached=$cache->exists(
- $hash=$this->hash($this->hive['VERB'].' '.
- $this->hive['URI']).'.url',$data);
- if ($cached) {
- if (isset($headers['If-Modified-Since']) &&
- strtotime($headers['If-Modified-Since'])+
- $ttl>$now) {
- $this->status(304);
- die;
- }
- // Retrieve from cache backend
- list($headers,$body,$result)=$data;
- if (!$this->hive['CLI'])
- array_walk($headers,'header');
- $this->expire($cached[0]+$ttl-$now);
- }
- else
- // Expire HTTP client-cached page
- $this->expire($ttl);
- }
- else
- $this->expire(0);
- if (!strlen($body)) {
- if (!$this->hive['RAW'] && !$this->hive['BODY'])
- $this->hive['BODY']=file_get_contents('php://input');
- ob_start();
- // Call route handler
- $result=$this->call($handler,[$this,$args,$handler],
- 'beforeroute,afterroute');
- $body=ob_get_clean();
- if (isset($cache) && !error_get_last()) {
- // Save to cache backend
- $cache->set($hash,[
- // Remove cookies
- preg_grep('/Set-Cookie\:/',headers_list(),
- PREG_GREP_INVERT),$body,$result],$ttl);
- }
- }
- $this->hive['RESPONSE']=$body;
- if (!$this->hive['QUIET']) {
- if ($kbps) {
- $ctr=0;
- foreach (str_split($body,1024) as $part) {
- // Throttle output
- $ctr++;
- if ($ctr/$kbps>($elapsed=microtime(TRUE)-$now) &&
- !connection_aborted())
- usleep(1e6*($ctr/$kbps-$elapsed));
- echo $part;
- }
- }
- else
- echo $body;
- }
- if ($result || $this->hive['VERB']!='OPTIONS')
- return $result;
- }
- $allowed=array_merge($allowed,array_keys($route));
- }
- if (!$allowed)
- // URL doesn't match any route
- $this->error(404);
- elseif (!$this->hive['CLI']) {
- if (!preg_grep('/Allow:/',$headers_send=headers_list()))
- // Unhandled HTTP method
- header('Allow: '.implode(',',array_unique($allowed)));
- if ($cors) {
- if (!preg_grep('/Access-Control-Allow-Methods:/',$headers_send))
- header('Access-Control-Allow-Methods: OPTIONS,'.
- implode(',',$allowed));
- if ($cors['headers'] &&
- !preg_grep('/Access-Control-Allow-Headers:/',$headers_send))
- header('Access-Control-Allow-Headers: '.
- (is_array($cors['headers'])?
- implode(',',$cors['headers']):
- $cors['headers']));
- if ($cors['ttl']>0)
- header('Access-Control-Max-Age: '.$cors['ttl']);
- }
- if ($this->hive['VERB']!='OPTIONS')
- $this->error(405);
- }
- return FALSE;
- }
- /**
- * Loop until callback returns TRUE (for long polling)
- * @return mixed
- * @param $func callback
- * @param $args array
- * @param $timeout int
- **/
- function until($func,$args=NULL,$timeout=60) {
- if (!$args)
- $args=[];
- $time=time();
- $max=ini_get('max_execution_time');
- $limit=max(0,($max?min($timeout,$max):$timeout)-1);
- $out='';
- // Turn output buffering on
- ob_start();
- // Not for the weak of heart
- while (
- // No error occurred
- !$this->hive['ERROR'] &&
- // Got time left?
- time()-$time+1<$limit &&
- // Still alive?
- !connection_aborted() &&
- // Restart session
- !headers_sent() &&
- (session_status()==PHP_SESSION_ACTIVE || session_start()) &&
- // CAUTION: Callback will kill host if it never becomes truthy!
- !$out=$this->call($func,$args)) {
- if (!$this->hive['CLI'])
- session_commit();
- // Hush down
- sleep(1);
- }
- ob_flush();
- flush();
- return $out;
- }
- /**
- * Disconnect HTTP client;
- * Set FcgidOutputBufferSize to zero if server uses mod_fcgid;
- * Disable mod_deflate when rendering text/html output
- **/
- function abort() {
- if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- session_start();
- $out='';
- while (ob_get_level())
- $out=ob_get_clean().$out;
- if (!headers_sent()) {
- header('Content-Length: '.strlen($out));
- header('Connection: close');
- }
- session_commit();
- echo $out;
- flush();
- if (function_exists('fastcgi_finish_request'))
- fastcgi_finish_request();
- }
- /**
- * Grab the real route handler behind the string expression
- * @return string|array
- * @param $func string
- * @param $args array
- **/
- function grab($func,$args=NULL) {
- if (preg_match('/(.+)\h*(->|::)\h*(.+)/s',$func,$parts)) {
- // Convert string to executable PHP callback
- if (!class_exists($parts[1]))
- user_error(sprintf(self::E_Class,$parts[1]),E_USER_ERROR);
- if ($parts[2]=='->') {
- if (is_subclass_of($parts[1],'Prefab'))
- $parts[1]=call_user_func($parts[1].'::instance');
- elseif (isset($this->hive['CONTAINER'])) {
- $container=$this->hive['CONTAINER'];
- if (is_object($container) && is_callable([$container,'has'])
- && $container->has($parts[1])) // PSR11
- $parts[1]=call_user_func([$container,'get'],$parts[1]);
- elseif (is_callable($container))
- $parts[1]=call_user_func($container,$parts[1],$args);
- elseif (is_string($container) &&
- is_subclass_of($container,'Prefab'))
- $parts[1]=call_user_func($container.'::instance')->
- get($parts[1]);
- else
- user_error(sprintf(self::E_Class,
- $this->stringify($parts[1])),
- E_USER_ERROR);
- }
- else {
- $ref=new ReflectionClass($parts[1]);
- $parts[1]=method_exists($parts[1],'__construct') && $args?
- $ref->newinstanceargs($args):
- $ref->newinstance();
- }
- }
- $func=[$parts[1],$parts[3]];
- }
- return $func;
- }
- /**
- * Execute callback/hooks (supports 'class->method' format)
- * @return mixed|FALSE
- * @param $func callback
- * @param $args mixed
- * @param $hooks string
- **/
- function call($func,$args=NULL,$hooks='') {
- if (!is_array($args))
- $args=[$args];
- // Grab the real handler behind the string representation
- if (is_string($func))
- $func=$this->grab($func,$args);
- // Execute function; abort if callback/hook returns FALSE
- if (!is_callable($func))
- // No route handler
- if ($hooks=='beforeroute,afterroute') {
- $allowed=[];
- if (is_array($func))
- $allowed=array_intersect(
- array_map('strtoupper',get_class_methods($func[0])),
- explode('|',self::VERBS)
- );
- header('Allow: '.implode(',',$allowed));
- $this->error(405);
- }
- else
- user_error(sprintf(self::E_Method,
- is_string($func)?$func:$this->stringify($func)),
- E_USER_ERROR);
- $obj=FALSE;
- if (is_array($func)) {
- $hooks=$this->split($hooks);
- $obj=TRUE;
- }
- // Execute pre-route hook if any
- if ($obj && $hooks && in_array($hook='beforeroute',$hooks) &&
- method_exists($func[0],$hook) &&
- call_user_func_array([$func[0],$hook],$args)===FALSE)
- return FALSE;
- // Execute callback
- $out=call_user_func_array($func,$args?:[]);
- if ($out===FALSE)
- return FALSE;
- // Execute post-route hook if any
- if ($obj && $hooks && in_array($hook='afterroute',$hooks) &&
- method_exists($func[0],$hook) &&
- call_user_func_array([$func[0],$hook],$args)===FALSE)
- return FALSE;
- return $out;
- }
- /**
- * Execute specified callbacks in succession; Apply same arguments
- * to all callbacks
- * @return array
- * @param $funcs array|string
- * @param $args mixed
- **/
- function chain($funcs,$args=NULL) {
- $out=[];
- foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
- $out[]=$this->call($func,$args);
- return $out;
- }
- /**
- * Execute specified callbacks in succession; Relay result of
- * previous callback as argument to the next callback
- * @return array
- * @param $funcs array|string
- * @param $args mixed
- **/
- function relay($funcs,$args=NULL) {
- foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func)
- $args=[$this->call($func,$args)];
- return array_shift($args);
- }
- /**
- * Configure framework according to .ini-style file settings;
- * If optional 2nd arg is provided, template strings are interpreted
- * @return object
- * @param $source string|array
- * @param $allow bool
- **/
- function config($source,$allow=FALSE) {
- if (is_string($source))
- $source=$this->split($source);
- if ($allow)
- $preview=Preview::instance();
- foreach ($source as $file) {
- preg_match_all(
- '/(?<=^|\n)(?:'.
- '\[(?<section>.+?)\]|'.
- '(?<lval>[^\h\r\n;].*?)\h*=\h*'.
- '(?<rval>(?:\\\\\h*\r?\n|.+?)*)'.
- ')(?=\r?\n|$)/',
- $this->read($file),
- $matches,PREG_SET_ORDER);
- if ($matches) {
- $sec='globals';
- $cmd=[];
- foreach ($matches as $match) {
- if ($match['section']) {
- $sec=$match['section'];
- if (preg_match(
- '/^(?!(?:global|config|route|map|redirect)s\b)'.
- '(.*?)(?:\s*[:>])/i',$sec,$msec) &&
- !$this->exists($msec[1]))
- $this->set($msec[1],NULL);
- preg_match('/^(config|route|map|redirect)s\b|'.
- '^(.+?)\s*\>\s*(.*)/i',$sec,$cmd);
- continue;
- }
- if ($allow)
- foreach (['lval','rval'] as $ndx)
- $match[$ndx]=$preview->
- resolve($match[$ndx],NULL,0,FALSE,FALSE);
- if (!empty($cmd)) {
- isset($cmd[3])?
- $this->call($cmd[3],
- [$match['lval'],$match['rval'],$cmd[2]]):
- call_user_func_array(
- [$this,$cmd[1]],
- array_merge([$match['lval']],
- str_getcsv($cmd[1]=='config'?
- $this->cast($match['rval']):
- $match['rval']))
- );
- }
- else {
- $rval=preg_replace(
- '/\\\\\h*(\r?\n)/','\1',$match['rval']);
- $ttl=NULL;
- if (preg_match('/^(.+)\|\h*(\d+)$/',$rval,$tmp)) {
- array_shift($tmp);
- list($rval,$ttl)=$tmp;
- }
- $args=array_map(
- function($val) {
- $val=$this->cast($val);
- if (is_string($val))
- $val=strlen($val)?
- preg_replace('/\\\\"/','"',$val):
- NULL;
- return $val;
- },
- // Mark quoted strings with 0x00 whitespace
- str_getcsv(preg_replace(
- '/(?<!\\\\)(")(.*?)\1/',
- "\\1\x00\\2\\1",trim($rval)))
- );
- preg_match('/^(?<section>[^:]+)(?:\:(?<func>.+))?/',
- $sec,$parts);
- $func=isset($parts['func'])?$parts['func']:NULL;
- $custom=(strtolower($parts['section'])!='globals');
- if ($func)
- $args=[$this->call($func,$args)];
- if (count($args)>1)
- $args=[$args];
- if (isset($ttl))
- $args=array_merge($args,[$ttl]);
- call_user_func_array(
- [$this,'set'],
- array_merge(
- [
- ($custom?($parts['section'].'.'):'').
- $match['lval']
- ],
- $args
- )
- );
- }
- }
- }
- }
- return $this;
- }
- /**
- * Create mutex, invoke callback then drop ownership when done
- * @return mixed
- * @param $id string
- * @param $func callback
- * @param $args mixed
- **/
- function mutex($id,$func,$args=NULL) {
- if (!is_dir($tmp=$this->hive['TEMP']))
- mkdir($tmp,self::MODE,TRUE);
- // Use filesystem lock
- if (is_file($lock=$tmp.
- $this->hive['SEED'].'.'.$this->hash($id).'.lock') &&
- filemtime($lock)+ini_get('max_execution_time')<microtime(TRUE))
- // Stale lock
- @unlink($lock);
- while (!($handle=@fopen($lock,'x')) && !connection_aborted())
- usleep(mt_rand(0,100));
- $this->locks[$id]=$lock;
- $out=$this->call($func,$args);
- fclose($handle);
- @unlink($lock);
- unset($this->locks[$id]);
- return $out;
- }
- /**
- * Read file (with option to apply Unix LF as standard line ending)
- * @return string
- * @param $file string
- * @param $lf bool
- **/
- function read($file,$lf=FALSE) {
- $out=@file_get_contents($file);
- return $lf?preg_replace('/\r\n|\r/',"\n",$out):$out;
- }
- /**
- * Exclusive file write
- * @return int|FALSE
- * @param $file string
- * @param $data mixed
- * @param $append bool
- **/
- function write($file,$data,$append=FALSE) {
- return file_put_contents($file,$data,$this->hive['LOCK']|($append?FILE_APPEND:0));
- }
- /**
- * Apply syntax highlighting
- * @return string
- * @param $text string
- **/
- function highlight($text) {
- $out='';
- $pre=FALSE;
- $text=trim($text);
- if ($text && !preg_match('/^<\?php/',$text)) {
- $text='<?php '.$text;
- $pre=TRUE;
- }
- foreach (token_get_all($text) as $token)
- if ($pre)
- $pre=FALSE;
- else
- $out.='<span'.
- (is_array($token)?
- (' class="'.
- substr(strtolower(token_name($token[0])),2).'">'.
- $this->encode($token[1]).''):
- ('>'.$this->encode($token))).
- '</span>';
- return $out?('<code>'.$out.'</code>'):$text;
- }
- /**
- * Dump expression with syntax highlighting
- * @param $expr mixed
- **/
- function dump($expr) {
- echo $this->highlight($this->stringify($expr));
- }
- /**
- * Return path (and query parameters) relative to the base directory
- * @return string
- * @param $url string
- **/
- function rel($url) {
- return preg_replace('/^(?:https?:\/\/)?'.
- preg_quote($this->hive['BASE'],'/').'(\/.*|$)/','\1',$url);
- }
- /**
- * Namespace-aware class autoloader
- * @return mixed
- * @param $class string
- **/
- protected function autoload($class) {
- $class=$this->fixslashes(ltrim($class,'\\'));
- /** @var callable $func */
- $func=NULL;
- if (is_array($path=$this->hive['AUTOLOAD']) &&
- isset($path[1]) && is_callable($path[1]))
- list($path,$func)=$path;
- foreach ($this->split($this->hive['PLUGINS'].';'.$path) as $auto)
- if ($func && is_file($file=$func($auto.$class).'.php') ||
- is_file($file=$auto.$class.'.php') ||
- is_file($file=$auto.strtolower($class).'.php') ||
- is_file($file=strtolower($auto.$class).'.php'))
- return require($file);
- }
- /**
- * Execute framework/application shutdown sequence
- * @param $cwd string
- **/
- function unload($cwd) {
- chdir($cwd);
- if (!($error=error_get_last()) &&
- session_status()==PHP_SESSION_ACTIVE)
- session_commit();
- foreach ($this->locks as $lock)
- @unlink($lock);
- $handler=$this->hive['UNLOAD'];
- if ((!$handler || $this->call($handler,$this)===FALSE) &&
- $error && in_array($error['type'],
- [E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR]))
- // Fatal error detected
- $this->error(500,
- sprintf(self::E_Fatal,$error['message']),[$error]);
- }
- /**
- * Convenience method for checking hive key
- * @return mixed
- * @param $key string
- **/
- function offsetexists($key) {
- return $this->exists($key);
- }
- /**
- * Convenience method for assigning hive value
- * @return mixed
- * @param $key string
- * @param $val mixed
- **/
- function offsetset($key,$val) {
- return $this->set($key,$val);
- }
- /**
- * Convenience method for retrieving hive value
- * @return mixed
- * @param $key string
- **/
- function &offsetget($key) {
- $val=&$this->ref($key);
- return $val;
- }
- /**
- * Convenience method for removing hive key
- * @param $key string
- **/
- function offsetunset($key) {
- $this->clear($key);
- }
- /**
- * Alias for offsetexists()
- * @return mixed
- * @param $key string
- **/
- function __isset($key) {
- return $this->offsetexists($key);
- }
- /**
- * Alias for offsetset()
- * @return mixed
- * @param $key string
- * @param $val mixed
- **/
- function __set($key,$val) {
- return $this->offsetset($key,$val);
- }
- /**
- * Alias for offsetget()
- * @return mixed
- * @param $key string
- **/
- function &__get($key) {
- $val=&$this->offsetget($key);
- return $val;
- }
- /**
- * Alias for offsetunset()
- * @param $key string
- **/
- function __unset($key) {
- $this->offsetunset($key);
- }
- /**
- * Call function identified by hive key
- * @return mixed
- * @param $key string
- * @param $args array
- **/
- function __call($key,array $args) {
- if ($this->exists($key,$val))
- return call_user_func_array($val,$args);
- user_error(sprintf(self::E_Method,$key),E_USER_ERROR);
- }
- //! Prohibit cloning
- private function __clone() {
- }
- //! Bootstrap
- function __construct() {
- // Managed directives
- ini_set('default_charset',$charset='UTF-8');
- if (extension_loaded('mbstring'))
- mb_internal_encoding($charset);
- ini_set('display_errors',0);
- // Deprecated directives
- @ini_set('magic_quotes_gpc',0);
- @ini_set('register_globals',0);
- // Intercept errors/exceptions; PHP5.3-compatible
- $check=error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE));
- set_exception_handler(
- function($obj) {
- /** @var Exception $obj */
- $this->hive['EXCEPTION']=$obj;
- $this->error(500,
- $obj->getmessage().' '.
- '['.$obj->getFile().':'.$obj->getLine().']',
- $obj->gettrace());
- }
- );
- set_error_handler(
- function($level,$text,$file,$line) {
- if ($level & error_reporting())
- $this->error(500,$text,NULL,$level);
- }
- );
- if (!isset($_SERVER['SERVER_NAME']) || $_SERVER['SERVER_NAME']==='')
- $_SERVER['SERVER_NAME']=gethostname();
- $headers=[];
- if ($cli=PHP_SAPI=='cli') {
- // Emulate HTTP request
- $_SERVER['REQUEST_METHOD']='GET';
- if (!isset($_SERVER['argv'][1])) {
- $_SERVER['argc']++;
- $_SERVER['argv'][1]='/';
- }
- $req=$query='';
- if (substr($_SERVER['argv'][1],0,1)=='/') {
- $req=$_SERVER['argv'][1];
- $query=parse_url($req,PHP_URL_QUERY);
- } else {
- foreach($_SERVER['argv'] as $i=>$arg) {
- if (!$i) continue;
- if (preg_match('/^\-(\-)?(\w+)(?:\=(.*))?$/',$arg,$m)) {
- foreach($m[1]?[$m[2]]:str_split($m[2]) as $k)
- $query.=($query?'&':'').urlencode($k).'=';
- if (isset($m[3]))
- $query.=urlencode($m[3]);
- } else
- $req.='/'.$arg;
- }
- if (!$req)
- $req='/';
- if ($query)
- $req.='?'.$query;
- }
- $_SERVER['REQUEST_URI']=$req;
- parse_str($query,$GLOBALS['_GET']);
- }
- elseif (function_exists('getallheaders')) {
- foreach (getallheaders() as $key=>$val) {
- $tmp=strtoupper(strtr($key,'-','_'));
- // TODO: use ucwords delimiters for php 5.4.32+ & 5.5.16+
- $key=strtr(ucwords(strtolower(strtr($key,'-',' '))),' ','-');
- $headers[$key]=$val;
- if (isset($_SERVER['HTTP_'.$tmp]))
- $headers[$key]=&$_SERVER['HTTP_'.$tmp];
- }
- }
- else {
- if (isset($_SERVER['CONTENT_LENGTH']))
- $headers['Content-Length']=&$_SERVER['CONTENT_LENGTH'];
- if (isset($_SERVER['CONTENT_TYPE']))
- $headers['Content-Type']=&$_SERVER['CONTENT_TYPE'];
- foreach (array_keys($_SERVER) as $key)
- if (substr($key,0,5)=='HTTP_')
- $headers[strtr(ucwords(strtolower(strtr(
- substr($key,5),'_',' '))),' ','-')]=&$_SERVER[$key];
- }
- if (isset($headers['X-HTTP-Method-Override']))
- $_SERVER['REQUEST_METHOD']=$headers['X-HTTP-Method-Override'];
- elseif ($_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['_method']))
- $_SERVER['REQUEST_METHOD']=strtoupper($_POST['_method']);
- $scheme=isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on' ||
- isset($headers['X-Forwarded-Proto']) &&
- $headers['X-Forwarded-Proto']=='https'?'https':'http';
- // Create hive early on to expose header methods
- $this->hive=['HEADERS'=>&$headers];
- if (function_exists('apache_setenv')) {
- // Work around Apache pre-2.4 VirtualDocumentRoot bug
- $_SERVER['DOCUMENT_ROOT']=str_replace($_SERVER['SCRIPT_NAME'],'',
- $_SERVER['SCRIPT_FILENAME']);
- apache_setenv("DOCUMENT_ROOT",$_SERVER['DOCUMENT_ROOT']);
- }
- $_SERVER['DOCUMENT_ROOT']=realpath($_SERVER['DOCUMENT_ROOT']);
- $base='';
- if (!$cli)
- $base=rtrim($this->fixslashes(
- dirname($_SERVER['SCRIPT_NAME'])),'/');
- $uri=parse_url((preg_match('/^\w+:\/\//',$_SERVER['REQUEST_URI'])?'':
- $scheme.'://'.$_SERVER['SERVER_NAME']).$_SERVER['REQUEST_URI']);
- $_SERVER['REQUEST_URI']=$uri['path'].
- (isset($uri['query'])?'?'.$uri['query']:'').
- (isset($uri['fragment'])?'#'.$uri['fragment']:'');
- $path=preg_replace('/^'.preg_quote($base,'/').'/','',$uri['path']);
- $jar=[
- 'expire'=>0,
- 'lifetime'=>0,
- 'path'=>$base?:'/',
- 'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) &&
- !filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
- $_SERVER['SERVER_NAME']:'',
- 'secure'=>($scheme=='https'),
- 'httponly'=>TRUE,
- 'samesite'=>'Lax',
- ];
- $port=80;
- if (isset($headers['X-Forwarded-Port']))
- $port=$headers['X-Forwarded-Port'];
- elseif (isset($_SERVER['SERVER_PORT']))
- $port=$_SERVER['SERVER_PORT'];
- // Default configuration
- $this->hive+=[
- 'AGENT'=>$this->agent(),
- 'AJAX'=>$this->ajax(),
- 'ALIAS'=>NULL,
- 'ALIASES'=>[],
- 'AUTOLOAD'=>'./',
- 'BASE'=>$base,
- 'BITMASK'=>ENT_COMPAT,
- 'BODY'=>NULL,
- 'CACHE'=>FALSE,
- 'CASELESS'=>TRUE,
- 'CLI'=>$cli,
- 'CORS'=>[
- 'headers'=>'',
- 'origin'=>FALSE,
- 'credentials'=>FALSE,
- 'expose'=>FALSE,
- 'ttl'=>0
- ],
- 'DEBUG'=>0,
- 'DIACRITICS'=>[],
- 'DNSBL'=>'',
- 'EMOJI'=>[],
- 'ENCODING'=>$charset,
- 'ERROR'=>NULL,
- 'ESCAPE'=>TRUE,
- 'EXCEPTION'=>NULL,
- 'EXEMPT'=>NULL,
- 'FALLBACK'=>$this->fallback,
- 'FORMATS'=>[],
- 'FRAGMENT'=>isset($uri['fragment'])?$uri['fragment']:'',
- 'HALT'=>TRUE,
- 'HIGHLIGHT'=>FALSE,
- 'HOST'=>$_SERVER['SERVER_NAME'],
- 'IP'=>$this->ip(),
- 'JAR'=>$jar,
- 'LANGUAGE'=>isset($headers['Accept-Language'])?
- $this->language($headers['Accept-Language']):
- $this->fallback,
- 'LOCALES'=>'./',
- 'LOCK'=>LOCK_EX,
- 'LOGGABLE'=>'*',
- 'LOGS'=>'./',
- 'MB'=>extension_loaded('mbstring'),
- 'ONERROR'=>NULL,
- 'ONREROUTE'=>NULL,
- 'PACKAGE'=>self::PACKAGE,
- 'PARAMS'=>[],
- 'PATH'=>$path,
- 'PATTERN'=>NULL,
- 'PLUGINS'=>$this->fixslashes(__DIR__).'/',
- 'PORT'=>$port,
- 'PREFIX'=>NULL,
- 'PREMAP'=>'',
- 'QUERY'=>isset($uri['query'])?$uri['query']:'',
- 'QUIET'=>FALSE,
- 'RAW'=>FALSE,
- 'REALM'=>$scheme.'://'.$_SERVER['SERVER_NAME'].
- ($port && !in_array($port,[80,443])?(':'.$port):'').
- $_SERVER['REQUEST_URI'],
- 'RESPONSE'=>'',
- 'ROOT'=>$_SERVER['DOCUMENT_ROOT'],
- 'ROUTES'=>[],
- 'SCHEME'=>$scheme,
- 'SEED'=>$this->hash($_SERVER['SERVER_NAME'].$base),
- 'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
- 'TEMP'=>'tmp/',
- 'TIME'=>&$_SERVER['REQUEST_TIME_FLOAT'],
- 'TZ'=>@date_default_timezone_get(),
- 'UI'=>'./',
- 'UNLOAD'=>NULL,
- 'UPLOADS'=>'./',
- 'URI'=>&$_SERVER['REQUEST_URI'],
- 'VERB'=>&$_SERVER['REQUEST_METHOD'],
- 'VERSION'=>self::VERSION,
- 'XFRAME'=>'SAMEORIGIN'
- ];
- if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) {
- unset($jar['expire']);
- session_cache_limiter('');
- if (version_compare(PHP_VERSION, '7.3.0') >= 0)
- session_set_cookie_params($jar);
- else {
- unset($jar['samesite']);
- call_user_func_array('session_set_cookie_params',$jar);
- }
- }
- if (PHP_SAPI=='cli-server' &&
- preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
- $this->reroute('/');
- if (ini_get('auto_globals_jit'))
- // Override setting
- $GLOBALS+=['_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST];
- // Sync PHP globals with corresponding hive keys
- $this->init=$this->hive;
- foreach (explode('|',self::GLOBALS) as $global) {
- $sync=$this->sync($global);
- $this->init+=[
- $global=>preg_match('/SERVER|ENV/',$global)?$sync:[]
- ];
- }
- if ($check && $error=error_get_last())
- // Error detected
- $this->error(500,
- sprintf(self::E_Fatal,$error['message']),[$error]);
- date_default_timezone_set($this->hive['TZ']);
- // Register framework autoloader
- spl_autoload_register([$this,'autoload']);
- // Register shutdown handler
- register_shutdown_function([$this,'unload'],getcwd());
- }
- }
- //! Cache engine
- class Cache extends Prefab {
- protected
- //! Cache DSN
- $dsn,
- //! Prefix for cache entries
- $prefix,
- //! MemCache or Redis object
- $ref;
- /**
- * Return timestamp and TTL of cache entry or FALSE if not found
- * @return array|FALSE
- * @param $key string
- * @param $val mixed
- **/
- function exists($key,&$val=NULL) {
- $fw=Base::instance();
- if (!$this->dsn)
- return FALSE;
- $ndx=$this->prefix.'.'.$key;
- $parts=explode('=',$this->dsn,2);
- switch ($parts[0]) {
- case 'apc':
- case 'apcu':
- $raw=call_user_func($parts[0].'_fetch',$ndx);
- break;
- case 'redis':
- $raw=$this->ref->get($ndx);
- break;
- case 'memcache':
- $raw=memcache_get($this->ref,$ndx);
- break;
- case 'memcached':
- $raw=$this->ref->get($ndx);
- break;
- case 'wincache':
- $raw=wincache_ucache_get($ndx);
- break;
- case 'xcache':
- $raw=xcache_get($ndx);
- break;
- case 'folder':
- $raw=$fw->read($parts[1].$ndx);
- break;
- }
- if (!empty($raw)) {
- list($val,$time,$ttl)=(array)$fw->unserialize($raw);
- if ($ttl===0 || $time+$ttl>microtime(TRUE))
- return [$time,$ttl];
- $val=null;
- $this->clear($key);
- }
- return FALSE;
- }
- /**
- * Store value in cache
- * @return mixed|FALSE
- * @param $key string
- * @param $val mixed
- * @param $ttl int
- **/
- function set($key,$val,$ttl=0) {
- $fw=Base::instance();
- if (!$this->dsn)
- return TRUE;
- $ndx=$this->prefix.'.'.$key;
- if ($cached=$this->exists($key))
- $ttl=$cached[1];
- $data=$fw->serialize([$val,microtime(TRUE),$ttl]);
- $parts=explode('=',$this->dsn,2);
- switch ($parts[0]) {
- case 'apc':
- case 'apcu':
- return call_user_func($parts[0].'_store',$ndx,$data,$ttl);
- case 'redis':
- return $this->ref->set($ndx,$data,$ttl?['ex'=>$ttl]:[]);
- case 'memcache':
- return memcache_set($this->ref,$ndx,$data,0,$ttl);
- case 'memcached':
- return $this->ref->set($ndx,$data,$ttl);
- case 'wincache':
- return wincache_ucache_set($ndx,$data,$ttl);
- case 'xcache':
- return xcache_set($ndx,$data,$ttl);
- case 'folder':
- return $fw->write($parts[1].
- str_replace(['/','\\'],'',$ndx),$data);
- }
- return FALSE;
- }
- /**
- * Retrieve value of cache entry
- * @return mixed|FALSE
- * @param $key string
- **/
- function get($key) {
- return $this->dsn && $this->exists($key,$data)?$data:FALSE;
- }
- /**
- * Delete cache entry
- * @return bool
- * @param $key string
- **/
- function clear($key) {
- if (!$this->dsn)
- return;
- $ndx=$this->prefix.'.'.$key;
- $parts=explode('=',$this->dsn,2);
- switch ($parts[0]) {
- case 'apc':
- case 'apcu':
- return call_user_func($parts[0].'_delete',$ndx);
- case 'redis':
- return $this->ref->del($ndx);
- case 'memcache':
- return memcache_delete($this->ref,$ndx);
- case 'memcached':
- return $this->ref->delete($ndx);
- case 'wincache':
- return wincache_ucache_delete($ndx);
- case 'xcache':
- return xcache_unset($ndx);
- case 'folder':
- return @unlink($parts[1].$ndx);
- }
- return FALSE;
- }
- /**
- * Clear contents of cache backend
- * @return bool
- * @param $suffix string
- **/
- function reset($suffix=NULL) {
- if (!$this->dsn)
- return TRUE;
- $regex='/'.preg_quote($this->prefix.'.','/').'.*'.
- preg_quote($suffix,'/').'/';
- $parts=explode('=',$this->dsn,2);
- switch ($parts[0]) {
- case 'apc':
- case 'apcu':
- $info=call_user_func($parts[0].'_cache_info',
- $parts[0]=='apcu'?FALSE:'user');
- if (!empty($info['cache_list'])) {
- $key=array_key_exists('info',
- $info['cache_list'][0])?'info':'key';
- foreach ($info['cache_list'] as $item)
- if (preg_match($regex,$item[$key]))
- call_user_func($parts[0].'_delete',$item[$key]);
- }
- return TRUE;
- case 'redis':
- $keys=$this->ref->keys($this->prefix.'.*'.$suffix);
- foreach($keys as $key)
- $this->ref->del($key);
- return TRUE;
- case 'memcache':
- foreach (memcache_get_extended_stats(
- $this->ref,'slabs') as $slabs)
- foreach (array_filter(array_keys($slabs),'is_numeric')
- as $id)
- foreach (memcache_get_extended_stats(
- $this->ref,'cachedump',$id) as $data)
- if (is_array($data))
- foreach (array_keys($data) as $key)
- if (preg_match($regex,$key))
- memcache_delete($this->ref,$key);
- return TRUE;
- case 'memcached':
- foreach ($this->ref->getallkeys()?:[] as $key)
- if (preg_match($regex,$key))
- $this->ref->delete($key);
- return TRUE;
- case 'wincache':
- $info=wincache_ucache_info();
- foreach ($info['ucache_entries'] as $item)
- if (preg_match($regex,$item['key_name']))
- wincache_ucache_delete($item['key_name']);
- return TRUE;
- case 'xcache':
- if ($suffix && !ini_get('xcache.admin.enable_auth')) {
- $cnt=xcache_count(XC_TYPE_VAR);
- for ($i=0;$i<$cnt;$i++) {
- $list=xcache_list(XC_TYPE_VAR,$i);
- foreach ($list['cache_list'] as $item)
- if (preg_match($regex,$item['name']))
- xcache_unset($item['name']);
- }
- } else
- xcache_unset_by_prefix($this->prefix.'.');
- return TRUE;
- case 'folder':
- if ($glob=@glob($parts[1].'*'))
- foreach ($glob as $file)
- if (preg_match($regex,basename($file)))
- @unlink($file);
- return TRUE;
- }
- return FALSE;
- }
- /**
- * Load/auto-detect cache backend
- * @return string
- * @param $dsn bool|string
- * @param $seed bool|string
- **/
- function load($dsn,$seed=NULL) {
- $fw=Base::instance();
- if ($dsn=trim($dsn)) {
- if (preg_match('/^redis=(.+)/',$dsn,$parts) &&
- extension_loaded('redis')) {
- list($host,$port,$db,$password)=explode(':',$parts[1])+[1=>6379,2=>NULL,3=>NULL];
- $this->ref=new Redis;
- if(!$this->ref->connect($host,$port,2))
- $this->ref=NULL;
- if(!empty($password))
- $this->ref->auth($password);
- if(isset($db))
- $this->ref->select($db);
- }
- elseif (preg_match('/^memcache=(.+)/',$dsn,$parts) &&
- extension_loaded('memcache'))
- foreach ($fw->split($parts[1]) as $server) {
- list($host,$port)=explode(':',$server)+[1=>11211];
- if (empty($this->ref))
- $this->ref=@memcache_connect($host,$port)?:NULL;
- else
- memcache_add_server($this->ref,$host,$port);
- }
- elseif (preg_match('/^memcached=(.+)/',$dsn,$parts) &&
- extension_loaded('memcached'))
- foreach ($fw->split($parts[1]) as $server) {
- list($host,$port)=explode(':',$server)+[1=>11211];
- if (empty($this->ref))
- $this->ref=new Memcached();
- $this->ref->addServer($host,$port);
- }
- if (empty($this->ref) && !preg_match('/^folder\h*=/',$dsn))
- $dsn=($grep=preg_grep('/^(apc|wincache|xcache)/',
- array_map('strtolower',get_loaded_extensions())))?
- // Auto-detect
- current($grep):
- // Use filesystem as fallback
- ('folder='.$fw->TEMP.'cache/');
- if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) &&
- !is_dir($parts[1]))
- mkdir($parts[1],Base::MODE,TRUE);
- }
- $this->prefix=$seed?:$fw->SEED;
- return $this->dsn=$dsn;
- }
- /**
- * Class constructor
- * @param $dsn bool|string
- **/
- function __construct($dsn=FALSE) {
- if ($dsn)
- $this->load($dsn);
- }
- }
- //! View handler
- class View extends Prefab {
- private
- //! Temporary hive
- $temp;
- protected
- //! Template file
- $file,
- //! Post-rendering handler
- $trigger,
- //! Nesting level
- $level=0;
- /** @var \Base Framework instance */
- protected $fw;
- function __construct() {
- $this->fw=\Base::instance();
- }
- /**
- * Encode characters to equivalent HTML entities
- * @return string
- * @param $arg mixed
- **/
- function esc($arg) {
- return $this->fw->recursive($arg,
- function($val) {
- return is_string($val)?$this->fw->encode($val):$val;
- }
- );
- }
- /**
- * Decode HTML entities to equivalent characters
- * @return string
- * @param $arg mixed
- **/
- function raw($arg) {
- return $this->fw->recursive($arg,
- function($val) {
- return is_string($val)?$this->fw->decode($val):$val;
- }
- );
- }
- /**
- * Create sandbox for template execution
- * @return string
- * @param $hive array
- * @param $mime string
- **/
- protected function sandbox(array $hive=NULL,$mime=NULL) {
- $fw=$this->fw;
- $implicit=FALSE;
- if (is_null($hive)) {
- $implicit=TRUE;
- $hive=$fw->hive();
- }
- if ($this->level<1 || $implicit) {
- if (!$fw->CLI && $mime && !headers_sent() &&
- !preg_grep ('/^Content-Type:/',headers_list()))
- header('Content-Type: '.$mime.'; '.
- 'charset='.$fw->ENCODING);
- if ($fw->ESCAPE && (!$mime ||
- preg_match('/^(text\/html|(application|text)\/(.+\+)?xml)$/i',$mime)))
- $hive=$this->esc($hive);
- if (isset($hive['ALIASES']))
- $hive['ALIASES']=$fw->build($hive['ALIASES']);
- }
- $this->temp=$hive;
- unset($fw,$hive,$implicit,$mime);
- extract($this->temp);
- $this->temp=NULL;
- $this->level++;
- ob_start();
- require($this->file);
- $this->level--;
- return ob_get_clean();
- }
- /**
- * Render template
- * @return string
- * @param $file string
- * @param $mime string
- * @param $hive array
- * @param $ttl int
- **/
- function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
- $fw=$this->fw;
- $cache=Cache::instance();
- foreach ($fw->split($fw->UI) as $dir) {
- if ($cache->exists($hash=$fw->hash($dir.$file),$data))
- return $data;
- if (is_file($this->file=$fw->fixslashes($dir.$file))) {
- if (isset($_COOKIE[session_name()]) &&
- !headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- session_start();
- $fw->sync('SESSION');
- $data=$this->sandbox($hive,$mime);
- if (isset($this->trigger['afterrender']))
- foreach($this->trigger['afterrender'] as $func)
- $data=$fw->call($func,[$data, $dir.$file]);
- if ($ttl)
- $cache->set($hash,$data,$ttl);
- return $data;
- }
- }
- user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
- }
- /**
- * post rendering handler
- * @param $func callback
- */
- function afterrender($func) {
- $this->trigger['afterrender'][]=$func;
- }
- }
- //! Lightweight template engine
- class Preview extends View {
- protected
- //! token filter
- $filter=[
- 'c'=>'$this->c',
- 'esc'=>'$this->esc',
- 'raw'=>'$this->raw',
- 'export'=>'Base::instance()->export',
- 'alias'=>'Base::instance()->alias',
- 'format'=>'Base::instance()->format'
- ];
- protected
- //! newline interpolation
- $interpolation=true;
- /**
- * Enable/disable markup parsing interpolation
- * mainly used for adding appropriate newlines
- * @param $bool bool
- */
- function interpolation($bool) {
- $this->interpolation=$bool;
- }
- /**
- * Return C-locale equivalent of number
- * @return string
- * @param $val int|float
- **/
- function c($val) {
- $locale=setlocale(LC_NUMERIC,0);
- setlocale(LC_NUMERIC,'C');
- $out=(string)(float)$val;
- $locale=setlocale(LC_NUMERIC,$locale);
- return $out;
- }
- /**
- * Convert token to variable
- * @return string
- * @param $str string
- **/
- function token($str) {
- $str=trim(preg_replace('/\{\{(.+?)\}\}/s','\1',$this->fw->compile($str)));
- if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
- $str,$parts)) {
- $str=trim($parts[1]);
- foreach ($this->fw->split(trim($parts[2],"\xC2\xA0")) as $func)
- $str=((empty($this->filter[$cmd=$func]) &&
- function_exists($cmd)) ||
- is_string($cmd=$this->filter($func)))?
- $cmd.'('.$str.')':
- 'Base::instance()->'.
- 'call($this->filter(\''.$func.'\'),['.$str.'])';
- }
- return $str;
- }
- /**
- * Register or get (one specific or all) token filters
- * @param string $key
- * @param string|closure $func
- * @return array|closure|string
- */
- function filter($key=NULL,$func=NULL) {
- if (!$key)
- return array_keys($this->filter);
- $key=strtolower($key);
- if (!$func)
- return $this->filter[$key];
- $this->filter[$key]=$func;
- }
- /**
- * Assemble markup
- * @return string
- * @param $node string
- **/
- protected function build($node) {
- return preg_replace_callback(
- '/\{~(.+?)~\}|\{\*(.+?)\*\}|\{\-(.+?)\-\}|'.
- '\{\{(.+?)\}\}((\r?\n)*)/s',
- function($expr) {
- if ($expr[1])
- $str='<?php '.$this->token($expr[1]).' ?>';
- elseif ($expr[2])
- return '';
- elseif ($expr[3])
- $str=$expr[3];
- else {
- $str='<?= ('.trim($this->token($expr[4])).')'.
- ($this->interpolation?
- (!empty($expr[6])?'."'.$expr[6].'"':''):'').' ?>';
- if (isset($expr[5]))
- $str.=$expr[5];
- }
- return $str;
- },
- $node
- );
- }
- /**
- * Render template string
- * @return string
- * @param $node string|array
- * @param $hive array
- * @param $ttl int
- * @param $persist bool
- * @param $escape bool
- **/
- function resolve($node,array $hive=NULL,$ttl=0,$persist=FALSE,$escape=NULL) {
- $fw=$this->fw;
- $cache=Cache::instance();
- if ($escape!==NULL) {
- $esc=$fw->ESCAPE;
- $fw->ESCAPE=$escape;
- }
- if ($ttl || $persist)
- $hash=$fw->hash($fw->serialize($node));
- if ($ttl && $cache->exists($hash,$data))
- return $data;
- if ($persist) {
- if (!is_dir($tmp=$fw->TEMP))
- mkdir($tmp,Base::MODE,TRUE);
- if (!is_file($this->file=($tmp.
- $fw->SEED.'.'.$hash.'.php')))
- $fw->write($this->file,$this->build($node));
- if (isset($_COOKIE[session_name()]) &&
- !headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- session_start();
- $fw->sync('SESSION');
- $data=$this->sandbox($hive);
- }
- else {
- if (!$hive)
- $hive=$fw->hive();
- if ($fw->ESCAPE)
- $hive=$this->esc($hive);
- extract($hive);
- unset($hive);
- ob_start();
- eval(' ?>'.$this->build($node).'<?php ');
- $data=ob_get_clean();
- }
- if ($ttl)
- $cache->set($hash,$data,$ttl);
- if ($escape!==NULL)
- $fw->ESCAPE=$esc;
- return $data;
- }
- /**
- * Parse template string
- * @return string
- * @param $text string
- **/
- function parse($text) {
- // Remove PHP code and comments
- return preg_replace(
- '/\h*<\?(?!xml)(?:php|\s*=)?.+?\?>\h*|'.
- '\{\*.+?\*\}/is','', $text);
- }
- /**
- * Render template
- * @return string
- * @param $file string
- * @param $mime string
- * @param $hive array
- * @param $ttl int
- **/
- function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
- $fw=$this->fw;
- $cache=Cache::instance();
- if (!is_dir($tmp=$fw->TEMP))
- mkdir($tmp,Base::MODE,TRUE);
- foreach ($fw->split($fw->UI) as $dir) {
- if ($cache->exists($hash=$fw->hash($dir.$file),$data))
- return $data;
- if (is_file($view=$fw->fixslashes($dir.$file))) {
- if (!is_file($this->file=($tmp.
- $fw->SEED.'.'.$fw->hash($view).'.php')) ||
- filemtime($this->file)<filemtime($view)) {
- $contents=$fw->read($view);
- if (isset($this->trigger['beforerender']))
- foreach ($this->trigger['beforerender'] as $func)
- $contents=$fw->call($func, [$contents, $view]);
- $text=$this->parse($contents);
- $fw->write($this->file,$this->build($text));
- }
- if (isset($_COOKIE[session_name()]) &&
- !headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
- session_start();
- $fw->sync('SESSION');
- $data=$this->sandbox($hive,$mime);
- if(isset($this->trigger['afterrender']))
- foreach ($this->trigger['afterrender'] as $func)
- $data=$fw->call($func, [$data, $view]);
- if ($ttl)
- $cache->set($hash,$data,$ttl);
- return $data;
- }
- }
- user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
- }
- /**
- * post rendering handler
- * @param $func callback
- */
- function beforerender($func) {
- $this->trigger['beforerender'][]=$func;
- }
- }
- //! ISO language/country codes
- class ISO extends Prefab {
- //@{ ISO 3166-1 country codes
- const
- CC_af='Afghanistan',
- CC_ax='Åland Islands',
- CC_al='Albania',
- CC_dz='Algeria',
- CC_as='American Samoa',
- CC_ad='Andorra',
- CC_ao='Angola',
- CC_ai='Anguilla',
- CC_aq='Antarctica',
- CC_ag='Antigua and Barbuda',
- CC_ar='Argentina',
- CC_am='Armenia',
- CC_aw='Aruba',
- CC_au='Australia',
- CC_at='Austria',
- CC_az='Azerbaijan',
- CC_bs='Bahamas',
- CC_bh='Bahrain',
- CC_bd='Bangladesh',
- CC_bb='Barbados',
- CC_by='Belarus',
- CC_be='Belgium',
- CC_bz='Belize',
- CC_bj='Benin',
- CC_bm='Bermuda',
- CC_bt='Bhutan',
- CC_bo='Bolivia',
- CC_bq='Bonaire, Sint Eustatius and Saba',
- CC_ba='Bosnia and Herzegovina',
- CC_bw='Botswana',
- CC_bv='Bouvet Island',
- CC_br='Brazil',
- CC_io='British Indian Ocean Territory',
- CC_bn='Brunei Darussalam',
- CC_bg='Bulgaria',
- CC_bf='Burkina Faso',
- CC_bi='Burundi',
- CC_kh='Cambodia',
- CC_cm='Cameroon',
- CC_ca='Canada',
- CC_cv='Cape Verde',
- CC_ky='Cayman Islands',
- CC_cf='Central African Republic',
- CC_td='Chad',
- CC_cl='Chile',
- CC_cn='China',
- CC_cx='Christmas Island',
- CC_cc='Cocos (Keeling) Islands',
- CC_co='Colombia',
- CC_km='Comoros',
- CC_cg='Congo',
- CC_cd='Congo, The Democratic Republic of',
- CC_ck='Cook Islands',
- CC_cr='Costa Rica',
- CC_ci='Côte d\'ivoire',
- CC_hr='Croatia',
- CC_cu='Cuba',
- CC_cw='Curaçao',
- CC_cy='Cyprus',
- CC_cz='Czech Republic',
- CC_dk='Denmark',
- CC_dj='Djibouti',
- CC_dm='Dominica',
- CC_do='Dominican Republic',
- CC_ec='Ecuador',
- CC_eg='Egypt',
- CC_sv='El Salvador',
- CC_gq='Equatorial Guinea',
- CC_er='Eritrea',
- CC_ee='Estonia',
- CC_et='Ethiopia',
- CC_fk='Falkland Islands (Malvinas)',
- CC_fo='Faroe Islands',
- CC_fj='Fiji',
- CC_fi='Finland',
- CC_fr='France',
- CC_gf='French Guiana',
- CC_pf='French Polynesia',
- CC_tf='French Southern Territories',
- CC_ga='Gabon',
- CC_gm='Gambia',
- CC_ge='Georgia',
- CC_de='Germany',
- CC_gh='Ghana',
- CC_gi='Gibraltar',
- CC_gr='Greece',
- CC_gl='Greenland',
- CC_gd='Grenada',
- CC_gp='Guadeloupe',
- CC_gu='Guam',
- CC_gt='Guatemala',
- CC_gg='Guernsey',
- CC_gn='Guinea',
- CC_gw='Guinea-Bissau',
- CC_gy='Guyana',
- CC_ht='Haiti',
- CC_hm='Heard Island and McDonald Islands',
- CC_va='Holy See (Vatican City State)',
- CC_hn='Honduras',
- CC_hk='Hong Kong',
- CC_hu='Hungary',
- CC_is='Iceland',
- CC_in='India',
- CC_id='Indonesia',
- CC_ir='Iran, Islamic Republic of',
- CC_iq='Iraq',
- CC_ie='Ireland',
- CC_im='Isle of Man',
- CC_il='Israel',
- CC_it='Italy',
- CC_jm='Jamaica',
- CC_jp='Japan',
- CC_je='Jersey',
- CC_jo='Jordan',
- CC_kz='Kazakhstan',
- CC_ke='Kenya',
- CC_ki='Kiribati',
- CC_kp='Korea, Democratic People\'s Republic of',
- CC_kr='Korea, Republic of',
- CC_kw='Kuwait',
- CC_kg='Kyrgyzstan',
- CC_la='Lao People\'s Democratic Republic',
- CC_lv='Latvia',
- CC_lb='Lebanon',
- CC_ls='Lesotho',
- CC_lr='Liberia',
- CC_ly='Libya',
- CC_li='Liechtenstein',
- CC_lt='Lithuania',
- CC_lu='Luxembourg',
- CC_mo='Macao',
- CC_mk='Macedonia, The Former Yugoslav Republic of',
- CC_mg='Madagascar',
- CC_mw='Malawi',
- CC_my='Malaysia',
- CC_mv='Maldives',
- CC_ml='Mali',
- CC_mt='Malta',
- CC_mh='Marshall Islands',
- CC_mq='Martinique',
- CC_mr='Mauritania',
- CC_mu='Mauritius',
- CC_yt='Mayotte',
- CC_mx='Mexico',
- CC_fm='Micronesia, Federated States of',
- CC_md='Moldova, Republic of',
- CC_mc='Monaco',
- CC_mn='Mongolia',
- CC_me='Montenegro',
- CC_ms='Montserrat',
- CC_ma='Morocco',
- CC_mz='Mozambique',
- CC_mm='Myanmar',
- CC_na='Namibia',
- CC_nr='Nauru',
- CC_np='Nepal',
- CC_nl='Netherlands',
- CC_nc='New Caledonia',
- CC_nz='New Zealand',
- CC_ni='Nicaragua',
- CC_ne='Niger',
- CC_ng='Nigeria',
- CC_nu='Niue',
- CC_nf='Norfolk Island',
- CC_mp='Northern Mariana Islands',
- CC_no='Norway',
- CC_om='Oman',
- CC_pk='Pakistan',
- CC_pw='Palau',
- CC_ps='Palestinian Territory, Occupied',
- CC_pa='Panama',
- CC_pg='Papua New Guinea',
- CC_py='Paraguay',
- CC_pe='Peru',
- CC_ph='Philippines',
- CC_pn='Pitcairn',
- CC_pl='Poland',
- CC_pt='Portugal',
- CC_pr='Puerto Rico',
- CC_qa='Qatar',
- CC_re='Réunion',
- CC_ro='Romania',
- CC_ru='Russian Federation',
- CC_rw='Rwanda',
- CC_bl='Saint Barthélemy',
- CC_sh='Saint Helena, Ascension and Tristan da Cunha',
- CC_kn='Saint Kitts and Nevis',
- CC_lc='Saint Lucia',
- CC_mf='Saint Martin (French Part)',
- CC_pm='Saint Pierre and Miquelon',
- CC_vc='Saint Vincent and The Grenadines',
- CC_ws='Samoa',
- CC_sm='San Marino',
- CC_st='Sao Tome and Principe',
- CC_sa='Saudi Arabia',
- CC_sn='Senegal',
- CC_rs='Serbia',
- CC_sc='Seychelles',
- CC_sl='Sierra Leone',
- CC_sg='Singapore',
- CC_sk='Slovakia',
- CC_sx='Sint Maarten (Dutch Part)',
- CC_si='Slovenia',
- CC_sb='Solomon Islands',
- CC_so='Somalia',
- CC_za='South Africa',
- CC_gs='South Georgia and The South Sandwich Islands',
- CC_ss='South Sudan',
- CC_es='Spain',
- CC_lk='Sri Lanka',
- CC_sd='Sudan',
- CC_sr='Suriname',
- CC_sj='Svalbard and Jan Mayen',
- CC_sz='Swaziland',
- CC_se='Sweden',
- CC_ch='Switzerland',
- CC_sy='Syrian Arab Republic',
- CC_tw='Taiwan, Province of China',
- CC_tj='Tajikistan',
- CC_tz='Tanzania, United Republic of',
- CC_th='Thailand',
- CC_tl='Timor-Leste',
- CC_tg='Togo',
- CC_tk='Tokelau',
- CC_to='Tonga',
- CC_tt='Trinidad and Tobago',
- CC_tn='Tunisia',
- CC_tr='Turkey',
- CC_tm='Turkmenistan',
- CC_tc='Turks and Caicos Islands',
- CC_tv='Tuvalu',
- CC_ug='Uganda',
- CC_ua='Ukraine',
- CC_ae='United Arab Emirates',
- CC_gb='United Kingdom',
- CC_us='United States',
- CC_um='United States Minor Outlying Islands',
- CC_uy='Uruguay',
- CC_uz='Uzbekistan',
- CC_vu='Vanuatu',
- CC_ve='Venezuela',
- CC_vn='Viet Nam',
- CC_vg='Virgin Islands, British',
- CC_vi='Virgin Islands, U.S.',
- CC_wf='Wallis and Futuna',
- CC_eh='Western Sahara',
- CC_ye='Yemen',
- CC_zm='Zambia',
- CC_zw='Zimbabwe';
- //@}
- //@{ ISO 639-1 language codes (Windows-compatibility subset)
- const
- LC_af='Afrikaans',
- LC_am='Amharic',
- LC_ar='Arabic',
- LC_as='Assamese',
- LC_ba='Bashkir',
- LC_be='Belarusian',
- LC_bg='Bulgarian',
- LC_bn='Bengali',
- LC_bo='Tibetan',
- LC_br='Breton',
- LC_ca='Catalan',
- LC_co='Corsican',
- LC_cs='Czech',
- LC_cy='Welsh',
- LC_da='Danish',
- LC_de='German',
- LC_dv='Divehi',
- LC_el='Greek',
- LC_en='English',
- LC_es='Spanish',
- LC_et='Estonian',
- LC_eu='Basque',
- LC_fa='Persian',
- LC_fi='Finnish',
- LC_fo='Faroese',
- LC_fr='French',
- LC_gd='Scottish Gaelic',
- LC_gl='Galician',
- LC_gu='Gujarati',
- LC_he='Hebrew',
- LC_hi='Hindi',
- LC_hr='Croatian',
- LC_hu='Hungarian',
- LC_hy='Armenian',
- LC_id='Indonesian',
- LC_ig='Igbo',
- LC_is='Icelandic',
- LC_it='Italian',
- LC_ja='Japanese',
- LC_ka='Georgian',
- LC_kk='Kazakh',
- LC_km='Khmer',
- LC_kn='Kannada',
- LC_ko='Korean',
- LC_lb='Luxembourgish',
- LC_lo='Lao',
- LC_lt='Lithuanian',
- LC_lv='Latvian',
- LC_mi='Maori',
- LC_ml='Malayalam',
- LC_mr='Marathi',
- LC_ms='Malay',
- LC_mt='Maltese',
- LC_ne='Nepali',
- LC_nl='Dutch',
- LC_no='Norwegian',
- LC_oc='Occitan',
- LC_or='Oriya',
- LC_pl='Polish',
- LC_ps='Pashto',
- LC_pt='Portuguese',
- LC_qu='Quechua',
- LC_ro='Romanian',
- LC_ru='Russian',
- LC_rw='Kinyarwanda',
- LC_sa='Sanskrit',
- LC_si='Sinhala',
- LC_sk='Slovak',
- LC_sl='Slovenian',
- LC_sq='Albanian',
- LC_sv='Swedish',
- LC_ta='Tamil',
- LC_te='Telugu',
- LC_th='Thai',
- LC_tk='Turkmen',
- LC_tr='Turkish',
- LC_tt='Tatar',
- LC_uk='Ukrainian',
- LC_ur='Urdu',
- LC_vi='Vietnamese',
- LC_wo='Wolof',
- LC_yo='Yoruba',
- LC_zh='Chinese';
- //@}
- /**
- * Return list of languages indexed by ISO 639-1 language code
- * @return array
- **/
- function languages() {
- return \Base::instance()->constants($this,'LC_');
- }
- /**
- * Return list of countries indexed by ISO 3166-1 country code
- * @return array
- **/
- function countries() {
- return \Base::instance()->constants($this,'CC_');
- }
- }
- //! Container for singular object instances
- final class Registry {
- private static
- //! Object catalog
- $table;
- /**
- * Return TRUE if object exists in catalog
- * @return bool
- * @param $key string
- **/
- static function exists($key) {
- return isset(self::$table[$key]);
- }
- /**
- * Add object to catalog
- * @return object
- * @param $key string
- * @param $obj object
- **/
- static function set($key,$obj) {
- return self::$table[$key]=$obj;
- }
- /**
- * Retrieve object from catalog
- * @return object
- * @param $key string
- **/
- static function get($key) {
- return self::$table[$key];
- }
- /**
- * Delete object from catalog
- * @param $key string
- **/
- static function clear($key) {
- self::$table[$key]=NULL;
- unset(self::$table[$key]);
- }
- //! Prohibit cloning
- private function __clone() {
- }
- //! Prohibit instantiation
- private function __construct() {
- }
- }
- return Base::instance();
|