- upgrade PHP "Fat Free Framework" v3.6.3 -> v3.6.4, #595

This commit is contained in:
Mark Friedrich
2018-06-07 19:35:23 +02:00
parent e30f8a754c
commit f839de71cf
10 changed files with 394 additions and 109 deletions

View File

@@ -1,5 +1,145 @@
CHANGELOG
3.6.4 (19 April 2018)
* NEW: Added Dependency Injection support with CONTAINER variable [#221](https://github.com/bcosca/fatfree-core/issues/221)
* NEW: configurable LOGGABLE error codes [#1091](https://github.com/bcosca/fatfree/issues/1091#issuecomment-364674701)
* NEW: JAR.lifetime option, [#178](https://github.com/bcosca/fatfree-core/issues/178)
* Template: reduced Prefab calls
* Template: optimized reflection for better derivative support, [bcosca/fatfree#1088](https://github.com/bcosca/fatfree/issues/1088)
* Template: optimized parsing for template attributes and tokens
* DB\Mongo: fixed logging with mongodb extention
* DB\Jig: added lazy-loading [#7e1cd9b9b89](https://github.com/bcosca/fatfree-core/commit/7e1cd9b9b89c4175d0f6b86ced9d9bd49c04ac39)
* DB\Jig\Mapper: Added group feature, bcosca/fatfree#616
* DB\SQL\Mapper: fix PostgreSQL RETURNING ID when no pkey is available, [bcosca/fatfree#1069](https://github.com/bcosca/fatfree/issues/1069), [#230](https://github.com/bcosca/fatfree-core/issues/230)
* DB\SQL\Mapper: disable order clause auto-quoting when it's already been quoted
* Web->location: add failsafe for geoip_region_name_by_code() [#GB:Bxyn9xn9AgAJ](https://groups.google.com/d/msg/f3-framework/APau4wnwNzE/Bxyn9xn9AgAJ)
* Web->request: Added proxy support [#e936361b](https://github.com/bcosca/fatfree-core/commit/e936361bc03010c4c7c38a396562e5e96a8a100d)
* Web->mime: Added JFIF format
* Markdown: handle line breaks in paragraph blocks, [bcosca/fatfree#1100](https://github.com/bcosca/fatfree/issues/1100)
* config: reduced cast calls on parsing config sections
* Patch empty SERVER_NAME [bcosca/fatfree#1084](https://github.com/bcosca/fatfree/issues/1084)
* Bugfix: unreliable request headers in Web->request() response [bcosca/fatfree#1092](https://github.com/bcosca/fatfree/issues/1092)
* Fixed, View->render: utilizing multiple UI paths, [bcosca/fatfree#1083](https://github.com/bcosca/fatfree/issues/1083)
* Fixed URL parsing with PHP 5.4 [#247](https://github.com/bcosca/fatfree-core/issues/247)
* Fixed PHP 7.2 warnings when session is active prematurely, [#238](https://github.com/bcosca/fatfree-core/issues/238)
* Fixed setcookie $expire variable type [#240](https://github.com/bcosca/fatfree-core/issues/240)
* Fixed expiration time when updating an existing cookie
3.6.3 (31 December 2017)
* PHP7 fix: remove deprecated (unset) cast
* Web->request: restricted follow_location to 3XX responses only
* CLI mode: refactored arguments parsing
* CLI mode: fixed query string encoding
* SMTP: Refactor parsing of attachments
* SMTP: clean-up mail headers for multipart messages, [#1065](https://github.com/bcosca/fatfree/issues/1065)
* config: fixed performance issues on parsing config files
* config: cast command parameters in config entries to php type & constant, [#1030](https://github.com/bcosca/fatfree/issues/1030)
* config: reduced registry calls
* config: skip hive escaping when resolving dynamic config vars, [#1030](https://github.com/bcosca/fatfree/issues/1030)
* Bug fix: Incorrect cookie lifetime computation, [#1070](https://github.com/bcosca/fatfree/issues/1070), [#1016](https://github.com/bcosca/fatfree/issues/1016)
* DB\SQL\Mapper: use RETURNING option instead of a sequence query to get lastInsertId in PostgreSQL, [#1069](https://github.com/bcosca/fatfree/issues/1069), [#230](https://github.com/bcosca/fatfree-core/issues/230)
* DB\SQL\Session: check if _agent is too long for SQL based sessions [#236](https://github.com/bcosca/fatfree-core/issues/236)
* DB\SQL\Session: fix Session handler table creation issue on SQL Server, [#899](https://github.com/bcosca/fatfree/issues/899)
* DB\SQL: fix oracle db issue with empty error variable, [#1072](https://github.com/bcosca/fatfree/issues/1072)
* DB\SQL\Mapper: fix sorting issues on SQL Server, [#1052](https://github.com/bcosca/fatfree/issues/1052) [#225](https://github.com/bcosca/fatfree-core/issues/225)
* Prevent directory traversal attacks on filesystem based cache [#1073](https://github.com/bcosca/fatfree/issues/1073)
* Bug fix, Template: PHP constants used in include with attribute, [#983](https://github.com/bcosca/fatfree/issues/983)
* Bug fix, Template: Numeric value in expression alters PHP_EOL context
* Template: use existing linefeed instead of PHP_EOL, [#1048](https://github.com/bcosca/fatfree/issues/1048)
* Template: make newline interpolation handling configurable [#223](https://github.com/bcosca/fatfree-core/issues/223)
* Template: add beforerender to Preview
* fix custom FORMATS without modifiers
* Cache: Refactor Cache->reset for XCache
* Cache: loosen reset cache key pattern, [#1041](https://github.com/bcosca/fatfree/issues/1041)
* XCache: suffix reset only works if xcache.admin.enable_auth is disabled
* Added HTTP 103 as recently approved by the IETF
* LDAP changes to for AD flexibility [#227](https://github.com/bcosca/fatfree-core/issues/227)
* Hide debug trace from ajax errors when DEBUG=0 [#1071](https://github.com/bcosca/fatfree/issues/1071)
* fix View->render using potentially wrong cache entry
3.6.2 (26 June 2017)
* Return a status code > 0 when dying on error [#220](https://github.com/bcosca/fatfree-core/issues/220)
* fix SMTP line width [#215](https://github.com/bcosca/fatfree-core/issues/215)
* Allow using a custom field for ldap user id checking [#217](https://github.com/bcosca/fatfree-core/issues/217)
* NEW: DB\SQL->exists: generic method to check if SQL table exists
* Pass handler to route handler and hooks [#1035](https://github.com/bcosca/fatfree/issues/1035)
* pass carriage return of multiline dictionary keys
* Better Web->slug customization
* fix incorrect header issue [#211](https://github.com/bcosca/fatfree-core/issues/211)
* fix schema issue on databases with case-sensitive collation, fixes [#209](https://github.com/bcosca/fatfree-core/issues/209)
* Add filter for deriving C-locale equivalent of a number
* Bug fix: @LANGUAGE remains unchanged after override
* abort: added Header pre-check
* Assemble URL after ONREROUTE
* Add reroute argument to skip script termination
* Invoke ONREROUTE after headers are sent
* SQLite switch to backtick as quote
* Bug fix: Incorrect timing in SQL query logs
* DB\SQL\Mapper: Cast return value of count to integer
* Patched $_SERVER['REQUEST_URI'] to ensure it contains a relative URI
* Tweak debug verbosity
* fix php carriage return issue in preview->build [#205](https://github.com/bcosca/fatfree-core/pull/205)
* fixed template string resolution [#205](https://github.com/bcosca/fatfree-core/pull/205)
* Fixed unexpected default seed on CACHE set [#1028](https://github.com/bcosca/fatfree/issues/1028)
* DB\SQL\Mapper: Optimized field escaping on options
* Optimize template conversion to PHP file
3.6.1 (2 April 2017)
* NEW: Recaptcha plugin [#194](https://github.com/bcosca/fatfree-core/pull/194)
* NEW: MB variable for detecting multibyte support
* NEW: DB\SQL: Cache parsed schema for the TTL duration
* NEW: quick erase flag on Jig/Mongo/SQL mappers [#193](https://github.com/bcosca/fatfree-core/pull/193)
* NEW: Allow OPTIONS method to return a response body [#171](https://github.com/bcosca/fatfree-core/pull/171)
* NEW: Add support for Memcached (bcosca/fatfree#997)
* NEW: Rudimentary preload resource (HTTP2 server) support via template push()
* NEW: Add support for new MongoDB driver [#177](https://github.com/bcosca/fatfree-core/pull/177)
* Changed: template filter are all lowercase now
* Changed: Fix template lookup inconsistency: removed base dir from UI on render
* Changed: count() method now has an options argument [#192](https://github.com/bcosca/fatfree-core/pull/192)
* Changed: SMTP, Spit out error message if any
* \DB\SQL\Mapper: refactored row count strategy
* DB\SQL\Mapper: Allow non-scalar values to be assigned as mapper property
* DB\SQL::PARAM_FLOAT: remove cast to float (#106 and bcosca/fatfree#984) (#191)
* DB\SQL\mapper->erase: allow empty string
* DB\SQL\mapper->insert: fields reset after successful INSERT
* Add option to debounce Cursor->paginate subset [#195](https://github.com/bcosca/fatfree-core/pull/195)
* View: Don't delete sandboxed variables (#198)
* Preview: Optimize compilation of template expressions
* Preview: Use shorthand tag for direct rendering
* Preview->resolve(): new tweak to allow template persistence as option
* Web: Expose diacritics translation table
* SMTP: Enable logging of message body only when $log argument is 'verbose'
* SMTP: Convert headers to camelcase for consistency
* make cache seed more flexible, #164
* Improve trace details for DEBUG>2
* Enable config() to read from an array of input files
* Improved alias and reroute regex
* Make camelCase and snakeCase Unicode-aware
* format: Provision for optional whitespaces
* Break APCu-BC dependence
* Old PHP 5.3 cleanup
* Debug log must include HTTP query
* Recognize X-Forwarded-Port header (bcosca/fatfree#1002)
* Avoid use of deprecated mcrypt module
* Return only the client's IP when using the `X-Forwarded-For` header to deduce an IP address
* Remove orphan mutex locks on termination (#157)
* Use 80 as default port number to avoid issues when `$_SERVER['SERVER_PORT']` is not existing
* fread replaced with readfile() for simple send() usecase
* Bug fix: request URI with multiple leading slashes, #203
* Bug fix: Query generates wrong adhoc field value
* Bug fix: SMTP stream context issue #200
* Bug fix: child pseudo class selector in minify, bcosca/fatfree#1008
* Bug fix: "Undefined index: CLI" error (#197)
* Bug fix: cast Cache-Control expire time to int, bcosca/fatfree#1004
* Bug fix: Avoid issuance of multiple Content-Type headers for nested templates
* Bug fix: wildcard token issue with digits (bcosca/fatfree#996)
* Bug fix: afterupdate ignored when row does not change
* Bug fix: session handler read() method for PHP7 (need strict string) #184 #185
* Bug fix: reroute mocking in CLI mode (#183)
* Bug fix: Reroute authoritative relative references (#181)
* Bug fix: locales order and charset hyphen
* Bug fix: base stripped twice in router (#176)
3.6.0 (19 November 2016)
* NEW: [cli] request type
* NEW: console-friendly CLI mode
@@ -89,6 +229,7 @@ CHANGELOG
* 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: Image->render and Image->dump, removed unnecessary 2nd argument (#146)
* 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)

View File

@@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess {
//@{ Framework details
const
PACKAGE='Fat-Free Framework',
VERSION='3.6.3-Release';
VERSION='3.6.4-Release';
//@}
//@{ HTTP status codes (RFC 2616)
@@ -346,17 +346,16 @@ final class Base extends Prefab implements ArrayAccess {
* @param $ttl int
**/
function set($key,$val,$ttl=0) {
$time=$this->hive['TIME'];
$time=(int)$this->hive['TIME'];
if (preg_match('/^(GET|POST|COOKIE)\b(.+)/',$key,$expr)) {
$this->set('REQUEST'.$expr[2],$val);
if ($expr[1]=='COOKIE') {
$parts=$this->cut($key);
$jar=$this->unserialize($this->serialize($this->hive['JAR']));
if (isset($_COOKIE[$parts[1]])) {
$jar['expire']=0;
unset($jar['lifetime']);
if (isset($_COOKIE[$parts[1]]))
call_user_func_array('setcookie',
array_merge([$parts[1],NULL],$jar));
}
array_merge([$parts[1],NULL],['expire'=>0]+$jar));
if ($ttl)
$jar['expire']=$time+$ttl;
call_user_func_array('setcookie',[$parts[1],$val]+$jar);
@@ -395,9 +394,17 @@ final class Base extends Prefab implements ArrayAccess {
$ref=&$this->ref($key);
$ref=$val;
if (preg_match('/^JAR\b/',$key)) {
$jar=$this->unserialize($this->serialize($this->hive['JAR']));
$jar['expire']-=$time;
call_user_func_array('session_set_cookie_params',$jar);
if ($key=='JAR.lifetime')
$this->set('JAR.expire',$val==0?0:
(is_int($val)?$time+$val:strtotime($val)));
else {
if ($key=='JAR.expire')
$this->hive['JAR']['lifetime']=max(0,$val-$time);
$jar=$this->unserialize($this->serialize($this->hive['JAR']));
unset($jar['expire']);
if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE)
call_user_func_array('session_set_cookie_params',$jar);
}
}
if ($ttl)
// Persist the key-value pair
@@ -442,6 +449,7 @@ final class Base extends Prefab implements ArrayAccess {
if ($expr[1]=='COOKIE') {
$parts=$this->cut($key);
$jar=$this->hive['JAR'];
unset($jar['lifetime']);
$jar['expire']=0;
call_user_func_array('setcookie',
array_merge([$parts[1],NULL],$jar));
@@ -1231,11 +1239,18 @@ final class Base extends Prefab implements ArrayAccess {
$req.='?'.$this->hive['QUERY'];
if (!$text)
$text='HTTP '.$code.' ('.$req.')';
error_log($text);
$trace=$this->trace($trace);
foreach (explode("\n",$trace) as $nexus)
if ($nexus)
error_log($nexus);
$loggable=$this->hive['LOGGABLE'];
if (!is_array($loggable))
$loggable=$this->split($loggable);
foreach ($loggable as $status)
if ($status=='*' || preg_match('/^'.preg_replace('/\D/','\d',$status).'$/',$code)) {
error_log($text);
foreach (explode("\n",$trace) as $nexus)
if ($nexus)
error_log($nexus);
break;
}
if ($highlight=!$this->hive['CLI'] && !$this->hive['AJAX'] &&
$this->hive['HIGHLIGHT'] && is_file($css=__DIR__.'/'.self::CSS))
$trace=$this->highlight($trace);
@@ -1750,6 +1765,19 @@ final class Base extends Prefab implements ArrayAccess {
if ($parts[2]=='->') {
if (is_subclass_of($parts[1],'Prefab'))
$parts[1]=call_user_func($parts[1].'::instance');
elseif ($container=$this->get('CONTAINER')) {
if (is_object($container) && is_callable([$container,'has'])
&& $container->has($parts[1])) // PSR11
$parts[1]=call_user_func([$container,'get'],$parts[1]);
elseif (is_callable($container))
$parts[1]=call_user_func($container,$parts[1],$args);
elseif (is_string($container) && is_subclass_of($container,'Prefab'))
$parts[1]=call_user_func($container.'::instance')->get($parts[1]);
else
user_error(sprintf(self::E_Class,
$this->stringify($container)),
E_USER_ERROR);
}
else {
$ref=new ReflectionClass($parts[1]);
$parts[1]=method_exists($parts[1],'__construct') && $args?
@@ -1888,7 +1916,8 @@ final class Base extends Prefab implements ArrayAccess {
call_user_func_array(
[$this,$cmd[1]],
array_merge([$match['lval']],
str_getcsv($this->cast($match['rval'])))
str_getcsv($cmd[1]=='config'?$this->cast($match['rval']):
$match['rval']))
);
}
else {
@@ -2190,7 +2219,7 @@ final class Base extends Prefab implements ArrayAccess {
$this->error(500,$text,NULL,$level);
}
);
if (!isset($_SERVER['SERVER_NAME']))
if (!isset($_SERVER['SERVER_NAME']) || $_SERVER['SERVER_NAME']==='')
$_SERVER['SERVER_NAME']=gethostname();
if ($cli=PHP_SAPI=='cli') {
// Emulate HTTP request
@@ -2266,14 +2295,14 @@ final class Base extends Prefab implements ArrayAccess {
$base=rtrim($this->fixslashes(
dirname($_SERVER['SCRIPT_NAME'])),'/');
$uri=parse_url((preg_match('/^\w+:\/\//',$_SERVER['REQUEST_URI'])?'':
'//'.$_SERVER['SERVER_NAME']).$_SERVER['REQUEST_URI']);
$scheme.'://'.$_SERVER['SERVER_NAME']).$_SERVER['REQUEST_URI']);
$_SERVER['REQUEST_URI']=$uri['path'].
(isset($uri['query'])?'?'.$uri['query']:'').
(isset($uri['fragment'])?'#'.$uri['fragment']:'');
$path=preg_replace('/^'.preg_quote($base,'/').'/','',$uri['path']);
session_cache_limiter('');
$jar=[
'expire'=>0,
'lifetime'=>0,
'path'=>$base?:'/',
'domain'=>is_int(strpos($_SERVER['SERVER_NAME'],'.')) &&
!filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
@@ -2281,7 +2310,6 @@ final class Base extends Prefab implements ArrayAccess {
'secure'=>($scheme=='https'),
'httponly'=>TRUE
];
call_user_func_array('session_set_cookie_params',$jar);
$port=80;
if (isset($headers['X-Forwarded-Port']))
$port=$headers['X-Forwarded-Port'];
@@ -2328,6 +2356,7 @@ final class Base extends Prefab implements ArrayAccess {
$this->language($headers['Accept-Language']):
$this->fallback,
'LOCALES'=>'./',
'LOGGABLE'=>'*',
'LOGS'=>'./',
'MB'=>extension_loaded('mbstring'),
'ONERROR'=>NULL,
@@ -2363,6 +2392,11 @@ final class Base extends Prefab implements ArrayAccess {
'VERSION'=>self::VERSION,
'XFRAME'=>'SAMEORIGIN'
];
if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) {
unset($jar['expire']);
session_cache_limiter('');
call_user_func_array('session_set_cookie_params',$jar);
}
if (PHP_SAPI=='cli-server' &&
preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
$this->reroute('/');
@@ -2673,16 +2707,22 @@ class View extends Prefab {
//! Nesting level
$level=0;
/** @var \Base Framework instance */
protected $fw;
function __construct() {
$this->fw=\Base::instance();
}
/**
* Encode characters to equivalent HTML entities
* @return string
* @param $arg mixed
**/
function esc($arg) {
$fw=Base::instance();
return $fw->recursive($arg,
function($val) use($fw) {
return is_string($val)?$fw->encode($val):$val;
return $this->fw->recursive($arg,
function($val) {
return is_string($val)?$this->fw->encode($val):$val;
}
);
}
@@ -2693,10 +2733,9 @@ class View extends Prefab {
* @param $arg mixed
**/
function raw($arg) {
$fw=Base::instance();
return $fw->recursive($arg,
function($val) use($fw) {
return is_string($val)?$fw->decode($val):$val;
return $this->fw->recursive($arg,
function($val) {
return is_string($val)?$this->fw->decode($val):$val;
}
);
}
@@ -2708,7 +2747,7 @@ class View extends Prefab {
* @param $mime string
**/
protected function sandbox(array $hive=NULL,$mime=NULL) {
$fw=Base::instance();
$fw=$this->fw;
$implicit=FALSE;
if (is_null($hive)) {
$implicit=TRUE;
@@ -2744,9 +2783,9 @@ class View extends Prefab {
* @param $ttl int
**/
function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
$fw=Base::instance();
$fw=$this->fw;
$cache=Cache::instance();
foreach ($fw->split($fw->UI) as $dir)
foreach ($fw->split($fw->UI) as $dir) {
if ($cache->exists($hash=$fw->hash($dir.$file),$data))
return $data;
if (is_file($this->file=$fw->fixslashes($dir.$file))) {
@@ -2762,6 +2801,7 @@ class View extends Prefab {
$cache->set($hash,$data,$ttl);
return $data;
}
}
user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
}
@@ -2807,7 +2847,6 @@ class Preview extends View {
* @param $val int|float
**/
function c($val) {
$fw=Base::instance();
$locale=setlocale(LC_NUMERIC,0);
setlocale(LC_NUMERIC,'C');
$out=(string)(float)$val;
@@ -2821,13 +2860,13 @@ class Preview extends View {
* @param $str string
**/
function token($str) {
$fw = Base::instance();
$fw=$this->fw;
$str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
$fw->compile($str)));
if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
$str,$parts)) {
$str=trim($parts[1]);
foreach ($fw->split($parts[2]) as $func)
foreach ($fw->split(trim($parts[2],"\xC2\xA0")) as $func)
$str=is_string($cmd=$this->filter($func))?
$cmd.'('.$str.')':
'Base::instance()->'.
@@ -2890,7 +2929,7 @@ class Preview extends View {
* @param $escape bool
**/
function resolve($node,array $hive=NULL,$ttl=0,$persist=FALSE,$escape=NULL) {
$fw=Base::instance();
$fw=$this->fw;
$cache=Cache::instance();
if ($escape!==NULL) {
$esc=$fw->ESCAPE;
@@ -2951,7 +2990,7 @@ class Preview extends View {
* @param $ttl int
**/
function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
$fw=Base::instance();
$fw=$this->fw;
$cache=Cache::instance();
if (!is_dir($tmp=$fw->TEMP))
mkdir($tmp,Base::MODE,TRUE);

View File

@@ -41,7 +41,9 @@ class Jig {
//! Jig log
$log,
//! Memory-held data
$data;
$data,
//! lazy load/save files
$lazy;
/**
* Read data from memory/file
@@ -54,6 +56,8 @@ class Jig {
$this->data[$file]=[];
return $this->data[$file];
}
if ($this->lazy && isset($this->data[$file]))
return $this->data[$file];
$fw=\Base::instance();
$raw=$fw->read($dst);
switch ($this->format) {
@@ -75,7 +79,7 @@ class Jig {
* @param $data array
**/
function write($file,array $data=NULL) {
if (!$this->dir)
if (!$this->dir || $this->lazy)
return count($this->data[$file]=$data);
$fw=\Base::instance();
switch ($this->format) {
@@ -131,6 +135,8 @@ class Jig {
* @return NULL
**/
function drop() {
if ($this->lazy) // intentional
$this->data=[];
if (!$this->dir)
$this->data=[];
elseif ($glob=@glob($this->dir.'/*',GLOB_NOSORT))
@@ -147,11 +153,23 @@ class Jig {
* @param $dir string
* @param $format int
**/
function __construct($dir=NULL,$format=self::FORMAT_JSON) {
function __construct($dir=NULL,$format=self::FORMAT_JSON,$lazy=FALSE) {
if ($dir && !is_dir($dir))
mkdir($dir,\Base::MODE,TRUE);
$this->uuid=\Base::instance()->hash($this->dir=$dir);
$this->format=$format;
$this->lazy=$lazy;
}
/**
* save file on destruction
**/
function __destruct() {
if ($this->lazy) {
$this->lazy = FALSE;
foreach ($this->data as $file => $data)
$this->write($file,$data);
}
}
}

View File

@@ -33,7 +33,9 @@ class Mapper extends \DB\Cursor {
//! Document identifier
$id,
//! Document contents
$document=[];
$document=[],
//! field map-reduce handlers
$_reduce;
/**
* Return database type
@@ -160,7 +162,8 @@ class Mapper extends \DB\Cursor {
$options+=[
'order'=>NULL,
'limit'=>0,
'offset'=>0
'offset'=>0,
'group'=>NULL,
];
$fw=\Base::instance();
$cache=\Cache::instance();
@@ -232,30 +235,46 @@ class Mapper extends \DB\Cursor {
}
);
}
if (isset($options['order'])) {
$cols=$fw->split($options['order']);
uasort(
$data,
function($val1,$val2) use($cols) {
foreach ($cols as $col) {
$parts=explode(' ',$col,2);
$order=empty($parts[1])?
SORT_ASC:
constant($parts[1]);
$col=$parts[0];
if (!array_key_exists($col,$val1))
$val1[$col]=NULL;
if (!array_key_exists($col,$val2))
$val2[$col]=NULL;
list($v1,$v2)=[$val1[$col],$val2[$col]];
if ($out=strnatcmp($v1,$v2)*
(($order==SORT_ASC)*2-1))
return $out;
}
return 0;
if (isset($options['group'])) {
$cols=array_reverse($fw->split($options['group']));
// sort into groups
$data=$this->sort($data,$options['group']);
foreach($data as $i=>&$row) {
if (!isset($prev)) {
$prev=$row;
$prev_i=$i;
}
$drop=false;
foreach ($cols as $col)
if ($prev_i!=$i && array_key_exists($col,$row) &&
array_key_exists($col,$prev) && $row[$col]==$prev[$col])
// reduce/modify
$drop=!isset($this->_reduce[$col]) || call_user_func_array(
$this->_reduce[$col][0],[&$prev,&$row])!==FALSE;
elseif (isset($this->_reduce[$col])) {
$null=null;
// initial
call_user_func_array($this->_reduce[$col][0],[&$row,&$null]);
}
if ($drop)
unset($data[$i]);
else {
$prev=&$row;
$prev_i=$i;
}
unset($row);
}
// finalize
if ($this->_reduce[$col][1])
foreach($data as $i=>&$row) {
$row=call_user_func($this->_reduce[$col][1],$row);
if (!$row)
unset($data[$i]);
unset($row);
}
);
}
if (isset($options['order']))
$data=$this->sort($data,$options['order']);
$data=array_slice($data,
$options['offset'],$options['limit']?:NULL,TRUE);
if ($fw->CACHE && $ttl)
@@ -281,6 +300,48 @@ class Mapper extends \DB\Cursor {
return $out;
}
/**
* Sort a collection
* @param $data
* @param $cond
* @return mixed
*/
protected function sort($data,$cond) {
$cols=\Base::instance()->split($cond);
uasort(
$data,
function($val1,$val2) use($cols) {
foreach ($cols as $col) {
$parts=explode(' ',$col,2);
$order=empty($parts[1])?
SORT_ASC:
constant($parts[1]);
$col=$parts[0];
if (!array_key_exists($col,$val1))
$val1[$col]=NULL;
if (!array_key_exists($col,$val2))
$val2[$col]=NULL;
list($v1,$v2)=[$val1[$col],$val2[$col]];
if ($out=strnatcmp($v1,$v2)*
(($order==SORT_ASC)*2-1))
return $out;
}
return 0;
}
);
return $data;
}
/**
* Add reduce handler for grouped fields
* @param $key string
* @param $handler callback
* @param $finalize callback
*/
function reduce($key,$handler,$finalize=null){
$this->_reduce[$key]=[$handler,$finalize];
}
/**
* Count records that match criteria
* @return int

View File

@@ -68,8 +68,9 @@ class Mongo {
$cursor=$this->db->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) '.
$this->log.=date('r',$this->legacy() ?
$frame['ts']->sec : (round((string)$frame['ts'])/1000)).
' ('.sprintf('%.1f',$frame['millis']).'ms) '.
$frame['ns'].' ['.$frame['op'].'] '.
(empty($frame['query'])?
'':json_encode($frame['query'])).

View File

@@ -153,7 +153,7 @@ class Mapper extends \DB\Cursor {
/**
* Convert array to mapper object
* @return object
* @return static
* @param $row array
**/
protected function factory($row) {
@@ -236,14 +236,15 @@ class Mapper extends \DB\Cursor {
explode(',',$options['group'])));
}
if ($options['order']) {
$order=' ORDER BY '.implode(',',array_map(
function($str) use($db) {
$char=substr($db->quotekey(''),0,1);// quoting char
$order=' ORDER BY '.(FALSE===strpos($options['order'],$char)?
implode(',',array_map(function($str) use($db) {
return preg_match('/^\h*(\w+[._\-\w]*)(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i',
$str,$parts)?
($db->quotekey($parts[1]).
(isset($parts[2])?(' '.$parts[2]):'')):$str;
},
explode(',',$options['order'])));
},explode(',',$options['order']))):
$options['order']);
}
// SQL Server fixes
if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) &&
@@ -285,7 +286,7 @@ class Mapper extends \DB\Cursor {
/**
* Build query string and execute
* @return object
* @return static[]
* @param $fields string
* @param $filter string|array
* @param $options array
@@ -360,7 +361,7 @@ class Mapper extends \DB\Cursor {
/**
* Return record at specified offset using same criteria as
* previous load() call and make it active
* @return array
* @return static
* @param $ofs int
**/
function skip($ofs=1) {
@@ -385,7 +386,7 @@ class Mapper extends \DB\Cursor {
/**
* Insert new record
* @return object
* @return static
**/
function insert() {
$args=[];
@@ -424,8 +425,8 @@ class Mapper extends \DB\Cursor {
}
}
if ($fields) {
$add='';
if ($this->engine=='pgsql') {
$add=$aik='';
if ($this->engine=='pgsql' && !empty($pkeys)) {
$names=array_keys($pkeys);
$aik=end($names);
$add=' RETURNING '.$this->db->quotekey($aik);
@@ -437,12 +438,12 @@ class Mapper extends \DB\Cursor {
'INSERT INTO '.$this->table.' ('.$fields.') '.
'VALUES ('.$values.')'.$add,$args
);
if ($this->engine=='pgsql' && $lID)
if ($this->engine=='pgsql' && $lID && $aik)
$this->_id=$lID[0][$aik];
elseif ($this->engine!='oci')
$this->_id=$this->db->lastinsertid();
// Reload to obtain default and auto-increment field values
if ($reload=($inc || $filter))
if ($reload=(($inc && $this->_id) || $filter))
$this->load($inc?
[$inc.'=?',$this->db->value(
$this->fields[$inc]['pdo_type'],$this->_id)]:
@@ -463,7 +464,7 @@ class Mapper extends \DB\Cursor {
/**
* Update current record
* @return object
* @return static
**/
function update() {
$args=[];
@@ -652,7 +653,7 @@ class Mapper extends \DB\Cursor {
/**
* Instantiate class
* @param $db object
* @param $db \DB\SQL
* @param $table string
* @param $fields array|string
* @param $ttl int|array

View File

@@ -306,6 +306,7 @@ class Markdown extends Prefab {
},
$str
);
$str=preg_replace('/\s{2}\r?\n/','<br />',$str);
return '<p>'.$this->scan($str).'</p>'."\n\n";
}
return '';

View File

@@ -273,7 +273,7 @@ class Template extends Preview {
// Build tree structure
for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;)
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
'('.$this->tags.')\b((?:\s+[\w-]+'.
'('.$this->tags.')\b((?:\s+[\w-.:@!]+'.
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
substr($text,$ptr),$match)) {
@@ -303,19 +303,18 @@ class Template extends Preview {
if ($match[4]) {
// Process attributes
preg_match_all(
'/(?:\b([\w-]+)\h*'.
'(?:=\h*(?:"(.*?)"|\'(.*?)\'))?|'.
'(\{\{.+?\}\}))/s',
'/(?:(\{\{.+?\}\})|([^\s\/"\'=]+))'.
'\h*(?:=\h*(?:"(.*?)"|\'(.*?)\'))?/s',
$match[4],$attr,PREG_SET_ORDER);
foreach ($attr as $kv)
if (isset($kv[4]))
$node['@attrib'][]=$kv[4];
if (!empty($kv[1]) && !isset($kv[3]) && !isset($kv[4]))
$node['@attrib'][]=$kv[1];
else
$node['@attrib'][$kv[1]]=
(isset($kv[2]) && $kv[2]!==''?
$kv[2]:
(isset($kv[3]) && $kv[3]!==''?
$kv[3]:NULL));
$node['@attrib'][$kv[1]?:$kv[2]]=
(isset($kv[3]) && $kv[3]!==''?
$kv[3]:
(isset($kv[4]) && $kv[4]!==''?
$kv[4]:NULL));
}
}
$tmp='';
@@ -342,12 +341,13 @@ class Template extends Preview {
* return object
**/
function __construct() {
$ref=new ReflectionClass(__CLASS__);
$ref=new ReflectionClass(get_called_class());
$this->tags='';
foreach ($ref->getmethods() as $method)
if (preg_match('/^_(?=[[:alpha:]])/',$method->name))
$this->tags.=(strlen($this->tags)?'|':'').
substr($method->name,1);
parent::__construct();
}
}

View File

@@ -52,7 +52,7 @@ class Web extends Prefab {
'hqx'=>'application/mac-binhex40',
'html?'=>'text/html',
'jar'=>'application/java-archive',
'jpe?g'=>'image/jpeg',
'jpe?g|jfif?'=>'image/jpeg',
'js'=>'application/x-javascript',
'midi'=>'audio/x-midi',
'mp3'=>'audio/mpeg',
@@ -281,6 +281,8 @@ class Web extends Prefab {
curl_setopt($curl,CURLOPT_HTTPHEADER,$options['header']);
if (isset($options['content']))
curl_setopt($curl,CURLOPT_POSTFIELDS,$options['content']);
if (isset($options['proxy']))
curl_setopt($curl,CURLOPT_PROXY,$options['proxy']);
curl_setopt($curl,CURLOPT_ENCODING,'gzip,deflate');
$timeout=isset($options['timeout'])?
$options['timeout']:
@@ -333,6 +335,12 @@ class Web extends Prefab {
**/
protected function _stream($url,$options) {
$eol="\r\n";
if (isset($options['proxy'])) {
$options['proxy']=preg_replace('/https?/i','tcp',$options['proxy']);
$options['request_fulluri']=true;
if (preg_match('/socks4?/i',$options['proxy']))
return $this->_socket($url,$options);
}
$options['header']=implode($eol,$options['header']);
$body=@file_get_contents($url,FALSE,
stream_context_create(['http'=>$options]));
@@ -378,25 +386,46 @@ class Web extends Prefab {
$headers=[];
$body='';
$parts=parse_url($url);
$empty=empty($parts['port']);
if ($parts['scheme']=='https') {
$hostname=$parts['host'];
$proxy=false;
if ($parts['scheme']=='https')
$parts['host']='ssl://'.$parts['host'];
if ($empty)
$parts['port']=443;
}
elseif ($empty)
$parts['port']=80;
if (empty($parts['port']))
$parts['port']=$parts['scheme']=='https'?443:80;
if (empty($parts['path']))
$parts['path']='/';
if (empty($parts['query']))
$parts['query']='';
if ($socket=@fsockopen($parts['host'],$parts['port'],$code,$err)) {
if (isset($options['proxy'])) {
$req=$url;
$pp=parse_url($options['proxy']);
$proxy=$pp['scheme'];
if ($pp['scheme']=='https')
$pp['host']='ssl://'.$pp['host'];
if (empty($pp['port']))
$pp['port']=$pp['scheme']=='https'?443:80;
$socket=@fsockopen($pp['host'],$pp['port'],$code,$err);
} else {
$req=$parts['path'].($parts['query']?('?'.$parts['query']):'');
$socket=@fsockopen($parts['host'],$parts['port'],$code,$err);
}
if ($socket) {
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
);
if ($proxy=='socks4') {
// SOCKS4; http://en.wikipedia.org/wiki/SOCKS#Protocol
$packet="\x04\x01".pack("n", $parts['port']).
pack("H*",dechex(ip2long(gethostbyname($hostname))))."\0";
fputs($socket, $packet, strlen($packet));
$response=fread($socket, 9);
if (strlen($response)==8 && (ord($response[0])==0 || ord($response[0])==4)
&& ord($response[1])==90) {
$options['header'][]='Host: '.$hostname;
} else
$err='Socket Status '.ord($response[1]);
}
fputs($socket,$options['method'].' '.$req.' HTTP/1.0'.$eol);
fputs($socket,implode($eol,$options['header']).$eol.$eol);
if (isset($options['content']))
fputs($socket,$options['content'].$eol);
@@ -508,12 +537,6 @@ class Web extends Prefab {
$this->engine();
if ($this->wrapper!='stream') {
// PHP streams can't cope with redirects when Host header is set
foreach ($options['header'] as &$header)
if (preg_match('/^Host:/',$header)) {
$header='Host: '.$parts['host'];
unset($header);
break;
}
$this->subst($options['header'],'Host: '.$parts['host']);
}
$this->subst($options['header'],

View File

@@ -64,8 +64,8 @@ class Geo extends \Prefab {
$out=@geoip_record_by_name($ip)) {
$out['request']=$ip;
$out['region_code']=$out['region'];
$out['region_name']=geoip_region_name_by_code(
$out['country_code'],$out['region']);
$out['region_name']=(!empty($out['country_code']) && !empty($out['region']))
? geoip_region_name_by_code($out['country_code'],$out['region']) : '';
unset($out['country_code3'],$out['region'],$out['postal_code']);
return $out;
}