From ac417fb19ffe372115377c1f5eabe6fa6e4b08fe Mon Sep 17 00:00:00 2001 From: Mark Friedrich Date: Sat, 6 Jan 2018 19:51:16 +0100 Subject: [PATCH] - upgraded FatFreeFramework `3.6.1` -> `3.6.2` --- app/lib/audit.php | 6 +- app/lib/auth.php | 21 +- app/lib/base.php | 612 ++++++++++++++++++------------- app/lib/basket.php | 2 +- app/lib/db/cursor.php | 12 +- app/lib/db/jig.php | 2 +- app/lib/db/jig/mapper.php | 22 +- app/lib/db/jig/session.php | 13 +- app/lib/db/mongo/mapper.php | 17 +- app/lib/db/mongo/session.php | 13 +- app/lib/db/sql.php | 129 ++++--- app/lib/db/sql/mapper.php | 178 +++++---- app/lib/db/sql/session.php | 20 +- app/lib/image.php | 18 +- app/lib/log.php | 2 +- app/lib/markdown.php | 52 ++- app/lib/session.php | 14 +- app/lib/smtp.php | 26 +- app/lib/template.php | 23 +- app/lib/utf.php | 2 +- app/lib/web.php | 150 ++++---- app/lib/web/geo.php | 2 +- app/lib/web/google/recaptcha.php | 58 +++ app/lib/web/oauth2.php | 2 +- app/lib/web/openid.php | 4 +- app/lib/web/pingback.php | 22 +- 26 files changed, 842 insertions(+), 580 deletions(-) create mode 100644 app/lib/web/google/recaptcha.php diff --git a/app/lib/audit.php b/app/lib/audit.php index f4075542..0e69b56a 100644 --- a/app/lib/audit.php +++ b/app/lib/audit.php @@ -108,7 +108,7 @@ class Audit extends Prefab { **/ function isdesktop($agent=NULL) { if (!isset($agent)) - $agent=Base::instance()->get('AGENT'); + $agent=Base::instance()->AGENT; return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) && !$this->ismobile($agent); } @@ -120,7 +120,7 @@ class Audit extends Prefab { **/ function ismobile($agent=NULL) { if (!isset($agent)) - $agent=Base::instance()->get('AGENT'); + $agent=Base::instance()->AGENT; return (bool)preg_match('/('.self::UA_Mobile.')/i',$agent); } @@ -131,7 +131,7 @@ class Audit extends Prefab { **/ function isbot($agent=NULL) { if (!isset($agent)) - $agent=Base::instance()->get('AGENT'); + $agent=Base::instance()->AGENT; return (bool)preg_match('/('.self::UA_Bot.')/i',$agent); } diff --git a/app/lib/auth.php b/app/lib/auth.php index 6fd79099..c1e8a570 100644 --- a/app/lib/auth.php +++ b/app/lib/auth.php @@ -115,18 +115,27 @@ class Auth { * @param $pw string **/ protected function _ldap($id,$pw) { - $dc=@ldap_connect($this->args['dc']); + $port=(int)($this->args['port']?:389); + $filter=$this->args['filter']=$this->args['filter']?:"uid=".$id; + $this->args['attr']=$this->args['attr']?:["uid"]; + array_walk($this->args['attr'], + function($attr)use(&$filter,$id) { + $filter=str_ireplace($attr."=*",$attr."=".$id,$filter);}); + $dc=@ldap_connect($this->args['dc'],$port); if ($dc && ldap_set_option($dc,LDAP_OPT_PROTOCOL_VERSION,3) && ldap_set_option($dc,LDAP_OPT_REFERRALS,0) && ldap_bind($dc,$this->args['rdn'],$this->args['pw']) && ($result=ldap_search($dc,$this->args['base_dn'], - 'uid='.$id)) && + $filter,$this->args['attr'])) && ldap_count_entries($dc,$result) && ($info=ldap_get_entries($dc,$result)) && + $info['count']==1 && @ldap_bind($dc,$info[0]['dn'],$pw) && @ldap_close($dc)) { - return $info[0]['uid'][0]==$id; + return in_array($id,(array_map(function($value){return $value[0];}, + array_intersect_key($info[0], + array_flip($this->args['attr'])))),TRUE); } user_error(self::E_LDAP,E_USER_ERROR); } @@ -160,12 +169,12 @@ class Auth { stream_set_blocking($socket,TRUE); $dialog(); $fw=Base::instance(); - $dialog('EHLO '.$fw->get('HOST')); + $dialog('EHLO '.$fw->HOST); if (strtolower($this->args['scheme'])=='tls') { $dialog('STARTTLS'); stream_socket_enable_crypto( $socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT); - $dialog('EHLO '.$fw->get('HOST')); + $dialog('EHLO '.$fw->HOST); } // Authenticate $dialog('AUTH LOGIN'); @@ -196,7 +205,7 @@ class Auth { **/ function basic($func=NULL) { $fw=Base::instance(); - $realm=$fw->get('REALM'); + $realm=$fw->REALM; $hdr=NULL; if (isset($_SERVER['HTTP_AUTHORIZATION'])) $hdr=$_SERVER['HTTP_AUTHORIZATION']; diff --git a/app/lib/base.php b/app/lib/base.php index df49ed60..62fa14fb 100644 --- a/app/lib/base.php +++ b/app/lib/base.php @@ -45,13 +45,14 @@ final class Base extends Prefab implements ArrayAccess { //@{ Framework details const PACKAGE='Fat-Free Framework', - VERSION='3.6.1-Dev'; + VERSION='3.6.3-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', @@ -185,24 +186,6 @@ final class Base extends Prefab implements ArrayAccess { return $url; } - /** - * Assemble url from alias name - * @return string - * @param $name string - * @param $params array|string - * @param $query string|array - **/ - function alias($name,$params=[],$query=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):''); - } - /** * Parse string containing key-value pairs * @return array @@ -223,37 +206,53 @@ final class Base extends Prefab implements ArrayAccess { return $out; } + /** + * cast string variable to php type or constant + * @param $val + * @return mixed + */ + function cast($val) { + 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 **/ function compile($str) { - $fw=$this; return preg_replace_callback( - '/(?|::)*)/', - function($var) use($fw) { - return '$'.preg_replace_callback( - '/\.(\w+)\(|\.(\w+)|\[((?:[^\[\]]*|(?R))*)\]/', - function($expr) use($fw) { - return $expr[1]? - ((function_exists($expr[1])? - ('.'.$expr[1]): - ('['.var_export($expr[1],TRUE).']')).'('): - ('['. - (isset($expr[3])? - (strlen($expr[3])? - var_export( - trim($fw->compile($expr[3])), - TRUE): - ''): - var_export(ctype_digit($expr[2])? - (int)$expr[2]: - $expr[2],TRUE)). - ']'); - }, - $var[1] - ); + '/(?|::)\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]): + var_export($sub[1],TRUE)). + ']'; + } + else + $out=function_exists($sub[1])? + $sub[0]: + ('['.var_export($sub[1],TRUE).']'.$sub[2]); + return $out; + }, + $expr[2] + ); + return $str; }, $str ); @@ -347,14 +346,14 @@ final class Base extends Prefab implements ArrayAccess { * @param $ttl int **/ function set($key,$val,$ttl=0) { - $time=time(); + $time=$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'])); if (isset($_COOKIE[$parts[1]])) { - $jar['expire']=strtotime('-1 year'); + $jar['expire']=0; call_user_func_array('setcookie', array_merge([$parts[1],NULL],$jar)); } @@ -367,7 +366,7 @@ final class Base extends Prefab implements ArrayAccess { } else switch ($key) { case 'CACHE': - $val=Cache::instance()->load($val,TRUE); + $val=Cache::instance()->load($val); break; case 'ENCODING': ini_set('default_charset',$val); @@ -443,7 +442,7 @@ final class Base extends Prefab implements ArrayAccess { if ($expr[1]=='COOKIE') { $parts=$this->cut($key); $jar=$this->hive['JAR']; - $jar['expire']=strtotime('-1 year'); + $jar['expire']=0; call_user_func_array('setcookie', array_merge([$parts[1],NULL],$jar)); unset($_COOKIE[$parts[1]]); @@ -563,7 +562,7 @@ final class Base extends Prefab implements ArrayAccess { **/ function push($key,$val) { $ref=&$this->ref($key); - $ref[] = $val; + $ref[]=$val; return $val; } @@ -627,7 +626,8 @@ final class Base extends Prefab implements ArrayAccess { $ref=&$this->ref($key); if (!$ref) $ref=[]; - $out=array_replace_recursive(is_string($src)?$this->hive[$src]:$src,$ref); + $out=array_replace_recursive( + is_string($src)?$this->hive[$src]:$src,$ref); if ($keep) $ref=$out; return $out; @@ -707,7 +707,7 @@ final class Base extends Prefab implements ArrayAccess { **/ function camelcase($str) { return preg_replace_callback( - '/_(\w)/', + '/_(\pL)/u', function($match) { return strtoupper($match[1]); }, @@ -721,7 +721,7 @@ final class Base extends Prefab implements ArrayAccess { * @param $str string **/ function snakecase($str) { - return strtolower(preg_replace('/(?!^)[[:upper:]]/','_\0',$str)); + return strtolower(preg_replace('/(?!^)\p{Lu}/u','_\0',$str)); } /** @@ -841,12 +841,11 @@ final class Base extends Prefab implements ArrayAccess { * @param $tags string **/ function clean($arg,$tags=NULL) { - $fw=$this; return $this->recursive($arg, - function($val) use($fw,$tags) { + function($val) use($tags) { if ($tags!='*') $val=trim(strip_tags($val, - '<'.implode('><',$fw->split($tags)).'>')); + '<'.implode('><',$this->split($tags)).'>')); return trim(preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/','',$val)); } @@ -873,9 +872,9 @@ final class Base extends Prefab implements ArrayAccess { // Get formatting rules $conv=localeconv(); return preg_replace_callback( - '/\{(?P\d+)\s*(?:,\s*(?P\w+)\s*'. + '/\{\s*(?P\d+)\s*(?:,\s*(?P\w+)\s*'. '(?:,\s*(?P(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'. - '(?:,\s*(?P.+?))?)?)?\}/', + '(?:,\s*(?P.+?))?)?)?\s*\}/', function($expr) use($args,$conv) { extract($expr); extract($conv); @@ -884,7 +883,7 @@ final class Base extends Prefab implements ArrayAccess { if (isset($type)) { if (isset($this->hive['FORMATS'][$type])) return $this->call($this->hive['FORMATS'][$type], - [$args[$pos],$mod,isset($prop)?$prop:null]); + [$args[$pos],isset($mod)?$mod:null,isset($prop)?$prop:null]); switch ($type) { case 'plural': preg_match_all('/(?\w+)'. @@ -904,7 +903,7 @@ final class Base extends Prefab implements ArrayAccess { return number_format( $args[$pos],0,'',$thousands_sep); case 'currency': - $int=$cstm=false; + $int=$cstm=FALSE; if (isset($prop) && $cstm=!$int=($prop=='int')) $currency_symbol=$prop; @@ -1016,7 +1015,7 @@ final class Base extends Prefab implements ArrayAccess { $locales[]=$locale; } setlocale(LC_ALL,$locales); - return implode(',',$this->languages); + return $this->hive['LANGUAGE']=implode(',',$this->languages); } /** @@ -1054,7 +1053,7 @@ final class Base extends Prefab implements ArrayAccess { elseif (!array_key_exists( $key=$prefix.$match['lval'],$lex)) $lex[$key]=trim(preg_replace( - '/\\\\\h*\r?\n/','',$match['rval'])); + '/\\\\\h*\r?\n/',"\n",$match['rval'])); } } if ($ttl) @@ -1109,6 +1108,7 @@ final class Base extends Prefab implements ArrayAccess { **/ 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']) @@ -1118,7 +1118,7 @@ final class Base extends Prefab implements ArrayAccess { if ($this->hive['VERB']=='GET' && $secs) { $time=microtime(TRUE); header_remove('Pragma'); - header('Cache-Control: max-age='.(int)$secs); + header('Cache-Control: max-age='.$secs); header('Expires: '.gmdate('r',$time+$secs)); header('Last-Modified: '.gmdate('r')); } @@ -1186,10 +1186,11 @@ final class Base extends Prefab implements ArrayAccess { $trace, function($frame) use($debug) { return isset($frame['file']) && - ($frame['file']!=__FILE__ || $debug>1) && + ($debug>1 || + ($frame['file']!=__FILE__ || $debug) && (empty($frame['function']) || !preg_match('/^(?:(?:trigger|user)_error|'. - '__call|call_user_func)/',$frame['function'])); + '__call|call_user_func)/',$frame['function']))); } ); if (!$format) @@ -1226,6 +1227,8 @@ final class Base extends Prefab implements ArrayAccess { $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.')'; error_log($text); @@ -1252,7 +1255,7 @@ final class Base extends Prefab implements ArrayAccess { 'beforeroute,afterroute')===FALSE) && !$prior && !$this->hive['CLI'] && !$this->hive['QUIET']) echo $this->hive['AJAX']? - json_encode($this->hive['ERROR']): + json_encode(array_diff_key($this->hive['ERROR'],$this->hive['DEBUG']?[]:['trace'=>1])): (''.$eol. ''.$eol. ''. @@ -1267,7 +1270,7 @@ final class Base extends Prefab implements ArrayAccess { ''.$eol. ''); if ($this->hive['HALT']) - die; + die(1); } /** @@ -1318,6 +1321,24 @@ final class Base extends Prefab implements ArrayAccess { return $this->run(); } + /** + * Assemble url from alias name + * @return string + * @param $name string + * @param $params array|string + * @param $query string|array + **/ + function alias($name,$params=[],$query=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):''); + } + /** * Bind handler to route pattern * @return NULL @@ -1334,8 +1355,8 @@ final class Base extends Prefab implements ArrayAccess { $this->route($item,$handler,$ttl,$kbps); return; } - preg_match('/([\|\w]+)\h+(?:(?:@(\w+)\h*:\h*)?(@(\w+)|[^\h]+))'. - '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts); + preg_match('/([\|\w]+)\h+(?:(?:@?(.+?)\h*:\h*)?(@(\w+)|[^\h]+))'. + '(?:\h+\[('.implode('|',$types).')\])?/u',$pattern,$parts); if (isset($parts[2]) && $parts[2]) $this->hive['ALIASES'][$alias=$parts[2]]=$parts[3]; elseif (!empty($parts[4])) { @@ -1359,17 +1380,17 @@ final class Base extends Prefab implements ArrayAccess { * @return NULL * @param $url array|string * @param $permanent bool + * @param $die bool **/ - function reroute($url=NULL,$permanent=FALSE) { + 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('/^(?:@(\w+)(?:(\(.+?)\))*(\?.+)*)/',$url,$parts)) { - if (empty($this->hive['ALIASES'][$parts[1]])) - user_error(sprintf(self::E_Named,$parts[1]),E_USER_ERROR); + elseif (preg_match('/^(?:@?([^\/()?]+)(?:(\(.+?)\))*(\?.+)*)/', + $url,$parts) && + isset($this->hive['ALIASES'][$parts[1]])) $url=$this->hive['ALIASES'][$parts[1]]; - } $url=$this->build($url,isset($parts[2])?$this->parse($parts[2]):[]). (isset($parts[3])?$parts[3]:''); if (($handler=$this->hive['ONREROUTE']) && @@ -1377,16 +1398,18 @@ final class Base extends Prefab implements ArrayAccess { return; if ($url[0]=='/' && (empty($url[1]) || $url[1]!='/')) { $port=$this->hive['PORT']; - $port=in_array($port,[80,443])?'':':'.$port; + $port=in_array($port,[80,443])?'':(':'.$port); $url=$this->hive['SCHEME'].'://'. $this->hive['HOST'].$port.$this->hive['BASE'].$url; } - if (!$this->hive['CLI']) { + if ($this->hive['CLI']) + $this->mock('GET '.$url.' [cli]'); + else { header('Location: '.$url); $this->status($permanent?301:302); - die; + if ($die) + die; } - $this->mock('GET '.$url.' [cli]'); } /** @@ -1513,12 +1536,15 @@ final class Base extends Prefab implements ArrayAccess { $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: '. - ($cors['credentials']?'true':'false')); + var_export($cors['credentials'],TRUE)); + $preflight= + isset($this->hive['HEADERS']['Access-Control-Request-Method']); } $allowed=[]; foreach ($this->hive['ROUTES'] as $pattern=>$routes) { @@ -1532,7 +1558,7 @@ final class Base extends Prefab implements ArrayAccess { $route=$routes[$ptr]; if (!$route) continue; - if (isset($route[$this->hive['VERB']])) { + 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). @@ -1597,7 +1623,7 @@ final class Base extends Prefab implements ArrayAccess { $this->hive['BODY']=file_get_contents('php://input'); ob_start(); // Call route handler - $result=$this->call($handler,[$this,$args], + $result=$this->call($handler,[$this,$args,$handler], 'beforeroute,afterroute'); $body=ob_get_clean(); if (isset($cache) && !error_get_last()) { @@ -1697,12 +1723,13 @@ final class Base extends Prefab implements ArrayAccess { function abort() { if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) session_start(); - session_commit(); $out=''; while (ob_get_level()) $out=ob_get_clean().$out; + header('Content-Encoding: none'); header('Content-Length: '.strlen($out)); header('Connection: close'); + session_commit(); echo $out; flush(); if (function_exists('fastcgi_finish_request')) @@ -1818,46 +1845,51 @@ final class Base extends Prefab implements ArrayAccess { * Configure framework according to .ini-style file settings; * If optional 2nd arg is provided, template strings are interpreted * @return object - * @param $file string + * @param $source string|array * @param $allow bool **/ - function config($file,$allow=FALSE) { - preg_match_all( - '/(?<=^|\n)(?:'. - '\[(?
.+?)\]|'. - '(?[^\h\r\n;].*?)\h*=\h*'. - '(?(?:\\\\\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)'. - '((?:\.?\w)+)/i',$sec,$msec) && - !$this->exists($msec[0])) - $this->set($msec[0],NULL); - preg_match('/^(config|route|map|redirect)s\b|'. - '^((?:\.?\w)+)\s*\>\s*(.*)/i',$sec,$cmd); - } - else { - if ($allow) { - $match['lval']=Preview::instance()-> - resolve($match['lval']); - $match['rval']=Preview::instance()-> - resolve($match['rval']); + 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)(?:'. + '\[(?
.+?)\]|'. + '(?[^\h\r\n;].*?)\h*=\h*'. + '(?(?:\\\\\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)'. + '((?:\.?\w)+)/i',$sec,$msec) && + !$this->exists($msec[0])) + $this->set($msec[0],NULL); + preg_match('/^(config|route|map|redirect)s\b|'. + '^((?:\.?\w)+)\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( + 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($match['rval']))); + str_getcsv($this->cast($match['rval']))) + ); } else { $rval=preg_replace( @@ -1869,16 +1901,14 @@ final class Base extends Prefab implements ArrayAccess { } $args=array_map( function($val) { - if (is_numeric($val)) - return $val+0; - $val=trim($val); - if (preg_match('/^\w+$/i',$val) && - defined($val)) - return constant($val); - return preg_replace('/\\\\"/','"',$val); + $val=$this->cast($val); + return is_string($val) + ? preg_replace('/\\\\"/','"',$val) + : $val; }, // Mark quoted strings with 0x00 whitespace - str_getcsv(preg_replace('/(?[^:]+)(?:\:(?.+))?/', @@ -2123,8 +2153,10 @@ final class Base extends Prefab implements ArrayAccess { * @param $key string * @param $args array **/ - function __call($key,$args) { - return call_user_func_array($this->get($key),$args); + 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 @@ -2143,20 +2175,19 @@ final class Base extends Prefab implements ArrayAccess { @ini_set('register_globals',0); // Intercept errors/exceptions; PHP5.3-compatible $check=error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE)); - $fw=$this; set_exception_handler( - function($obj) use($fw) { - $fw->hive['EXCEPTION']=$obj; - $fw->error(500, + function($obj) { + $this->hive['EXCEPTION']=$obj; + $this->error(500, $obj->getmessage().' '. '['.$obj->getFile().':'.$obj->getLine().']', $obj->gettrace()); } ); set_error_handler( - function($level,$text) use($fw) { + function($level,$text,$file,$line) { if ($level & error_reporting()) - $fw->error(500,$text,NULL,$level); + $this->error(500,$text,NULL,$level); } ); if (!isset($_SERVER['SERVER_NAME'])) @@ -2168,23 +2199,28 @@ final class Base extends Prefab implements ArrayAccess { $_SERVER['argc']++; $_SERVER['argv'][1]='/'; } - if (substr($_SERVER['argv'][1],0,1)=='/') - $_SERVER['REQUEST_URI']=$_SERVER['argv'][1]; - else { - $req=$opts=''; + $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) - $opts.=($opts?'&':'').$k.'='; + $query.=($query?'&':'').urlencode($k).'='; if (isset($m[3])) - $opts.=$m[3]; + $query.=urlencode($m[3]); } else $req.='/'.$arg; } - $_SERVER['REQUEST_URI']=($req?:'/').'?'.urlencode($opts); - parse_str($opts,$GLOBALS['_GET']); + if (!$req) + $req='/'; + if ($query) + $req.='?'.$query; } + $_SERVER['REQUEST_URI']=$req; + parse_str($query,$GLOBALS['_GET']); } $headers=[]; if (!$cli) { @@ -2229,20 +2265,23 @@ final class Base extends Prefab implements ArrayAccess { if (!$cli) $base=rtrim($this->fixslashes( dirname($_SERVER['SCRIPT_NAME'])),'/'); - $uri=parse_url($_SERVER['REQUEST_URI']); + $uri=parse_url((preg_match('/^\w+:\/\//',$_SERVER['REQUEST_URI'])?'': + '//'.$_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']); session_cache_limiter(''); - call_user_func_array('session_set_cookie_params', - $jar=[ - 'expire'=>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 - ] - ); + $jar=[ + 'expire'=>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 + ]; + call_user_func_array('session_set_cookie_params',$jar); $port=80; if (isset($headers['X-Forwarded-Port'])) $port=$headers['X-Forwarded-Port']; @@ -2290,6 +2329,7 @@ final class Base extends Prefab implements ArrayAccess { $this->fallback, 'LOCALES'=>'./', 'LOGS'=>'./', + 'MB'=>extension_loaded('mbstring'), 'ONERROR'=>NULL, 'ONREROUTE'=>NULL, 'PACKAGE'=>self::PACKAGE, @@ -2376,7 +2416,7 @@ class Cache extends Prefab { switch ($parts[0]) { case 'apc': case 'apcu': - $raw=apc_fetch($ndx); + $raw=call_user_func($parts[0].'_fetch',$ndx); break; case 'redis': $raw=$this->ref->get($ndx); @@ -2427,9 +2467,9 @@ class Cache extends Prefab { switch ($parts[0]) { case 'apc': case 'apcu': - return apc_store($ndx,$data,$ttl); + return call_user_func($parts[0].'_store',$ndx,$data,$ttl); case 'redis': - return $this->ref->set($ndx,$data, $ttl ? ['ex'=>$ttl] : []); + return $this->ref->set($ndx,$data,$ttl?['ex'=>$ttl]:[]); case 'memcache': return memcache_set($this->ref,$ndx,$data,0,$ttl); case 'memcached': @@ -2439,7 +2479,7 @@ class Cache extends Prefab { case 'xcache': return xcache_set($ndx,$data,$ttl); case 'folder': - return $fw->write($parts[1].$ndx,$data); + return $fw->write($parts[1].str_replace(['/','\\'],'',$ndx),$data); } return FALSE; } @@ -2466,7 +2506,7 @@ class Cache extends Prefab { switch ($parts[0]) { case 'apc': case 'apcu': - return apc_delete($ndx); + return call_user_func($parts[0].'_delete',$ndx); case 'redis': return $this->ref->del($ndx); case 'memcache': @@ -2491,31 +2531,28 @@ class Cache extends Prefab { function reset($suffix=NULL) { if (!$this->dsn) return TRUE; - $regex='/'.preg_quote($this->prefix.'.','/').'.+'. + $regex='/'.preg_quote($this->prefix.'.','/').'.*'. preg_quote($suffix,'/').'/'; $parts=explode('=',$this->dsn,2); switch ($parts[0]) { case 'apc': case 'apcu': - $info=apc_cache_info('user'); + $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'; - $mtkey=array_key_exists('mtime',$info['cache_list'][0])? - 'mtime':'modification_time'; foreach ($info['cache_list'] as $item) if (preg_match($regex,$item[$key])) - apc_delete($item[$key]); + call_user_func($parts[0].'_delete',$item[$key]); } return TRUE; case 'redis': - $fw=Base::instance(); $keys=$this->ref->keys($this->prefix.'.*'.$suffix); foreach($keys as $key) $this->ref->del($key); return TRUE; case 'memcache': - $fw=Base::instance(); foreach (memcache_get_extended_stats( $this->ref,'slabs') as $slabs) foreach (array_filter(array_keys($slabs),'is_numeric') @@ -2528,8 +2565,7 @@ class Cache extends Prefab { memcache_delete($this->ref,$key); return TRUE; case 'memcached': - $fw=Base::instance(); - foreach ($this->ref->getallkeys() as $key) + foreach ($this->ref->getallkeys()?:[] as $key) if (preg_match($regex,$key)) $this->ref->delete($key); return TRUE; @@ -2540,7 +2576,16 @@ class Cache extends Prefab { wincache_ucache_delete($item['key_name']); return TRUE; case 'xcache': - xcache_unset_by_prefix($this->prefix.'.'); + 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].'*')) @@ -2556,8 +2601,9 @@ class Cache extends Prefab { * Load/auto-detect cache backend * @return string * @param $dsn bool|string + * @param $seed bool|string **/ - function load($dsn) { + function load($dsn,$seed=NULL) { $fw=Base::instance(); if ($dsn=trim($dsn)) { if (preg_match('/^redis=(.+)/',$dsn,$parts) && @@ -2592,18 +2638,17 @@ class Cache extends Prefab { // Auto-detect current($grep): // Use filesystem as fallback - ('folder='.$fw->get('TEMP').'cache/'); + ('folder='.$fw->TEMP.'cache/'); if (preg_match('/^folder\h*=\h*(.+)/',$dsn,$parts) && !is_dir($parts[1])) mkdir($parts[1],Base::MODE,TRUE); } - $this->prefix=$fw->get('SEED'); + $this->prefix=$seed?:$fw->SEED; return $this->dsn=$dsn; } /** * Class constructor - * @return object * @param $dsn bool|string **/ function __construct($dsn=FALSE) { @@ -2616,10 +2661,14 @@ class Cache extends Prefab { //! View handler class View extends Prefab { + private + //! Temporary hive + $temp; + protected //! Template file - $view, - //! post-rendering handler + $file, + //! Post-rendering handler $trigger, //! Nesting level $level=0; @@ -2652,24 +2701,6 @@ class View extends Prefab { ); } - /** - * Send resource to browser using HTTP/2 server push - * @return string - * @param $file string - **/ - function push($file) { - $fw=Base::instance(); - $hive=$fw->hive(); - if ($hive['SCHEME']=='https') { - $base=''; - if (!preg_match('/^[.\/]/',$file)) - $base=$hive['BASE'].'/'; - if (preg_match('/'.$key.'$/',$file)) - header('Link: '.'<'.$base.$file.'>; '.'rel=preload',FALSE); - } - return $file; - } - /** * Create sandbox for template execution * @return string @@ -2684,20 +2715,22 @@ class View extends Prefab { $hive=$fw->hive(); } if ($this->level<1 || $implicit) { - if (!$hive['CLI'] && !headers_sent() && + if (!$fw->CLI && $mime && !headers_sent() && !preg_grep ('/^Content-Type:/',headers_list())) - header('Content-Type: '.($mime?:'text/html').'; '. - 'charset='.$fw->get('ENCODING')); - if ($fw->get('ESCAPE')) + header('Content-Type: '.$mime.'; '. + 'charset='.$fw->ENCODING); + if ($fw->ESCAPE) $hive=$this->esc($hive); if (isset($hive['ALIASES'])) $hive['ALIASES']=$fw->build($hive['ALIASES']); } - extract($hive); + $this->temp=$hive; unset($fw,$hive,$implicit,$mime); + extract($this->temp); + $this->temp=NULL; $this->level++; ob_start(); - require($this->view); + require($this->file); $this->level--; return ob_get_clean(); } @@ -2710,21 +2743,21 @@ class View extends Prefab { * @param $hive array * @param $ttl int **/ - function render($file,$mime=NULL,array $hive=NULL,$ttl=0) { + function render($file,$mime='text/html',array $hive=NULL,$ttl=0) { $fw=Base::instance(); $cache=Cache::instance(); - if ($cache->exists($hash=$fw->hash($file),$data)) - return $data; - foreach ($fw->split($fw->get('UI')) as $dir) - if (is_file($this->view=$fw->fixslashes($dir.$file))) { + 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'])) + if (isset($this->trigger['afterrender'])) foreach($this->trigger['afterrender'] as $func) - $data=$fw->call($func,$data); + $data=$fw->call($func,[$data, $dir.$file]); if ($ttl) $cache->set($hash,$data,$ttl); return $data; @@ -2748,29 +2781,57 @@ class Preview extends View { protected //! token filter $filter=[ + 'c'=>'$this->c', 'esc'=>'$this->esc', 'raw'=>'$this->raw', - 'push'=>'$this->push', - 'alias'=>'\Base::instance()->alias', - 'format'=>'\Base::instance()->format' + '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) { + $fw=Base::instance(); + $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) { + $fw = Base::instance(); $str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'), - Base::instance()->compile($str))); + $fw->compile($str))); if (preg_match('/^(.+)(?split($parts[2]) as $func) + foreach ($fw->split($parts[2]) as $func) $str=is_string($cmd=$this->filter($func))? $cmd.'('.$str.')': - '\Base::instance()->call('. - '$this->filter(\''.$func.'\'),['.$str.'])'; + 'Base::instance()->'. + 'call($this->filter(\''.$func.'\'),['.$str.'])'; } return $str; } @@ -2784,6 +2845,7 @@ class Preview extends View { 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; @@ -2795,41 +2857,89 @@ class Preview extends View { * @param $node string **/ protected function build($node) { - $self=$this; return preg_replace_callback( - '/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n+)?|(\{\*.*?\*\})/s', - function($expr) use($self) { + '/\{~(.+?)~\}|\{\*(.+?)\*\}|\{\-(.+?)\-\}|'. + '\{\{(.+?)\}\}((\r?\n)*)/s', + function($expr) { if ($expr[1]) - return $expr[1]; - $str=trim($self->token($expr[2])); - return empty($expr[4])? - (''. - (isset($expr[3])?$expr[3]."\n":'')): - ''; + $str='token($expr[1]).' ?>'; + elseif ($expr[2]) + return ''; + elseif ($expr[3]) + $str=$expr[3]; + else { + $str='token($expr[4])).')'. + ($this->interpolation? + (!empty($expr[6])?'."'.$expr[6].'"':''):'').' ?>'; + if (isset($expr[5])) + $str.=$expr[5]; + } + return $str; }, - preg_replace_callback( - '/\{~(.+?)~\}/s', - function($expr) use($self) { - return 'token($expr[1]).' ?>'; - }, - $node - ) + $node ); } /** * Render template string * @return string - * @param $str string + * @param $node string|array * @param $hive array + * @param $ttl int + * @param $persist bool + * @param $escape bool **/ - function resolve($str,array $hive=NULL) { - if (!$hive) - $hive=\Base::instance()->hive(); - extract($hive); - ob_start(); - eval(' ?>'.$this->build($str).'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).'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); } /** @@ -2840,26 +2950,24 @@ class Preview extends View { * @param $hive array * @param $ttl int **/ - function render($file,$mime=NULL,array $hive=NULL,$ttl=0) { + function render($file,$mime='text/html',array $hive=NULL,$ttl=0) { $fw=Base::instance(); $cache=Cache::instance(); - if (!is_dir($tmp=$fw->get('TEMP'))) + if (!is_dir($tmp=$fw->TEMP)) mkdir($tmp,Base::MODE,TRUE); - foreach ($fw->split($fw->get('UI')) as $dir) { + 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->view=($tmp. - $fw->get('SEED').'.'.$fw->hash($view).'.php')) || - filemtime($this->view)\h*'. - '|\{\*.+?\*\}/is','', - $fw->read($view)); - if (method_exists($this,'parse')) - $text=$this->parse($text); - $fw->write($this->view,$this->build($text)); + if (!is_file($this->file=($tmp. + $fw->SEED.'.'.$fw->hash($view).'.php')) || + filemtime($this->file)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) @@ -2868,7 +2976,7 @@ class Preview extends View { $data=$this->sandbox($hive,$mime); if(isset($this->trigger['afterrender'])) foreach ($this->trigger['afterrender'] as $func) - $data = $fw->call($func, $data); + $data=$fw->call($func, [$data, $view]); if ($ttl) $cache->set($hash,$data,$ttl); return $data; @@ -2877,6 +2985,14 @@ class Preview extends View { 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 diff --git a/app/lib/basket.php b/app/lib/basket.php index 20a01b74..f939c7cd 100644 --- a/app/lib/basket.php +++ b/app/lib/basket.php @@ -193,7 +193,7 @@ class Basket extends Magic { **/ function copyfrom($var) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; foreach ($var as $key=>$val) $this->set($key,$val); } diff --git a/app/lib/db/cursor.php b/app/lib/db/cursor.php index b844e90c..6edbc0e0 100644 --- a/app/lib/db/cursor.php +++ b/app/lib/db/cursor.php @@ -143,24 +143,26 @@ abstract class Cursor extends \Magic implements \IteratorAggregate { * @param $filter string|array * @param $options array * @param $ttl int + * @param $bounce bool **/ function paginate( - $pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0) { + $pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) { $total=$this->count($filter,$options,$ttl); $count=ceil($total/$size); - $pos=max(0,min($pos,$count-1)); + if ($bounce) + $pos=max(0,min($pos,$count-1)); return [ - 'subset'=>$this->find($filter, + 'subset'=>($bounce || $pos<$count)?$this->find($filter, array_merge( $options?:[], ['limit'=>$size,'offset'=>$pos*$size] ), $ttl - ), + ):[], 'total'=>$total, 'limit'=>$size, 'count'=>$count, - 'pos'=>$pos<$count?$pos:0 + 'pos'=>$bounce?($pos<$count?$pos:0):$pos ]; } diff --git a/app/lib/db/jig.php b/app/lib/db/jig.php index 4221448a..d0730144 100644 --- a/app/lib/db/jig.php +++ b/app/lib/db/jig.php @@ -80,7 +80,7 @@ class Jig { $fw=\Base::instance(); switch ($this->format) { case self::FORMAT_JSON: - $out=json_encode($data,@constant('JSON_PRETTY_PRINT')); + $out=json_encode($data,JSON_PRETTY_PRINT); break; case self::FORMAT_Serialized: $out=$fw->serialize($data); diff --git a/app/lib/db/jig/mapper.php b/app/lib/db/jig/mapper.php index 860c92a9..135dd612 100644 --- a/app/lib/db/jig/mapper.php +++ b/app/lib/db/jig/mapper.php @@ -120,21 +120,20 @@ class Mapper extends \DB\Cursor { * @param $str string **/ function token($str) { - $self=$this; $str=preg_replace_callback( '/(?stringify(substr($expr[1],1)): (preg_match('/^\w+/', - $mix=$self->token($expr[2]))? + $mix=$this->token($expr[2]))? $fw->stringify($mix): $mix)). ']'; @@ -168,7 +167,7 @@ class Mapper extends \DB\Cursor { $db=$this->db; $now=microtime(TRUE); $data=[]; - if (!$fw->get('CACHE') || !$ttl || !($cached=$cache->exists( + if (!$fw->CACHE || !$ttl || !($cached=$cache->exists( $hash=$fw->hash($this->db->dir(). $fw->stringify([$filter,$options])).'.jig',$data)) || $cached[0]+$ttlget('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$data,$ttl); } @@ -367,15 +366,16 @@ class Mapper extends \DB\Cursor { * Delete current record * @return bool * @param $filter array + * @param $quick bool **/ - function erase($filter=NULL) { + function erase($filter=NULL,$quick=FALSE) { $db=$this->db; $now=microtime(TRUE); $data=&$db->read($this->file); $pkey=['_id'=>$this->id]; if ($filter) { foreach ($this->find($filter,NULL,FALSE) as $mapper) - if (!$mapper->erase()) + if (!$mapper->erase(null,$quick)) return FALSE; return TRUE; } @@ -385,7 +385,7 @@ class Mapper extends \DB\Cursor { } else return FALSE; - if (isset($this->trigger['beforeerase']) && + if (!$quick && isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], [$this,$pkey])===FALSE) return FALSE; @@ -404,7 +404,7 @@ class Mapper extends \DB\Cursor { $db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. $this->file.' [erase] '. ($filter?preg_replace($keys,$vals,$filter[0],1):'')); - if (isset($this->trigger['aftererase'])) + if (!$quick && isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], [$this,$pkey]); return TRUE; @@ -428,7 +428,7 @@ class Mapper extends \DB\Cursor { **/ function copyfrom($var,$func=NULL) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/app/lib/db/jig/session.php b/app/lib/db/jig/session.php index 5ebb2069..e7fecb3a 100644 --- a/app/lib/db/jig/session.php +++ b/app/lib/db/jig/session.php @@ -70,10 +70,11 @@ class Session extends Mapper { $fw=\Base::instance(); if (!isset($this->onsuspect) || $fw->call($this->onsuspect,[$this,$id])===FALSE) { - //NB: `session_destroy` can't be called at that stage (`session_start` not completed) + // NB: `session_destroy` can't be called at that stage; + // `session_start` not completed $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -178,12 +179,12 @@ class Session extends Mapper { ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/app/lib/db/mongo/mapper.php b/app/lib/db/mongo/mapper.php index 85f68129..f038b6c0 100644 --- a/app/lib/db/mongo/mapper.php +++ b/app/lib/db/mongo/mapper.php @@ -146,7 +146,7 @@ class Mapper extends \DB\Cursor { ] ); $tmp=$this->db->selectcollection( - $fw->get('HOST').'.'.$fw->get('BASE').'.'. + $fw->HOST.'.'.$fw->BASE.'.'. uniqid(NULL,TRUE).'.tmp' ); $tmp->batchinsert($grp['retval'],['w'=>1]); @@ -179,7 +179,7 @@ class Mapper extends \DB\Cursor { } if ($options['group']) $tmp->drop(); - if ($fw->get('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } @@ -222,7 +222,7 @@ class Mapper extends \DB\Cursor { [$filter])).'.mongo',$result)) || !$ttl || $cached[0]+$ttlcollection->count($filter?:[]); - if ($fw->get('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } @@ -292,10 +292,17 @@ class Mapper extends \DB\Cursor { /** * Delete current record * @return bool + * @param $quick bool * @param $filter array **/ - function erase($filter=NULL) { + function erase($filter=NULL,$quick=TRUE) { if ($filter) { + if (!$quick) { + foreach ($this->find($filter) as $mapper) + if (!$mapper->erase()) + return FALSE; + return TRUE; + } return $this->legacy? $this->collection->remove($filter): $this->collection->deletemany($filter); @@ -332,7 +339,7 @@ class Mapper extends \DB\Cursor { **/ function copyfrom($var,$func=NULL) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/app/lib/db/mongo/session.php b/app/lib/db/mongo/session.php index 09f123a9..98bafd63 100644 --- a/app/lib/db/mongo/session.php +++ b/app/lib/db/mongo/session.php @@ -70,10 +70,11 @@ class Session extends Mapper { $fw=\Base::instance(); if (!isset($this->onsuspect) || $fw->call($this->onsuspect,[$this,$id])===FALSE) { - //NB: `session_destroy` can't be called at that stage (`session_start` not completed) + // NB: `session_destroy` can't be called at that stage; + // `session_start` not completed $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -178,12 +179,12 @@ class Session extends Mapper { ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/app/lib/db/sql.php b/app/lib/db/sql.php index 005d6430..39e77f0c 100644 --- a/app/lib/db/sql.php +++ b/app/lib/db/sql.php @@ -124,7 +124,7 @@ class SQL { $val=str_replace(',','.',$val); return $val; case \PDO::PARAM_NULL: - return (unset)$val; + return NULL; case \PDO::PARAM_INT: return (int)$val; case \PDO::PARAM_BOOL: @@ -185,7 +185,7 @@ class SQL { continue; $now=microtime(TRUE); $keys=$vals=[]; - if ($fw->get('CACHE') && $ttl && ($cached=$cache->exists( + if ($fw->CACHE && $ttl && ($cached=$cache->exists( $hash=$fw->hash($this->dsn.$cmd. $fw->stringify($arg)).($tag?'.'.$tag:'').'.sql',$result)) && $cached[0]+$ttl>microtime(TRUE)) { @@ -220,13 +220,15 @@ class SQL { '/'; } if ($log) - $this->log.=($stamp?(date('r').' '):'').'('. - sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. + $this->log.=($stamp?(date('r').' '):'').' (-0ms) '. preg_replace($keys,$vals, str_replace('?',chr(0).'?',$cmd),1).PHP_EOL; $query->execute(); - $error=$query->errorinfo(); - if ($error[0]!=\PDO::ERR_NONE) { + if ($log) + $this->log=str_replace('(-0ms)', + '('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms)', + $this->log); + if (($error=$query->errorinfo()) && $error[0]!=\PDO::ERR_NONE) { // Statement-level error occurred if ($this->trans) $this->rollback(); @@ -246,7 +248,7 @@ class SQL { $result[$pos][trim($key,'\'"[]`')]=$val; } $this->rows=count($result); - if ($fw->get('CACHE') && $ttl) + if ($fw->CACHE && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); } @@ -255,15 +257,13 @@ class SQL { $query->closecursor(); unset($query); } - else { - $error=$this->errorinfo(); - if ($error[0]!=\PDO::ERR_NONE) { - // PDO-level error occurred - if ($this->trans) - $this->rollback(); - user_error('PDO: '.$error[2],E_USER_ERROR); - } + elseif (($error=$this->errorinfo()) && $error[0]!=\PDO::ERR_NONE) { + // PDO-level error occurred + if ($this->trans) + $this->rollback(); + user_error('PDO: '.$error[2],E_USER_ERROR); } + } if ($this->trans && $auto) $this->commit(); @@ -280,8 +280,8 @@ class SQL { /** * Return SQL profiler results (or disable logging) - * @param $flag bool * @return string + * @param $flag bool **/ function log($flag=TRUE) { if ($flag) @@ -289,6 +289,20 @@ class SQL { $this->log=FALSE; } + /** + * Return TRUE if table exists + * @return bool + * @param $table string + **/ + function exists($table) { + $mode=$this->pdo->getAttribute(\PDO::ATTR_ERRMODE); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_SILENT); + $out=$this->pdo-> + query('SELECT 1 FROM '.$this->quotekey($table).' LIMIT 1'); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE,$mode); + return is_object($out); + } + /** * Retrieve schema of SQL table * @return array|FALSE @@ -297,43 +311,50 @@ class SQL { * @param $ttl int|array **/ function schema($table,$fields=NULL,$ttl=0) { + $fw=\Base::instance(); + $cache=\Cache::instance(); + if ($fw->CACHE && $ttl && + ($cached=$cache->exists( + $hash=$fw->hash($this->dsn.$table).'.schema',$result)) && + $cached[0]+$ttl>microtime(TRUE)) + return $result; if (strpos($table,'.')) list($schema,$table)=explode('.',$table); // Supported engines $cmd=[ 'sqlite2?'=>[ - 'PRAGMA table_info("'.$table.'")', + 'PRAGMA table_info(`'.$table.'`)', 'name','type','dflt_value','notnull',0,'pk',TRUE], 'mysql'=>[ 'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`', 'Field','Type','Default','Null','YES','Key','PRI'], 'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>[ 'SELECT '. - 'c.column_name AS field,'. - 'c.data_type AS type,'. - 'c.column_default AS defval,'. - 'c.is_nullable AS nullable,'. - 't.constraint_type AS pkey '. - 'FROM information_schema.columns AS c '. + 'C.COLUMN_NAME AS field,'. + 'C.DATA_TYPE AS type,'. + 'C.COLUMN_DEFAULT AS defval,'. + 'C.IS_NULLABLE AS nullable,'. + 'T.CONSTRAINT_TYPE AS pkey '. + 'FROM INFORMATION_SCHEMA.COLUMNS AS C '. 'LEFT OUTER JOIN '. - 'information_schema.key_column_usage AS k '. + 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K '. 'ON '. - 'c.table_name=k.table_name AND '. - 'c.column_name=k.column_name AND '. - 'c.table_schema=k.table_schema '. + 'C.TABLE_NAME=K.TABLE_NAME AND '. + 'C.COLUMN_NAME=K.COLUMN_NAME AND '. + 'C.TABLE_SCHEMA=K.TABLE_SCHEMA '. ($this->dbname? - ('AND c.table_catalog=k.table_catalog '):''). + ('AND C.TABLE_CATALOG=K.TABLE_CATALOG '):''). 'LEFT OUTER JOIN '. - 'information_schema.table_constraints AS t ON '. - 'k.table_name=t.table_name AND '. - 'k.constraint_name=t.constraint_name AND '. - 'k.table_schema=t.table_schema '. + 'INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS T ON '. + 'K.TABLE_NAME=T.TABLE_NAME AND '. + 'K.CONSTRAINT_NAME=T.CONSTRAINT_NAME AND '. + 'K.TABLE_SCHEMA=T.TABLE_SCHEMA '. ($this->dbname? - ('AND k.table_catalog=t.table_catalog '):''). + ('AND K.TABLE_CATALOG=T.TABLE_CATALOG '):''). 'WHERE '. - 'c.table_name='.$this->quote($table). + 'C.TABLE_NAME='.$this->quote($table). ($this->dbname? - (' AND c.table_catalog='. + (' AND C.TABLE_CATALOG='. $this->quote($this->dbname)):''), 'field','type','defval','nullable','YES','pkey','PRIMARY KEY'], 'oci'=>[ @@ -354,32 +375,34 @@ class SQL { ]; if (is_string($fields)) $fields=\Base::instance()->split($fields); + $conv=[ + 'int\b|integer'=>\PDO::PARAM_INT, + 'bool'=>\PDO::PARAM_BOOL, + 'blob|bytea|image|binary'=>\PDO::PARAM_LOB, + 'float|real|double|decimal|numeric'=>self::PARAM_FLOAT, + '.+'=>\PDO::PARAM_STR + ]; foreach ($cmd as $key=>$val) if (preg_match('/'.$key.'/',$this->engine)) { $rows=[]; - foreach ($this->exec($val[0],NULL,$ttl) as $row) { - if (!$fields || in_array($row[$val[1]],$fields)) + foreach ($this->exec($val[0],NULL) as $row) + if (!$fields || in_array($row[$val[1]],$fields)) { + foreach ($conv as $regex=>$type) + if (preg_match('/'.$regex.'/i',$row[$val[2]])) + break; $rows[$row[$val[1]]]=[ 'type'=>$row[$val[2]], - 'pdo_type'=> - preg_match('/int\b|integer/i',$row[$val[2]])? - \PDO::PARAM_INT: - (preg_match('/bool/i',$row[$val[2]])? - \PDO::PARAM_BOOL: - (preg_match( - '/blob|bytea|image|binary/i', - $row[$val[2]])?\PDO::PARAM_LOB: - (preg_match( - '/float|decimal|real|numeric|double/i', - $row[$val[2]])?self::PARAM_FLOAT: - \PDO::PARAM_STR))), + 'pdo_type'=>$type, 'default'=>is_string($row[$val[3]])? preg_replace('/^\s*([\'"])(.*)\1\s*/','\2', $row[$val[3]]):$row[$val[3]], 'nullable'=>$row[$val[4]]==$val[5], 'pkey'=>$row[$val[6]]==$val[7] ]; - } + } + if ($fw->CACHE && $ttl) + // Save to cache backend + $cache->set($hash,$rows,$ttl); return $rows; } user_error(sprintf(self::E_PKey,$table),E_USER_ERROR); @@ -448,8 +471,8 @@ class SQL { **/ function quotekey($key, $split=TRUE) { $delims=[ - 'mysql'=>'``', - 'sqlite2?|pgsql|oci'=>'""', + 'sqlite2?|mysql'=>'``', + 'pgsql|oci'=>'""', 'mssql|sqlsrv|odbc|sybase|dblib'=>'[]' ]; $use=''; @@ -492,7 +515,7 @@ class SQL { $options=[]; if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql') $options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '. - strtolower(str_replace('-','',$fw->get('ENCODING'))).';']; + strtolower(str_replace('-','',$fw->ENCODING)).';']; $this->pdo=new \PDO($dsn,$user,$pw,$options); $this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME); } diff --git a/app/lib/db/sql/mapper.php b/app/lib/db/sql/mapper.php index 5548d36a..f78fc36c 100644 --- a/app/lib/db/sql/mapper.php +++ b/app/lib/db/sql/mapper.php @@ -39,7 +39,9 @@ class Mapper extends \DB\Cursor { //! Defined fields $fields, //! Adhoc fields - $adhoc=[]; + $adhoc=[], + //! Dynamic properties + $props=[]; /** * Return database type @@ -57,15 +59,6 @@ class Mapper extends \DB\Cursor { return $this->source; } - /** - * Return TRUE if field is defined - * @return bool - * @param $key string - **/ - function exists($key) { - return array_key_exists($key,$this->fields+$this->adhoc); - } - /** * Return TRUE if any/specified field value has changed * @return bool @@ -80,6 +73,15 @@ class Mapper extends \DB\Cursor { return FALSE; } + /** + * Return TRUE if field is defined + * @return bool + * @param $key string + **/ + function exists($key) { + return array_key_exists($key,$this->fields+$this->adhoc); + } + /** * Assign value to field * @return scalar @@ -95,12 +97,14 @@ class Mapper extends \DB\Cursor { $this->fields[$key]['changed']=TRUE; return $this->fields[$key]['value']=$val; } - // adjust result on existing expressions + // Adjust result on existing expressions if (isset($this->adhoc[$key])) $this->adhoc[$key]['value']=$val; - else + elseif (is_string($val)) // Parenthesize expression in case it's a subquery $this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL]; + else + $this->props[$key]=$val; return $val; } @@ -116,6 +120,8 @@ class Mapper extends \DB\Cursor { return $this->fields[$key]['value']; elseif (array_key_exists($key,$this->adhoc)) return $this->adhoc[$key]['value']; + elseif (array_key_exists($key,$this->props)) + return $this->props[$key]; user_error(sprintf(self::E_Field,$key),E_USER_ERROR); } @@ -127,26 +133,22 @@ class Mapper extends \DB\Cursor { function clear($key) { if (array_key_exists($key,$this->adhoc)) unset($this->adhoc[$key]); + else + unset($this->props[$key]); } /** - * Get PHP type equivalent of PDO constant - * @return string - * @param $pdo string + * Invoke dynamic method + * @return mixed + * @param $func string + * @param $args array **/ - function type($pdo) { - switch ($pdo) { - case \PDO::PARAM_NULL: - return 'unset'; - case \PDO::PARAM_INT: - return 'int'; - case \PDO::PARAM_BOOL: - return 'bool'; - case \PDO::PARAM_STR: - return 'string'; - case \DB\SQL::PARAM_FLOAT: - return 'float'; - } + function __call($func,$args) { + return call_user_func_array( + (array_key_exists($func,$this->props)? + $this->props[$func]: + $this->$func),$args + ); } /** @@ -192,14 +194,13 @@ class Mapper extends \DB\Cursor { } /** - * Build query string and execute - * @return static[] + * Build query string and arguments + * @return array * @param $fields string * @param $filter string|array * @param $options array - * @param $ttl int|array **/ - function select($fields,$filter=NULL,array $options=NULL,$ttl=0) { + function stringify($fields,$filter=NULL,array $options=NULL) { if (!$options) $options=[]; $options+=[ @@ -224,7 +225,7 @@ class Mapper extends \DB\Cursor { $sql.=' GROUP BY '.implode(',',array_map( function($str) use($db) { return preg_replace_callback( - '/\b(\w+)\h*(HAVING.+|$)/i', + '/\b(\w+[._\-\w]*)\h*(HAVING.+|$)/i', function($parts) use($db) { return $db->quotekey($parts[1]). (isset($parts[2])?(' '.$parts[2]):''); @@ -235,47 +236,63 @@ class Mapper extends \DB\Cursor { explode(',',$options['group']))); } if ($options['order']) { - $sql.=' ORDER BY '.implode(',',array_map( + $order=' ORDER BY '.implode(',',array_map( function($str) use($db) { - return preg_match('/^(\w+)(?:\h+(ASC|DESC))?\h*(?:,|$)/i', + return preg_match('/^\h*(\w+[._\-\w]*)(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i', $str,$parts)? ($db->quotekey($parts[1]). (isset($parts[2])?(' '.$parts[2]):'')):$str; }, explode(',',$options['order']))); } + // SQL Server fixes if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) && ($options['limit'] || $options['offset'])) { - $pkeys=[]; - foreach ($this->fields as $key=>$field) - if ($field['pkey']) - $pkeys[]=$key; + // order by pkey when no ordering option was given + if (!$options['order']) + foreach ($this->fields as $key=>$field) + if ($field['pkey']) { + $order=' ORDER BY '.$db->quotekey($key); + break; + } $ofs=$options['offset']?(int)$options['offset']:0; $lmt=$options['limit']?(int)$options['limit']:0; if (strncmp($db->version(),'11',2)>=0) { - // SQL Server 2012 - if (!$options['order']) - $sql.=' ORDER BY '.$db->quotekey($pkeys[0]); - $sql.=' OFFSET '.$ofs.' ROWS'; + // SQL Server >= 2012 + $sql.=$order.' OFFSET '.$ofs.' ROWS'; if ($lmt) $sql.=' FETCH NEXT '.$lmt.' ROWS ONLY'; } else { // SQL Server 2008 - $sql=str_replace('SELECT', + $sql=preg_replace('/SELECT/', 'SELECT '. ($lmt>0?'TOP '.($ofs+$lmt):'').' ROW_NUMBER() '. - 'OVER (ORDER BY '. - $db->quotekey($pkeys[0]).') AS rnum,',$sql); + 'OVER ('.$order.') AS rnum,',$sql.$order,1); $sql='SELECT * FROM ('.$sql.') x WHERE rnum > '.($ofs); } } else { + if (isset($order)) + $sql.=$order; if ($options['limit']) $sql.=' LIMIT '.(int)$options['limit']; if ($options['offset']) $sql.=' OFFSET '.(int)$options['offset']; } + return [$sql,$args]; + } + + /** + * Build query string and execute + * @return object + * @param $fields string + * @param $filter string|array + * @param $options array + * @param $ttl int|array + **/ + function select($fields,$filter=NULL,array $options=NULL,$ttl=0) { + list($sql,$args)=$this->stringify($fields,$filter,$options); $result=$this->db->exec($sql,$args,$ttl); $out=[]; foreach ($result as &$row) { @@ -285,8 +302,6 @@ class Mapper extends \DB\Cursor { $val=$this->db->value( $this->fields[$field]['pdo_type'],$val); } - elseif (array_key_exists($field,$this->adhoc)) - $this->adhoc[$field]['value']=$val; unset($val); } $out[]=$this->factory($row); @@ -329,14 +344,17 @@ class Mapper extends \DB\Cursor { * @param $ttl int|array **/ function count($filter=NULL,array $options=NULL,$ttl=0) { - $expr='COUNT(*)'; - $field='rows'; - $this->adhoc[$field]=['expr'=>$expr,'value'=>NULL]; - $result=$this->select($expr.' AS '.$this->db->quotekey($field), - $filter,$options,$ttl); - $out=$result[0]->adhoc[$field]['value']; - unset($this->adhoc[$field]); - return $out; + $adhoc=''; + foreach ($this->adhoc as $key=>$field) + $adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key); + $fields='*'.$adhoc; + if (preg_match('/mssql|dblib|sqlsrv/',$this->engine)) + $fields='TOP 100 PERCENT '.$fields; + list($sql,$args)=$this->stringify($fields,$filter,$options); + $sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '. + 'FROM ('.$sql.') AS '.$this->db->quotekey('_temp'); + $result=$this->db->exec($sql,$args,$ttl); + return (int)$result[0]['_rows']; } /** @@ -404,28 +422,27 @@ class Mapper extends \DB\Cursor { $actr++; $ckeys[]=$key; } - $field['changed']=FALSE; - unset($field); } if ($fields) { - $this->db->exec( + $add=''; + if ($this->engine=='pgsql') { + $names=array_keys($pkeys); + $aik=end($names); + $add=' RETURNING '.$this->db->quotekey($aik); + } + $lID=$this->db->exec( (preg_match('/mssql|dblib|sqlsrv/',$this->engine) && array_intersect(array_keys($pkeys),$ckeys)? 'SET IDENTITY_INSERT '.$this->table.' ON;':''). 'INSERT INTO '.$this->table.' ('.$fields.') '. - 'VALUES ('.$values.')',$args + 'VALUES ('.$values.')'.$add,$args ); - $seq=NULL; - if ($this->engine=='pgsql') { - $names=array_keys($pkeys); - $aik=end($names); - if ($this->fields[$aik]['pdo_type']==\PDO::PARAM_INT) - $seq=$this->source.'_'.$aik.'_seq'; - } - if ($this->engine!='oci' && !($this->engine=='pgsql' && !$seq)) - $this->_id=$this->db->lastinsertid($seq); + if ($this->engine=='pgsql' && $lID) + $this->_id=$lID[0][$aik]; + elseif ($this->engine!='oci') + $this->_id=$this->db->lastinsertid(); // Reload to obtain default and auto-increment field values - if ($inc || $filter) + if ($reload=($inc || $filter)) $this->load($inc? [$inc.'=?',$this->db->value( $this->fields[$inc]['pdo_type'],$this->_id)]: @@ -433,6 +450,13 @@ class Mapper extends \DB\Cursor { if (isset($this->trigger['afterinsert'])) \Base::instance()->call($this->trigger['afterinsert'], [$this,$pkeys]); + // reset changed flag after calling afterinsert + if (!$reload) + foreach ($this->fields as $key=>&$field) { + $field['changed']=FALSE; + $field['initial']=$field['value']; + unset($field); + } } return $this; } @@ -484,10 +508,17 @@ class Mapper extends \DB\Cursor { /** * Delete current record * @return int + * @param $quick bool * @param $filter string|array **/ - function erase($filter=NULL) { + function erase($filter=NULL,$quick=TRUE) { if (isset($filter)) { + if (!$quick) { + $out=0; + foreach ($this->find($filter) as $mapper) + $out+=$mapper->erase(); + return $out; + } $args=[]; if (is_array($filter)) { $args=isset($filter[1]) && is_array($filter[1])? @@ -497,7 +528,8 @@ class Mapper extends \DB\Cursor { list($filter)=$filter; } return $this->db-> - exec('DELETE FROM '.$this->table.($filter?' WHERE '.$filter:'').';',$args); + exec('DELETE FROM '.$this->table. + ($filter?' WHERE '.$filter:'').';',$args); } $args=[]; $ctr=0; @@ -561,7 +593,7 @@ class Mapper extends \DB\Cursor { **/ function copyfrom($var,$func=NULL) { if (is_string($var)) - $var=\Base::instance()->get($var); + $var=\Base::instance()->$var; if ($func) $var=call_user_func($func,$var); foreach ($var as $key=>$val) diff --git a/app/lib/db/sql/session.php b/app/lib/db/sql/session.php index d02f46e2..799cd0ff 100644 --- a/app/lib/db/sql/session.php +++ b/app/lib/db/sql/session.php @@ -73,7 +73,7 @@ class Session extends Mapper { //NB: `session_destroy` can't be called at that stage (`session_start` not completed) $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -170,8 +170,9 @@ class Session extends Mapper { if ($force) { $eol="\n"; $tab="\t"; + $sqlsrv=preg_match('/mssql|sqlsrv|sybase/',$db->driver()); $db->exec( - (preg_match('/mssql|sqlsrv|sybase/',$db->driver())? + ($sqlsrv? ('IF NOT EXISTS (SELECT * FROM sysobjects WHERE '. 'name='.$db->quote($table).' AND xtype=\'U\') '. 'CREATE TABLE dbo.'): @@ -179,12 +180,14 @@ class Session extends Mapper { ((($name=$db->name())&&$db->driver()!='pgsql')? ($db->quotekey($name,FALSE).'.'):''))). $db->quotekey($table,FALSE).' ('.$eol. + ($sqlsrv?$tab.$db->quotekey('id').' INT IDENTITY,'.$eol:''). $tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol. $tab.$db->quotekey('data').' TEXT,'.$eol. $tab.$db->quotekey('ip').' VARCHAR(45),'.$eol. $tab.$db->quotekey('agent').' VARCHAR(300),'.$eol. $tab.$db->quotekey('stamp').' INTEGER,'.$eol. - $tab.'PRIMARY KEY ('.$db->quotekey('session_id').')'.$eol. + $tab.'PRIMARY KEY ('.$db->quotekey($sqlsrv?'id':'session_id').')'.$eol. + ($sqlsrv?',CONSTRAINT [UK_session_id] UNIQUE(session_id)':''). ');' ); } @@ -200,12 +203,15 @@ class Session extends Mapper { ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + if (strlen($this->_agent) > 300) { + $this->_agent = substr($this->_agent, 0, 300); + } + $this->_ip=$fw->IP; } } diff --git a/app/lib/image.php b/app/lib/image.php index ad63bf87..3efc0cca 100644 --- a/app/lib/image.php +++ b/app/lib/image.php @@ -409,7 +409,7 @@ class Image { return FALSE; } $fw=Base::instance(); - foreach ($fw->split($path?:$fw->get('UI').';./') as $dir) + foreach ($fw->split($path?:$fw->UI.';./') as $dir) if (is_file($path=$dir.$font)) { $seed=strtoupper(substr( $ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(), @@ -448,7 +448,7 @@ class Image { } imagesavealpha($this->data,TRUE); if ($key) - $fw->set($key,$seed); + $fw->$key=$seed; return $this->save(); } user_error(self::E_Font,E_USER_ERROR); @@ -480,7 +480,7 @@ class Image { $format=$args?array_shift($args):'png'; if (PHP_SAPI!='cli') { header('Content-Type: image/'.$format); - header('X-Powered-By: '.Base::instance()->get('PACKAGE')); + header('X-Powered-By: '.Base::instance()->PACKAGE); } call_user_func_array( 'image'.$format, @@ -518,10 +518,10 @@ class Image { function save() { $fw=Base::instance(); if ($this->flag) { - if (!is_dir($dir=$fw->get('TEMP'))) + if (!is_dir($dir=$fw->TEMP)) mkdir($dir,Base::MODE,TRUE); $this->count++; - $fw->write($dir.'/'.$fw->get('SEED').'.'. + $fw->write($dir.'/'.$fw->SEED.'.'. $fw->hash($this->file).'-'.$this->count.'.png', $this->dump()); } @@ -535,8 +535,8 @@ class Image { **/ function restore($state=1) { $fw=Base::instance(); - if ($this->flag && is_file($file=($path=$fw->get('TEMP'). - $fw->get('SEED').'.'.$fw->hash($this->file).'-').$state.'.png')) { + if ($this->flag && is_file($file=($path=$fw->TEMP. + $fw->SEED.'.'.$fw->hash($this->file).'-').$state.'.png')) { if (is_resource($this->data)) imagedestroy($this->data); $this->data=imagecreatefromstring($fw->read($file)); @@ -589,7 +589,7 @@ class Image { // Create image from file $this->file=$file; if (!isset($path)) - $path=$fw->get('UI').';./'; + $path=$fw->UI.';./'; foreach ($fw->split($path,FALSE) as $dir) if (is_file($dir.$file)) return $this->load($fw->read($dir.$file)); @@ -605,7 +605,7 @@ class Image { if (is_resource($this->data)) { imagedestroy($this->data); $fw=Base::instance(); - $path=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash($this->file); + $path=$fw->TEMP.$fw->SEED.'.'.$fw->hash($this->file); if ($glob=@glob($path.'*.png',GLOB_NOSORT)) foreach ($glob as $match) if (preg_match('/-(\d+)\.png/',$match)) diff --git a/app/lib/log.php b/app/lib/log.php index 19337218..422baf25 100644 --- a/app/lib/log.php +++ b/app/lib/log.php @@ -59,7 +59,7 @@ class Log { **/ function __construct($file) { $fw=Base::instance(); - if (!is_dir($dir=$fw->get('LOGS'))) + if (!is_dir($dir=$fw->LOGS)) mkdir($dir,Base::MODE,TRUE); $this->file=$dir.$file; } diff --git a/app/lib/markdown.php b/app/lib/markdown.php index e05ea791..18995fb7 100644 --- a/app/lib/markdown.php +++ b/app/lib/markdown.php @@ -64,7 +64,7 @@ class Markdown extends Prefab { protected function _fence($hint,$str) { $str=$this->snip($str); $fw=Base::instance(); - if ($fw->get('HIGHLIGHT')) { + if ($fw->HIGHLIGHT) { switch (strtolower($hint)) { case 'php': $str=$fw->highlight($str); @@ -288,20 +288,19 @@ class Markdown extends Prefab { if (strlen($str)) { if (preg_match('/^(.+?\n)([>#].+)$/s',$str,$parts)) return $this->_p($parts[1]).$this->build($parts[2]); - $self=$this; $str=preg_replace_callback( '/([^<>\[]+)?(<[\?%].+?[\?%]>|<.+?>|\[.+?\]\s*\(.+?\))|'. '(.+)/s', - function($expr) use($self) { + function($expr) { $tmp=''; if (isset($expr[4])) - $tmp.=$self->esc($expr[4]); + $tmp.=$this->esc($expr[4]); else { if (isset($expr[1])) - $tmp.=$self->esc($expr[1]); + $tmp.=$this->esc($expr[1]); $tmp.=$expr[2]; if (isset($expr[3])) - $tmp.=$self->esc($expr[3]); + $tmp.=$this->esc($expr[3]); } return $tmp; }, @@ -347,17 +346,16 @@ class Markdown extends Prefab { * @param $str string **/ protected function _img($str) { - $self=$this; return preg_replace_callback( '/!(?:\[(.+?)\])?\h*\(?(?:\h*"(.*?)"\h*)?\)/', - function($expr) use($self) { + function($expr) { return ''.$self->esc($expr[1]).''; + (' title="'.$this->esc($expr[3]).'"')).' />'; }, $str ); @@ -369,15 +367,14 @@ class Markdown extends Prefab { * @param $str string **/ protected function _a($str) { - $self=$this; return preg_replace_callback( '/(??(?:\h*"(.*?)"\h*)?\)/', - function($expr) use($self) { - return ''.$self->scan($expr[1]).''; + (' title="'.$this->esc($expr[3]).'"')). + '>'.$this->scan($expr[1]).''; }, $str ); @@ -389,12 +386,11 @@ class Markdown extends Prefab { * @param $str string **/ protected function _auto($str) { - $self=$this; return preg_replace_callback( '/`.*?<(.+?)>.*?`|<(.+?)>/', - function($expr) use($self) { + function($expr) { if (empty($expr[1]) && parse_url($expr[2],PHP_URL_SCHEME)) { - $expr[2]=$self->esc($expr[2]); + $expr[2]=$this->esc($expr[2]); return ''.$expr[2].''; } return $expr[0]; @@ -409,12 +405,11 @@ class Markdown extends Prefab { * @param $str string **/ protected function _code($str) { - $self=$this; return preg_replace_callback( '/`` (.+?) ``|(?'. - $self->esc(empty($expr[1])?$expr[2]:$expr[1]).''; + $this->esc(empty($expr[1])?$expr[2]:$expr[1]).''; }, $str ); @@ -436,7 +431,7 @@ class Markdown extends Prefab { foreach ($this->special as $key=>$val) $str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str); return htmlspecialchars($str,ENT_COMPAT, - Base::instance()->get('ENCODING'),FALSE); + Base::instance()->ENCODING,FALSE); } /** @@ -488,7 +483,6 @@ class Markdown extends Prefab { 'p'=>'/^(.+?(?:\n{2,}|\n*$))/s' ]; } - $self=$this; // Treat lines with nothing but whitespaces as empty lines $str=preg_replace('/\n\h+(?=\n)/',"\n",$str); // Initialize block parser @@ -509,16 +503,16 @@ class Markdown extends Prefab { '/(?esc($match[2]).'"'. + (''. + $this->esc($match[3]).'"')).'>'. // Link - $self->scan( + $this->scan( empty($expr[3])? (empty($expr[1])? $expr[4]: @@ -530,11 +524,11 @@ class Markdown extends Prefab { (empty($expr[2])? '': (' alt="'. - $self->esc($expr[3]).'"')). + $this->esc($expr[3]).'"')). (empty($match[3])? '': (' title="'. - $self->esc($match[3]).'"')). + $this->esc($match[3]).'"')). ' />'); }, $tmp=$dst diff --git a/app/lib/session.php b/app/lib/session.php index 7df365e9..3434f8d2 100644 --- a/app/lib/session.php +++ b/app/lib/session.php @@ -72,7 +72,7 @@ class Session { //NB: `session_destroy` can't be called at that stage (`session_start` not completed) $this->destroy($id); $this->close(); - $fw->clear('COOKIE.'.session_name()); + unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } @@ -87,7 +87,7 @@ class Session { **/ function write($id,$data) { $fw=Base::instance(); - $jar=$fw->get('JAR'); + $jar=$fw->JAR; $this->_cache->set($id.'.@', [ 'data'=>$data, @@ -95,7 +95,7 @@ class Session { 'agent'=>$this->_agent, 'stamp'=>time() ], - $jar['expire']?($jar['expire']-time()):0 + $jar['expire'] ); return TRUE; } @@ -181,12 +181,12 @@ class Session { ); register_shutdown_function('session_commit'); $fw=\Base::instance(); - $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); + $headers=$fw->HEADERS; + $this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand()); if ($key) - $fw->set($key,$this->_csrf); + $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; - $this->_ip=$fw->get('IP'); + $this->_ip=$fw->IP; } } diff --git a/app/lib/smtp.php b/app/lib/smtp.php index 64e8d04f..6a8e5333 100644 --- a/app/lib/smtp.php +++ b/app/lib/smtp.php @@ -171,7 +171,7 @@ class SMTP extends Magic { if (!is_file($file)) user_error(sprintf(self::E_Attach,$file),E_USER_ERROR); if ($alias) - $file=[$alias=>$file]; + $file=[$alias,$file]; $this->attachments[]=['filename'=>$file,'cid'=>$cid]; } @@ -206,14 +206,15 @@ class SMTP extends Magic { // Get server's initial response $this->dialog(NULL,TRUE,$mock); // Announce presence - $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock); + $reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock); if (strtolower($this->scheme)=='tls') { $this->dialog('STARTTLS',$log,$mock); if (!$mock) stream_socket_enable_crypto( $socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT); - $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock); + $reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock); } + $message=wordwrap($message,998); if (preg_match('/8BITMIME/',$reply)) $headers['Content-Transfer-Encoding']='8bit'; else { @@ -243,14 +244,8 @@ class SMTP extends Magic { if (empty($headers[$id])) user_error(sprintf(self::E_Header,$id),E_USER_ERROR); $eol="\r\n"; - $str=''; // Stringify headers foreach ($headers as $key=>&$val) { - if (!in_array($key,$reqd) && - (!$this->attachments || - $key!='Content-Type' && - $key!='Content-Transfer-Encoding')) - $str.=$key.': '.$val.$eol; if (in_array($key,['From','To','Cc','Bcc'])) { $email=''; preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/', @@ -290,11 +285,11 @@ class SMTP extends Magic { $out.='--'.$hash.$eol; $out.='Content-Type: '.$type.$eol; $out.='Content-Transfer-Encoding: '.$enc.$eol; - $out.=$str.$eol; + $out.=$eol; $out.=$message.$eol; foreach ($this->attachments as $attachment) { if (is_array($attachment['filename'])) - list($alias,$file)=each($attachment['filename']); + list($alias,$file)=$attachment['filename']; else $alias=basename($file=$attachment['filename']); $out.='--'.$hash.$eol; @@ -338,20 +333,21 @@ class SMTP extends Magic { * @param $scheme string * @param $user string * @param $pw string + * @param $ctx resource **/ function __construct( $host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL,$ctx=NULL) { $this->headers=[ 'MIME-Version'=>'1.0', 'Content-Type'=>'text/plain; '. - 'charset='.Base::instance()->get('ENCODING') + 'charset='.Base::instance()->ENCODING ]; - $this->host=(strtolower($this->scheme=strtolower($scheme))=='ssl'? - 'ssl':'tcp').'://'.$host; + $this->host=strtolower((($this->scheme=strtolower($scheme))=='ssl'? + 'ssl':'tcp').'://'.$host); $this->port=$port; $this->user=$user; $this->pw=$pw; - $this->context=$ctx; + $this->context=stream_context_create($ctx); } } diff --git a/app/lib/template.php b/app/lib/template.php index a8277376..5d4c74b0 100644 --- a/app/lib/template.php +++ b/app/lib/template.php @@ -65,8 +65,8 @@ class Template extends Preview { return '\''.$pair[1].'\'=>'. (preg_match('/^\'.*\'$/',$pair[2]) || preg_match('/\$/',$pair[2])? - $pair[2]: - \Base::instance()->stringify($pair[2])); + $pair[2]:Base::instance()->stringify( + Base::instance()->cast($pair[2]))); },$pairs)).']+get_defined_vars()'): 'get_defined_vars()'; $ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0; @@ -77,7 +77,7 @@ class Template extends Preview { (preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])? $this->token($attrib['href']): Base::instance()->stringify($attrib['href'])).','. - '$this->mime,'.$hive.','.$ttl.'); ?>'); + 'NULL,'.$hive.','.$ttl.'); ?>'); } /** @@ -252,7 +252,7 @@ class Template extends Preview { /** * Call custom tag handler * @return string|FALSE - * @param $func callback + * @param $func string * @param $args array **/ function __call($func,array $args) { @@ -265,10 +265,11 @@ class Template extends Preview { /** * Parse string for template directives and tokens - * @return string|array + * @return array * @param $text string **/ function parse($text) { + $text=parent::parse($text); // Build tree structure for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;) if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'. @@ -276,21 +277,23 @@ class Template extends Preview { '(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'. '\h*\{\{.+?\}\})*)\h*(\/?)>/is', substr($text,$ptr),$match)) { - if (strlen($tmp)||$match[1]) + if (strlen($tmp) || $match[1]) $tree[]=$tmp.$match[1]; // Element node if ($match[2]) { // Find matching start tag $stack=[]; for($i=count($tree)-1;$i>=0;$i--) { - $item = $tree[$i]; - if (is_array($item) && array_key_exists($match[3],$item) - && !isset($item[$match[3]][0])) { + $item=$tree[$i]; + if (is_array($item) && + array_key_exists($match[3],$item) && + !isset($item[$match[3]][0])) { // Start tag found $tree[$i][$match[3]]+=array_reverse($stack); $tree=array_slice($tree,0,$i+1); break; - } else $stack[]=$item; + } + else $stack[]=$item; } } else { diff --git a/app/lib/utf.php b/app/lib/utf.php index bcc083be..a010c66f 100644 --- a/app/lib/utf.php +++ b/app/lib/utf.php @@ -191,7 +191,7 @@ class UTF extends Prefab { ':,'=>'\u1f60f', // think ':/'=>'\u1f623', // skeptic '8O'=>'\u1f632', // oops - ]+Base::instance()->get('EMOJI'); + ]+Base::instance()->EMOJI; return $this->translate(str_replace(array_keys($map), array_values($map),$str)); } diff --git a/app/lib/web.php b/app/lib/web.php index 204d9490..dd817ce2 100644 --- a/app/lib/web.php +++ b/app/lib/web.php @@ -138,7 +138,7 @@ class Web extends Prefab { 'filename="'.($name!==NULL?$name:basename($file)).'"'); header('Accept-Ranges: bytes'); header('Content-Length: '.$size); - header('X-Powered-By: '.Base::instance()->get('PACKAGE')); + header('X-Powered-By: '.Base::instance()->PACKAGE); } if (!$kbps && $flush) { while (ob_get_level()) @@ -179,13 +179,13 @@ class Web extends Prefab { **/ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { $fw=Base::instance(); - $dir=$fw->get('UPLOADS'); + $dir=$fw->UPLOADS; if (!is_dir($dir)) mkdir($dir,Base::MODE,TRUE); - if ($fw->get('VERB')=='PUT') { - $tmp=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash(uniqid()); - if (!$fw->get('RAW')) - $fw->write($tmp,$fw->get('BODY')); + if ($fw->VERB=='PUT') { + $tmp=$fw->TEMP.$fw->SEED.'.'.$fw->hash(uniqid()); + if (!$fw->RAW) + $fw->write($tmp,$fw->BODY); else { $src=@fopen('php://input','r'); $dst=@fopen($tmp,'w'); @@ -198,7 +198,7 @@ class Web extends Prefab { fclose($dst); fclose($src); } - $base=basename($fw->get('URI')); + $base=basename($fw->URI); $file=[ 'name'=>$dir. ($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)? @@ -305,6 +305,7 @@ class Web extends Prefab { $body=ob_get_clean(); if (!$err && $options['follow_location'] && + preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) && preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) { $options['max_redirects']--; if($loc[1][0] == '/') { @@ -424,6 +425,7 @@ class Web extends Prefab { break; } if ($options['follow_location'] && + preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) && preg_match('/Location: (.+?)'.preg_quote($eol).'/', $html[0],$loc)) { $options['max_redirects']--; @@ -489,9 +491,9 @@ class Web extends Prefab { $parts=parse_url($url); if (empty($parts['scheme'])) { // Local URL - $url=$fw->get('SCHEME').'://'. - $fw->get('HOST'). - ($url[0]!='/'?($fw->get('BASE').'/'):'').$url; + $url=$fw->SCHEME.'://'.$fw->HOST. + (in_array($fw->PORT,[80,443])?'':(':'.$fw->PORT)). + ($url[0]!='/'?($fw->BASE.'/'):'').$url; $parts=parse_url($url); } elseif (!preg_match('/https?/',$parts['scheme'])) @@ -544,7 +546,7 @@ class Web extends Prefab { 'ignore_errors'=>FALSE ]; $eol="\r\n"; - if ($fw->get('CACHE') && + if ($fw->CACHE && preg_match('/GET|HEAD/',$options['method'])) { $cache=Cache::instance(); if ($cache->exists( @@ -591,13 +593,13 @@ class Web extends Prefab { $cache=Cache::instance(); $dst=''; if (!isset($path)) - $path=$fw->get('UI').';./'; + $path=$fw->UI.';./'; foreach ($fw->split($path,FALSE) as $dir) foreach ($files as $file) if (is_file($save=$fw->fixslashes($dir.$file)) && is_bool(strpos($save,'../')) && preg_match('/\.(css|js)$/i',$file)) { - if ($fw->get('CACHE') && + if ($fw->CACHE && ($cached=$cache->exists( $hash=$fw->hash($save).'.'.$ext[0],$data)) && $cached[0]>filemtime($save)) @@ -692,7 +694,9 @@ class Web extends Prefab { preg_match('/[\w'.($ext[0]=='css'? '#\.%+\-*()\[\]':'\$').']{2}|'. '[+\-]{2}/', - substr($data,-1).$src[$ptr+1])) + substr($data,-1).$src[$ptr+1]) || + ($ext[0]=='css' && $ptr+2get('CACHE')) + if ($ext[0]=='css') + $data=str_replace(';}','}',$data); + if ($fw->CACHE) $cache->set($hash,$data); $dst.=$data; } } if (PHP_SAPI!='cli' && $header) - header('Content-Type: '.$mime.'; charset='.$fw->get('ENCODING')); + header('Content-Type: '.$mime.'; charset='.$fw->ENCODING); return $dst; } @@ -773,6 +779,63 @@ class Web extends Prefab { return $info['timed_out']?FALSE:trim($response); } + /** + * Return preset diacritics translation table + * @return array + **/ + function diacritics() { + return [ + 'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A', + 'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A', + 'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C', + 'Č'=>'C','Ċ'=>'C','Ц'=>'C','Ч'=>'Ch','Ð'=>'Dj','Đ'=>'Dj', + 'Ď'=>'Dj','Д'=>'Dj','É'=>'E','Ę'=>'E','Ё'=>'E','Ė'=>'E', + 'Ê'=>'E','Ě'=>'E','Ē'=>'E','È'=>'E','Е'=>'E','Э'=>'E', + 'Ë'=>'E','Ĕ'=>'E','Ф'=>'F','Г'=>'G','Ģ'=>'G','Ġ'=>'G', + 'Ĝ'=>'G','Ğ'=>'G','Х'=>'H','Ĥ'=>'H','Ħ'=>'H','Ï'=>'I', + 'Ĭ'=>'I','İ'=>'I','Į'=>'I','Ī'=>'I','Í'=>'I','Ì'=>'I', + 'И'=>'I','Ǐ'=>'I','Ĩ'=>'I','Î'=>'I','IJ'=>'IJ','Ĵ'=>'J', + 'Й'=>'J','Я'=>'Ja','Ю'=>'Ju','К'=>'K','Ķ'=>'K','Ĺ'=>'L', + 'Л'=>'L','Ł'=>'L','Ŀ'=>'L','Ļ'=>'L','Ľ'=>'L','М'=>'M', + 'Н'=>'N','Ń'=>'N','Ñ'=>'N','Ņ'=>'N','Ň'=>'N','Ō'=>'O', + 'О'=>'O','Ǿ'=>'O','Ǒ'=>'O','Ơ'=>'O','Ŏ'=>'O','Ő'=>'O', + 'Ø'=>'O','Ö'=>'Oe','Õ'=>'O','Ó'=>'O','Ò'=>'O','Ô'=>'O', + 'Œ'=>'OE','П'=>'P','Ŗ'=>'R','Р'=>'R','Ř'=>'R','Ŕ'=>'R', + 'Ŝ'=>'S','Ş'=>'S','Š'=>'S','Ș'=>'S','Ś'=>'S','С'=>'S', + 'Ш'=>'Sh','Щ'=>'Shch','Ť'=>'T','Ŧ'=>'T','Ţ'=>'T','Ț'=>'T', + 'Т'=>'T','Ů'=>'U','Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U', + 'Ū'=>'U','Ǜ'=>'U','Ǚ'=>'U','Ù'=>'U','Ú'=>'U','Ü'=>'Ue', + 'Ǘ'=>'U','Ǖ'=>'U','У'=>'U','Ư'=>'U','Ǔ'=>'U','Û'=>'U', + 'В'=>'V','Ŵ'=>'W','Ы'=>'Y','Ŷ'=>'Y','Ý'=>'Y','Ÿ'=>'Y', + 'Ź'=>'Z','З'=>'Z','Ż'=>'Z','Ž'=>'Z','Ж'=>'Zh','á'=>'a', + 'ă'=>'a','â'=>'a','à'=>'a','ā'=>'a','ǻ'=>'a','å'=>'a', + 'ä'=>'ae','ą'=>'a','ǎ'=>'a','ã'=>'a','а'=>'a','ª'=>'a', + 'æ'=>'ae','ǽ'=>'ae','б'=>'b','č'=>'c','ç'=>'c','ц'=>'c', + 'ċ'=>'c','ĉ'=>'c','ć'=>'c','ч'=>'ch','ð'=>'dj','ď'=>'dj', + 'д'=>'dj','đ'=>'dj','э'=>'e','é'=>'e','ё'=>'e','ë'=>'e', + 'ê'=>'e','е'=>'e','ĕ'=>'e','è'=>'e','ę'=>'e','ě'=>'e', + 'ė'=>'e','ē'=>'e','ƒ'=>'f','ф'=>'f','ġ'=>'g','ĝ'=>'g', + 'ğ'=>'g','г'=>'g','ģ'=>'g','х'=>'h','ĥ'=>'h','ħ'=>'h', + 'ǐ'=>'i','ĭ'=>'i','и'=>'i','ī'=>'i','ĩ'=>'i','į'=>'i', + 'ı'=>'i','ì'=>'i','î'=>'i','í'=>'i','ï'=>'i','ij'=>'ij', + 'ĵ'=>'j','й'=>'j','я'=>'ja','ю'=>'ju','ķ'=>'k','к'=>'k', + 'ľ'=>'l','ł'=>'l','ŀ'=>'l','ĺ'=>'l','ļ'=>'l','л'=>'l', + 'м'=>'m','ņ'=>'n','ñ'=>'n','ń'=>'n','н'=>'n','ň'=>'n', + 'ʼn'=>'n','ó'=>'o','ò'=>'o','ǒ'=>'o','ő'=>'o','о'=>'o', + 'ō'=>'o','º'=>'o','ơ'=>'o','ŏ'=>'o','ô'=>'o','ö'=>'oe', + 'õ'=>'o','ø'=>'o','ǿ'=>'o','œ'=>'oe','п'=>'p','р'=>'r', + 'ř'=>'r','ŕ'=>'r','ŗ'=>'r','ſ'=>'s','ŝ'=>'s','ș'=>'s', + 'š'=>'s','ś'=>'s','с'=>'s','ş'=>'s','ш'=>'sh','щ'=>'shch', + 'ß'=>'ss','ţ'=>'t','т'=>'t','ŧ'=>'t','ť'=>'t','ț'=>'t', + 'у'=>'u','ǘ'=>'u','ŭ'=>'u','û'=>'u','ú'=>'u','ų'=>'u', + 'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u', + 'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v', + 'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z', + 'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>'', + '\''=>'', + ]; + } + /** * Return a URL/filesystem-friendly version of string * @return string @@ -780,56 +843,7 @@ class Web extends Prefab { **/ function slug($text) { return trim(strtolower(preg_replace('/([^\pL\pN])+/u','-', - trim(strtr(str_replace('\'','',$text), - [ - 'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A', - 'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A', - 'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C', - 'Č'=>'C','Ċ'=>'C','Ц'=>'C','Ч'=>'Ch','Ð'=>'Dj','Đ'=>'Dj', - 'Ď'=>'Dj','Д'=>'Dj','É'=>'E','Ę'=>'E','Ё'=>'E','Ė'=>'E', - 'Ê'=>'E','Ě'=>'E','Ē'=>'E','È'=>'E','Е'=>'E','Э'=>'E', - 'Ë'=>'E','Ĕ'=>'E','Ф'=>'F','Г'=>'G','Ģ'=>'G','Ġ'=>'G', - 'Ĝ'=>'G','Ğ'=>'G','Х'=>'H','Ĥ'=>'H','Ħ'=>'H','Ï'=>'I', - 'Ĭ'=>'I','İ'=>'I','Į'=>'I','Ī'=>'I','Í'=>'I','Ì'=>'I', - 'И'=>'I','Ǐ'=>'I','Ĩ'=>'I','Î'=>'I','IJ'=>'IJ','Ĵ'=>'J', - 'Й'=>'J','Я'=>'Ja','Ю'=>'Ju','К'=>'K','Ķ'=>'K','Ĺ'=>'L', - 'Л'=>'L','Ł'=>'L','Ŀ'=>'L','Ļ'=>'L','Ľ'=>'L','М'=>'M', - 'Н'=>'N','Ń'=>'N','Ñ'=>'N','Ņ'=>'N','Ň'=>'N','Ō'=>'O', - 'О'=>'O','Ǿ'=>'O','Ǒ'=>'O','Ơ'=>'O','Ŏ'=>'O','Ő'=>'O', - 'Ø'=>'O','Ö'=>'Oe','Õ'=>'O','Ó'=>'O','Ò'=>'O','Ô'=>'O', - 'Œ'=>'OE','П'=>'P','Ŗ'=>'R','Р'=>'R','Ř'=>'R','Ŕ'=>'R', - 'Ŝ'=>'S','Ş'=>'S','Š'=>'S','Ș'=>'S','Ś'=>'S','С'=>'S', - 'Ш'=>'Sh','Щ'=>'Shch','Ť'=>'T','Ŧ'=>'T','Ţ'=>'T','Ț'=>'T', - 'Т'=>'T','Ů'=>'U','Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U', - 'Ū'=>'U','Ǜ'=>'U','Ǚ'=>'U','Ù'=>'U','Ú'=>'U','Ü'=>'Ue', - 'Ǘ'=>'U','Ǖ'=>'U','У'=>'U','Ư'=>'U','Ǔ'=>'U','Û'=>'U', - 'В'=>'V','Ŵ'=>'W','Ы'=>'Y','Ŷ'=>'Y','Ý'=>'Y','Ÿ'=>'Y', - 'Ź'=>'Z','З'=>'Z','Ż'=>'Z','Ž'=>'Z','Ж'=>'Zh','á'=>'a', - 'ă'=>'a','â'=>'a','à'=>'a','ā'=>'a','ǻ'=>'a','å'=>'a', - 'ä'=>'ae','ą'=>'a','ǎ'=>'a','ã'=>'a','а'=>'a','ª'=>'a', - 'æ'=>'ae','ǽ'=>'ae','б'=>'b','č'=>'c','ç'=>'c','ц'=>'c', - 'ċ'=>'c','ĉ'=>'c','ć'=>'c','ч'=>'ch','ð'=>'dj','ď'=>'dj', - 'д'=>'dj','đ'=>'dj','э'=>'e','é'=>'e','ё'=>'e','ë'=>'e', - 'ê'=>'e','е'=>'e','ĕ'=>'e','è'=>'e','ę'=>'e','ě'=>'e', - 'ė'=>'e','ē'=>'e','ƒ'=>'f','ф'=>'f','ġ'=>'g','ĝ'=>'g', - 'ğ'=>'g','г'=>'g','ģ'=>'g','х'=>'h','ĥ'=>'h','ħ'=>'h', - 'ǐ'=>'i','ĭ'=>'i','и'=>'i','ī'=>'i','ĩ'=>'i','į'=>'i', - 'ı'=>'i','ì'=>'i','î'=>'i','í'=>'i','ï'=>'i','ij'=>'ij', - 'ĵ'=>'j','й'=>'j','я'=>'ja','ю'=>'ju','ķ'=>'k','к'=>'k', - 'ľ'=>'l','ł'=>'l','ŀ'=>'l','ĺ'=>'l','ļ'=>'l','л'=>'l', - 'м'=>'m','ņ'=>'n','ñ'=>'n','ń'=>'n','н'=>'n','ň'=>'n', - 'ʼn'=>'n','ó'=>'o','ò'=>'o','ǒ'=>'o','ő'=>'o','о'=>'o', - 'ō'=>'o','º'=>'o','ơ'=>'o','ŏ'=>'o','ô'=>'o','ö'=>'oe', - 'õ'=>'o','ø'=>'o','ǿ'=>'o','œ'=>'oe','п'=>'p','р'=>'r', - 'ř'=>'r','ŕ'=>'r','ŗ'=>'r','ſ'=>'s','ŝ'=>'s','ș'=>'s', - 'š'=>'s','ś'=>'s','с'=>'s','ş'=>'s','ш'=>'sh','щ'=>'shch', - 'ß'=>'ss','ţ'=>'t','т'=>'t','ŧ'=>'t','ť'=>'t','ț'=>'t', - 'у'=>'u','ǘ'=>'u','ŭ'=>'u','û'=>'u','ú'=>'u','ų'=>'u', - 'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u', - 'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v', - 'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z', - 'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>'' - ]+Base::instance()->get('DIACRITICS'))))),'-'); + trim(strtr($text,Base::instance()->DIACRITICS+$this->diacritics())))),'-'); } /** @@ -881,9 +895,9 @@ if (!function_exists('gzdecode')) { **/ function gzdecode($str) { $fw=Base::instance(); - if (!is_dir($tmp=$fw->get('TEMP'))) + if (!is_dir($tmp=$fw->TEMP)) mkdir($tmp,Base::MODE,TRUE); - file_put_contents($file=$tmp.'/'.$fw->get('SEED').'.'. + file_put_contents($file=$tmp.'/'.$fw->SEED.'.'. $fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX); ob_start(); readgzfile($file); diff --git a/app/lib/web/geo.php b/app/lib/web/geo.php index d7cb9cba..c669024d 100644 --- a/app/lib/web/geo.php +++ b/app/lib/web/geo.php @@ -55,7 +55,7 @@ class Geo extends \Prefab { $fw=\Base::instance(); $web=\Web::instance(); if (!$ip) - $ip=$fw->get('IP'); + $ip=$fw->IP; $public=filter_var($ip,FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6| FILTER_FLAG_NO_RES_RANGE|FILTER_FLAG_NO_PRIV_RANGE); diff --git a/app/lib/web/google/recaptcha.php b/app/lib/web/google/recaptcha.php new file mode 100644 index 00000000..9c32979c --- /dev/null +++ b/app/lib/web/google/recaptcha.php @@ -0,0 +1,58 @@ +. + +*/ + +namespace Web\Google; + +//! Google ReCAPTCHA v2 plug-in +class Recaptcha { + + const + //! API URL + URL_Recaptcha='https://www.google.com/recaptcha/api/siteverify'; + + /** + * Verify reCAPTCHA response + * @param string $secret + * @param string $response + * @return bool + **/ + static function verify($secret,$response=NULL) { + $fw=\Base::instance(); + if (!isset($response)) + $response=$fw->{'POST.g-recaptcha-response'}; + if (!$response) + return FALSE; + $web=\Web::instance(); + $out=$web->request(self::URL_Recaptcha,[ + 'method'=>'POST', + 'content'=>http_build_query([ + 'secret'=>$secret, + 'response'=>$response, + 'remoteip'=>$fw->IP + ]), + ]); + return isset($out['body']) && + ($json=json_decode($out['body'],TRUE)) && + isset($json['success']) && $json['success']; + } + +} diff --git a/app/lib/web/oauth2.php b/app/lib/web/oauth2.php index 5bf4dcb8..916674f8 100644 --- a/app/lib/web/oauth2.php +++ b/app/lib/web/oauth2.php @@ -63,7 +63,7 @@ class OAuth2 extends \Magic { ); $response=$web->request($uri,$options); if ($response['error']) - user_error($response['error']); + user_error($response['error'],E_USER_ERROR); return $response['body'] && preg_grep('/HTTP\/1\.\d 200/',$response['headers'])? json_decode($response['body'],TRUE): diff --git a/app/lib/web/openid.php b/app/lib/web/openid.php index d19ff6a9..435d89ca 100644 --- a/app/lib/web/openid.php +++ b/app/lib/web/openid.php @@ -143,9 +143,9 @@ class OpenID extends \Magic { **/ function auth($proxy=NULL,$attr=[],array $reqd=NULL) { $fw=\Base::instance(); - $root=$fw->get('SCHEME').'://'.$fw->get('HOST'); + $root=$fw->SCHEME.'://'.$fw->HOST; if (empty($this->args['trust_root'])) - $this->args['trust_root']=$root.$fw->get('BASE').'/'; + $this->args['trust_root']=$root.$fw->BASE.'/'; if (empty($this->args['return_to'])) $this->args['return_to']=$root.$_SERVER['REQUEST_URI']; $this->args['mode']='checkid_setup'; diff --git a/app/lib/web/pingback.php b/app/lib/web/pingback.php index a9fca95b..a68430ca 100644 --- a/app/lib/web/pingback.php +++ b/app/lib/web/pingback.php @@ -66,9 +66,9 @@ class Pingback extends \Prefab { $web=\Web::instance(); $parts=parse_url($source); if (empty($parts['scheme']) || empty($parts['host']) || - $parts['host']==$fw->get('HOST')) { + $parts['host']==$fw->HOST) { $req=$web->request($source); - $doc=new \DOMDocument('1.0',$fw->get('ENCODING')); + $doc=new \DOMDocument('1.0',$fw->ENCODING); $doc->stricterrorchecking=FALSE; $doc->recover=TRUE; if (@$doc->loadhtml($req['body'])) { @@ -85,7 +85,7 @@ class Pingback extends \Prefab { 'content'=>xmlrpc_encode_request( 'pingback.ping', [$source,$permalink], - ['encoding'=>$fw->get('ENCODING')] + ['encoding'=>$fw->ENCODING] ) ] ); @@ -110,29 +110,29 @@ class Pingback extends \Prefab { function listen($func,$path=NULL) { $fw=\Base::instance(); if (PHP_SAPI!='cli') { - header('X-Powered-By: '.$fw->get('PACKAGE')); + header('X-Powered-By: '.$fw->PACKAGE); header('Content-Type: application/xml; '. - 'charset='.$charset=$fw->get('ENCODING')); + 'charset='.$charset=$fw->ENCODING); } if (!$path) - $path=$fw->get('BASE'); + $path=$fw->BASE; $web=\Web::instance(); - $args=xmlrpc_decode_request($fw->get('BODY'),$method,$charset); + $args=xmlrpc_decode_request($fw->BODY,$method,$charset); $options=['encoding'=>$charset]; if ($method=='pingback.ping' && isset($args[0],$args[1])) { list($source,$permalink)=$args; - $doc=new \DOMDocument('1.0',$fw->get('ENCODING')); + $doc=new \DOMDocument('1.0',$fw->ENCODING); // Check local page if pingback-enabled $parts=parse_url($permalink); if ((empty($parts['scheme']) || - $parts['host']==$fw->get('HOST')) && + $parts['host']==$fw->HOST) && preg_match('/^'.preg_quote($path,'/').'/'. - ($fw->get('CASELESS')?'i':''),$parts['path']) && + ($fw->CASELESS?'i':''),$parts['path']) && $this->enabled($permalink)) { // Check source $parts=parse_url($source); if ((empty($parts['scheme']) || - $parts['host']==$fw->get('HOST')) && + $parts['host']==$fw->HOST) && ($req=$web->request($source)) && $doc->loadhtml($req['body'])) { $links=$doc->getelementsbytagname('a');