From 2a2affd840a6365ae927a9cae9fa2dc756a837d7 Mon Sep 17 00:00:00 2001 From: ikkez Date: Sat, 19 Nov 2016 14:31:15 +0100 Subject: [PATCH] 3.6.0-Release --- lib/CHANGELOG | 107 ++++++ lib/COPYING | 0 lib/audit.php | 7 +- lib/auth.php | 33 +- lib/base.php | 727 +++++++++++++++++++++-------------- lib/basket.php | 15 +- lib/bcrypt.php | 41 +- lib/cli/ws.php | 516 +++++++++++++++++++++++++ lib/code.css | 0 lib/db/cursor.php | 18 +- lib/db/jig.php | 19 +- lib/db/jig/mapper.php | 50 +-- lib/db/jig/session.php | 26 +- lib/db/mongo.php | 45 ++- lib/db/mongo/mapper.php | 76 ++-- lib/db/mongo/session.php | 26 +- lib/db/sql.php | 126 +++--- lib/db/sql/mapper.php | 133 ++++--- lib/db/sql/session.php | 36 +- lib/f3.php | 4 +- lib/image.php | 101 ++--- lib/log.php | 2 +- lib/magic.php | 4 +- lib/markdown.php | 16 +- lib/matrix.php | 21 +- lib/session.php | 41 +- lib/smtp.php | 150 +++++--- lib/template.php | 28 +- lib/test.php | 6 +- lib/utf.php | 6 +- lib/web.php | 243 ++++++------ lib/web/geo.php | 24 +- lib/web/google/staticmap.php | 2 +- lib/web/oauth2.php | 134 +++++++ lib/web/openid.php | 30 +- lib/web/pingback.php | 21 +- readme.md | 4 +- ui/welcome.htm | 2 +- 38 files changed, 1954 insertions(+), 886 deletions(-) mode change 100644 => 100755 lib/CHANGELOG mode change 100644 => 100755 lib/COPYING create mode 100644 lib/cli/ws.php mode change 100644 => 100755 lib/code.css mode change 100644 => 100755 lib/log.php mode change 100644 => 100755 lib/web/google/staticmap.php create mode 100644 lib/web/oauth2.php diff --git a/lib/CHANGELOG b/lib/CHANGELOG old mode 100644 new mode 100755 index cf2dab512..f831d7d64 --- a/lib/CHANGELOG +++ b/lib/CHANGELOG @@ -1,5 +1,112 @@ CHANGELOG +3.6.0 (19 November 2016) +* NEW: [cli] request type +* NEW: console-friendly CLI mode +* NEW: lexicon caching +* NEW: Silent operator skips startup error check (#125) +* NEW: DB\SQL->trans() +* NEW: custom config section parser, i.e. [conf > Foo::bar] +* NEW: support for cache tags in SQL +* NEW: custom FORMATS +* NEW: Mongo mapper fields whitelist +* NEW: WebSocket server +* NEW: Base->extend method (#158) +* NEW: Implement framework variable caching via config, i.e. FOO = "bar" | 3600 +* NEW: Lightweight OAuth2 client +* NEW: SEED variable, configurable app-specific hashing prefix (#149, bcosca/fatfree#951, bcosca/fatfree#884, bcosca/fatfree#629) +* NEW: CLI variable +* NEW: Web->send, specify custom filename (#124) +* NEW: Web->send, added flushing flag (#131) +* NEW: Indexed route wildcards, now exposed in PARAMS['*'] +* Changed: PHP 5.4 is now the minimum version requirement +* Changed: Prevent database wrappers from being cloned +* Changed: Router works on PATH instead of URI (#126) NB: PARAMS.0 no longer contains the query string +* Changed: Removed ALIASES autobuilding (#118) +* Changed: Route wildcards match empty strings (#119) +* Changed: Disable default debug highlighting, HIGHLIGHT is false now +* General PHP 5.4 optimizations +* Optimized config parsing +* Optimized Base->recursive +* Optimized header extraction +* Optimized cache/expire headers +* Optimized session_start behaviour (bcosca/fatfree#673) +* Optimized reroute regex +* Tweaked cookie removal +* Better route precedence order +* Performance tweak: reduced cache calls +* Refactored lexicon (LOCALES) build-up, much faster now +* Added turkish locale bug workaround +* Geo->tzinfo Update to UTC +* Added Xcache reset (bcosca/fatfree#928) +* Redis cache: allow db name in dsn +* SMTP: Improve server emulation responses +* SMTP: Optimize transmission envelope +* SMTP: Implement mock transmission +* SMTP: Various bug fixes and feature improvements +* SMTP: quit on failed authentication +* Geo->weather: force metric units +* Base->until: Implement CLI interoperability +* Base->format: looser plural syntax +* Base->format: Force decimal as default number format +* Base->merge: Added $keep flag to save result to the hive key +* Base->reroute: Allow array as URL argument for aliasing +* Base->alias: Allow query string (or array) to be appended to alias +* Permit reroute to named routes with URL query segment +* Sync COOKIE global on set() +* Permit non-hive variables to use JS dot notation +* RFC2616: Use absolute URIs for Location header +* Matrix->calendar: Check if calendar extension is loaded +* Markdown: require start of line/whitespace for text processing (#136) +* DB\[SQL|Jig|Mongo]->log(FALSE) disables logging +* DB\SQL->exec: Added timestamp toggle to db log +* DB\SQL->schema: Remove unnecessary line terminators +* DB\SQL\Mapper: allow array filter with empty string +* DB\SQL\Mapper: optimized handling for key-less tables +* DB\SQL\Mapper: added float support (#106) +* DB\SQL\Session: increased default column sizes (#148, bcosca/fatfree#931, bcosca/fatfree#950) +* Web: Catch cURL errors +* Optimize Web->receive (bcosca/fatfree#930) +* Web->minify: fix arbitrary file download vulnerability +* Web->request: fix cache control max-age detection (bcosca/fatfree#908) +* Web->request: Add request headers & error message to return value (bcosca/fatfree#737) +* Web->request: Refactored response to HTTP request +* Web->send flush while sending big files +* Image->rgb: allow hex strings +* Image->captcha: Check if GD module supports TrueType +* Image->load: Return FALSE on load failure +* Image->resize: keep aspect ratio when only width or height was given +* Updated OpenID lib (bcosca/fatfree#965) +* Audit->card: add new mastercard "2" BIN range (bcosca/fatfree#954) +* Deprecated: Bcrypt class +* Preview->render: optimized detection to remove short open PHP tags and allow xml tags (#133) +* Display file and line number in exception handler (bcosca/fatfree#967) +* Added error reporting level to Base->error and ERROR.level (bcosca/fatfree#957) +* Added optional custom cache instance to Session (#141) +* CLI-aware mock() +* XFRAME and PACKAGE can be switched off now (#128) +* Bug fix: wrong time calculation on memcache reset (#170) +* Bug fix: encode CLI parameters +* Bug fix: Close connection on abort explicitly (#162) +* Bug fix: Image->identicon, Avoid double-size sprite rotation (and possible segfault) +* Bug fix: Magic->offsetset, access property as array element (#147) +* Bug fix: multi-line custom template tag parsing (bcosca/fatfree#935) +* Bug fix: cache headers on errors (bcosca/fatfree#885) +* Bug fix: Web, deprecated CURLOPT_SSL_VERIFYHOST in curl +* Bug fix: Web, Invalid user error constant (bcosca/fatfree#962) +* Bug fix: Web->request, redirections for domain-less location (#135) +* Bug fix: DB\SQL\Mapper, reset changed flag after update (#142, #152) +* Bug fix: DB\SQL\Mapper, fix changed flag when using assignment operator #143 #150 #151 +* Bug fix: DB\SQL\Mapper, revival of the HAVING clause +* Bug fix: DB\SQL\Mapper, pgsql with non-integer primary keys (bcosca/fatfree#916) +* Bug fix: DB\SQL\Session, quote table name (bcosca/fatfree#977) +* Bug fix: snakeCase returns word starting with underscore (bcosca/fatfree#927) +* Bug fix: mock does not populate PATH variable +* Bug fix: Geo->weather API key (#129) +* Bug fix: Incorrect compilation of array element with zero index +* Bug fix: Compilation of array construct is incorrect +* Bug fix: Trailing slash redirection on UTF-8 paths (#121) + 3.5.1 (31 December 2015) * NEW: ttl attribute in template tag * NEW: allow anonymous function for template filter diff --git a/lib/COPYING b/lib/COPYING old mode 100644 new mode 100755 diff --git a/lib/audit.php b/lib/audit.php index 1338ca8cc..4c91740d3 100644 --- a/lib/audit.php +++ b/lib/audit.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -47,7 +47,7 @@ function url($str) { * @param $mx boolean **/ function email($str,$mx=TRUE) { - $hosts=array(); + $hosts=[]; return is_string(filter_var($str,FILTER_VALIDATE_EMAIL)) && (!$mx || getmxrr(substr($str,strrpos($str,'@')+1),$hosts)); } @@ -166,7 +166,8 @@ function card($id) { return 'Discover'; if (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$id)) return 'JCB'; - if (preg_match('/^5[1-5][0-9]{14}$/',$id)) + if (preg_match('/^5[1-5][0-9]{14}$|'. + '^(222[1-9]|2[3-6]\d{2}|27[0-1]\d|2720)\d{12}$/',$id)) return 'MasterCard'; if (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$id)) return 'Visa'; diff --git a/lib/auth.php b/lib/auth.php index 5f7e05084..56f89ed59 100644 --- a/lib/auth.php +++ b/lib/auth.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -20,7 +20,6 @@ */ - //! Authorization/authentication plug-in class Auth { @@ -48,19 +47,19 @@ class Auth { protected function _jig($id,$pw,$realm) { return (bool) call_user_func_array( - array($this->mapper,'load'), - array( + [$this->mapper,'load'], + [ array_merge( - array( + [ '@'.$this->args['id'].'==? AND '. '@'.$this->args['pw'].'==?'. (isset($this->args['realm'])? (' AND @'.$this->args['realm'].'==?'):''), $id,$pw - ), - (isset($this->args['realm'])?array($realm):array()) + ], + (isset($this->args['realm'])?[$realm]:[]) ) - ) + ] ); } @@ -74,12 +73,12 @@ protected function _jig($id,$pw,$realm) { protected function _mongo($id,$pw,$realm) { return (bool) $this->mapper->load( - array( + [ $this->args['id']=>$id, $this->args['pw']=>$pw - )+ + ]+ (isset($this->args['realm'])? - array($this->args['realm']=>$realm):array()) + [$this->args['realm']=>$realm]:[]) ); } @@ -93,19 +92,19 @@ protected function _mongo($id,$pw,$realm) { protected function _sql($id,$pw,$realm) { return (bool) call_user_func_array( - array($this->mapper,'load'), - array( + [$this->mapper,'load'], + [ array_merge( - array( + [ $this->args['id'].'=? AND '. $this->args['pw'].'=?'. (isset($this->args['realm'])? (' AND '.$this->args['realm'].'=?'):''), $id,$pw - ), - (isset($this->args['realm'])?array($realm):array()) + ], + (isset($this->args['realm'])?[$realm]:[]) ) - ) + ] ); } diff --git a/lib/base.php b/lib/base.php index ea1a1f09a..b5a7bdca8 100644 --- a/lib/base.php +++ b/lib/base.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess { //@{ Framework details const PACKAGE='Fat-Free Framework', - VERSION='3.5.1-Release'; + VERSION='3.6.0-Release'; //@} //@{ HTTP status codes (RFC 2616) @@ -102,10 +102,11 @@ final class Base extends Prefab implements ArrayAccess { //! Syntax highlighting stylesheet CSS='code.css'; - //@{ HTTP request types + //@{ Request types const REQ_SYNC=1, - REQ_AJAX=2; + REQ_AJAX=2, + REQ_CLI=4; //@} //@{ Error messages @@ -153,26 +154,30 @@ private function cut($key) { * Replace tokenized URL with available token values * @return string * @param $url array|string - * @param $params array + * @param $args array **/ - function build($url,$params=array()) { - $params+=$this->hive['PARAMS']; + function build($url,$args=[]) { + $args+=$this->hive['PARAMS']; if (is_array($url)) foreach ($url as &$var) { - $var=$this->build($var,$params); + $var=$this->build($var,$args); unset($var); } else { $i=0; - $url=preg_replace_callback('/@(\w+)|\*/', - function($match) use(&$i,$params) { - $i++; + $url=preg_replace_callback('/@(\w+)|(\*)/', + function($match) use(&$i,$args) { if (isset($match[1]) && - array_key_exists($match[1],$params)) - return $params[$match[1]]; - return array_key_exists($i,$params)? - $params[$i]: - $match[0]; + array_key_exists($match[1],$args)) + return $args[$match[1]]; + if (isset($match[2]) && + array_key_exists($match[2],$args)) { + if (!is_array($args[$match[2]])) + return $args[$match[2]]; + $i++; + return $args[$match[2]][$i-1]; + } + return $match[0]; },$url); } return $url; @@ -183,14 +188,17 @@ function($match) use(&$i,$params) { * @return string * @param $name string * @param $params array|string + * @param $query string|array **/ - function alias($name,$params=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); - return $url; + if (is_array($query)) + $query=http_build_query($query); + return $url.($query?('?'.$query):''); } /** @@ -199,11 +207,17 @@ function alias($name,$params=array()) { * @param $str string **/ function parse($str) { - preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/', + preg_match_all('/(\w+|\*)\h*=\h*(?:\[(.+?)\]|(.+?))(?=,|$)/', $str,$pairs,PREG_SET_ORDER); - $out=array(); + $out=[]; foreach ($pairs as $pair) - $out[$pair[1]]=trim($pair[2]); + 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; } @@ -224,12 +238,17 @@ function($expr) use($fw) { ((function_exists($expr[1])? ('.'.$expr[1]): ('['.var_export($expr[1],TRUE).']')).'('): - ('['.var_export( - isset($expr[3])? - trim($fw->compile($expr[3])): - (ctype_digit($expr[2])? + ('['. + (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).']'); + $expr[2],TRUE)). + ']'); }, $var[1] ); @@ -244,21 +263,25 @@ function($expr) use($fw) { * @return mixed * @param $key string * @param $add bool + * @param $var mixed **/ - function &ref($key,$add=TRUE) { + function &ref($key,$add=TRUE,&$var=NULL) { $null=NULL; $parts=$this->cut($key); if ($parts[0]=='SESSION') { - @session_start(); + 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 ($add) - $var=&$this->hive; - else - $var=$this->hive; + if (is_null($var)) { + if ($add) + $var=&$this->hive; + else + $var=$this->hive; + } $obj=FALSE; foreach ($parts as $part) if ($part=='->') @@ -276,7 +299,7 @@ function &ref($key,$add=TRUE) { } else { if (!is_array($var)) - $var=array(); + $var=[]; if ($add || array_key_exists($part,$var)) $var=&$var[$part]; else { @@ -284,8 +307,6 @@ function &ref($key,$add=TRUE) { break; } } - if ($parts[0]=='ALIASES') - $var=$this->build($var); return $var; } @@ -330,9 +351,15 @@ function set($key,$val,$ttl=0) { 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'); + call_user_func_array('setcookie', + array_merge([$parts[1],NULL],$jar)); + } if ($ttl) $jar['expire']=$time+$ttl; - call_user_func_array('setcookie',array($parts[1],$val)+$jar); + call_user_func_array('setcookie',[$parts[1],$val]+$jar); + $_COOKIE[$parts[1]]=$val; return $val; } } @@ -351,10 +378,14 @@ function set($key,$val,$ttl=0) { case 'LANGUAGE': if (!isset($lang)) $val=$this->language($val); - $lex=$this->lexicon($this->hive['LOCALES']); + $lex=$this->lexicon($this->hive['LOCALES'],$ttl); case 'LOCALES': - if (isset($lex) || $lex=$this->lexicon($val)) - $this->mset($lex,$this->hive['PREFIX'],$ttl); + 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); @@ -367,10 +398,9 @@ function set($key,$val,$ttl=0) { $jar['expire']-=$time; call_user_func_array('session_set_cookie_params',$jar); } - $cache=Cache::instance(); - if ($cache->exists($hash=$this->hash($key).'.var') || $ttl) + if ($ttl) // Persist the key-value pair - $cache->set($hash,$val,$ttl); + Cache::instance()->set($this->hash($key).'.var',$val,$ttl); return $ref; } @@ -383,8 +413,8 @@ function set($key,$val,$ttl=0) { function get($key,$args=NULL) { if (is_string($val=$this->ref($key,FALSE)) && !is_null($args)) return call_user_func_array( - array($this,'format'), - array_merge(array($val),is_array($args)?$args:array($args)) + [$this,'format'], + array_merge([$val],is_array($args)?$args:[$args]) ); if (is_null($val)) { // Attempt to retrieve from cache @@ -413,12 +443,13 @@ function clear($key) { $jar=$this->hive['JAR']; $jar['expire']=strtotime('-1 year'); call_user_func_array('setcookie', - array_merge(array($parts[1],''),$jar)); + array_merge([$parts[1],NULL],$jar)); unset($_COOKIE[$parts[1]]); } } elseif ($parts[0]=='SESSION') { - @session_start(); + if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) + session_start(); if (empty($parts[1])) { // End session session_unset(); @@ -571,10 +602,33 @@ function shift($key) { * @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 merge($key,$src) { + function extend($key,$src,$keep=FALSE) { $ref=&$this->ref($key); - return array_merge($ref,is_string($src)?$this->hive[$src]:$src); + if (!$ref) + $ref=[]; + $out=array_replace_recursive(is_string($src)?$this->hive[$src]:$src,$ref); + if ($keep) + $ref=$out; + return $out; } /** @@ -610,7 +664,7 @@ function stringify($arg,array $stack=NULL) { return '*RECURSION*'; } else - $stack=array(); + $stack=[]; switch (gettype($arg)) { case 'object': $str=''; @@ -618,8 +672,8 @@ function stringify($arg,array $stack=NULL) { $str.=($str?',':''). var_export($key,TRUE).'=>'. $this->stringify($val, - array_merge($stack,array($arg))); - return get_class($arg).'::__set_state(array('.$str.'))'; + array_merge($stack,[$arg])); + return get_class($arg).'::__set_state(['.$str.'])'; case 'array': $str=''; $num=isset($arg[0]) && @@ -627,9 +681,8 @@ function stringify($arg,array $stack=NULL) { foreach ($arg as $key=>$val) $str.=($str?',':''). ($num?'':(var_export($key,TRUE).'=>')). - $this->stringify($val, - array_merge($stack,array($arg))); - return 'array('.$str.')'; + $this->stringify($val,array_merge($stack,[$arg])); + return '['.$str.']'; default: return var_export($arg,TRUE); } @@ -642,7 +695,7 @@ function stringify($arg,array $stack=NULL) { **/ function csv(array $args) { return implode(',',array_map('stripcslashes', - array_map(array($this,'stringify'),$args))); + array_map([$this,'stringify'],$args))); } /** @@ -666,7 +719,7 @@ function($match) { * @param $str string **/ function snakecase($str) { - return strtolower(preg_replace('/[[:upper:]]/','_\0',$str)); + return strtolower(preg_replace('/(?!^)[[:upper:]]/','_\0',$str)); } /** @@ -680,14 +733,15 @@ function sign($num) { } /** - * Extract values of an associative array whose keys start with the given prefix + * Extract values of array whose keys start with the given prefix * @return array * @param $arr array * @param $prefix string **/ function extract($arr,$prefix) { - $out=array(); - foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr)) as $key) + $out=[]; + foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr)) + as $key) $out[substr($key,strlen($prefix))]=$arr[$key]; return $out; } @@ -749,33 +803,29 @@ function decode($str) { * @param $func callback * @param $stack array **/ - function recursive($arg,$func,$stack=NULL) { + function recursive($arg,$func,$stack=[]) { if ($stack) { foreach ($stack as $node) if ($arg===$node) return $arg; } - else - $stack=array(); switch (gettype($arg)) { case 'object': - if (method_exists('ReflectionClass','iscloneable')) { - $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,array($arg))); - } + $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=array(); + $copy=[]; foreach ($arg as $key=>$val) $copy[$key]=$this->recursive($val,$func, - array_merge($stack,array($arg))); + array_merge($stack,[$arg])); return $copy; } return $func($arg); @@ -822,20 +872,23 @@ function format() { $conv=localeconv(); return preg_replace_callback( '/\{(?P\d+)\s*(?:,\s*(?P\w+)\s*'. - '(?:,\s*(?P(?:\w+(?:\s*\{.+?\}\s*,?)?)*)'. + '(?:,\s*(?P(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'. '(?:,\s*(?P.+?))?)?)?\}/', function($expr) use($args,$conv) { extract($expr); extract($conv); if (!array_key_exists($pos,$args)) return $expr[0]; - if (isset($type)) + if (isset($type)) { + if (isset($this->hive['FORMATS'][$type])) + return $this->call($this->hive['FORMATS'][$type], + [$args[$pos],$mod,isset($prop)?$prop:null]); switch ($type) { case 'plural': preg_match_all('/(?\w+)'. '(?:\s*\{\s*(?.+?)\s*\})/', $mod,$matches,PREG_SET_ORDER); - $ord=array('zero','one','two'); + $ord=['zero','one','two']; foreach ($matches as $match) { extract($match); if (isset($ord[$args[$pos]]) && @@ -850,12 +903,14 @@ function($expr) use($args,$conv) { $args[$pos],0,'',$thousands_sep); case 'currency': $int=$cstm=false; - if (isset($prop) && $cstm=!$int=($prop=='int')) + if (isset($prop) && + $cstm=!$int=($prop=='int')) $currency_symbol=$prop; - if (!$cstm && function_exists('money_format')) + if (!$cstm && + function_exists('money_format')) return money_format( '%'.($int?'i':'n'),$args[$pos]); - $fmt=array( + $fmt=[ 0=>'(nc)',1=>'(n c)', 2=>'(nc)',10=>'+nc', 11=>'+n c',12=>'+ nc', @@ -871,7 +926,7 @@ function($expr) use($args,$conv) { 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'; @@ -881,14 +936,14 @@ function($expr) use($args,$conv) { $pre='p'; } return str_replace( - array('+','n','c'), - array($sgn,number_format( + ['+','n','c'], + [$sgn,number_format( abs($args[$pos]), $frac_digits, $decimal_point, $thousands_sep), $int?$int_curr_symbol - :$currency_symbol), + :$currency_symbol], $fmt[(int)( (${$pre.'_cs_precedes'}%2). (${$pre.'_sign_posn'}%5). @@ -899,12 +954,10 @@ function($expr) use($args,$conv) { return number_format( $args[$pos]*100,0,$decimal_point, $thousands_sep).'%'; - case 'decimal': - return number_format( - $args[$pos],isset($prop)?$prop:2, - $decimal_point,$thousands_sep); } - break; + return number_format( + $args[$pos],isset($prop)?$prop:2, + $decimal_point,$thousands_sep); case 'date': if (empty($mod) || $mod=='short') $prop='%x'; @@ -918,6 +971,7 @@ function($expr) use($args,$conv) { default: return $expr[0]; } + } return $args[$pos]; }, $val @@ -932,8 +986,8 @@ function($expr) use($args,$conv) { function language($code) { $code=preg_replace('/\h+|;q=[0-9.]+/','',$code); $code.=($code?',':'').$this->fallback; - $this->languages=array(); - foreach (array_reverse(explode(',',$code)) as $lang) { + $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]); @@ -943,11 +997,11 @@ function language($code) { array_unshift($this->languages,$parts[0]); } } - } $this->languages=array_unique($this->languages); - $locales=array(); + $locales=[]; $windows=preg_match('/^win/i',PHP_OS); - foreach ($this->languages as $locale) { + // 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]); @@ -966,10 +1020,16 @@ function language($code) { * Return lexicon entries * @return array * @param $path string + * @param $ttl int **/ - function lexicon($path) { - $lex=array(); - foreach ($this->languages?:explode(',',$this->fallback) as $lang) + function lexicon($path,$ttl=0) { + $languages=$this->languages?:explode(',',$this->fallback); + $cache=Cache::instance(); + if ($cache->exists( + $hash=$this->hash(implode(',',$languages)).'.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')) && @@ -994,6 +1054,8 @@ function lexicon($path) { '/\\\\\h*\r?\n/','',$match['rval'])); } } + if ($ttl) + $cache->set($hash,$lex,$ttl); return $lex; } @@ -1032,7 +1094,7 @@ function unserialize($arg) { **/ function status($code) { $reason=@constant('self::HTTP_'.$code); - if (PHP_SAPI!='cli' && !headers_sent()) + if (!$this->hive['CLI'] && !headers_sent()) header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason); return $reason; } @@ -1043,20 +1105,25 @@ function status($code) { * @param $secs int **/ function expire($secs=0) { - if (PHP_SAPI!='cli') { - header('X-Content-Type-Options: nosniff'); - header('X-Frame-Options: '.$this->hive['XFRAME']); - header('X-Powered-By: '.$this->hive['PACKAGE']); + if (!$this->hive['CLI'] && !headers_sent()) { + 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'); - if ($secs) { + header('X-Content-Type-Options: nosniff'); + if ($this->hive['VERB']=='GET' && $secs) { $time=microtime(TRUE); header_remove('Pragma'); + header('Cache-Control: max-age='.(int)$secs); header('Expires: '.gmdate('r',$time+$secs)); - header('Cache-Control: max-age='.$secs); header('Last-Modified: '.gmdate('r')); } - else + else { + header('Pragma: no-cache'); header('Cache-Control: no-cache, no-store, must-revalidate'); + header('Expires: '.gmdate('r',0)); + } } } @@ -1099,14 +1166,14 @@ function ip() { } /** - * Return filtered, formatted stack trace + * 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) { + function trace(array $trace=NULL,$format=TRUE) { if (!$trace) { - $trace=debug_backtrace(FALSE); + $trace=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $frame=$trace[0]; if (isset($frame['file']) && $frame['file']==__FILE__) array_shift($trace); @@ -1150,8 +1217,9 @@ function($frame) use($debug) { * @param $code int * @param $text string * @param $trace array + * @param $level int **/ - function error($code,$text='',array $trace=NULL) { + function error($code,$text='',array $trace=NULL,$level=0) { $prior=$this->hive['ERROR']; $header=$this->status($code); $req=$this->hive['VERB'].' '.$this->hive['PATH']; @@ -1162,22 +1230,24 @@ function error($code,$text='',array $trace=NULL) { foreach (explode("\n",$trace) as $nexus) if ($nexus) error_log($nexus); - if ($highlight=PHP_SAPI!='cli' && !$this->hive['AJAX'] && + if ($highlight=!$this->hive['CLI'] && !$this->hive['AJAX'] && $this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS)) $trace=$this->highlight($trace); - $this->hive['ERROR']=array( + $this->hive['ERROR']=[ 'status'=>$header, 'code'=>$code, 'text'=>$text, - 'trace'=>$trace - ); + 'trace'=>$trace, + 'level'=>$level + ]; + $this->expire(-1); $handler=$this->hive['ONERROR']; $this->hive['ONERROR']=NULL; $eol="\n"; if ((!$handler || - $this->call($handler,array($this,$this->hive['PARAMS']), + $this->call($handler,[$this,$this->hive['PARAMS']], 'beforeroute,afterroute')===FALSE) && - !$prior && PHP_SAPI!='cli' && !$this->hive['QUIET']) + !$prior && !$this->hive['CLI'] && !$this->hive['QUIET']) echo $this->hive['AJAX']? json_encode($this->hive['ERROR']): (''.$eol. @@ -1208,8 +1278,8 @@ function error($code,$text='',array $trace=NULL) { function mock($pattern, array $args=NULL,array $headers=NULL,$body=NULL) { if (!$args) - $args=array(); - $types=array('sync','ajax'); + $args=[]; + $types=['sync','ajax','cli']; preg_match('/([\|\w]+)\h+(?:@(\w+)(?:(\(.+?)\))*|([^\h]+))'. '(?:\h+\[('.implode('|',$types).')\])?/',$pattern,$parts); $verb=strtoupper($parts[1]); @@ -1218,7 +1288,7 @@ function mock($pattern, 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]):array()); + isset($parts[3])?$this->parse($parts[3]):[]); } if (empty($parts[4])) user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR); @@ -1226,11 +1296,12 @@ function mock($pattern, parse_str(@$url['query'],$GLOBALS['_GET']); if (preg_match('/GET|HEAD/',$verb)) $GLOBALS['_GET']=array_merge($GLOBALS['_GET'],$args); - $GLOBALS['_POST']=$verb=='POST'?$args:array(); + $GLOBALS['_POST']=$verb=='POST'?$args:[]; $GLOBALS['_REQUEST']=array_merge($GLOBALS['_GET'],$GLOBALS['_POST']); - foreach ($headers?:array() as $key=>$val) + 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']); @@ -1239,6 +1310,8 @@ function mock($pattern, $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(); } @@ -1251,7 +1324,7 @@ function mock($pattern, * @param $kbps int **/ function route($pattern,$handler,$ttl=0,$kbps=0) { - $types=array('sync','ajax'); + $types=['sync','ajax','cli']; $alias=null; if (is_array($pattern)) { foreach ($pattern as $item) @@ -1269,39 +1342,43 @@ function route($pattern,$handler,$ttl=0,$kbps=0) { } if (empty($parts[3])) user_error(sprintf(self::E_Pattern,$pattern),E_USER_ERROR); - $type=empty($parts[5])? - self::REQ_SYNC|self::REQ_AJAX: - constant('self::REQ_'.strtoupper($parts[5])); + $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)]= - array($handler,$ttl,$kbps,$alias); + [$handler,$ttl,$kbps,$alias]; } } /** * Reroute to specified URI * @return NULL - * @param $url string + * @param $url array|string * @param $permanent bool **/ function reroute($url=NULL,$permanent=FALSE) { if (!$url) $url=$this->hive['REALM']; - if (preg_match('/^(?:@(\w+)(?:(\(.+?)\))*)/',$url,$parts)) { + 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); $url=$this->hive['ALIASES'][$parts[1]]; } - $url=$this->build($url, - isset($parts[2])?$this->parse($parts[2]):array()); + $url=$this->build($url,isset($parts[2])?$this->parse($parts[2]):[]). + (isset($parts[3])?$parts[3]:''); if (($handler=$this->hive['ONREROUTE']) && - $this->call($handler,array($url,$permanent))!==FALSE) + $this->call($handler,[$url,$permanent])!==FALSE) return; - if ($url[0]=='/') - $url=$this->hive['BASE'].$url; - if (PHP_SAPI!='cli') { + if ($url[0]=='/') { + $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']) { header('Location: '.$url); $this->status($permanent?301:302); die; @@ -1326,7 +1403,7 @@ function map($url,$class,$ttl=0,$kbps=0) { foreach (explode('|',self::VERBS) as $method) $this->route($method.' '.$url,is_string($class)? $class.'->'.$this->hive['PREMAP'].strtolower($method): - array($class,$this->hive['PREMAP'].strtolower($method)), + [$class,$this->hive['PREMAP'].strtolower($method)], $ttl,$kbps); } @@ -1381,10 +1458,31 @@ 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>[^\/\?]+)', - str_replace('\*','([^\?]+)',preg_quote($pattern,'/'))). - '\/?(?:\?.*)?$/'.$case.'um',$url,$args); + 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; } @@ -1400,14 +1498,18 @@ function run() { // No routes defined user_error(self::E_Routes,E_USER_ERROR); // Match specific routes first - $paths=array(); - foreach ($keys=array_keys($this->hive['ROUTES']) as $key) - $paths[]=str_replace('@','*@',$key); + $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=$this->rel(urldecode($this->hive['URI'])); + $req=$this->rel(urldecode($this->hive['PATH'])); if ($cors=(isset($this->hive['HEADERS']['Origin']) && $this->hive['CORS']['origin'])) { $cors=$this->hive['CORS']; @@ -1415,45 +1517,42 @@ function run() { header('Access-Control-Allow-Credentials: '. ($cors['credentials']?'true':'false')); } - $allowed=array(); + $allowed=[]; foreach ($this->hive['ROUTES'] as $pattern=>$routes) { if (!$args=$this->mask($pattern,$req)) continue; ksort($args); $route=NULL; - if (isset( - $routes[$ptr=$this->hive['AJAX']+1][$this->hive['VERB']])) + $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]; - elseif (isset($routes[self::REQ_SYNC|self::REQ_AJAX])) - $route=$routes[self::REQ_SYNC|self::REQ_AJAX]; if (!$route) continue; if ($this->hive['VERB']!='OPTIONS' && isset($route[$this->hive['VERB']])) { - $parts=parse_url($req); if ($this->hive['VERB']=='GET' && - preg_match('/.+\/$/',$parts['path'])) - $this->reroute(substr($parts['path'],0,-1). - (isset($parts['query'])?('?'.$parts['query']):'')); + 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']]; - if (is_bool(strpos($pattern,'/*'))) - foreach (array_keys($args) as $key) - if (is_numeric($key) && $key) - unset($args[$key]); // 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'])); + 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]; + return isset($args[$id[$pid]])? + $args[$id[$pid]]: + $id[0]; }, $handler ); @@ -1481,7 +1580,7 @@ function($id) use($args) { } // Retrieve from cache backend list($headers,$body,$result)=$data; - if (PHP_SAPI!='cli') + if (!$this->hive['CLI']) array_walk($headers,'header'); $this->expire($cached[0]+$ttl-$now); } @@ -1496,15 +1595,15 @@ function($id) use($args) { $this->hive['BODY']=file_get_contents('php://input'); ob_start(); // Call route handler - $result=$this->call($handler,array($this,$args), + $result=$this->call($handler,[$this,$args], 'beforeroute,afterroute'); $body=ob_get_clean(); if (isset($cache) && !error_get_last()) { // Save to cache backend - $cache->set($hash,array( + $cache->set($hash,[ // Remove cookies preg_grep('/Set-Cookie\:/',headers_list(), - PREG_GREP_INVERT),$body,$result),$ttl); + PREG_GREP_INVERT),$body,$result],$ttl); } } $this->hive['RESPONSE']=$body; @@ -1530,7 +1629,7 @@ function($id) use($args) { if (!$allowed) // URL doesn't match any route $this->error(404); - elseif (PHP_SAPI!='cli') { + elseif (!$this->hive['CLI']) { // Unhandled HTTP method header('Allow: '.implode(',',array_unique($allowed))); if ($cors) { @@ -1559,9 +1658,10 @@ function($id) use($args) { **/ function until($func,$args=NULL,$timeout=60) { if (!$args) - $args=array(); + $args=[]; $time=time(); - $limit=max(0,min($timeout,$max=ini_get('max_execution_time')-1)); + $max=ini_get('max_execution_time'); + $limit=max(0,($max?min($timeout,$max):$timeout)-1); $out=''; // Turn output buffering on ob_start(); @@ -1569,15 +1669,17 @@ function until($func,$args=NULL,$timeout=60) { while ( // No error occurred !$this->hive['ERROR'] && + // Got time left? + time()-$time+1<$limit && // Still alive? !connection_aborted() && - // Got time left? - (time()-$time+1<$limit) && // Restart session - @session_start() && + !headers_sent() && + (session_status()==PHP_SESSION_ACTIVE || session_start()) && // CAUTION: Callback will kill host if it never becomes truthy! - !($out=$this->call($func,$args))) { - session_commit(); + !$out=$this->call($func,$args)) { + if (!$this->hive['CLI']) + session_commit(); // Hush down sleep(1); } @@ -1590,12 +1692,14 @@ function until($func,$args=NULL,$timeout=60) { * Disconnect HTTP client **/ function abort() { - @session_start(); + if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) + session_start(); session_commit(); $out=''; while (ob_get_level()) $out=ob_get_clean().$out; header('Content-Length: '.strlen($out)); + header('Connection: close'); echo $out; flush(); if (function_exists('fastcgi_finish_request')) @@ -1618,12 +1722,12 @@ function grab($func,$args=NULL) { $parts[1]=call_user_func($parts[1].'::instance'); else { $ref=new ReflectionClass($parts[1]); - $parts[1]=method_exists($parts[1],'__construct')? + $parts[1]=method_exists($parts[1],'__construct') && $args? $ref->newinstanceargs($args): $ref->newinstance(); } } - $func=array($parts[1],$parts[3]); + $func=[$parts[1],$parts[3]]; } return $func; } @@ -1637,7 +1741,7 @@ function grab($func,$args=NULL) { **/ function call($func,$args=NULL,$hooks='') { if (!is_array($args)) - $args=array($args); + $args=[$args]; // Grab the real handler behind the string representation if (is_string($func)) $func=$this->grab($func,$args); @@ -1645,7 +1749,7 @@ function call($func,$args=NULL,$hooks='') { if (!is_callable($func)) // No route handler if ($hooks=='beforeroute,afterroute') { - $allowed=array(); + $allowed=[]; if (is_array($func)) $allowed=array_intersect( array_map('strtoupper',get_class_methods($func[0])), @@ -1666,16 +1770,16 @@ function call($func,$args=NULL,$hooks='') { // Execute pre-route hook if any if ($obj && $hooks && in_array($hook='beforeroute',$hooks) && method_exists($func[0],$hook) && - call_user_func_array(array($func[0],$hook),$args)===FALSE) + call_user_func_array([$func[0],$hook],$args)===FALSE) return FALSE; // Execute callback - $out=call_user_func_array($func,$args?:array()); + $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(array($func[0],$hook),$args)===FALSE) + call_user_func_array([$func[0],$hook],$args)===FALSE) return FALSE; return $out; } @@ -1688,7 +1792,7 @@ function call($func,$args=NULL,$hooks='') { * @param $args mixed **/ function chain($funcs,$args=NULL) { - $out=array(); + $out=[]; foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func) $out[]=$this->call($func,$args); return $out; @@ -1703,7 +1807,7 @@ function chain($funcs,$args=NULL) { **/ function relay($funcs,$args=NULL) { foreach (is_array($funcs)?$funcs:$this->split($funcs) as $func) - $args=array($this->call($func,$args)); + $args=[$this->call($func,$args)]; return array_shift($args); } @@ -1725,12 +1829,17 @@ function config($file,$allow=FALSE) { $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])) + 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) { @@ -1739,45 +1848,54 @@ function config($file,$allow=FALSE) { $match['rval']=Preview::instance()-> resolve($match['rval']); } - if (preg_match('/^(config|route|map|redirect)s\b/i', - $sec,$cmd)) { - call_user_func_array( - array($this,$cmd[1]), - array_merge(array($match['lval']), + 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($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) { if (is_numeric($val)) return $val+0; - $val=ltrim($val); + $val=trim($val); if (preg_match('/^\w+$/i',$val) && defined($val)) return constant($val); - return trim(preg_replace( - array('/\\\\"/','/\\\\\h*(\r?\n)/'), - array('"','\1'),$val)); + return preg_replace('/\\\\"/','"',$val); }, // Mark quoted strings with 0x00 whitespace str_getcsv(preg_replace('/(?[^:]+)(?:\:(?.+))?/', $sec,$parts); $func=isset($parts['func'])?$parts['func']:NULL; $custom=(strtolower($parts['section'])!='globals'); if ($func) - $args=array($this->call($func, - count($args)>1?array($args):$args)); + $args=[$this->call($func,$args)]; + if (count($args)>1) + $args=[$args]; + if (isset($ttl)) + $args=array_merge($args,[$ttl]); call_user_func_array( - array($this,'set'), + [$this,'set'], array_merge( - array( + [ ($custom?($parts['section'].'.'):''). $match['lval'] - ), - count($args)>1?array($args):$args + ], + $args ) ); } @@ -1799,8 +1917,7 @@ function mutex($id,$func,$args=NULL) { mkdir($tmp,self::MODE,TRUE); // Use filesystem lock if (is_file($lock=$tmp. - $this->hash($this->hive['ROOT'].$this->hive['BASE']).'.'. - $this->hash($id).'.lock') && + $this->get('SEED').'.'.$this->hash($id).'.lock') && filemtime($lock)+ini_get('max_execution_time')hive['UNLOAD']; if ((!$handler || $this->call($handler,$this)===FALSE) && $error && in_array($error['type'], - array(E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR))) + [E_ERROR,E_PARSE,E_CORE_ERROR,E_COMPILE_ERROR])) // Fatal error detected - $this->error(500,sprintf(self::E_Fatal,$error['message']), - array($error)); + $this->error(500, + sprintf(self::E_Fatal,$error['message']),[$error]); } /** @@ -2018,37 +2135,73 @@ function __construct() { @ini_set('magic_quotes_gpc',0); @ini_set('register_globals',0); // Intercept errors/exceptions; PHP5.3-compatible - error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE)); + $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,$obj->getmessage(),$obj->gettrace()); + $fw->error(500, + $obj->getmessage().' '. + '['.$obj->getFile().':'.$obj->getLine().']', + $obj->gettrace()); } ); set_error_handler( - function($code,$text) use($fw) { - if ($code & error_reporting()) - $fw->error(500,$text); + function($level,$text) use($fw) { + if ($level & error_reporting()) + $fw->error(500,$text,NULL,$level); } ); if (!isset($_SERVER['SERVER_NAME'])) $_SERVER['SERVER_NAME']=gethostname(); - if (PHP_SAPI=='cli') { + if ($cli=PHP_SAPI=='cli') { // Emulate HTTP request - if (isset($_SERVER['argc']) && $_SERVER['argc']<2) { + $_SERVER['REQUEST_METHOD']='GET'; + if (!isset($_SERVER['argv'][1])) { $_SERVER['argc']++; $_SERVER['argv'][1]='/'; } - $_SERVER['REQUEST_METHOD']='GET'; - $_SERVER['REQUEST_URI']=$_SERVER['argv'][1]; + if (substr($_SERVER['argv'][1],0,1)=='/') + $_SERVER['REQUEST_URI']=$_SERVER['argv'][1]; + else { + $req=$opts=''; + 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.'='; + if (isset($m[3])) + $opts.=$m[3]; + } else + $req.='/'.$arg; + } + $_SERVER['REQUEST_URI']=($req?:'/').'?'.urlencode($opts); + parse_str($opts,$GLOBALS['_GET']); + } + } + $headers=[]; + if (!$cli) { + if (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]; + } } - $headers=array(); - if (PHP_SAPI!='cli') - 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'])) @@ -2057,7 +2210,7 @@ function($code,$text) use($fw) { isset($headers['X-Forwarded-Proto']) && $headers['X-Forwarded-Proto']=='https'?'https':'http'; // Create hive early on to expose header methods - $this->hive=array('HEADERS'=>$headers); + $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'],'', @@ -2066,13 +2219,14 @@ function($code,$text) use($fw) { } $_SERVER['DOCUMENT_ROOT']=realpath($_SERVER['DOCUMENT_ROOT']); $base=''; - if (PHP_SAPI!='cli') + if (!$cli) $base=rtrim($this->fixslashes( dirname($_SERVER['SCRIPT_NAME'])),'/'); $uri=parse_url($_SERVER['REQUEST_URI']); $path=preg_replace('/^'.preg_quote($base,'/').'/','',$uri['path']); + session_cache_limiter(''); call_user_func_array('session_set_cookie_params', - $jar=array( + $jar=[ 'expire'=>0, 'path'=>$base?:'/', 'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) && @@ -2080,43 +2234,45 @@ function($code,$text) use($fw) { $_SERVER['SERVER_NAME']:'', 'secure'=>($scheme=='https'), 'httponly'=>TRUE - ) + ] ); $port=0; if (isset($_SERVER['SERVER_PORT'])) $port=$_SERVER['SERVER_PORT']; // Default configuration - $this->hive+=array( + $this->hive+=[ 'AGENT'=>$this->agent(), 'AJAX'=>$this->ajax(), 'ALIAS'=>NULL, - 'ALIASES'=>array(), + 'ALIASES'=>[], 'AUTOLOAD'=>'./', 'BASE'=>$base, 'BITMASK'=>ENT_COMPAT, 'BODY'=>NULL, 'CACHE'=>FALSE, 'CASELESS'=>TRUE, - 'CONFIG'=>NULL, - 'CORS'=>array( + 'CLI'=>$cli, + 'CORS'=>[ 'headers'=>'', 'origin'=>FALSE, 'credentials'=>FALSE, 'expose'=>FALSE, - 'ttl'=>0), + 'ttl'=>0 + ], 'DEBUG'=>0, - 'DIACRITICS'=>array(), + 'DIACRITICS'=>[], 'DNSBL'=>'', - 'EMOJI'=>array(), + 'EMOJI'=>[], 'ENCODING'=>$charset, 'ERROR'=>NULL, 'ESCAPE'=>TRUE, 'EXCEPTION'=>NULL, 'EXEMPT'=>NULL, 'FALLBACK'=>$this->fallback, + 'FORMATS'=>[], 'FRAGMENT'=>isset($uri['fragment'])?$uri['fragment']:'', 'HALT'=>TRUE, - 'HIGHLIGHT'=>TRUE, + 'HIGHLIGHT'=>FALSE, 'HOST'=>$_SERVER['SERVER_NAME'], 'IP'=>$this->ip(), 'JAR'=>$jar, @@ -2128,7 +2284,7 @@ function($code,$text) use($fw) { 'ONERROR'=>NULL, 'ONREROUTE'=>NULL, 'PACKAGE'=>self::PACKAGE, - 'PARAMS'=>array(), + 'PARAMS'=>[], 'PATH'=>$path, 'PATTERN'=>NULL, 'PLUGINS'=>$this->fixslashes(__DIR__).'/', @@ -2143,11 +2299,12 @@ function($code,$text) use($fw) { (':'.$port):'').$_SERVER['REQUEST_URI'], 'RESPONSE'=>'', 'ROOT'=>$_SERVER['DOCUMENT_ROOT'], - 'ROUTES'=>array(), + 'ROUTES'=>[], 'SCHEME'=>$scheme, + 'SEED'=>$this->hash($_SERVER['SERVER_NAME'].$base), 'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php', 'TEMP'=>'tmp/', - 'TIME'=>microtime(TRUE), + 'TIME'=>&$_SERVER['REQUEST_TIME_FLOAT'], 'TZ'=>@date_default_timezone_get(), 'UI'=>'./', 'UNLOAD'=>NULL, @@ -2156,30 +2313,30 @@ function($code,$text) use($fw) { 'VERB'=>&$_SERVER['REQUEST_METHOD'], 'VERSION'=>self::VERSION, 'XFRAME'=>'SAMEORIGIN' - ); + ]; if (PHP_SAPI=='cli-server' && preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI'])) $this->reroute('/'); if (ini_get('auto_globals_jit')) // Override setting - $GLOBALS+=array('_ENV'=>$_ENV,'_REQUEST'=>$_REQUEST); + $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+=array( - $global=>preg_match('/SERVER|ENV/',$global)?$sync:array() - ); + $this->init+=[ + $global=>preg_match('/SERVER|ENV/',$global)?$sync:[] + ]; } - if ($error=error_get_last()) + if ($check && $error=error_get_last()) // Error detected - $this->error(500,sprintf(self::E_Fatal,$error['message']), - array($error)); + $this->error(500, + sprintf(self::E_Fatal,$error['message']),[$error]); date_default_timezone_set($this->hive['TZ']); // Register framework autoloader - spl_autoload_register(array($this,'autoload')); + spl_autoload_register([$this,'autoload']); // Register shutdown handler - register_shutdown_function(array($this,'unload'),getcwd()); + register_shutdown_function([$this,'unload'],getcwd()); } } @@ -2231,7 +2388,7 @@ function exists($key,&$val=NULL) { if (!empty($raw)) { list($val,$time,$ttl)=(array)$fw->unserialize($raw); if ($ttl===0 || $time+$ttl>microtime(TRUE)) - return array($time,$ttl); + return [$time,$ttl]; $val=null; $this->clear($key); } @@ -2253,14 +2410,14 @@ function set($key,$val,$ttl=0) { $time=microtime(TRUE); if ($cached=$this->exists($key)) list($time,$ttl)=$cached; - $data=$fw->serialize(array($val,$time,$ttl)); + $data=$fw->serialize([$val,$time,$ttl]); $parts=explode('=',$this->dsn,2); switch ($parts[0]) { case 'apc': case 'apcu': return apc_store($ndx,$data,$ttl); case 'redis': - return $this->ref->set($ndx,$data,array('ex'=>$ttl)); + return $this->ref->set($ndx,$data, $ttl ? ['ex'=>$ttl] : []); case 'memcache': return memcache_set($this->ref,$ndx,$data,0,$ttl); case 'wincache': @@ -2327,7 +2484,8 @@ function reset($suffix=NULL,$lifetime=0) { case 'apcu': $info=apc_cache_info('user'); if (!empty($info['cache_list'])) { - $key=array_key_exists('info',$info['cache_list'][0])?'info':'key'; + $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) @@ -2346,6 +2504,7 @@ function reset($suffix=NULL,$lifetime=0) { } 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') @@ -2353,8 +2512,9 @@ function reset($suffix=NULL,$lifetime=0) { foreach (memcache_get_extended_stats( $this->ref,'cachedump',$id) as $data) if (is_array($data)) - foreach ($data as $key=>$val) + foreach (array_keys($data) as $key) if (preg_match($regex,$key) && + ($val=$fw->unserialize(memcache_get($this->ref,$key))) && $val[1]+$lifetimeref,$key); return TRUE; @@ -2366,7 +2526,8 @@ function reset($suffix=NULL,$lifetime=0) { wincache_ucache_delete($item['key_name']); return TRUE; case 'xcache': - return TRUE; /* Not supported */ + xcache_unset_by_prefix($this->prefix.'.'); + return TRUE; case 'folder': if ($glob=@glob($parts[1].'*')) foreach ($glob as $file) @@ -2388,25 +2549,17 @@ function load($dsn) { if ($dsn=trim($dsn)) { if (preg_match('/^redis=(.+)/',$dsn,$parts) && extension_loaded('redis')) { - $port=6379; - $parts=explode(':',$parts[1],2); - if (count($parts)>1) - list($host,$port)=$parts; - else - $host=$parts[0]; + list($host,$port,$db)=explode(':',$parts[1])+[1=>6379,2=>NULL]; $this->ref=new Redis; if(!$this->ref->connect($host,$port,2)) $this->ref=NULL; + if(isset($db)) + $this->ref->select($db); } elseif (preg_match('/^memcache=(.+)/',$dsn,$parts) && extension_loaded('memcache')) foreach ($fw->split($parts[1]) as $server) { - $port=11211; - $parts=explode(':',$server,2); - if (count($parts)>1) - list($host,$port)=$parts; - else - $host=$parts[0]; + list($host,$port)=explode(':',$server)+[1=>11211]; if (empty($this->ref)) $this->ref=@memcache_connect($host,$port)?:NULL; else @@ -2423,7 +2576,7 @@ function load($dsn) { !is_dir($parts[1])) mkdir($parts[1],Base::MODE,TRUE); } - $this->prefix=$fw->hash($_SERVER['SERVER_NAME'].$fw->get('BASE')); + $this->prefix=$fw->get('SEED'); return $this->dsn=$dsn; } @@ -2486,9 +2639,9 @@ function($val) use($fw) { protected function sandbox(array $hive=NULL) { $this->level++; $fw=Base::instance(); - $implicit=false; - if ($hive === null) { - $implicit=true; + $implicit=FALSE; + if (is_null($hive)) { + $implicit=TRUE; $hive=$fw->hive(); } if ($this->level<2 || $implicit) { @@ -2497,7 +2650,7 @@ protected function sandbox(array $hive=NULL) { if (isset($hive['ALIASES'])) $hive['ALIASES']=$fw->build($hive['ALIASES']); } - unset($fw, $implicit); + unset($fw,$implicit); extract($hive); unset($hive); ob_start(); @@ -2521,10 +2674,11 @@ function render($file,$mime='text/html',array $hive=NULL,$ttl=0) { return $data; foreach ($fw->split($fw->get('UI').';./') as $dir) if (is_file($this->view=$fw->fixslashes($dir.$file))) { - if (isset($_COOKIE[session_name()])) - @session_start(); + if (isset($_COOKIE[session_name()]) && + !headers_sent() && session_status()!=PHP_SESSION_ACTIVE) + session_start(); $fw->sync('SESSION'); - if ($mime && PHP_SAPI!='cli' && !headers_sent()) + if ($mime && !$fw->get('CLI') && !headers_sent()) header('Content-Type: '.$mime.'; '. 'charset='.$fw->get('ENCODING')); $data=$this->sandbox($hive); @@ -2555,12 +2709,12 @@ class Preview extends View { //! MIME type $mime, //! token filter - $filter=array( + $filter=[ 'esc'=>'$this->esc', 'raw'=>'$this->raw', 'alias'=>'\Base::instance()->alias', 'format'=>'\Base::instance()->format' - ); + ]; /** * Convert token to variable @@ -2574,9 +2728,10 @@ function token($str) { $str,$parts)) { $str=trim($parts[1]); foreach (Base::instance()->split($parts[2]) as $func) - $str=is_string($cmd=$this->filter($func))?$cmd.'('.$str.')': + $str=is_string($cmd=$this->filter($func))? + $cmd.'('.$str.')': '\Base::instance()->call('. - '$this->filter(\''.$func.'\'),array('.$str.'))'; + '$this->filter(\''.$func.'\'),['.$str.'])'; } return $str; } @@ -2646,9 +2801,13 @@ function resolve($str,array $hive=NULL) { * @param $hive array * @param $ttl int **/ - function render($file,$mime='text/html',array $hive=NULL,$ttl=0) { + function render($file,$mime=NULL,array $hive=NULL,$ttl=0) { $fw=Base::instance(); $cache=Cache::instance(); + if ($mime) + $this->mime=$mime; + elseif (!$this->mime) + $this->mime='text/html'; if (!is_dir($tmp=$fw->get('TEMP'))) mkdir($tmp,Base::MODE,TRUE); foreach ($fw->split($fw->get('UI')) as $dir) { @@ -2656,23 +2815,23 @@ function render($file,$mime='text/html',array $hive=NULL,$ttl=0) { return $data; if (is_file($view=$fw->fixslashes($dir.$file))) { if (!is_file($this->view=($tmp. - $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash($view).'.php')) || + $fw->get('SEED').'.'.$fw->hash($view).'.php')) || filemtime($this->view)\h*'. - '(?!["\'])|\{\*.+?\*\}/is','', + '/\h*<\?(?!xml)(?:php|\s*=)?.+?\?>\h*'. + '|\{\*.+?\*\}/is','', $fw->read($view)); if (method_exists($this,'parse')) $text=$this->parse($text); $fw->write($this->view,$this->build($text)); } - if (isset($_COOKIE[session_name()])) - @session_start(); + if (isset($_COOKIE[session_name()]) && + !headers_sent() && session_status()!=PHP_SESSION_ACTIVE) + session_start(); $fw->sync('SESSION'); - if ($mime && PHP_SAPI!='cli' && !headers_sent()) - header('Content-Type: '.($this->mime=$mime).'; '. + if (!$fw->get('CLI') && !headers_sent()) + header('Content-Type: '.$this->mime.'; '. 'charset='.$fw->get('ENCODING')); $data=$this->sandbox($hive); if(isset($this->trigger['afterrender'])) diff --git a/lib/basket.php b/lib/basket.php index 94e030630..ffb498e7a 100644 --- a/lib/basket.php +++ b/lib/basket.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -34,7 +34,7 @@ class Basket extends Magic { //! Current item identifier $id, //! Current item contents - $item=array(); + $item=[]; /** * Return TRUE if field is defined @@ -86,7 +86,7 @@ function clear($key) { * @param $val mixed **/ function find($key=NULL,$val=NULL) { - $out=array(); + $out=[]; if (isset($_SESSION[$this->key])) { foreach ($_SESSION[$this->key] as $id=>$item) if (!isset($key) || @@ -122,7 +122,7 @@ function load($key,$val) { return $this->item=$found[0]->item; } $this->reset(); - return array(); + return []; } /** @@ -175,7 +175,7 @@ function erase($key,$val) { **/ function reset() { $this->id=NULL; - $this->item=array(); + $this->item=[]; } /** @@ -219,7 +219,7 @@ function checkout() { unset($_SESSION[$this->key]); return $out; } - return array(); + return []; } /** @@ -229,7 +229,8 @@ function checkout() { **/ function __construct($key='basket') { $this->key=$key; - @session_start(); + if (session_status()!=PHP_SESSION_ACTIVE) + session_start(); Base::instance()->sync('SESSION'); $this->reset(); } diff --git a/lib/bcrypt.php b/lib/bcrypt.php index 6ecd61e47..2f44d39b7 100644 --- a/lib/bcrypt.php +++ b/lib/bcrypt.php @@ -1,26 +1,27 @@ . +* +* @deprecated use http://php.net/manual/en/ref.password.php instead (PHP 5.5+ only) +**/ - Copyright (c) 2009-2015 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 . - -*/ - -//! Lightweight password hashing library class Bcrypt extends Prefab { //@{ Error messages diff --git a/lib/cli/ws.php b/lib/cli/ws.php new file mode 100644 index 000000000..390fa7331 --- /dev/null +++ b/lib/cli/ws.php @@ -0,0 +1,516 @@ +. + +*/ + +namespace CLI; + +//! RFC6455 WebSocket server +class WS { + + const + //! UUID magic string + Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11', + //! Max packet size + Packet=65536; + + //@{ Mask bits for first byte of header + const + Text=0x01, + Binary=0x02, + Close=0x08, + Ping=0x09, + Pong=0x0a, + OpCode=0x0f, + Finale=0x80; + //@} + + //@{ Mask bits for second byte of header + const + Length=0x7f; + //@} + + protected + $addr, + $ctx, + $wait, + $sockets, + $agents=[], + $events=[]; + + /** + * Allocate stream socket + * @return NULL + * @param $socket resource + **/ + function alloc($socket) { + if (is_bool($str=$this->read($socket))) { + $this->close($socket); + return; + } + // Get WebSocket headers + $hdrs=[]; + $CRLF="\r\n"; + $verb=NULL; + $uri=NULL; + foreach (explode($CRLF,trim($str)) as $line) + if (preg_match('/^(\w+)\s(.+)\sHTTP\/1\.\d$/', + trim($line),$match)) { + $verb=$match[1]; + $uri=$match[2]; + } + else + if (preg_match('/^(.+): (.+)/',trim($line),$match)) + // Standardize header + $hdrs[ + strtr( + ucwords( + strtolower( + strtr($match[1],'-',' ') + ) + ),' ','-' + ) + ]=$match[2]; + else { + $this->close($socket); + return; + } + if (empty($hdrs['Upgrade']) && + empty($hdrs['Sec-Websocket-Key'])) { + // Not a WebSocket request + if ($verb && $uri) + $this->write( + $socket, + $str='HTTP/1.1 400 Bad Request'.$CRLF. + 'Connection: close'.$CRLF.$CRLF + ); + $this->close($socket); + return; + } + // Handshake + $bytes=$this->write( + $socket, + $str='HTTP/1.1 101 Switching Protocols'.$CRLF. + 'Upgrade: websocket'.$CRLF. + 'Connection: Upgrade'.$CRLF. + 'Sec-WebSocket-Accept: '. + base64_encode( + sha1( + $hdrs['Sec-Websocket-Key']. + self::Magic, + TRUE + ) + ).$CRLF.$CRLF + ); + if ($bytes) { + // Connect agent to server + $this->sockets[]=$socket; + $this->agents[(int)$socket]= + new Agent($this,$socket,$verb,$uri,$hdrs); + } + else + $this->close($socket); + } + + /** + * Close stream socket + * @return NULL + * @param $socket resource + **/ + function close($socket) { + stream_socket_shutdown($socket,STREAM_SHUT_WR); + @fclose($socket); + } + + /** + * Free stream socket + * @return bool + * @param $socket resource + **/ + function free($socket) { + unset($this->sockets[array_search($socket,$this->sockets)]); + unset($this->agents[(int)$socket]); + $this->close($socket); + } + + /** + * Read from stream socket + * @return string|FALSE + * @param $socket resource + **/ + function read($socket) { + if (is_string($str=@fread($socket,self::Packet)) && + strlen($str) && + strlen($str)events['error']) && + is_callable($func=$this->events['error'])) + $func($this); + return FALSE; + } + + /** + * Write to stream socket + * @return int|FALSE + * @param $socket resource + * @param $str string + **/ + function write($socket,$str) { + for ($i=0,$bytes=0;$ievents['error']) && + is_callable($func=$this->events['error'])) + $func($this); + return FALSE; + } + return $bytes; + } + + /** + * Return socket agents + * @return array + * @param $uri string + ***/ + function agents($uri=NULL) { + return array_filter( + $this->agents, + function($val) use($uri) { + return $uri?($val->uri()==$uri):TRUE; + } + ); + } + + /** + * Return event handlers + * @return array + **/ + function events() { + return $this->events; + } + + /** + * Bind function to event handler + * @return object + * @param $event string + * @param $func callable + **/ + function on($event,$func) { + $this->events[$event]=$func; + return $this; + } + + /** + * Terminate server + * @return NULL + * @param $signal int + **/ + function kill($signal) { + die; + } + + /** + * Execute the server process + * @return object + **/ + function run() { + $fw=\Base::instance(); + // Assign signal handlers + declare(ticks=1); + pcntl_signal(SIGINT,[$this,'kill']); + pcntl_signal(SIGTERM,[$this,'kill']); + gc_enable(); + // Activate WebSocket listener + $listen=stream_socket_server( + $this->addr,$errno,$errstr, + STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, + $this->ctx + ); + $socket=socket_import_stream($listen); + register_shutdown_function(function() use($listen) { + foreach ($this->sockets as $socket) + if ($socket!=$listen) + $this->free($socket); + $this->close($listen); + if (isset($this->events['stop']) && + is_callable($func=$this->events['stop'])) + $func($this); + }); + if ($errstr) + user_error($errstr,E_USER_ERROR); + if (isset($this->events['start']) && + is_callable($func=$this->events['start'])) + $func($this); + $this->sockets=[$listen]; + $empty=[]; + $wait=$this->wait; + while (TRUE) { + $active=$this->sockets; + $mark=microtime(TRUE); + $count=@stream_select( + $active,$empty,$empty,(int)$wait,round(1e6*($wait-(int)$wait)) + ); + if (is_bool($count) && $wait) { + if (isset($this->events['error']) && + is_callable($func=$this->events['error'])) + $func($this); + die; + } + if ($count) { + // Process active connections + foreach ($active as $socket) { + if (!is_resource($socket)) + continue; + if ($socket==$listen) { + if ($socket=@stream_socket_accept($listen,0)) + $this->alloc($socket); + else + if (isset($this->events['error']) && + is_callable($func=$this->events['error'])) + $func($this); + } + else { + $id=(int)$socket; + if (isset($this->agents[$id]) && + $raw=$this->agents[$id]->fetch()) { + list($op,$data)=$raw; + // Dispatch + switch ($op & self::OpCode) { + case self::Ping: + $this->agents[$id]->send(self::Pong); + break; + case self::Close: + $this->free($socket); + break; + case self::Text: + $data=trim($data); + case self::Binary: + if (isset($this->events['receive']) && + is_callable($func=$this->events['receive'])) + $func($this->agents[$id],$op,$data); + break; + } + } + } + } + $wait-=microtime(TRUE)-$mark; + while ($wait<1e-6) { + $wait+=$this->wait; + $count=0; + } + } + if (!$count) { + $mark=microtime(TRUE); + foreach ($this->sockets as $socket) { + if (!is_resource($socket)) + continue; + $id=(int)$socket; + if ($socket!=$listen && + isset($this->agents[$id]) && + isset($this->events['idle']) && + is_callable($func=$this->events['idle'])) + $func($this->agents[$id]); + } + $wait=$this->wait-microtime(TRUE)+$mark; + } + gc_collect_cycles(); + } + } + + /** + * Instantiate object + * @return object + * @param $addr string + * @param $ctx resource + * @param $wait int + **/ + function __construct($addr,$ctx=NULL,$wait=60) { + $this->addr=$addr; + $this->ctx=$ctx?:stream_context_create(); + $this->wait=$wait; + $this->events=[]; + } + +} + +//! RFC6455 remote socket +class Agent { + + protected + $server, + $id, + $socket, + $flag, + $verb, + $uri, + $headers, + $events, + $buffer; + + /** + * Return server instance + * @return object + **/ + function server() { + return $this->server; + } + + /** + * Return socket ID + * @return string + **/ + function id() { + return $this->id; + } + + /** + * Return request method + * @return string + **/ + function verb() { + return $this->verb; + } + + /** + * Return request URI + * @return string + **/ + function uri() { + return $this->uri; + } + + /** + * Return socket headers + * @return string + **/ + function headers() { + return $this->headers; + } + + /** + * Frame and transmit payload + * @return string|FALSE + * @param $socket resource + * @param $op int + * @param $payload string + **/ + function send($op,$data='') { + $mask=WS::Finale | $op & WS::OpCode; + $len=strlen($data); + $str=''; + if ($len>0xffff) + $str=pack('CCNN',$mask,0x7f,$len); + else + if ($len>0x7d) + $str=pack('CCn',$mask,0x7e,$len); + else + $str=pack('CC',$mask,$len); + $str.=$data; + $server=$this->server(); + if (is_bool($server->write($this->socket,$str))) { + $this->free(); + return FALSE; + } + if (!in_array($op,[WS::Pong,WS::Close]) && + isset($this->events['send']) && + is_callable($func=$this->events['send'])) + $func($this,$op,$data); + return $data; + } + + /** + * Retrieve and unmask payload + * @return array|FALSE + **/ + function fetch() { + // Unmask payload + $server=$this->server(); + if (is_bool($buf=$server->read($this->socket))) { + $this->free(); + return FALSE; + } + $buf=($this->buffer.=$buf); + $op=ord($buf[0]) & WS::OpCode; + $len=ord($buf[1]) & WS::Length; + $pos=2; + if ($len==0x7e) { + $len=ord($buf[2])*256+ord($buf[3]); + $pos+=2; + } + else + if ($len==0x7f) { + for ($i=0,$len=0;$i<8;$i++) + $len=$len*256+ord($buf[$i+2]); + $pos+=8; + } + for ($i=0,$mask=[];$i<4;$i++) + $mask[$i]=ord($buf[$pos+$i]); + $pos+=4; + if (strlen($buf)<$len+$pos) + return FALSE; + for ($i=0,$data='';$i<$len;$i++) + $data.=chr(ord($buf[$pos+$i])^$mask[$i%4]); + $this->buffer=''; + return [$op,$data]; + } + + /** + * Free stream socket + * @return NULL + **/ + function free() { + $this->server->free($this->socket); + } + + /** + * Destroy object + * @return NULL + **/ + function __destruct() { + if (isset($this->events['disconnect']) && + is_callable($func=$this->events['disconnect'])) + $func($this); + } + + /** + * Instantiate object + * @return object + * @param $server object + * @param $socket resource + * @param $verb string + * @param $uri string + * @param $hdrs array + **/ + function __construct($server,$socket,$verb,$uri,array $hdrs) { + $this->server=$server; + $this->id=stream_socket_get_name($socket,TRUE); + $this->socket=$socket; + $this->verb=$verb; + $this->uri=$uri; + $this->headers=$hdrs; + $this->events=$server->events(); + $this->buffer=''; + if (isset($this->events['connect']) && + is_callable($func=$this->events['connect'])) + $func($this); + } + +} diff --git a/lib/code.css b/lib/code.css old mode 100644 new mode 100755 diff --git a/lib/db/cursor.php b/lib/db/cursor.php index baef95313..9ea7cdc90 100644 --- a/lib/db/cursor.php +++ b/lib/db/cursor.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -32,11 +32,11 @@ abstract class Cursor extends \Magic implements \IteratorAggregate { protected //! Query results - $query=array(), + $query=[], //! Current position $ptr=0, //! Event listeners - $trigger=array(); + $trigger=[]; /** * Return database type @@ -126,7 +126,7 @@ function dry() { **/ function findone($filter=NULL,array $options=NULL,$ttl=0) { if (!$options) - $options=array(); + $options=[]; // Override limit $options['limit']=1; return ($data=$this->find($filter,$options,$ttl))?$data[0]:FALSE; @@ -148,11 +148,11 @@ function paginate( $total=$this->count($filter,$ttl); $count=ceil($total/$size); $pos=max(0,min($pos,$count-1)); - return array( + return [ 'subset'=>$this->find($filter, array_merge( - $options?:array(), - array('limit'=>$size,'offset'=>$pos*$size) + $options?:[], + ['limit'=>$size,'offset'=>$pos*$size] ), $ttl ), @@ -160,7 +160,7 @@ function paginate( 'limit'=>$size, 'count'=>$count, 'pos'=>$pos<$count?$pos:0 - ); + ]; } /** @@ -378,7 +378,7 @@ function onerase($func) { * @return NULL **/ function reset() { - $this->query=array(); + $this->query=[]; $this->ptr=0; } diff --git a/lib/db/jig.php b/lib/db/jig.php index 736735c4b..d4337933f 100644 --- a/lib/db/jig.php +++ b/lib/db/jig.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -51,7 +51,7 @@ class Jig { function &read($file) { if (!$this->dir || !is_file($dst=$this->dir.$file)) { if (!isset($this->data[$file])) - $this->data[$file]=array(); + $this->data[$file]=[]; return $this->data[$file]; } $fw=\Base::instance(); @@ -106,11 +106,14 @@ function uuid() { } /** - * Return profiler results + * Return profiler results (or disable logging) + * @param $flag bool * @return string **/ - function log() { - return $this->log; + function log($flag=TRUE) { + if ($flag) + return $this->log; + $this->log=FALSE; } /** @@ -129,12 +132,16 @@ function jot($frame) { **/ function drop() { if (!$this->dir) - $this->data=array(); + $this->data=[]; elseif ($glob=@glob($this->dir.'/*',GLOB_NOSORT)) foreach ($glob as $file) @unlink($file); } + //! Prohibit cloning + private function __clone() { + } + /** * Instantiate class * @param $dir string diff --git a/lib/db/jig/mapper.php b/lib/db/jig/mapper.php index f0f3953c9..74335fcd8 100644 --- a/lib/db/jig/mapper.php +++ b/lib/db/jig/mapper.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -33,7 +33,7 @@ class Mapper extends \DB\Cursor { //! Document identifier $id, //! Document contents - $document=array(); + $document=[]; /** * Return database type @@ -97,7 +97,7 @@ protected function factory($id,$row) { $mapper->id=$id; foreach ($row as $field=>$val) $mapper->document[$field]=$val; - $mapper->query=array(clone($mapper)); + $mapper->query=[clone($mapper)]; if (isset($mapper->trigger['load'])) \Base::instance()->call($mapper->trigger['load'],$mapper); return $mapper; @@ -111,7 +111,7 @@ protected function factory($id,$row) { function cast($obj=NULL) { if (!$obj) $obj=$this; - return $obj->document+array('_id'=>$this->id); + return $obj->document+['_id'=>$this->id]; } /** @@ -157,20 +157,20 @@ function($expr) use($self) { **/ function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) { if (!$options) - $options=array(); - $options+=array( + $options=[]; + $options+=[ 'order'=>NULL, 'limit'=>0, 'offset'=>0 - ); + ]; $fw=\Base::instance(); $cache=\Cache::instance(); $db=$this->db; $now=microtime(TRUE); - $data=array(); + $data=[]; if (!$fw->get('CACHE') || !$ttl || !($cached=$cache->exists( $hash=$fw->hash($this->db->dir(). - $fw->stringify(array($filter,$options))).'.jig',$data)) || + $fw->stringify([$filter,$options])).'.jig',$data)) || $cached[0]+$ttlread($this->file); if (is_null($data)) @@ -188,8 +188,8 @@ function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) { $args=isset($filter[1]) && is_array($filter[1])? $filter[1]: array_slice($filter,1,NULL,TRUE); - $args=is_array($args)?$args:array(1=>$args); - $keys=$vals=array(); + $args=is_array($args)?$args:[1=>$args]; + $keys=$vals=[]; $tokens=array_slice( token_get_all('token($expr)),1); $data=array_filter($data, @@ -248,7 +248,7 @@ function($val1,$val2) use($cols) { $val1[$col]=NULL; if (!array_key_exists($col,$val2)) $val2[$col]=NULL; - list($v1,$v2)=array($val1[$col],$val2[$col]); + list($v1,$v2)=[$val1[$col],$val2[$col]]; if ($out=strnatcmp($v1,$v2)* (($order==SORT_ASC)*2-1)) return $out; @@ -263,7 +263,7 @@ function($val1,$val2) use($cols) { // Save to cache backend $cache->set($hash,$data,$ttl); } - $out=array(); + $out=[]; foreach ($data as $id=>&$doc) { unset($doc['_id']); $out[]=$this->factory($id,$doc); @@ -303,7 +303,7 @@ function count($filter=NULL,$ttl=0) { * @param $ofs int **/ function skip($ofs=1) { - $this->document=($out=parent::skip($ofs))?$out->document:array(); + $this->document=($out=parent::skip($ofs))?$out->document:[]; $this->id=$out?$out->id:NULL; if ($this->document && isset($this->trigger['load'])) \Base::instance()->call($this->trigger['load'],$this); @@ -324,10 +324,10 @@ function insert() { !connection_aborted()) usleep(mt_rand(0,100)); $this->id=$id; - $pkey=array('_id'=>$this->id); + $pkey=['_id'=>$this->id]; if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], - array($this,$pkey))===FALSE) + [$this,$pkey])===FALSE) return $this->document; $data[$id]=$this->document; $db->write($this->file,$data); @@ -335,8 +335,8 @@ function insert() { $this->file.' [insert] '.json_encode($this->document)); if (isset($this->trigger['afterinsert'])) \Base::instance()->call($this->trigger['afterinsert'], - array($this,$pkey)); - $this->load(array('@_id=?',$this->id)); + [$this,$pkey]); + $this->load(['@_id=?',$this->id]); return $this->document; } @@ -350,7 +350,7 @@ function update() { $data=&$db->read($this->file); if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], - array($this,array('_id'=>$this->id)))===FALSE) + [$this,['_id'=>$this->id]])===FALSE) return $this->document; $data[$this->id]=$this->document; $db->write($this->file,$data); @@ -358,7 +358,7 @@ function update() { $this->file.' [update] '.json_encode($this->document)); if (isset($this->trigger['afterupdate'])) \Base::instance()->call($this->trigger['afterupdate'], - array($this,array('_id'=>$this->id))); + [$this,['_id'=>$this->id]]); return $this->document; } @@ -371,7 +371,7 @@ function erase($filter=NULL) { $db=$this->db; $now=microtime(TRUE); $data=&$db->read($this->file); - $pkey=array('_id'=>$this->id); + $pkey=['_id'=>$this->id]; if ($filter) { foreach ($this->find($filter,NULL,FALSE) as $mapper) if (!$mapper->erase()) @@ -386,14 +386,14 @@ function erase($filter=NULL) { return FALSE; if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], - array($this,$pkey))===FALSE) + [$this,$pkey])===FALSE) return FALSE; $db->write($this->file,$data); if ($filter) { $args=isset($filter[1]) && is_array($filter[1])? $filter[1]: array_slice($filter,1,NULL,TRUE); - $args=is_array($args)?$args:array(1=>$args); + $args=is_array($args)?$args:[1=>$args]; foreach ($args as $key=>$val) { $vals[]=\Base::instance()-> stringify(is_array($val)?$val[0]:$val); @@ -405,7 +405,7 @@ function erase($filter=NULL) { ($filter?preg_replace($keys,$vals,$filter[0],1):'')); if (isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], - array($this,$pkey)); + [$this,$pkey]); return TRUE; } @@ -415,7 +415,7 @@ function erase($filter=NULL) { **/ function reset() { $this->id=NULL; - $this->document=array(); + $this->document=[]; parent::reset(); } diff --git a/lib/db/jig/session.php b/lib/db/jig/session.php index e4dca2fef..d3a92d9bb 100644 --- a/lib/db/jig/session.php +++ b/lib/db/jig/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -63,12 +63,13 @@ function close() { * @param $id string **/ function read($id) { - $this->load(array('@session_id=?',$this->sid=$id)); + $this->load(['@session_id=?',$this->sid=$id]); if ($this->dry()) return FALSE; if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { $fw=\Base::instance(); - if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) { + 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) $this->destroy($id); $this->close(); @@ -101,7 +102,7 @@ function write($id,$data) { * @param $id string **/ function destroy($id) { - $this->erase(array('@session_id=?',$id)); + $this->erase(['@session_id=?',$id]); return TRUE; } @@ -111,7 +112,7 @@ function destroy($id) { * @param $max int **/ function cleanup($max) { - $this->erase(array('@stamp+?erase(['@stamp+?onsuspect=$onsuspect; session_set_save_handler( - array($this,'open'), - array($this,'close'), - array($this,'read'), - array($this,'write'), - array($this,'destroy'), - array($this,'cleanup') + [$this,'open'], + [$this,'close'], + [$this,'read'], + [$this,'write'], + [$this,'destroy'], + [$this,'cleanup'] ); register_shutdown_function('session_commit'); $fw=\Base::instance(); $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash(mt_rand()); + $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); if ($key) $fw->set($key,$this->_csrf); $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; diff --git a/lib/db/mongo.php b/lib/db/mongo.php index ffe772e0e..fedec0cd8 100644 --- a/lib/db/mongo.php +++ b/lib/db/mongo.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -57,21 +57,27 @@ function uuid() { } /** - * Return MongoDB profiler results + * Return MongoDB profiler results (or disable logging) + * @param $flag bool * @return string **/ - function log() { - $cursor=$this->selectcollection('system.profile')->find(); - foreach (iterator_to_array($cursor) as $frame) - if (!preg_match('/\.system\..+$/',$frame['ns'])) - $this->log.=date('r',$frame['ts']->sec).' ('. - sprintf('%.1f',$frame['millis']).'ms) '. - $frame['ns'].' ['.$frame['op'].'] '. - (empty($frame['query'])? - '':json_encode($frame['query'])). - (empty($frame['command'])? - '':json_encode($frame['command'])). - PHP_EOL; + function log($flag=TRUE) { + if ($flag) { + $cursor=$this->selectcollection('system.profile')->find(); + foreach (iterator_to_array($cursor) as $frame) + if (!preg_match('/\.system\..+$/',$frame['ns'])) + $this->log.=date('r',$frame['ts']->sec).' ('. + sprintf('%.1f',$frame['millis']).'ms) '. + $frame['ns'].' ['.$frame['op'].'] '. + (empty($frame['query'])? + '':json_encode($frame['query'])). + (empty($frame['command'])? + '':json_encode($frame['command'])). + PHP_EOL; + } else { + $this->log=FALSE; + $this->setprofilinglevel(-1); + } return $this->log; } @@ -81,7 +87,8 @@ function log() { **/ function drop() { $out=$this->db->drop(); - $this->setprofilinglevel(2); + if ($this->log!==FALSE) + $this->setprofilinglevel(2); return $out; } @@ -92,7 +99,11 @@ function drop() { * @param $args array **/ function __call($func,array $args) { - return call_user_func_array(array($this->db,$func),$args); + return call_user_func_array([$this->db,$func],$args); + } + + //! Prohibit cloning + private function __clone() { } /** @@ -104,7 +115,7 @@ function __call($func,array $args) { function __construct($dsn,$dbname,array $options=NULL) { $this->uuid=\Base::instance()->hash($this->dsn=$dsn); $class=class_exists('\MongoClient')?'\MongoClient':'\Mongo'; - $this->db=new \MongoDB(new $class($dsn,$options?:array()),$dbname); + $this->db=new \MongoDB(new $class($dsn,$options?:[]),$dbname); $this->setprofilinglevel(2); } diff --git a/lib/db/mongo/mapper.php b/lib/db/mongo/mapper.php index e814b2937..411c524be 100644 --- a/lib/db/mongo/mapper.php +++ b/lib/db/mongo/mapper.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -31,9 +31,11 @@ class Mapper extends \DB\Cursor { //! Mongo collection $collection, //! Mongo document - $document=array(), + $document=[], //! Mongo cursor - $cursor; + $cursor, + //! Defined fields + $fields; /** * Return database type @@ -92,7 +94,7 @@ protected function factory($row) { $mapper->reset(); foreach ($row as $key=>$val) $mapper->document[$key]=$val; - $mapper->query=array(clone($mapper)); + $mapper->query=[clone($mapper)]; if (isset($mapper->trigger['load'])) \Base::instance()->call($mapper->trigger['load'],$mapper); return $mapper; @@ -119,48 +121,48 @@ function cast($obj=NULL) { **/ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { if (!$options) - $options=array(); - $options+=array( + $options=[]; + $options+=[ 'group'=>NULL, 'order'=>NULL, 'limit'=>0, 'offset'=>0 - ); + ]; $fw=\Base::instance(); $cache=\Cache::instance(); if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn(). - $fw->stringify(array($fields,$filter,$options))).'.mongo', + $fw->stringify([$fields,$filter,$options])).'.mongo', $result)) || !$ttl || $cached[0]+$ttlcollection->group( $options['group']['keys'], $options['group']['initial'], $options['group']['reduce'], - array( + [ 'condition'=>$filter, 'finalize'=>$options['group']['finalize'] - ) + ] ); $tmp=$this->db->selectcollection( $fw->get('HOST').'.'.$fw->get('BASE').'.'. uniqid(NULL,TRUE).'.tmp' ); - $tmp->batchinsert($grp['retval'],array('w'=>1)); - $filter=array(); + $tmp->batchinsert($grp['retval'],['w'=>1]); + $filter=[]; $collection=$tmp; } else { - $filter=$filter?:array(); + $filter=$filter?:[]; $collection=$this->collection; } - $this->cursor=$collection->find($filter,$fields?:array()); + $this->cursor=$collection->find($filter,$fields?:[]); if ($options['order']) $this->cursor=$this->cursor->sort($options['order']); if ($options['limit']) $this->cursor=$this->cursor->limit($options['limit']); if ($options['offset']) $this->cursor=$this->cursor->skip($options['offset']); - $result=array(); + $result=[]; while ($this->cursor->hasnext()) $result[]=$this->cursor->getnext(); if ($options['group']) @@ -169,7 +171,7 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { // Save to cache backend $cache->set($hash,$result,$ttl); } - $out=array(); + $out=[]; foreach ($result as $doc) $out[]=$this->factory($doc); return $out; @@ -184,14 +186,14 @@ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) { **/ function find($filter=NULL,array $options=NULL,$ttl=0) { if (!$options) - $options=array(); - $options+=array( + $options=[]; + $options+=[ 'group'=>NULL, 'order'=>NULL, 'limit'=>0, 'offset'=>0 - ); - return $this->select(NULL,$filter,$options,$ttl); + ]; + return $this->select($this->fields,$filter,$options,$ttl); } /** @@ -204,9 +206,9 @@ function count($filter=NULL,$ttl=0) { $fw=\Base::instance(); $cache=\Cache::instance(); if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify( - array($filter))).'.mongo',$result)) || !$ttl || + [$filter])).'.mongo',$result)) || !$ttl || $cached[0]+$ttlcollection->count($filter?:array()); + $result=$this->collection->count($filter?:[]); if ($fw->get('CACHE') && $ttl) // Save to cache backend $cache->set($hash,$result,$ttl); @@ -221,7 +223,7 @@ function count($filter=NULL,$ttl=0) { * @param $ofs int **/ function skip($ofs=1) { - $this->document=($out=parent::skip($ofs))?$out->document:array(); + $this->document=($out=parent::skip($ofs))?$out->document:[]; if ($this->document && isset($this->trigger['load'])) \Base::instance()->call($this->trigger['load'],$this); return $out; @@ -236,13 +238,13 @@ function insert() { return $this->update(); if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], - array($this,array('_id'=>$this->document['_id'])))===FALSE) + [$this,['_id'=>$this->document['_id']]])===FALSE) return $this->document; $this->collection->insert($this->document); - $pkey=array('_id'=>$this->document['_id']); + $pkey=['_id'=>$this->document['_id']]; if (isset($this->trigger['afterinsert'])) \Base::instance()->call($this->trigger['afterinsert'], - array($this,$pkey)); + [$this,$pkey]); $this->load($pkey); return $this->document; } @@ -252,16 +254,16 @@ function insert() { * @return array **/ function update() { - $pkey=array('_id'=>$this->document['_id']); + $pkey=['_id'=>$this->document['_id']]; if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], - array($this,$pkey))===FALSE) + [$this,$pkey])===FALSE) return $this->document; $this->collection->update( - $pkey,$this->document,array('upsert'=>TRUE)); + $pkey,$this->document,['upsert'=>TRUE]); if (isset($this->trigger['afterupdate'])) \Base::instance()->call($this->trigger['afterupdate'], - array($this,$pkey)); + [$this,$pkey]); return $this->document; } @@ -273,17 +275,17 @@ function update() { function erase($filter=NULL) { if ($filter) return $this->collection->remove($filter); - $pkey=array('_id'=>$this->document['_id']); + $pkey=['_id'=>$this->document['_id']]; if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], - array($this,$pkey))===FALSE) + [$this,$pkey])===FALSE) return FALSE; $result=$this->collection-> - remove(array('_id'=>$this->document['_id'])); + remove(['_id'=>$this->document['_id']]); parent::erase(); if (isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], - array($this,$pkey)); + [$this,$pkey]); return $result; } @@ -292,7 +294,7 @@ function erase($filter=NULL) { * @return NULL **/ function reset() { - $this->document=array(); + $this->document=[]; parent::reset(); } @@ -351,10 +353,12 @@ function getiterator() { * @return void * @param $db object * @param $collection string + * @param $fields array **/ - function __construct(\DB\Mongo $db,$collection) { + function __construct(\DB\Mongo $db,$collection,$fields=NULL) { $this->db=$db; $this->collection=$db->selectcollection($collection); + $this->fields=$fields; $this->reset(); } diff --git a/lib/db/mongo/session.php b/lib/db/mongo/session.php index 0b510f133..5ea894b8a 100644 --- a/lib/db/mongo/session.php +++ b/lib/db/mongo/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -63,12 +63,13 @@ function close() { * @param $id string **/ function read($id) { - $this->load(array('session_id'=>$this->sid=$id)); + $this->load(['session_id'=>$this->sid=$id]); if ($this->dry()) return FALSE; if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { $fw=\Base::instance(); - if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) { + 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) $this->destroy($id); $this->close(); @@ -101,7 +102,7 @@ function write($id,$data) { * @param $id string **/ function destroy($id) { - $this->erase(array('session_id'=>$id)); + $this->erase(['session_id'=>$id]); return TRUE; } @@ -111,7 +112,7 @@ function destroy($id) { * @param $max int **/ function cleanup($max) { - $this->erase(array('$where'=>'this.stamp+'.$max.'<'.time())); + $this->erase(['$where'=>'this.stamp+'.$max.'<'.time()]); return TRUE; } @@ -168,18 +169,17 @@ function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) parent::__construct($db,$table); $this->onsuspect=$onsuspect; session_set_save_handler( - array($this,'open'), - array($this,'close'), - array($this,'read'), - array($this,'write'), - array($this,'destroy'), - array($this,'cleanup') + [$this,'open'], + [$this,'close'], + [$this,'read'], + [$this,'write'], + [$this,'destroy'], + [$this,'cleanup'] ); register_shutdown_function('session_commit'); $fw=\Base::instance(); $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash(mt_rand()); + $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); if ($key) $fw->set($key,$this->_csrf); $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; diff --git a/lib/db/sql.php b/lib/db/sql.php index 600894464..48b49cc1d 100644 --- a/lib/db/sql.php +++ b/lib/db/sql.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -30,6 +30,9 @@ class SQL { E_PKey='Table %s does not have a primary key'; //@} + const + PARAM_FLOAT='float'; + protected //! UUID $uuid, @@ -78,6 +81,14 @@ function commit() { return $out; } + /** + * Return transaction flag + * @return bool + **/ + function trans() { + return $this->trans; + } + /** * Map data type of argument to a PDO constant * @return int @@ -93,6 +104,8 @@ function type($val) { return \PDO::PARAM_INT; case 'resource': return \PDO::PARAM_LOB; + case 'float': + return self::PARAM_FLOAT; default: return \PDO::PARAM_STR; } @@ -106,6 +119,10 @@ function type($val) { **/ function value($type,$val) { switch ($type) { + case self::PARAM_FLOAT: + return (float)(is_string($val) + ? str_replace(',','.',preg_replace('/([.,])(?!\d+$)/','',$val)) + : $val); case \PDO::PARAM_NULL: return (unset)$val; case \PDO::PARAM_INT: @@ -124,15 +141,19 @@ function value($type,$val) { * @return array|int|FALSE * @param $cmds string|array * @param $args string|array - * @param $ttl int + * @param $ttl int|array * @param $log bool + * @param $stamp bool **/ - function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) { + function exec($cmds,$args=NULL,$ttl=0,$log=TRUE,$stamp=FALSE) { + $tag=''; + if (is_array($ttl)) + list($ttl,$tag)=$ttl; $auto=FALSE; if (is_null($args)) - $args=array(); + $args=[]; elseif (is_scalar($args)) - $args=array(1=>$args); + $args=[1=>$args]; if (is_array($cmds)) { if (count($args)<($count=count($cmds))) // Apply arguments to SQL commands @@ -144,9 +165,11 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) { } else { $count=1; - $cmds=array($cmds); - $args=array($args); + $cmds=[$cmds]; + $args=[$args]; } + if ($this->log===FALSE) + $log=FALSE; $fw=\Base::instance(); $cache=\Cache::instance(); $result=FALSE; @@ -161,10 +184,10 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) { if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd)) continue; $now=microtime(TRUE); - $keys=$vals=array(); + $keys=$vals=[]; if ($fw->get('CACHE') && $ttl && ($cached=$cache->exists( $hash=$fw->hash($this->dsn.$cmd. - $fw->stringify($arg)).'.sql',$result)) && + $fw->stringify($arg)).($tag?'.'.$tag:'').'.sql',$result)) && $cached[0]+$ttl>microtime(TRUE)) { foreach ($arg as $key=>$val) { $vals[]=$fw->stringify(is_array($val)?$val[0]:$val); @@ -172,7 +195,7 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) { '/'; } if ($log) - $this->log.=date('r').' ('. + $this->log.=($stamp?(date('r').' '):'').'('. sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. '[CACHED] '. preg_replace($keys,$vals, @@ -182,20 +205,22 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) { foreach ($arg as $key=>$val) { if (is_array($val)) { // User-specified data type - $query->bindvalue($key,$val[0],$val[1]); + $query->bindvalue($key,$val[0], + $val[1]==self::PARAM_FLOAT?\PDO::PARAM_STR:$val[1]); $vals[]=$fw->stringify($this->value($val[1],$val[0])); } else { // Convert to PDO data type $query->bindvalue($key,$val, - $type=$this->type($val)); + ($type=$this->type($val))==self::PARAM_FLOAT? + \PDO::PARAM_STR:$type); $vals[]=$fw->stringify($this->value($type,$val)); } $keys[]='/'.preg_quote(is_numeric($key)?chr(0).'?':$key). '/'; } if ($log) - $this->log.=date('r').' ('. + $this->log.=($stamp?(date('r').' '):'').'('. sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '. preg_replace($keys,$vals, str_replace('?',chr(0).'?',$cmd),1).PHP_EOL; @@ -216,7 +241,7 @@ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) { if (preg_match('/sqlite2?/',$this->engine)) foreach ($result as $pos=>$rec) { unset($result[$pos]); - $result[$pos]=array(); + $result[$pos]=[]; foreach ($rec as $key=>$val) $result[$pos][trim($key,'\'"[]`')]=$val; } @@ -269,20 +294,20 @@ function log($flag=TRUE) { * @return array|FALSE * @param $table string * @param $fields array|string - * @param $ttl int + * @param $ttl int|array **/ function schema($table,$fields=NULL,$ttl=0) { if (strpos($table,'.')) list($schema,$table)=explode('.',$table); // Supported engines - $cmd=array( - 'sqlite2?'=>array( - 'PRAGMA table_info("'.$table.'");', - 'name','type','dflt_value','notnull',0,'pk',TRUE), - 'mysql'=>array( - 'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`;', - 'Field','Type','Default','Null','YES','Key','PRI'), - 'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>array( + $cmd=[ + 'sqlite2?'=>[ + '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,'. @@ -309,10 +334,9 @@ function schema($table,$fields=NULL,$ttl=0) { 'c.table_name='.$this->quote($table). ($this->dbname? (' AND c.table_catalog='. - $this->quote($this->dbname)):''). - ';', - 'field','type','defval','nullable','YES','pkey','PRIMARY KEY'), - 'oci'=>array( + $this->quote($this->dbname)):''), + 'field','type','defval','nullable','YES','pkey','PRIMARY KEY'], + 'oci'=>[ 'SELECT c.column_name AS field, '. 'c.data_type AS type, '. 'c.data_default AS defval, '. @@ -326,35 +350,35 @@ function schema($table,$fields=NULL,$ttl=0) { 'AND constraint_type='.$this->quote('P').') AS pkey '. 'FROM all_tab_cols c '. 'WHERE c.table_name='.$this->quote($table), - 'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P') - ); + 'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P'] + ]; if (is_string($fields)) $fields=\Base::instance()->split($fields); foreach ($cmd as $key=>$val) if (preg_match('/'.$key.'/',$this->engine)) { - // Improve InnoDB performance on MySQL with - // SET GLOBAL innodb_stats_on_metadata=0; - // This requires SUPER privilege! - $rows=array(); + $rows=[]; foreach ($this->exec($val[0],NULL,$ttl) as $row) { if (!$fields || in_array($row[$val[1]],$fields)) - $rows[$row[$val[1]]]=array( + $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: - \PDO::PARAM_STR)), + (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))), '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] - ); + ]; } return $rows; } @@ -420,20 +444,22 @@ function name() { * Return quoted identifier name * @return string * @param $key - **/ - function quotekey($key) { - $delims=array( + * @param bool $split + **/ + function quotekey($key, $split=TRUE) { + $delims=[ 'mysql'=>'``', 'sqlite2?|pgsql|oci'=>'""', 'mssql|sqlsrv|odbc|sybase|dblib'=>'[]' - ); + ]; $use=''; foreach ($delims as $engine=>$delim) if (preg_match('/'.$engine.'/',$this->engine)) { $use=$delim; break; } - return $use[0].implode($use[1].'.'.$use[0],explode('.',$key)).$use[1]; + return $use[0].($split ? implode($use[1].'.'.$use[0],explode('.',$key)) + : $key).$use[1]; } /** @@ -443,7 +469,11 @@ function quotekey($key) { * @param $args array **/ function __call($func,array $args) { - return call_user_func_array(array($this->pdo,$func),$args); + return call_user_func_array([$this->pdo,$func],$args); + } + + //! Prohibit cloning + private function __clone() { } /** @@ -459,10 +489,10 @@ function __construct($dsn,$user=NULL,$pw=NULL,array $options=NULL) { if (preg_match('/^.+?(?:dbname|database)=(.+?)(?=;|$)/is',$dsn,$parts)) $this->dbname=$parts[1]; if (!$options) - $options=array(); + $options=[]; if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql') - $options+=array(\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '. - strtolower(str_replace('-','',$fw->get('ENCODING'))).';'); + $options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '. + strtolower(str_replace('-','',$fw->get('ENCODING'))).';']; $this->pdo=new \PDO($dsn,$user,$pw,$options); $this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME); } diff --git a/lib/db/sql/mapper.php b/lib/db/sql/mapper.php index ca6f8493c..ac4dd87c4 100644 --- a/lib/db/sql/mapper.php +++ b/lib/db/sql/mapper.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -39,7 +39,7 @@ class Mapper extends \DB\Cursor { //! Defined fields $fields, //! Adhoc fields - $adhoc=array(); + $adhoc=[]; /** * Return database type @@ -90,7 +90,7 @@ function set($key,$val) { if (array_key_exists($key,$this->fields)) { $val=is_null($val) && $this->fields[$key]['nullable']? NULL:$this->db->value($this->fields[$key]['pdo_type'],$val); - if ($this->fields[$key]['value']!==$val || + if ($this->fields[$key]['initial']!==$val || $this->fields[$key]['default']!==$val && is_null($val)) $this->fields[$key]['changed']=TRUE; return $this->fields[$key]['value']=$val; @@ -100,7 +100,7 @@ function set($key,$val) { $this->adhoc[$key]['value']=$val; else // Parenthesize expression in case it's a subquery - $this->adhoc[$key]=array('expr'=>'('.$val.')','value'=>NULL); + $this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL]; return $val; } @@ -144,6 +144,8 @@ function type($pdo) { return 'bool'; case \PDO::PARAM_STR: return 'string'; + case \DB\SQL::PARAM_FLOAT: + return 'float'; } } @@ -163,10 +165,11 @@ protected function factory($row) { else continue; $mapper->{$var}[$key]['value']=$val; + $mapper->{$var}[$key]['initial']=$val; if ($var=='fields' && $mapper->{$var}[$key]['pkey']) $mapper->{$var}[$key]['previous']=$val; } - $mapper->query=array(clone($mapper)); + $mapper->query=[clone($mapper)]; if (isset($mapper->trigger['load'])) \Base::instance()->call($mapper->trigger['load'],$mapper); return $mapper; @@ -194,37 +197,36 @@ function($row) { * @param $fields string * @param $filter string|array * @param $options array - * @param $ttl int + * @param $ttl int|array **/ function select($fields,$filter=NULL,array $options=NULL,$ttl=0) { if (!$options) - $options=array(); - $options+=array( + $options=[]; + $options+=[ 'group'=>NULL, 'order'=>NULL, 'limit'=>0, 'offset'=>0 - ); + ]; $db=$this->db; $sql='SELECT '.$fields.' FROM '.$this->table; - $args=array(); - if ($filter) { - if (is_array($filter)) { - $args=isset($filter[1]) && is_array($filter[1])? - $filter[1]: - array_slice($filter,1,NULL,TRUE); - $args=is_array($args)?$args:array(1=>$args); - list($filter)=$filter; - } - $sql.=' WHERE '.$filter; + $args=[]; + if (is_array($filter)) { + $args=isset($filter[1]) && is_array($filter[1])? + $filter[1]: + array_slice($filter,1,NULL,TRUE); + $args=is_array($args)?$args:[1=>$args]; + list($filter)=$filter; } + if ($filter) + $sql.=' WHERE '.$filter; if ($options['group']) { $sql.=' GROUP BY '.implode(',',array_map( function($str) use($db) { return preg_replace_callback( '/\b(\w+)\h*(HAVING.+|$)/i', function($parts) use($db) { - return $db->quotekey($parts[1]); + return $db->quotekey($parts[1]).(isset($parts[2])?(' '.$parts[2]):''); }, $str ); @@ -243,7 +245,7 @@ function($str) use($db) { } if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) && ($options['limit'] || $options['offset'])) { - $pkeys=array(); + $pkeys=[]; foreach ($this->fields as $key=>$field) if ($field['pkey']) $pkeys[]=$key; @@ -274,7 +276,7 @@ function($str) use($db) { $sql.=' OFFSET '.(int)$options['offset']; } $result=$this->db->exec($sql,$args,$ttl); - $out=array(); + $out=[]; foreach ($result as &$row) { foreach ($row as $field=>&$val) { if (array_key_exists($field,$this->fields)) { @@ -297,24 +299,24 @@ function($str) use($db) { * @return static[] * @param $filter string|array * @param $options array - * @param $ttl int + * @param $ttl int|array **/ function find($filter=NULL,array $options=NULL,$ttl=0) { if (!$options) - $options=array(); - $options+=array( + $options=[]; + $options+=[ 'group'=>NULL, 'order'=>NULL, 'limit'=>0, 'offset'=>0 - ); + ]; $adhoc=''; foreach ($this->adhoc as $key=>$field) $adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key); return $this->select( ($options['group'] && !preg_match('/mysql|sqlite/',$this->engine)? $options['group']: - implode(',',array_map(array($this->db,'quotekey'), + implode(',',array_map([$this->db,'quotekey'], array_keys($this->fields)))).$adhoc,$filter,$options,$ttl); } @@ -322,18 +324,18 @@ function find($filter=NULL,array $options=NULL,$ttl=0) { * Count records that match criteria * @return int * @param $filter string|array - * @param $ttl int + * @param $ttl int|array **/ function count($filter=NULL,$ttl=0) { $sql='SELECT COUNT(*) AS '. $this->db->quotekey('rows').' FROM '.$this->table; - $args=array(); + $args=[]; if ($filter) { if (is_array($filter)) { $args=isset($filter[1]) && is_array($filter[1])? $filter[1]: array_slice($filter,1,NULL,TRUE); - $args=is_array($args)?$args:array(1=>$args); + $args=is_array($args)?$args:[1=>$args]; list($filter)=$filter; } $sql.=' WHERE '.$filter; @@ -353,6 +355,7 @@ function skip($ofs=1) { $dry=$this->dry(); foreach ($this->fields as $key=>&$field) { $field['value']=$dry?NULL:$out->fields[$key]['value']; + $field['initial']=$field['value']; $field['changed']=FALSE; if ($field['pkey']) $field['previous']=$dry?NULL:$out->fields[$key]['value']; @@ -372,22 +375,22 @@ function skip($ofs=1) { * @return object **/ function insert() { - $args=array(); + $args=[]; $actr=0; $nctr=0; $fields=''; $values=''; $filter=''; - $pkeys=array(); - $nkeys=array(); - $ckeys=array(); + $pkeys=[]; + $nkeys=[]; + $ckeys=[]; $inc=NULL; foreach ($this->fields as $key=>$field) if ($field['pkey']) $pkeys[$key]=$field['previous']; if (isset($this->trigger['beforeinsert']) && \Base::instance()->call($this->trigger['beforeinsert'], - array($this,$pkeys))===FALSE) + [$this,$pkeys])===FALSE) return $this; foreach ($this->fields as $key=>&$field) { if ($field['pkey']) { @@ -396,13 +399,13 @@ function insert() { empty($field['value']) && !$field['nullable']) $inc=$key; $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; - $nkeys[$nctr+1]=array($field['value'],$field['pdo_type']); + $nkeys[$nctr+1]=[$field['value'],$field['pdo_type']]; $nctr++; } if ($field['changed'] && $key!=$inc) { $fields.=($actr?',':'').$this->db->quotekey($key); $values.=($actr?',':'').'?'; - $args[$actr+1]=array($field['value'],$field['pdo_type']); + $args[$actr+1]=[$field['value'],$field['pdo_type']]; $actr++; $ckeys[]=$key; } @@ -420,18 +423,21 @@ function insert() { $seq=NULL; if ($this->engine=='pgsql') { $names=array_keys($pkeys); - $seq=$this->source.'_'.end($names).'_seq'; + $aik=end($names); + if ($this->fields[$aik]['pdo_type']==\PDO::PARAM_INT) + $seq=$this->source.'_'.$aik.'_seq'; } - if ($this->engine!='oci') + if ($this->engine!='oci' && !($this->engine=='pgsql' && !$seq)) $this->_id=$this->db->lastinsertid($seq); // Reload to obtain default and auto-increment field values - $this->load($inc? - array($inc.'=?',$this->db->value( - $this->fields[$inc]['pdo_type'],$this->_id)): - array($filter,$nkeys)); + if ($inc || $filter) + $this->load($inc? + [$inc.'=?',$this->db->value( + $this->fields[$inc]['pdo_type'],$this->_id)]: + [$filter,$nkeys]); if (isset($this->trigger['afterinsert'])) \Base::instance()->call($this->trigger['afterinsert'], - array($this,$pkeys)); + [$this,$pkeys]); } return $this; } @@ -441,38 +447,42 @@ function insert() { * @return object **/ function update() { - $args=array(); + $args=[]; $ctr=0; $pairs=''; $filter=''; - $pkeys=array(); + $pkeys=[]; foreach ($this->fields as $key=>$field) if ($field['pkey']) $pkeys[$key]=$field['previous']; if (isset($this->trigger['beforeupdate']) && \Base::instance()->call($this->trigger['beforeupdate'], - array($this,$pkeys))===FALSE) + [$this,$pkeys])===FALSE) return $this; foreach ($this->fields as $key=>$field) if ($field['changed']) { $pairs.=($pairs?',':'').$this->db->quotekey($key).'=?'; - $args[$ctr+1]=array($field['value'],$field['pdo_type']); - $ctr++; + $args[++$ctr]=[$field['value'],$field['pdo_type']]; } foreach ($this->fields as $key=>$field) if ($field['pkey']) { $filter.=($filter?' AND ':' WHERE '). $this->db->quotekey($key).'=?'; - $args[$ctr+1]=array($field['previous'],$field['pdo_type']); - $ctr++; + $args[++$ctr]=[$field['previous'],$field['pdo_type']]; } if ($pairs) { $sql='UPDATE '.$this->table.' SET '.$pairs.$filter; $this->db->exec($sql,$args); if (isset($this->trigger['afterupdate'])) \Base::instance()->call($this->trigger['afterupdate'], - array($this,$pkeys)); + [$this,$pkeys]); } + // reset changed flag after calling afterupdate + foreach ($this->fields as $key=>&$field) { + $field['changed']=FALSE; + $field['initial']=$field['value']; + unset($field); + } return $this; } @@ -483,25 +493,25 @@ function update() { **/ function erase($filter=NULL) { if ($filter) { - $args=array(); + $args=[]; if (is_array($filter)) { $args=isset($filter[1]) && is_array($filter[1])? $filter[1]: array_slice($filter,1,NULL,TRUE); - $args=is_array($args)?$args:array(1=>$args); + $args=is_array($args)?$args:[1=>$args]; list($filter)=$filter; } return $this->db-> exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args); } - $args=array(); + $args=[]; $ctr=0; $filter=''; - $pkeys=array(); + $pkeys=[]; foreach ($this->fields as $key=>&$field) { if ($field['pkey']) { $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?'; - $args[$ctr+1]=array($field['previous'],$field['pdo_type']); + $args[$ctr+1]=[$field['previous'],$field['pdo_type']]; $pkeys[$key]=$field['previous']; $ctr++; } @@ -518,13 +528,13 @@ function erase($filter=NULL) { parent::erase(); if (isset($this->trigger['beforeerase']) && \Base::instance()->call($this->trigger['beforeerase'], - array($this,$pkeys))===FALSE) + [$this,$pkeys])===FALSE) return 0; $out=$this->db-> exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args); if (isset($this->trigger['aftererase'])) \Base::instance()->call($this->trigger['aftererase'], - array($this,$pkeys)); + [$this,$pkeys]); return $out; } @@ -535,6 +545,7 @@ function erase($filter=NULL) { function reset() { foreach ($this->fields as &$field) { $field['value']=NULL; + $field['initial']=NULL; $field['changed']=FALSE; if ($field['pkey']) $field['previous']=NULL; @@ -591,7 +602,7 @@ function schema($fields=null) { * @param $adhoc bool **/ function fields($adhoc=TRUE) { - return array_keys($this->fields+($adhoc?$this->adhoc:array())); + return array_keys($this->fields+($adhoc?$this->adhoc:[])); } /** @@ -617,7 +628,7 @@ function getiterator() { * @param $db object * @param $table string * @param $fields array|string - * @param $ttl int + * @param $ttl int|array **/ function __construct(\DB\SQL $db,$table,$fields=NULL,$ttl=60) { $this->db=$db; diff --git a/lib/db/sql/session.php b/lib/db/sql/session.php index 607020500..56c1f4309 100644 --- a/lib/db/sql/session.php +++ b/lib/db/sql/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -63,12 +63,13 @@ function close() { * @param $id string **/ function read($id) { - $this->load(array('session_id=?',$this->sid=$id)); + $this->load(['session_id=?',$this->sid=$id]); if ($this->dry()) return FALSE; if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) { $fw=\Base::instance(); - if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) { + 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) $this->destroy($id); $this->close(); @@ -101,7 +102,7 @@ function write($id,$data) { * @param $id string **/ function destroy($id) { - $this->erase(array('session_id=?',$id)); + $this->erase(['session_id=?',$id]); return TRUE; } @@ -111,7 +112,7 @@ function destroy($id) { * @param $max int **/ function cleanup($max) { - $this->erase(array('stamp+?erase(['stamp+?name())&&$db->driver()!='pgsql')? - ($name.'.'):''))). - $table.' ('.$eol. - $tab.$db->quotekey('session_id').' VARCHAR(40),'.$eol. + ($db->quotekey($name,FALSE).'.'):''))). + $db->quotekey($table,FALSE).' ('.$eol. + $tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol. $tab.$db->quotekey('data').' TEXT,'.$eol. - $tab.$db->quotekey('ip').' VARCHAR(40),'.$eol. - $tab.$db->quotekey('agent').' VARCHAR(255),'.$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. ');' @@ -190,18 +191,17 @@ function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$ parent::__construct($db,$table); $this->onsuspect=$onsuspect; session_set_save_handler( - array($this,'open'), - array($this,'close'), - array($this,'read'), - array($this,'write'), - array($this,'destroy'), - array($this,'cleanup') + [$this,'open'], + [$this,'close'], + [$this,'read'], + [$this,'write'], + [$this,'destroy'], + [$this,'cleanup'] ); register_shutdown_function('session_commit'); $fw=\Base::instance(); $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash(mt_rand()); + $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); if ($key) $fw->set($key,$this->_csrf); $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; diff --git a/lib/f3.php b/lib/f3.php index 96c7845e0..d68cd1fb7 100644 --- a/lib/f3.php +++ b/lib/f3.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -36,7 +36,7 @@ class F3 { static function __callstatic($func,array $args) { if (!self::$fw) self::$fw=Base::instance(); - return call_user_func_array(array(self::$fw,$func),$args); + return call_user_func_array([self::$fw,$func],$args); } } diff --git a/lib/image.php b/lib/image.php index e1acd4832..a8514c227 100644 --- a/lib/image.php +++ b/lib/image.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -28,6 +28,7 @@ class Image { E_Color='Invalid color specified: %s', E_File='File not found', E_Font='CAPTCHA font not found', + E_TTF='No TrueType support in GD module', E_Length='Invalid CAPTCHA length: %s'; //@} @@ -54,9 +55,11 @@ class Image { /** * Convert RGB hex triad to array * @return array|FALSE - * @param $color int + * @param $color int|string **/ function rgb($color) { + if (is_string($color)) + $color=hexdec($color); $hex=str_pad($hex=dechex($color),$color<4096?3:6,'0',STR_PAD_LEFT); if (($len=strlen($hex))>6) user_error(sprintf(self::E_Color,'0x'.$hex),E_USER_ERROR); @@ -225,9 +228,17 @@ function crop($x1,$y1,$x2,$y2) { * @param $crop bool * @param $enlarge bool **/ - function resize($width,$height,$crop=TRUE,$enlarge=TRUE) { + function resize($width=NULL,$height=NULL,$crop=TRUE,$enlarge=TRUE) { + if (is_null($width) && is_null($height)) + return $this; + $origw=$this->width(); + $origh=$this->height(); + if (is_null($width)) + $width=round(($height/$origh)*$origw); + if (is_null($height)) + $height=round(($width/$origw)*$origh); // Adjust dimensions; retain aspect ratio - $ratio=($origw=imagesx($this->data))/($origh=imagesy($this->data)); + $ratio=$origw/$origh; if (!$crop) { if ($width/$ratio<=$height) $height=round($width/$ratio); @@ -331,24 +342,24 @@ function overlay(Image $img,$align=NULL,$alpha=100) { * @param $blocks int **/ function identicon($str,$size=64,$blocks=4) { - $sprites=array( - array(.5,1,1,0,1,1), - array(.5,0,1,0,.5,1,0,1), - array(.5,0,1,0,1,1,.5,1,1,.5), - array(0,.5,.5,0,1,.5,.5,1,.5,.5), - array(0,.5,1,0,1,1,0,1,1,.5), - array(1,0,1,1,.5,1,1,.5,.5,.5), - array(0,0,1,0,1,.5,0,0,.5,1,0,1), - array(0,0,.5,0,1,.5,.5,1,0,1,.5,.5), - array(.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5), - array(0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1), - array(0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1), - array(.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25), - array(0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1), - array(0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25), - array(0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1), - array(0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1) - ); + $sprites=[ + [.5,1,1,0,1,1], + [.5,0,1,0,.5,1,0,1], + [.5,0,1,0,1,1,.5,1,1,.5], + [0,.5,.5,0,1,.5,.5,1,.5,.5], + [0,.5,1,0,1,1,0,1,1,.5], + [1,0,1,1,.5,1,1,.5,.5,.5], + [0,0,1,0,1,.5,0,0,.5,1,0,1], + [0,0,.5,0,1,.5,.5,1,0,1,.5,.5], + [.5,0,.5,.5,1,.5,1,1,.5,1,.5,.5,0,.5], + [0,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1], + [0,.5,.5,1,1,.5,.5,0,1,0,1,1,0,1], + [.5,0,1,0,1,1,.5,1,1,.75,.5,.5,1,.25], + [0,.5,.5,0,.5,.5,1,0,1,.5,.5,1,.5,.5,0,1], + [0,0,1,0,1,1,0,1,1,.5,.5,.25,.5,.75,0,.5,.5,.25], + [0,.5,.5,.5,.5,0,1,0,.5,.5,1,.5,.5,1,.5,.5,0,1], + [0,0,1,0,.5,.5,.5,0,0,.5,1,.5,.5,1,.5,.5,0,1] + ]; $hash=sha1($str); $this->data=imagecreatetruecolor($size,$size); list($r,$g,$b)=$this->rgb(hexdec(substr($hash,-3))); @@ -360,15 +371,10 @@ function identicon($str,$size=64,$blocks=4) { for ($i=$j,$x=$blocks-1-$j;$i<$x;$i++) { $sprite=imagecreatetruecolor($dim,$dim); imagefill($sprite,0,0,IMG_COLOR_TRANSPARENT); - if ($block=$sprites[ - hexdec($hash[($j*$blocks+$i)*2])%$ctr]) { - for ($k=0,$pts=count($block);$k<$pts;$k++) - $block[$k]*=$dim; - imagefilledpolygon($sprite,$block,$pts/2,$fg); - } - $sprite=imagerotate($sprite, - 90*(hexdec($hash[($j*$blocks+$i)*2+1])%4), - imagecolorallocatealpha($sprite,0,0,0,127)); + $block=$sprites[hexdec($hash[($j*$blocks+$i)*2])%$ctr]; + for ($k=0,$pts=count($block);$k<$pts;$k++) + $block[$k]*=$dim; + imagefilledpolygon($sprite,$block,$pts/2,$fg); for ($k=0;$k<4;$k++) { imagecopyresampled($this->data,$sprite, $i*$dim/2,$j*$dim/2,0,0,$dim/2,$dim/2,$dim,$dim); @@ -398,6 +404,10 @@ function captcha($font,$size=24,$len=5, user_error(sprintf(self::E_Length,$len),E_USER_ERROR); return FALSE; } + if (!function_exists('imagettftext')) { + user_error(self::E_TTF,E_USER_ERROR); + return FALSE; + } $fw=Base::instance(); foreach ($fw->split($path?:$fw->get('UI').';./') as $dir) if (is_file($path=$dir.$font)) { @@ -405,7 +415,7 @@ function captcha($font,$size=24,$len=5, $ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(), -$len)); $block=$size*3; - $tmp=array(); + $tmp=[]; for ($i=0,$width=0,$height=0;$i<$len;$i++) { // Process at 2x magnification $box=imagettfbbox($size*2,0,$path,$seed[$i]); @@ -472,8 +482,10 @@ function render() { header('Content-Type: image/'.$format); header('X-Powered-By: '.Base::instance()->get('PACKAGE')); } - call_user_func_array('image'.$format, - array_merge(array($this->data),$args)); + call_user_func_array( + 'image'.$format, + array_merge([$this->data,NULL],$args) + ); } /** @@ -484,8 +496,10 @@ function dump() { $args=func_get_args(); $format=$args?array_shift($args):'png'; ob_start(); - call_user_func_array('image'.$format, - array_merge(array($this->data),$args)); + call_user_func_array( + 'image'.$format, + array_merge([$this->data,NULL],$args) + ); return ob_get_clean(); } @@ -507,8 +521,7 @@ function save() { if (!is_dir($dir=$fw->get('TEMP'))) mkdir($dir,Base::MODE,TRUE); $this->count++; - $fw->write($dir.'/'. - $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. + $fw->write($dir.'/'.$fw->get('SEED').'.'. $fw->hash($this->file).'-'.$this->count.'.png', $this->dump()); } @@ -523,8 +536,7 @@ function save() { function restore($state=1) { $fw=Base::instance(); if ($this->flag && is_file($file=($path=$fw->get('TEMP'). - $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash($this->file).'-').$state.'.png')) { + $fw->get('SEED').'.'.$fw->hash($this->file).'-').$state.'.png')) { if (is_resource($this->data)) imagedestroy($this->data); $this->data=imagecreatefromstring($fw->read($file)); @@ -553,11 +565,12 @@ function undo() { /** * Load string - * @return object + * @return object|FALSE * @param $str string **/ function load($str) { - $this->data=imagecreatefromstring($str); + if (!$this->data=@imagecreatefromstring($str)) + return FALSE; imagesavealpha($this->data,TRUE); $this->save(); return $this; @@ -592,9 +605,7 @@ function __destruct() { if (is_resource($this->data)) { imagedestroy($this->data); $fw=Base::instance(); - $path=$fw->get('TEMP'). - $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash($this->file); + $path=$fw->get('TEMP').$fw->get('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/lib/log.php b/lib/log.php old mode 100644 new mode 100755 index 6583cedbe..2bbe4c27d --- a/lib/log.php +++ b/lib/log.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/magic.php b/lib/magic.php index 5669908a2..76ea221ea 100644 --- a/lib/magic.php +++ b/lib/magic.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -70,7 +70,7 @@ function offsetexists($key) { **/ function offsetset($key,$val) { return Base::instance()->visible($this,$key)? - ($this->key=$val):$this->set($key,$val); + ($this->$key=$val):$this->set($key,$val); } /** diff --git a/lib/markdown.php b/lib/markdown.php index 9863d9117..1c3fb1634 100644 --- a/lib/markdown.php +++ b/lib/markdown.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -321,7 +321,7 @@ protected function _text($str) { $tmp=''; while ($str!=$tmp) $str=preg_replace_callback( - '/(?special) - $this->special=array( + $this->special=[ '...'=>'…', '(tm)'=>'™', '(r)'=>'®', '(c)'=>'©' - ); + ]; foreach ($this->special as $key=>$val) $str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str); return htmlspecialchars($str,ENT_COMPAT, @@ -454,7 +454,7 @@ protected function snip($str) { * @param $str string **/ function scan($str) { - $inline=array('img','a','text','auto','code'); + $inline=['img','a','text','auto','code']; foreach ($inline as $func) $str=$this->{'_'.$func}($str); return $str; @@ -468,7 +468,7 @@ function scan($str) { protected function build($str) { if (!$this->blocks) { // Regexes for capturing entire blocks - $this->blocks=array( + $this->blocks=[ 'blockquote'=>'/^(?:\h?>\h?.*?(?:\n+|$))+/', 'pre'=>'/^(?:(?: {4}|\t).+?(?:\n+|$))+/', 'fence'=>'/^`{3}\h*(\w+)?.*?[^\n]*\n+(.+?)`{3}[^\n]*'. @@ -486,7 +486,7 @@ protected function build($str) { '(?:\/>|>(?:(?>[^><]+)|(?R))*<\/\2>))'. '\h*(?:\n{2,}|\n*$)|<[\?%].+?[\?%]>\h*(?:\n?$|\n*))/s', 'p'=>'/^(.+?(?:\n{2,}|\n*$))/s' - ); + ]; } $self=$this; // Treat lines with nothing but whitespaces as empty lines @@ -546,7 +546,7 @@ function($expr) use($match,$self) { if (preg_match($regex,substr($str,$ptr),$match)) { $ptr+=strlen($match[0]); $dst.=call_user_func_array( - array($this,'_'.$func), + [$this,'_'.$func], count($match)>1?array_slice($match,1):$match ); break; diff --git a/lib/matrix.php b/lib/matrix.php index 1ebce6b99..f20e9890b 100644 --- a/lib/matrix.php +++ b/lib/matrix.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -45,7 +45,7 @@ function($row) use($col) { * @param $var array **/ function transpose(array &$var) { - $out=array(); + $out=[]; foreach ($var as $keyx=>$cols) foreach ($cols as $keyy=>$valy) $out[$keyy][$keyx]=$valy; @@ -63,7 +63,7 @@ function sort(array &$var,$col,$order=SORT_ASC) { uasort( $var, function($val1,$val2) use($col,$order) { - list($v1,$v2)=array($val1[$col],$val2[$col]); + list($v1,$v2)=[$val1[$col],$val2[$col]]; $out=is_numeric($v1) && is_numeric($v2)? Base::instance()->sign($v1-$v2):strcmp($v1,$v2); if ($order==SORT_DESC) @@ -96,12 +96,15 @@ function changekey(array &$var,$old,$new) { * @param $first int **/ function calendar($date='now',$first=0) { - $parts=getdate(strtotime($date)); - $days=cal_days_in_month(CAL_GREGORIAN,$parts['mon'],$parts['year']); - $ref=date('w',strtotime(date('Y-m',$parts[0]).'-01'))+(7-$first)%7; - $out=array(); - for ($i=0;$i<$days;$i++) - $out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1; + $out=FALSE; + if (extension_loaded('calendar')) { + $parts=getdate(strtotime($date)); + $days=cal_days_in_month(CAL_GREGORIAN,$parts['mon'],$parts['year']); + $ref=date('w',strtotime(date('Y-m',$parts[0]).'-01'))+(7-$first)%7; + $out=[]; + for ($i=0;$i<$days;$i++) + $out[floor(($ref+$i)/7)][($ref+$i)%7]=$i+1; + } return $out; } diff --git a/lib/session.php b/lib/session.php index 8b28c936c..583193d9b 100644 --- a/lib/session.php +++ b/lib/session.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -33,7 +33,9 @@ class Session { //! IP, $_ip, //! Suspect callback - $onsuspect; + $onsuspect, + //! Cache instance + $_cache; /** * Open session @@ -61,11 +63,12 @@ function close() { **/ function read($id) { $this->sid=$id; - if (!$data=Cache::instance()->get($id.'.@')) + if (!$data=$this->_cache->get($id.'.@')) return FALSE; if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) { $fw=Base::instance(); - if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) { + 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) $this->destroy($id); $this->close(); @@ -85,13 +88,13 @@ function read($id) { function write($id,$data) { $fw=Base::instance(); $jar=$fw->get('JAR'); - Cache::instance()->set($id.'.@', - array( + $this->_cache->set($id.'.@', + [ 'data'=>$data, 'ip'=>$this->_ip, 'agent'=>$this->_agent, 'stamp'=>time() - ), + ], $jar['expire']?($jar['expire']-time()):0 ); return TRUE; @@ -103,7 +106,7 @@ function write($id,$data) { * @param $id string **/ function destroy($id) { - Cache::instance()->clear($id.'.@'); + $this->_cache->clear($id.'.@'); return TRUE; } @@ -113,7 +116,7 @@ function destroy($id) { * @param $max int **/ function cleanup($max) { - Cache::instance()->reset('.@',$max); + $this->_cache->reset('.@',$max); return TRUE; } @@ -148,7 +151,7 @@ function ip() { function stamp() { if (!$this->sid) session_start(); - return Cache::instance()->exists($this->sid.'.@',$data)? + return $this->_cache->exists($this->sid.'.@',$data)? $data['stamp']:FALSE; } @@ -165,21 +168,21 @@ function agent() { * @param $onsuspect callback * @param $key string **/ - function __construct($onsuspect=NULL,$key=NULL) { + function __construct($onsuspect=NULL,$key=NULL,$cache=null) { $this->onsuspect=$onsuspect; + $this->_cache=$cache?:Cache::instance(); session_set_save_handler( - array($this,'open'), - array($this,'close'), - array($this,'read'), - array($this,'write'), - array($this,'destroy'), - array($this,'cleanup') + [$this,'open'], + [$this,'close'], + [$this,'read'], + [$this,'write'], + [$this,'destroy'], + [$this,'cleanup'] ); register_shutdown_function('session_commit'); $fw=\Base::instance(); $headers=$fw->get('HEADERS'); - $this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash(mt_rand()); + $this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand()); if ($key) $fw->set($key,$this->_csrf); $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; diff --git a/lib/smtp.php b/lib/smtp.php index 3c9e964ff..00ee9b2d7 100644 --- a/lib/smtp.php +++ b/lib/smtp.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -118,20 +118,41 @@ function log() { * @return string * @param $cmd string * @param $log bool + * @param $mock bool **/ - protected function dialog($cmd=NULL,$log=TRUE) { - $socket=&$this->socket; - if (!is_null($cmd)) - fputs($socket,$cmd."\r\n"); + protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) { $reply=''; - while (!feof($socket) && ($info=stream_get_meta_data($socket)) && - !$info['timed_out'] && $str=fgets($socket,4096)) { - $reply.=$str; - if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply)) + if ($mock) { + $host=str_replace('ssl://','',$this->host); + switch ($cmd) { + case NULL: + $reply='220 '.$host.' ESMTP ready'."\n"; break; + case 'DATA': + $reply='354 Go ahead'."\n"; + break; + case 'QUIT': + $reply='221 '.$host.' closing connection'."\n"; + break; + default: + $reply='250 OK'."\n"; + break; + } + } + else { + $socket=&$this->socket; + if ($cmd) + fputs($socket,$cmd."\r\n"); + while (!feof($socket) && ($info=stream_get_meta_data($socket)) && + !$info['timed_out'] && $str=fgets($socket,4096)) { + $reply.=$str; + if (preg_match('/(?:^|\n)\d{3} .+?\r\n/s',$reply)) + break; + } } if ($log) { - $this->log.=$cmd."\n"; + if ($cmd) + $this->log.=$cmd."\n"; $this->log.=str_replace("\r",'',$reply); } return $reply; @@ -147,9 +168,9 @@ protected function dialog($cmd=NULL,$log=TRUE) { function attach($file,$alias=NULL,$cid=NULL) { if (!is_file($file)) user_error(sprintf(self::E_Attach,$file),E_USER_ERROR); - if (is_string($alias)) - $file=array($alias=>$file); - $this->attachments[]=array('filename'=>$file,'cid'=>$cid); + if ($alias) + $file=[$alias=>$file]; + $this->attachments[]=['filename'=>$file,'cid'=>$cid]; } /** @@ -157,8 +178,9 @@ function attach($file,$alias=NULL,$cid=NULL) { * @return bool * @param $message string * @param $log bool + * @param $mock bool **/ - function send($message,$log=TRUE) { + function send($message,$log=TRUE,$mock=FALSE) { if ($this->scheme=='ssl' && !extension_loaded('openssl')) return FALSE; // Message should not be blank @@ -168,20 +190,25 @@ function send($message,$log=TRUE) { // Retrieve headers $headers=$this->headers; // Connect to the server - $socket=&$this->socket; - $socket=@fsockopen($this->host,$this->port); - if (!$socket) - return FALSE; - stream_set_blocking($socket,TRUE); + if (!$mock) { + $socket=&$this->socket; + $socket=@fsockopen($this->host,$this->port,$errno,$errstr); + if (!$socket) { + $fw->error(500,$errstr); + return FALSE; + } + stream_set_blocking($socket,TRUE); + } // Get server's initial response - $this->dialog(NULL,FALSE); + $this->dialog(NULL,TRUE,$mock); // Announce presence - $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log); + $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock); if (strtolower($this->scheme)=='tls') { - $this->dialog('STARTTLS',$log); - stream_socket_enable_crypto( - $socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT); - $reply=$this->dialog('EHLO '.$fw->get('HOST'),$log); + $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); } if (preg_match('/8BITMIME/',$reply)) $headers['Content-Transfer-Encoding']='8bit'; @@ -192,12 +219,22 @@ function send($message,$log=TRUE) { } if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) { // Authenticate - $this->dialog('AUTH LOGIN',$log); - $this->dialog(base64_encode($this->user),$log); - $this->dialog(base64_encode($this->pw),$log); + $this->dialog('AUTH LOGIN',$log,$mock); + $this->dialog(base64_encode($this->user),$log,$mock); + $auth_rply=$this->dialog(base64_encode($this->pw),$log,$mock); + if (!preg_match('/^235\s.*/',$auth_rply)) { + $this->dialog('QUIT',$log,$mock); + if (!$mock && $socket) + fclose($socket); + return FALSE; + } } + if (empty($headers['Message-ID'])) + $headers['Message-ID']='<'.uniqid('',TRUE).'@'.$this->host.'>'; + if (empty($headers['Date'])) + $headers['Date']=date('r'); // Required headers - $reqd=array('From','To','Subject'); + $reqd=['From','To','Subject']; foreach ($reqd as $id) if (empty($headers[$id])) user_error(sprintf(self::E_Header,$id),E_USER_ERROR); @@ -205,21 +242,32 @@ function send($message,$log=TRUE) { $str=''; // Stringify headers foreach ($headers as $key=>&$val) { - if (!in_array($key,$reqd) && (!$this->attachments || - $key!='Content-Type' && $key!='Content-Transfer-Encoding')) + if (!in_array($key,$reqd) && + (!$this->attachments || + $key!='Content-Type' && + $key!='Content-Transfer-Encoding')) $str.=$key.': '.$val.$eol; - if (in_array($key,array('From','To','Cc','Bcc')) && - !preg_match('/[<>]/',$val)) - $val='<'.$val.'>'; + if (in_array($key,['From','To','Cc','Bcc'])) { + $email=''; + preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/', + $val,$matches,PREG_SET_ORDER); + foreach ($matches as $raw) + $email.=($email?', ':''). + (preg_match('/<.+?>/',$raw[0])? + $raw[0]: + ('<'.$raw[0].'>')); + $val=$email; + } unset($val); } // Start message dialog - $this->dialog('MAIL FROM: '.strstr($headers['From'],'<'),$log); + $this->dialog('MAIL FROM: '.strstr($headers['From'],'<'),$log,$mock); foreach ($fw->split($headers['To']. (isset($headers['Cc'])?(';'.$headers['Cc']):''). - (isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) - $this->dialog('RCPT TO: '.strstr($dst,'<'),$log); - $this->dialog('DATA',$log); + (isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) { + $this->dialog('RCPT TO: '.strstr($dst,'<'),$log,$mock); + } + $this->dialog('DATA',$log,$mock); if ($this->attachments) { // Replace Content-Type $type=$headers['Content-Type']; @@ -241,28 +289,25 @@ function send($message,$log=TRUE) { $out.=$str.$eol; $out.=$message.$eol; foreach ($this->attachments as $attachment) { - if (is_array($attachment['filename'])) { + if (is_array($attachment['filename'])) list($alias,$file)=each($attachment['filename']); - $filename=$alias; - $attachment['filename']=$file; - } else - $filename=basename($attachment['filename']); + $alias=basename($file=$attachment['filename']); $out.='--'.$hash.$eol; $out.='Content-Type: application/octet-stream'.$eol; $out.='Content-Transfer-Encoding: base64'.$eol; if ($attachment['cid']) $out.='Content-ID: '.$attachment['cid'].$eol; $out.='Content-Disposition: attachment; '. - 'filename="'.$filename.'"'.$eol; + 'filename="'.$alias.'"'.$eol; $out.=$eol; $out.=chunk_split(base64_encode( - file_get_contents($attachment['filename']))).$eol; + file_get_contents($file))).$eol; } $out.=$eol; $out.='--'.$hash.'--'.$eol; $out.='.'; - $this->dialog($out,FALSE); + $this->dialog($out,TRUE,$mock); } else { // Send mail headers @@ -274,10 +319,10 @@ function send($message,$log=TRUE) { $out.=$message.$eol; $out.='.'; // Send message - $this->dialog($out); + $this->dialog($out,TRUE,$mock); } - $this->dialog('QUIT',$log); - if ($socket) + $this->dialog('QUIT',$log,$mock); + if (!$mock && $socket) fclose($socket); return TRUE; } @@ -290,12 +335,13 @@ function send($message,$log=TRUE) { * @param $user string * @param $pw string **/ - function __construct($host='localhost',$port=25,$scheme=null,$user=null,$pw=null) { - $this->headers=array( + function __construct( + $host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL) { + $this->headers=[ 'MIME-Version'=>'1.0', 'Content-Type'=>'text/plain; '. 'charset='.Base::instance()->get('ENCODING') - ); + ]; $this->host=$host; if (strtolower($this->scheme=strtolower($scheme))=='ssl') $this->host='ssl://'.$host; diff --git a/lib/template.php b/lib/template.php index 8b985cde7..c9c0d61b0 100644 --- a/lib/template.php +++ b/lib/template.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -32,7 +32,7 @@ class Template extends Preview { //! Template tags $tags, //! Custom tag handlers - $custom=array(); + $custom=[]; /** * Template -set- tag handler @@ -60,14 +60,14 @@ protected function _include(array $node) { ($attrib['with']=$this->token($attrib['with'])) && preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/', $attrib['with'],$pairs,PREG_SET_ORDER)? - 'array('.implode(',', + ('['.implode(',', array_map(function($pair) { return '\''.$pair[1].'\'=>'. (preg_match('/^\'.*\'$/',$pair[2]) || preg_match('/\$/',$pair[2])? $pair[2]: \Base::instance()->stringify($pair[2])); - },$pairs)).')+get_defined_vars()': + },$pairs)).']+get_defined_vars()'): 'get_defined_vars()'; $ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0; return @@ -127,7 +127,7 @@ protected function _repeat(array $node) { (isset($attrib['counter'])? (($ctr=$this->token($attrib['counter'])).'=0; '):''). 'foreach (('. - $this->token($attrib['group']).'?:array()) as '. + $this->token($attrib['group']).'?:[]) as '. (isset($attrib['key'])? ($this->token($attrib['key']).'=>'):''). $this->token($attrib['value']).'):'. @@ -147,12 +147,12 @@ protected function _check(array $node) { // Grab and blocks foreach ($node as $pos=>$block) if (isset($block['true'])) - $true=array($pos,$block); + $true=[$pos,$block]; elseif (isset($block['false'])) - $false=array($pos,$block); + $false=[$pos,$block]; if (isset($true,$false) && $true[0]>$false[0]) // Reverse and blocks - list($node[$true[0]],$node[$false[0]])=array($false[1],$true[1]); + list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]]; return 'token($attrib['if']).'): ?>'. $this->build($node). @@ -229,7 +229,7 @@ protected function _default(array $node) { * @return string * @param $node array|string **/ - protected function build($node) { + function build($node) { if (is_string($node)) return parent::build($node); $out=''; @@ -259,7 +259,7 @@ function __call($func,array $args) { if ($func[0]=='_') return call_user_func_array($this->custom[$func],$args); if (method_exists($this,$func)) - return call_user_func_array(array($this,$func),$args); + return call_user_func_array([$this,$func],$args); user_error(sprintf(self::E_Method,$func),E_USER_ERROR); } @@ -270,9 +270,9 @@ function __call($func,array $args) { **/ function parse($text) { // Build tree structure - for ($ptr=0,$w=5,$len=strlen($text),$tree=array(),$tmp='';$ptr<$len;) + for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;) if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'. - '('.$this->tags.')\b((?:\h+[\w-]+'. + '('.$this->tags.')\b((?:\s+[\w-]+'. '(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'. '\h*\{\{.+?\}\})*)\h*(\/?)>/is', substr($text,$ptr),$match)) { @@ -281,7 +281,7 @@ function parse($text) { // Element node if ($match[2]) { // Find matching start tag - $stack=array(); + $stack=[]; for($i=count($tree)-1;$i>=0;$i--) { $item = $tree[$i]; if (is_array($item) && array_key_exists($match[3],$item) @@ -296,7 +296,7 @@ function parse($text) { else { // Start tag $node=&$tree[][$match[3]]; - $node=array(); + $node=[]; if ($match[4]) { // Process attributes preg_match_all( diff --git a/lib/test.php b/lib/test.php index 05e6e6c5c..2070dab48 100644 --- a/lib/test.php +++ b/lib/test.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -32,7 +32,7 @@ class Test { protected //! Test results - $data=array(), + $data=[], //! Success indicator $passed=TRUE; @@ -61,7 +61,7 @@ function passed() { function expect($cond,$text=NULL) { $out=(bool)$cond; if ($this->level==$out || $this->level==self::FLAG_Both) { - $data=array('status'=>$out,'text'=>$text,'source'=>NULL); + $data=['status'=>$out,'text'=>$text,'source'=>NULL]; foreach (debug_backtrace() as $frame) if (isset($frame['file'])) { $data['source']=Base::instance()-> diff --git a/lib/utf.php b/lib/utf.php index fbfe00053..22b41c3d5 100644 --- a/lib/utf.php +++ b/lib/utf.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -180,7 +180,7 @@ function translate($str) { * @param $str string **/ function emojify($str) { - $map=array( + $map=[ ':('=>'\u2639', // frown ':)'=>'\u263a', // smile '<3'=>'\u2665', // heart @@ -191,7 +191,7 @@ function emojify($str) { ':,'=>'\u1f60f', // think ':/'=>'\u1f623', // skeptic '8O'=>'\u1f632', // oops - )+Base::instance()->get('EMOJI'); + ]+Base::instance()->get('EMOJI'); return $this->translate(str_replace(array_keys($map), array_values($map),$str)); } diff --git a/lib/web.php b/lib/web.php index 57fbf937e..22864898b 100644 --- a/lib/web.php +++ b/lib/web.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -39,7 +39,7 @@ class Web extends Prefab { **/ function mime($file) { if (preg_match('/\w+$/',$file,$ext)) { - $map=array( + $map=[ 'au'=>'audio/basic', 'avi'=>'video/avi', 'bmp'=>'image/bmp', @@ -77,7 +77,7 @@ function mime($file) { 'xls'=>'application/vnd.ms-excel', 'xml'=>'application/xml', 'zip'=>'application/x-zip-compressed' - ); + ]; foreach ($map as $key=>$val) if (preg_match('/'.$key.'/',strtolower($ext[0]))) return $val; @@ -93,7 +93,7 @@ function mime($file) { * @param $list string|array **/ function acceptable($list=NULL) { - $accept=array(); + $accept=[]; foreach (explode(',',str_replace(' ','',@$_SERVER['HTTP_ACCEPT'])) as $mime) if (preg_match('/(.+?)(?:;q=([\d\.]+)|$)/',$mime,$parts)) @@ -124,8 +124,10 @@ function acceptable($list=NULL) { * @param $mime string * @param $kbps int * @param $force bool + * @param $name string + * @param $flush bool **/ - function send($file,$mime=NULL,$kbps=0,$force=TRUE) { + function send($file,$mime=NULL,$kbps=0,$force=TRUE,$name=NULL,$flush=TRUE) { if (!is_file($file)) return FALSE; $size=filesize($file); @@ -133,7 +135,7 @@ function send($file,$mime=NULL,$kbps=0,$force=TRUE) { header('Content-Type: '.($mime?:$this->mime($file))); if ($force) header('Content-Disposition: attachment; '. - 'filename="'.basename($file).'"'); + 'filename="'.($name!==NULL?$name:basename($file)).'"'); header('Accept-Ranges: bytes'); header('Content-Length: '.$size); header('X-Powered-By: '.Base::instance()->get('PACKAGE')); @@ -152,6 +154,10 @@ function send($file,$mime=NULL,$kbps=0,$force=TRUE) { } // Send 1KiB and reset timer echo fread($handle,1024); + if ($flush) { + ob_flush(); + flush(); + } } fclose($handle); return $size; @@ -170,9 +176,7 @@ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { if (!is_dir($dir)) mkdir($dir,Base::MODE,TRUE); if ($fw->get('VERB')=='PUT') { - $tmp=$fw->get('TEMP'). - $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. - $fw->hash(uniqid()); + $tmp=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash(uniqid()); if (!$fw->get('RAW')) $fw->write($tmp,$fw->get('BODY')); else { @@ -188,7 +192,7 @@ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { fclose($src); } $base=basename($fw->get('URI')); - $file=array( + $file=[ 'name'=>$dir. ($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)? (is_callable($slug)? @@ -199,24 +203,24 @@ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { 'tmp_name'=>$tmp, 'type'=>$this->mime($base), 'size'=>filesize($tmp) - ); + ]; return (!file_exists($file['name']) || $overwrite) && - (!$func || $fw->call($func,array($file))!==FALSE) && + (!$func || $fw->call($func,[$file])!==FALSE) && rename($tmp,$file['name']); } - $fetch=function($arr)use(&$fetch){ + $fetch=function($arr) use(&$fetch) { if (!is_array($arr)) - return array($arr); - $data=array(); + return [$arr]; + $data=[]; foreach($arr as $k=>$sub) $data=array_merge($data,$fetch($sub)); return $data; }; - $out=array(); + $out=[]; foreach ($_FILES as $name=>$item) { - $files=array(); - foreach($item as $k=>$mix) - foreach($fetch($mix) as $i=>$val) + $files=[]; + foreach ($item as $k=>$mix) + foreach ($fetch($mix) as $i=>$val) $files[$i][$k]=$val; foreach ($files as $file) { if (empty($file['name'])) @@ -230,9 +234,8 @@ function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) { (isset($parts[2])?$parts[2]:''))): $base); $out[$file['name']]=!$file['error'] && - is_uploaded_file($file['tmp_name']) && (!file_exists($file['name']) || $overwrite) && - (!$func || $fw->call($func,array($file,$name))!==FALSE) && + (!$func || $fw->call($func,[$file,$name])!==FALSE) && move_uploaded_file($file['tmp_name'],$file['name']); } } @@ -277,7 +280,7 @@ protected function _curl($url,$options) { ini_get('default_socket_timeout'); curl_setopt($curl,CURLOPT_CONNECTTIMEOUT,$timeout); curl_setopt($curl,CURLOPT_TIMEOUT,$timeout); - $headers=array(); + $headers=[]; curl_setopt($curl,CURLOPT_HEADERFUNCTION, // Callback for response headers function($curl,$line) use(&$headers) { @@ -286,22 +289,32 @@ function($curl,$line) use(&$headers) { return strlen($line); } ); + curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,2); curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE); ob_start(); curl_exec($curl); + $err=curl_error($curl); curl_close($curl); $body=ob_get_clean(); - if ($options['follow_location'] && + if (!$err && + $options['follow_location'] && preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) { $options['max_redirects']--; + if($loc[1][0] == '/') { + $parts=parse_url($url); + $loc[1]=$parts['scheme'].'://'.$parts['host']. + ((isset($parts['port']) && !in_array($parts['port'],[80,443])) + ?':'.$parts['port']:'').$loc[1]; + } return $this->request($loc[1],$options); } - return array( + return [ 'body'=>$body, 'headers'=>$headers, 'engine'=>'cURL', - 'cached'=>FALSE - ); + 'cached'=>FALSE, + 'error'=>$err + ]; } /** @@ -314,28 +327,36 @@ protected function _stream($url,$options) { $eol="\r\n"; $options['header']=implode($eol,$options['header']); $body=@file_get_contents($url,FALSE, - stream_context_create(array('http'=>$options))); + stream_context_create(['http'=>$options])); $headers=isset($http_response_header)? - $http_response_header:array(); - $match=NULL; - foreach ($headers as $header) - if (preg_match('/Content-Encoding: (.+)/',$header,$match)) - break; - if ($match) - switch ($match[1]) { - case 'gzip': - $body=gzdecode($body); - break; - case 'deflate': - $body=gzuncompress($body); + $http_response_header:[]; + $err=''; + if (is_string($body)) { + $match=NULL; + foreach ($headers as $header) + if (preg_match('/Content-Encoding: (.+)/',$header,$match)) break; - } - return array( + if ($match) + switch ($match[1]) { + case 'gzip': + $body=gzdecode($body); + break; + case 'deflate': + $body=gzuncompress($body); + break; + } + } + else { + $tmp=error_get_last(); + $err=$tmp['message']; + } + return [ 'body'=>$body, 'headers'=>$headers, 'engine'=>'stream', - 'cached'=>FALSE - ); + 'cached'=>FALSE, + 'error'=>$err + ]; } /** @@ -346,7 +367,7 @@ protected function _stream($url,$options) { **/ protected function _socket($url,$options) { $eol="\r\n"; - $headers=array(); + $headers=[]; $body=''; $parts=parse_url($url); $empty=empty($parts['port']); @@ -361,54 +382,54 @@ protected function _socket($url,$options) { $parts['path']='/'; if (empty($parts['query'])) $parts['query']=''; - $socket=@fsockopen($parts['host'],$parts['port']); - if (!$socket) - return FALSE; - stream_set_blocking($socket,TRUE); - stream_set_timeout($socket,isset($options['timeout'])? - $options['timeout']:ini_get('default_socket_timeout')); - fputs($socket,$options['method'].' '.$parts['path']. - ($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol - ); - fputs($socket,implode($eol,$options['header']).$eol.$eol); - if (isset($options['content'])) - fputs($socket,$options['content'].$eol); - // Get response - $content=''; - while (!feof($socket) && - ($info=stream_get_meta_data($socket)) && - !$info['timed_out'] && !connection_aborted() && - $str=fgets($socket,4096)) - $content.=$str; - fclose($socket); - $html=explode($eol.$eol,$content,2); - $body=isset($html[1])?$html[1]:''; - $headers=array_merge($headers,$current=explode($eol,$html[0])); - $match=NULL; - foreach ($current as $header) - if (preg_match('/Content-Encoding: (.+)/',$header,$match)) - break; - if ($match) - switch ($match[1]) { - case 'gzip': - $body=gzdecode($body); - break; - case 'deflate': - $body=gzuncompress($body); + if ($socket=@fsockopen($parts['host'],$parts['port'],$code,$err)) { + stream_set_blocking($socket,TRUE); + stream_set_timeout($socket,isset($options['timeout'])? + $options['timeout']:ini_get('default_socket_timeout')); + fputs($socket,$options['method'].' '.$parts['path']. + ($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol + ); + fputs($socket,implode($eol,$options['header']).$eol.$eol); + if (isset($options['content'])) + fputs($socket,$options['content'].$eol); + // Get response + $content=''; + while (!feof($socket) && + ($info=stream_get_meta_data($socket)) && + !$info['timed_out'] && !connection_aborted() && + $str=fgets($socket,4096)) + $content.=$str; + fclose($socket); + $html=explode($eol.$eol,$content,2); + $body=isset($html[1])?$html[1]:''; + $headers=array_merge($headers,$current=explode($eol,$html[0])); + $match=NULL; + foreach ($current as $header) + if (preg_match('/Content-Encoding: (.+)/',$header,$match)) break; + if ($match) + switch ($match[1]) { + case 'gzip': + $body=gzdecode($body); + break; + case 'deflate': + $body=gzuncompress($body); + break; + } + if ($options['follow_location'] && + preg_match('/Location: (.+?)'.preg_quote($eol).'/', + $html[0],$loc)) { + $options['max_redirects']--; + return $this->request($loc[1],$options); } - if ($options['follow_location'] && - preg_match('/Location: (.+?)'.preg_quote($eol).'/', - $html[0],$loc)) { - $options['max_redirects']--; - return $this->request($loc[1],$options); } - return array( + return [ 'body'=>$body, 'headers'=>$headers, 'engine'=>'socket', - 'cached'=>FALSE - ); + 'cached'=>FALSE, + 'error'=>$err + ]; } /** @@ -419,17 +440,17 @@ protected function _socket($url,$options) { **/ function engine($arg='curl') { $arg=strtolower($arg); - $flags=array( + $flags=[ 'curl'=>extension_loaded('curl'), 'stream'=>ini_get('allow_url_fopen'), 'socket'=>function_exists('fsockopen') - ); + ]; if ($flags[$arg]) return $this->wrapper=$arg; foreach ($flags as $key=>$val) if ($val) return $this->wrapper=$key; - user_error(E_Request,E_USER_ERROR); + user_error(self::E_Request,E_USER_ERROR); } /** @@ -440,7 +461,7 @@ function engine($arg='curl') { **/ function subst(array &$old,$new) { if (is_string($new)) - $new=array($new); + $new=[$new]; foreach ($new as $hdr) { $old=preg_grep('/'.preg_quote(strstr($hdr,':',TRUE),'/').':.+/', $old,PREG_GREP_INVERT); @@ -469,11 +490,11 @@ function request($url,array $options=NULL) { elseif (!preg_match('/https?/',$parts['scheme'])) return FALSE; if (!is_array($options)) - $options=array(); + $options=[]; if (empty($options['header'])) - $options['header']=array(); + $options['header']=[]; elseif (is_string($options['header'])) - $options['header']=array($options['header']); + $options['header']=[$options['header']]; if (!$this->wrapper) $this->engine(); if ($this->wrapper!='stream') { @@ -487,13 +508,13 @@ function request($url,array $options=NULL) { $this->subst($options['header'],'Host: '.$parts['host']); } $this->subst($options['header'], - array( + [ 'Accept-Encoding: gzip,deflate', 'User-Agent: '.(isset($options['user_agent'])? $options['user_agent']: 'Mozilla/5.0 (compatible; '.php_uname('s').')'), 'Connection: close' - ) + ] ); if (isset($options['content']) && is_string($options['content'])) { if ($options['method']=='POST' && @@ -508,13 +529,13 @@ function request($url,array $options=NULL) { 'Authorization: Basic '. base64_encode($parts['user'].':'.$parts['pass']) ); - $options+=array( + $options+=[ 'method'=>'GET', 'header'=>$options['header'], 'follow_location'=>TRUE, 'max_redirects'=>20, 'ignore_errors'=>FALSE - ); + ]; $eol="\r\n"; if ($fw->get('CACHE') && preg_match('/GET|HEAD/',$options['method'])) { @@ -534,11 +555,14 @@ function request($url,array $options=NULL) { $result=$cache->get($hash); $result['cached']=TRUE; } - elseif (preg_match('/Cache-Control: max-age=(.+?)'. - preg_quote($eol).'/',implode($eol,$result['headers']),$exp)) + elseif (preg_match('/Cache-Control:(?:.*)max-age=(\d+)(?:,?.*'. + preg_quote($eol).')/',implode($eol,$result['headers']),$exp)) $cache->set($hash,$result,$exp[1]); } - return $result; + $req=[$options['method'].' '.$url]; + foreach ($options['header'] as $header) + array_push($req,$header); + return array_merge(['request'=>$req],$result); } /** @@ -563,7 +587,9 @@ function minify($files,$mime=NULL,$header=TRUE,$path=NULL) { $path=$fw->get('UI').';./'; foreach ($fw->split($path,FALSE) as $dir) foreach ($files as $file) - if (is_file($save=$fw->fixslashes($dir.$file))) { + if (is_file($save=$fw->fixslashes($dir.$file)) && + is_bool(strpos($save,'../')) && + preg_match('/\.(css|js)$/i',$file)) { if ($fw->get('CACHE') && ($cached=$cache->exists( $hash=$fw->hash($save).'.'.$ext[0],$data)) && @@ -637,7 +663,7 @@ function minify($files,$mime=NULL,$header=TRUE,$path=NULL) { } continue; } - if (in_array($src[$ptr],array('\'','"'))) { + if (in_array($src[$ptr],['\'','"'])) { $match=$src[$ptr]; $data.=$match; $ptr++; @@ -693,14 +719,14 @@ function rss($url,$max=10,$tags=NULL) { NULL,LIBXML_NOBLANKS|LIBXML_NOERROR); if (!is_object($xml)) return FALSE; - $out=array(); + $out=[]; if (isset($xml->channel)) { $out['source']=(string)$xml->channel->title; $max=min($max,count($xml->channel->item)); for ($i=0;$i<$max;$i++) { $item=$xml->channel->item[$i]; - $list=array(''=>NULL)+$item->getnamespaces(TRUE); - $fields=array(); + $list=[''=>NULL]+$item->getnamespaces(TRUE); + $fields=[]; foreach ($list as $ns=>$uri) foreach ($item->children($uri) as $key=>$val) $fields[$ns.($ns?':':'').$key]=(string)$val; @@ -748,7 +774,7 @@ function whois($addr,$server='whois.internic.net') { function slug($text) { return trim(strtolower(preg_replace('/([^\pL\pN])+/u','-', trim(strtr(str_replace('\'','',$text), - array( + [ 'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A', 'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A', 'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C', @@ -796,7 +822,7 @@ function slug($text) { 'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v', 'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z', 'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>'' - )+Base::instance()->get('DIACRITICS'))))),'-'); + ]+Base::instance()->get('DIACRITICS'))))),'-'); } /** @@ -850,8 +876,7 @@ function gzdecode($str) { $fw=Base::instance(); if (!is_dir($tmp=$fw->get('TEMP'))) mkdir($tmp,Base::MODE,TRUE); - file_put_contents($file=$tmp.'/'. - $fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'. + file_put_contents($file=$tmp.'/'.$fw->get('SEED').'.'. $fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX); ob_start(); readgzfile($file); diff --git a/lib/web/geo.php b/lib/web/geo.php index 498f857d4..7d7a0bce0 100644 --- a/lib/web/geo.php +++ b/lib/web/geo.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -34,14 +34,14 @@ function tzinfo($zone) { $ref=new \DateTimeZone($zone); $loc=$ref->getLocation(); $trn=$ref->getTransitions($now=time(),$now); - $out=array( + $out=[ 'offset'=>$ref-> - getOffset(new \DateTime('now',new \DateTimeZone('GMT')))/3600, + getOffset(new \DateTime('now',new \DateTimeZone('UTC')))/3600, 'country'=>$loc['country_code'], 'latitude'=>$loc['latitude'], 'longitude'=>$loc['longitude'], 'dst'=>$trn[0]['isdst'] - ); + ]; unset($ref); return $out; } @@ -72,7 +72,7 @@ function location($ip=NULL) { if (($req=$web->request('http://www.geoplugin.net/json.gp'. ($public?('?ip='.$ip):''))) && $data=json_decode($req['body'],TRUE)) { - $out=array(); + $out=[]; foreach ($data as $key=>$val) if (!strpos($key,'currency') && $key!=='geoplugin_status' && $key!=='geoplugin_region') @@ -87,17 +87,17 @@ function location($ip=NULL) { * @return array|FALSE * @param $latitude float * @param $longitude float + * @param $key string **/ - function weather($latitude,$longitude) { + function weather($latitude,$longitude,$key) { $fw=\Base::instance(); $web=\Web::instance(); - $query=array( + $query=[ 'lat'=>$latitude, - 'lon'=>$longitude - ); - $req=$web->request( - 'http://api.openweathermap.org/data/2.5/weather?'. - http_build_query($query)); + 'lon'=>$longitude, + 'APPID'=>$key, + 'units'=>'metric' + ]; return ($req=$web->request( 'http://api.openweathermap.org/data/2.5/weather?'. http_build_query($query)))? diff --git a/lib/web/google/staticmap.php b/lib/web/google/staticmap.php old mode 100644 new mode 100755 index 71acc5506..6a3f16017 --- a/lib/web/google/staticmap.php +++ b/lib/web/google/staticmap.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). diff --git a/lib/web/oauth2.php b/lib/web/oauth2.php new file mode 100644 index 000000000..a41f9fb38 --- /dev/null +++ b/lib/web/oauth2.php @@ -0,0 +1,134 @@ +. + +*/ + +namespace Web; + +//! Lightweight OAuth2 client +class OAuth2 extends \Magic { + + protected + //! Scopes and claims + $args=[]; + + /** + * Return OAuth2 authentication URI + * @return string + * @param $endpoint string + **/ + function uri($endpoint) { + return $endpoint.'?'.http_build_query($this->args); + } + + /** + * Send request to API/token endpoint + * @return string|FALSE + * @param $uri string + * @param $method string + * @param $token array + **/ + function request($uri,$method,$token=NULL) { + $web=\Web::instance(); + $options=[ + 'method'=>$method, + 'content'=>http_build_query($this->args), + 'header'=>['Accept: application/json'] + ]; + if ($token) + array_push($options['header'],'Authorization: Bearer '.$token); + elseif ($method=='POST') + array_push($options['header'],'Authorization: Basic '. + base64_encode( + $this->args['client_id'].':'. + $this->args['client_secret'] + ) + ); + $response=$web->request($uri,$options); + return $response['body'] && + preg_grep('/HTTP\/1\.\d 200/',$response['headers'])? + json_decode($response['body'],TRUE): + FALSE; + } + + /** + * Parse JSON Web token + * @return array + * @param $token string + **/ + function jwt($token) { + return json_decode( + base64_decode( + str_replace( + ['-','_'], + ['+','/'], + explode('.',$token)[1] + ) + ), + TRUE + ); + } + + /** + * Return TRUE if scope/claim exists + * @return bool + * @param $key string + **/ + function exists($key) { + return isset($this->args[$key]); + } + + /** + * Bind value to scope/claim + * @return string + * @param $key string + * @param $val string + **/ + function set($key,$val) { + return $this->args[$key]=$val; + } + + /** + * Return value of scope/claim + * @return mixed + * @param $key string + **/ + function &get($key) { + if (isset($this->args[$key])) + $val=&$this->args[$key]; + else + $val=NULL; + return $val; + } + + /** + * Remove scope/claim + * @return NULL + * @param $key + **/ + function clear($key=NULL) { + if ($key) + unset($this->args[$key]); + else + $this->args=[]; + } + +} + diff --git a/lib/web/openid.php b/lib/web/openid.php index 89173d0be..f795cb37c 100644 --- a/lib/web/openid.php +++ b/lib/web/openid.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -29,7 +29,7 @@ class OpenID extends \Magic { //! OpenID provider endpoint URL $url, //! HTTP request parameters - $args=array(); + $args=[]; /** * Determine OpenID provider @@ -38,11 +38,11 @@ class OpenID extends \Magic { **/ protected function discover($proxy) { // Normalize - if (!preg_match('/https?:\/\//i',$this->args['identity'])) - $this->args['identity']='http://'.$this->args['identity']; - $url=parse_url($this->args['identity']); + if (!preg_match('/https?:\/\//i',$this->args['endpoint'])) + $this->args['endpoint']='http://'.$this->args['endpoint']; + $url=parse_url($this->args['endpoint']); // Remove fragment; reconnect parts - $this->args['identity']=$url['scheme'].'://'. + $this->args['endpoint']=$url['scheme'].'://'. (isset($url['user'])? ($url['user']. (isset($url['pass'])?(':'.$url['pass']):'').'@'):''). @@ -50,7 +50,7 @@ protected function discover($proxy) { (isset($url['query'])?('?'.$url['query']):''); // HTML-based discovery of OpenID provider $req=\Web::instance()-> - request($this->args['identity'],array('proxy'=>$proxy)); + request($this->args['endpoint'],['proxy'=>$proxy]); if (!$req) return FALSE; $type=array_values(preg_grep('/Content-Type:/',$req['headers'])); @@ -63,8 +63,9 @@ protected function discover($proxy) { $svc=$xrds['XRD']['Service']; if (isset($svc[0])) $svc=$svc[0]; + $svc_type=is_array($svc['Type'])?$svc['Type']:array($svc['Type']); if (preg_grep('/http:\/\/specs\.openid\.net\/auth\/2.0\/'. - '(?:server|signon)/',$svc['Type'])) { + '(?:server|signon)/',$svc_type)) { $this->args['provider']=$svc['URI']; if (isset($svc['LocalID'])) $this->args['localidentity']=$svc['LocalID']; @@ -89,7 +90,7 @@ protected function discover($proxy) { preg_match_all('/\b(rel|href)\h*=\h*'. '(?:"(.+?)"|\'(.+?)\')/s',$parts[1],$attr, PREG_SET_ORDER)) { - $node=array(); + $node=[]; foreach ($attr as $kv) $node[$kv[1]]=isset($kv[2])?$kv[2]:$kv[3]; if (isset($node['rel']) && @@ -140,7 +141,7 @@ protected function discover($proxy) { * @param $attr array * @param $reqd string|array **/ - function auth($proxy=NULL,$attr=array(),array $reqd=NULL) { + function auth($proxy=NULL,$attr=[],array $reqd=NULL) { $fw=\Base::instance(); $root=$fw->get('SCHEME').'://'.$fw->get('HOST'); if (empty($this->args['trust_root'])) @@ -157,7 +158,7 @@ function auth($proxy=NULL,$attr=array(),array $reqd=NULL) { $this->args['ax.required']=is_string($reqd)? $reqd:implode(',',$reqd); } - $var=array(); + $var=[]; foreach ($this->args as $key=>$val) $var['openid.'.$key]=$val; $fw->reroute($this->url.'?'.http_build_query($var)); @@ -179,16 +180,16 @@ function verified($proxy=NULL) { $this->args['mode']!='error' && $this->url=$this->discover($proxy)) { $this->args['mode']='check_authentication'; - $var=array(); + $var=[]; foreach ($this->args as $key=>$val) $var['openid.'.$key]=$val; $req=\Web::instance()->request( $this->url, - array( + [ 'method'=>'POST', 'content'=>http_build_query($var), 'proxy'=>$proxy - ) + ] ); return (bool)preg_match('/is_valid:true/i',$req['body']); } @@ -245,4 +246,3 @@ function clear($key) { } } - diff --git a/lib/web/pingback.php b/lib/web/pingback.php index 9dd750a3b..f063f1040 100644 --- a/lib/web/pingback.php +++ b/lib/web/pingback.php @@ -2,7 +2,7 @@ /* - Copyright (c) 2009-2015 F3::Factory/Bong Cosca, All rights reserved. + Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved. This file is part of the Fat-Free Framework (http://fatfreeframework.com). @@ -38,7 +38,7 @@ protected function enabled($url) { $web=\Web::instance(); $req=$web->request($url); $found=FALSE; - if ($req && $req['body']) { + if ($req['body']) { // Look for pingback header foreach ($req['headers'] as $header) if (preg_match('/^X-Pingback:\h*(.+)/',$header,$href)) { @@ -71,7 +71,7 @@ function inspect($source) { $doc=new \DOMDocument('1.0',$fw->get('ENCODING')); $doc->stricterrorchecking=FALSE; $doc->recover=TRUE; - if ($req && @$doc->loadhtml($req['body'])) { + if (@$doc->loadhtml($req['body'])) { // Parse anchor tags $links=$doc->getelementsbytagname('a'); foreach ($links as $link) { @@ -79,17 +79,17 @@ function inspect($source) { // Find pingback-enabled resources if ($permalink && $found=$this->enabled($permalink)) { $req=$web->request($found, - array( + [ 'method'=>'POST', 'header'=>'Content-Type: application/xml', 'content'=>xmlrpc_encode_request( 'pingback.ping', - array($source,$permalink), - array('encoding'=>$fw->get('ENCODING')) + [$source,$permalink], + ['encoding'=>$fw->get('ENCODING')] ) - ) + ] ); - if ($req && $req['body']) + if ($req['body']) $this->log.=date('r').' '. $permalink.' [permalink:'.$found.']'.PHP_EOL. $req['body'].PHP_EOL; @@ -118,7 +118,7 @@ function listen($func,$path=NULL) { $path=$fw->get('BASE'); $web=\Web::instance(); $args=xmlrpc_decode_request($fw->get('BODY'),$method,$charset); - $options=array('encoding'=>$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')); @@ -138,8 +138,7 @@ function listen($func,$path=NULL) { $links=$doc->getelementsbytagname('a'); foreach ($links as $link) { if ($link->getattribute('href')==$permalink) { - call_user_func_array($func, - array($source,$req['body'])); + call_user_func_array($func,[$source,$req['body']]); // Success die(xmlrpc_encode_request(NULL,$source,$options)); } diff --git a/readme.md b/readme.md index e49cff9be..64c3b69dd 100644 --- a/readme.md +++ b/readme.md @@ -55,9 +55,9 @@ The philosophy behind the framework and its approach to software architecture is [![Twitter](ui/images/twitter.png)](https://twitter.com/phpfatfree) -### Version 3.5 Is Finally Released! +### Version 3.6 Is Finally Released! -The latest official release welcomes the summer with a bang and marks the final milestone in this version of the Fat-Free Framework. Packed with exciting new features and outstanding documentation that consumed significant time and effort to develop and refine, version 3.5 is now available for download. This edition is packed with a bunch of new usability and security features. +The latest official release welcomes the summer with a bang and marks the final milestone in this version of the Fat-Free Framework. Packed with exciting new features and outstanding documentation that consumed significant time and effort to develop and refine, version 3.6 is now available for download. This edition is packed with a bunch of new usability and security features. F3 has a stable enterprise-class architecture. Unbeatable performance, user-friendly features and a lightweight footprint. What more can you ask for? diff --git a/ui/welcome.htm b/ui/welcome.htm index 89b0aa521..08f1d8aba 100644 --- a/ui/welcome.htm +++ b/ui/welcome.htm @@ -56,6 +56,6 @@

Support F3